xamldata-bindingmauidatatemplate

.NET MAUI - XAML : How can a view have additionnal controls according to the object binded?


I am trying to custom a view with the object binded on it. This view is a preview of a taken photo. The photo can be an uploaded photo (already upload on the server) or an offline photo (if the smartphone hasn't a internet connection or if a problem occured during the upload). UploadedPhotoVM and OfflinePhotoVM inherits of PhotoVM. The object OfflinePhotoVM has more properties as IsInError and UploadedPourcentage.

I want to add additional information into the view if the object is an offlinePhoto, for exemplate a label with the upload pourcentage or a button to retry the upload.

However, I don't want to have binding errors if a property is missing and I want to avoid to duplicate code as the view is the same for the two objects except for the label/button.

I know there is "DataTemplate" & "DataTemplateSelector" which worked for items of a list but here this isn't a list, it is only a binded object and I didn't see a template control which can be used for my purpose.

As I don't find a solution to bind a template, I just try to do classic binding :

<Grid x:Name="previewPageContainer">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    
    <!-- Some information about offline photos -->
    <Grid Grid.Row="0" BindingContext="{Binding Photo}" IsVisible="{Binding ., Converter={StaticResource IsOfflinePhotoConverter}}" >
        <Border
            BackgroundColor="Green" Stroke="Transparent">
            <Label 
                TextColor="White"
                Text="{Binding UploadedPourcentage}" />
            <Border.Triggers>
                <DataTrigger TargetType="Border" Binding="{Binding IsInError}" Value="True">
                    <Setter Property="IsVisible" Value="False"/>
                </DataTrigger>
            </Border.Triggers>
        </Border>
        <Button 
            BackgroundColor="Red" 
            TextColor="White" 
            Command="{Binding BindingContext.RetryCommand, Source={x:Reference previewPageContainer}}" 
            IsVisible="{Binding IsInError}" />
    </Grid>
            
    <!-- The picture -->
    <Image Grid.Row="1">
        [...]
    </Image>
            
    <!-- Details infos -->
    <Border Grid.Row="2">
        [...]
    </Border>
</Grid>

-> This XAML works but I have binding errors with the property "IsInError" when a UploadedPhotoVM is binded.


Solution

  • In a similar spirit to DataTemplateSelector, I would recommend creating your own ControlTemplateSelector using a ContentView, along the lines of:

    <ContentView x:Name="customContentView">
        <ContentView.Resources>
             <ResourceDictionary>
                 <ControlTemplate x:Key="template_False" >
                     <!-- ... ->
                 </ControlTemplate
                 <ControlTemplate x:Key="template_True" >
                     <!-- ... ->
                 </ControlTemplate
             </ResourceDictionary>
        </ControlView>
    </ContentView>
    

    Then, in the code-behind, you can implement the selector with the help of CommunityToolkit.Mvvm.Markup. For example:

    using CommunityToolkit.Maui.Markup;
    
    public partial class CustomContentView : ContentView
    {
        public static readonly BindableProperty IsInErrorProperty = BindableProperty.Create(nameof(IsInError), typeof(bool), typeof(CustomContentView), false);
        public bool IsInError
        {
            get => (bool)GetValue(IsInErrorProperty);
            set => SetValue(IsInErrorProperty, value);
        }
    
        // ...
    
        public CustomContentView()
        {
            InitializeComponent();
    
            // A basic ControlTemplate selector.
            this
                .Bind(
                    ContentView.ControlTemplateProperty,
                    (CustomContentView ctx) => ctx.IsInError,
                    source: this,
                    convert: (bool isInError) => isInError switch
                    {
                        false => Resources["template_False"],
                        true => Resources["template_True"]
                    }
                );
         }
    }
    

    [EDIT]

    I included the bool isInError in the resource keys, so you could simplify the converter like this:

    convert: (bool isInError) => Resources[$"template_{isInError}"]
    

    You can generalize it by replacing the bool with an int or enum to support numerous ControlTemplates.

    References: