0

I am using MasterDetailsView control for my app. It offers a way to customize the details panel when no items are selected, but it doesn't offer a way to customize the master panel when no items are available.

Basically, what I want to accomplish is to display a message (TextBlock) instead of the default ListView when the latter has no items.

I just can't get it to work. My guess is that the ListView is nested inside a ControlTemplate that defines the MasterDetailsView control.

The only way I managed to do this statically (without any update at runtime) was to overwrite MasterDetailsView.Resources, where I add a Controltemplate for ListView, like in the markup below:

<Page
    ...
    xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls
    ...>

    <controls:MasterDetailsView.Resources>
        <Style TargetType="ListView" >
            <Setter Property="Template" >
                <Setter.Value>
                    <ControlTemplate TargetType="ListView">
                        <Grid>
                            <TextBlock
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center"
                                Style="{StaticResource SubtitleTextBlockStyle}"
                                TextAlignment="Center"
                                Text="No content"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </controls:MasterDetailsView.Resources>

However, as I mentioned, that gives me the static behavior. I need this TextBlock to be displayed only when the data source my ListView is bound to runs out of items.

I tried to solve this by binding the Visibility property of my TextBlock to a Converter, but the converter isn't even reached (I debugged after adding a breakpoint to the Convert() method). I'm not sure if I used it properly though (I ommited the declaration of VisibleWhenZeroConverter and its source code for brevity):

<TextBlock
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Style="{StaticResource SubtitleTextBlockStyle}"
    TextAlignment="Center"
    Text="No content"
    Visibility="{Binding ElementName=MyMasterDetailsView, Path=ViewModel.SampleItems.Count, Converter={StaticResource VisibleWhenZeroConverter}}"/>

Obs: ViewModel is a property of MyMasterDetailsView, and it has a ObservableCollection, named SampleItems, which is never null.

I also tried to work this out by using DataTriggerBehavior with a ChangePropertyAction (from Microsoft.Xaml.Interactions.Core), but without any luck either. I'm also not sure if I did it the right way.

In case someone can answer me if this is even possible with MasterDetailsView control, I'd appreciate. Or maybe give an example of how I'd do this using one of the approaches from above, or even another one.

Best regards!

Alexandre Bodi
  • 344
  • 2
  • 12

1 Answers1

1

It should be noted that binding the Count of the collection cannot dynamically change the state. Although the Count property is indeed modified, the converter will not take effect because the UI is not notified.

If you are using ObservableCollection as the ItemsSource, you can listen to the ObservableCollection.CollectionChanged event and change the display of MasterDetailsView in the event callback.

On this basis, you can create a custom dependency property by deriving MasterDetailsView to change the display.

CustomMasterDetailsView.cs

public class CustomMasterDetailsView : MasterDetailsView
{
    public CustomMasterDetailsView() : base()
    {
        this.DefaultStyleKey = typeof(CustomMasterDetailsView);
    }

    public Visibility EmptyTipVisibility
    {
        get { return (Visibility)GetValue(EmptyTipVisibilityProperty); }
        set { SetValue(EmptyTipVisibilityProperty, value); }
    }

    public static readonly DependencyProperty EmptyTipVisibilityProperty =
        DependencyProperty.Register("EmptyTipVisibility", typeof(Visibility), typeof(CustomMasterDetailsView), new PropertyMetadata(Visibility.Collapsed));
}

CustomMasterDetailsView.xaml (ResourceDictionary)

<Style TargetType="local:CustomMasterDetailsView">
    <Setter Property="Background" Value="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
    <Setter Property="BorderBrush" Value="{ThemeResource ApplicationForegroundThemeBrush}" />
    <Setter Property="BorderThickness" Value="0" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:CustomMasterDetailsView">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid x:Name="RootPanel">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition x:Name="MasterColumn"
                                              Width="Auto" />
                            <ColumnDefinition x:Name="DetailsColumn"
                                              Width="*" />
                        </Grid.ColumnDefinitions>
                        <Grid x:Name="MasterPanel"
                              Width="{TemplateBinding MasterPaneWidth}"
                              Background="{TemplateBinding MasterPaneBackground}"
                              BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="0,0,1,0">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="*" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <ContentPresenter x:Name="HeaderContentPresenter"
                                              Margin="12,0"
                                              x:DeferLoadStrategy="Lazy"
                                              Content="{TemplateBinding MasterHeader}"
                                              ContentTemplate="{TemplateBinding MasterHeaderTemplate}"
                                              Visibility="Collapsed" />
                            <ListView x:Name="MasterList"
                                      Grid.Row="1"
                                      IsTabStop="False"
                                      ItemContainerStyle="{TemplateBinding ItemContainerStyle}"
                                      ItemContainerStyleSelector="{TemplateBinding ItemContainerStyleSelector}"
                                      ItemTemplate="{TemplateBinding ItemTemplate}"
                                      ItemTemplateSelector="{TemplateBinding ItemTemplateSelector}"
                                      ItemsSource="{TemplateBinding ItemsSource}"
                                      SelectedItem="{Binding SelectedItem, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
                            <TextBlock HorizontalAlignment="Center"
                                       VerticalAlignment="Center"
                                       Style="{StaticResource SubtitleTextBlockStyle}"
                                       TextAlignment="Center"
                                       Grid.Row="1" Visibility="{TemplateBinding EmptyTipVisibility}"
                                       Text="No content"/> 
                            <Grid x:Name="MasterCommandBarPanel" Grid.Row="2"></Grid>
                        </Grid>
                        <Grid x:Name="DetailsPanel"
                              Grid.Column="1">
                            <ContentPresenter x:Name="NoSelectionPresenter"
                                              Content="{TemplateBinding NoSelectionContent}"
                                              ContentTemplate="{TemplateBinding NoSelectionContentTemplate}" />
                            <Grid x:Name="SelectionDetailsPanel">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="*" />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>
                                <Grid Background="{TemplateBinding Background}">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>
                                    <Button x:Name="MasterDetailsBackButton"
                                            Background="Transparent"
                                            Height="44" 
                                            Width="48" 
                                            Visibility="Collapsed">
                                        <SymbolIcon Symbol="Back"/>
                                    </Button>
                                    <ContentPresenter x:Name="DetailsHeaderPresenter"
                                                      Content="{TemplateBinding DetailsHeader}"
                                                      ContentTemplate="{TemplateBinding DetailsHeaderTemplate}"
                                                      Grid.Column="1"/>
                                </Grid>
                                <ContentPresenter x:Name="DetailsPresenter"
                                                  Background="{TemplateBinding Background}"
                                                  ContentTemplate="{TemplateBinding DetailsTemplate}"
                                                  Grid.Row="1">
                                </ContentPresenter>
                                <Grid x:Name="DetailsCommandBarPanel" Grid.Row="2"></Grid>
                                <Grid.RenderTransform>
                                    <TranslateTransform x:Name="DetailsPresenterTransform" />
                                </Grid.RenderTransform>
                            </Grid>
                        </Grid>
                    </Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="SelectionStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition From="NoSelectionWide"
                                                  To="HasSelection">
                                    <Storyboard>
                                        <DrillInThemeAnimation EntranceTargetName="SelectionDetailsPanel"
                                                               ExitTargetName="NoSelectionPresenter" />
                                    </Storyboard>
                                </VisualTransition>
                                <VisualTransition From="NoSelectionNarrow"
                                                  To="HasSelection">
                                    <Storyboard>
                                        <DoubleAnimation BeginTime="0:0:0"
                                                         Storyboard.TargetName="DetailsPresenterTransform"
                                                         Storyboard.TargetProperty="X"
                                                         From="200"
                                                         To="0"
                                                         Duration="0:0:0.25">
                                            <DoubleAnimation.EasingFunction>
                                                <QuarticEase EasingMode="EaseOut" />
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                        <DoubleAnimation BeginTime="0:0:0"
                                                         Storyboard.TargetName="SelectionDetailsPanel"
                                                         Storyboard.TargetProperty="Opacity"
                                                         From="0"
                                                         To="1"
                                                         Duration="0:0:0.25">
                                            <DoubleAnimation.EasingFunction>
                                                <QuarticEase EasingMode="EaseOut" />
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                    </Storyboard>
                                </VisualTransition>
                                <VisualTransition From="HasSelection"
                                                  To="NoSelectionWide">
                                    <Storyboard>
                                        <DrillOutThemeAnimation EntranceTargetName="NoSelectionPresenter"
                                                                ExitTargetName="SelectionDetailsPanel" />
                                    </Storyboard>
                                </VisualTransition>
                                <VisualTransition From="HasSelection"
                                                  To="NoSelectionNarrow">
                                    <Storyboard>
                                        <DoubleAnimation BeginTime="0:0:0"
                                                         Storyboard.TargetName="DetailsPresenterTransform"
                                                         Storyboard.TargetProperty="X"
                                                         From="0"
                                                         To="200"
                                                         Duration="0:0:0.25" />
                                        <DoubleAnimation BeginTime="0:0:0.08"
                                                         Storyboard.TargetName="SelectionDetailsPanel"
                                                         Storyboard.TargetProperty="Opacity"
                                                         From="1"
                                                         To="0"
                                                         Duration="0:0:0.17">
                                            <DoubleAnimation.EasingFunction>
                                                <QuarticEase EasingMode="EaseOut" />
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                        <DoubleAnimation BeginTime="0:0:0.0"
                                                         Storyboard.TargetName="MasterPanel"
                                                         Storyboard.TargetProperty="Opacity"
                                                         From="0"
                                                         To="1"
                                                         Duration="0:0:0.25">
                                            <DoubleAnimation.EasingFunction>
                                                <QuarticEase EasingMode="EaseIn" />
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                    </Storyboard>
                                </VisualTransition>
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="NoSelectionWide">
                                <VisualState.Setters>
                                    <Setter Target="SelectionDetailsPanel.Visibility" Value="Collapsed" />
                                    <Setter Target="MasterPanel.Visibility" Value="Visible" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="HasSelectionWide">
                                <VisualState.Setters>
                                    <Setter Target="NoSelectionPresenter.Visibility" Value="Collapsed" />
                                    <Setter Target="MasterPanel.Visibility" Value="Visible" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="HasSelectionNarrow">
                                <VisualState.Setters>
                                    <Setter Target="MasterPanel.Visibility" Value="Collapsed" />
                                    <Setter Target="NoSelectionPresenter.Visibility" Value="Collapsed" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="NoSelectionNarrow">
                                <VisualState.Setters>
                                    <Setter Target="NoSelectionPresenter.Visibility" Value="Collapsed" />
                                    <Setter Target="SelectionDetailsPanel.Visibility" Value="Collapsed" />
                                    <Setter Target="MasterPanel.Visibility" Value="Visible" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="WidthStates">
                            <VisualState x:Name="NarrowState">
                                <VisualState.Setters>
                                    <Setter Target="MasterColumn.Width" Value="*" />
                                    <Setter Target="DetailsColumn.Width" Value="0" />
                                    <Setter Target="DetailsPanel.(Grid.Column)" Value="0" />
                                    <Setter Target="NoSelectionPresenter.Visibility" Value="Collapsed" />
                                    <Setter Target="MasterPanel.BorderThickness" Value="0" />
                                    <Setter Target="MasterPanel.Width" Value="NaN" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="WideState">
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

App.xaml

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            ...
            <ResourceDictionary Source="ms-appx:///Controls/CustomMasterDetailsView.xaml" />
            ...
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Usage

if(SampleItems.Count == 0)
{
    MyMasterDetailsView.EmptyTipVisibility = Visibility.Visible;
}
else
{
    MyMasterDetailsView.EmptyTipVisibility = Visibility.Collapsed;
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Richard Zhang
  • 7,523
  • 1
  • 7
  • 13
  • Thank you so much, Richard. I realized about the need of a `CollectionChanged ` after I posted my question. You're definitely right about that. The only problem with this approach, from my perspective, is that any updates to the XAML that defines the original `MasterDetailsView` control (in case any comes up) will be missed by my app, won't they? – Alexandre Bodi Jul 27 '20 at 12:57
  • Hello, can you give an example of what XAML update you are referring to? – Richard Zhang Jul 27 '20 at 23:05
  • Sure, Richard. Like this one: https://github.com/windows-toolkit/WindowsCommunityToolkit/commit/354fd17fd108e9dcc54d9d7e3699eaae977e19b6#diff-8dc1f109c1120a6ade07b35faf1702fd. – Alexandre Bodi Jul 28 '20 at 11:32
  • Hello, generally speaking, if the control does not meet our current use needs, we needs to derive the control by itself and add the functions what we needs. This does not require that it must be updated synchronously with the original control, unless the original control updates a necessary function. – Richard Zhang Jul 28 '20 at 22:52