Wrong type in ListDetailsView DetailsTemplate

0

I'm working on a UWP app using the Windows Community Toolkit. In some pages, I use the ListDetailsView (FKA MasterDetailsView) like this :

<!-- page and stuff... -->
<controls:ListDetailsView
         x:Name="ListViewParameters"
         ItemsSource="{x:Bind ViewModel.ParametersList, Mode=OneWay}"
         SelectedItem="{x:Bind ViewModel.Selected, Mode=TwoWay}"
         CompactModeThresholdWidth="720"
         BorderBrush="Transparent"
         NoSelectionContent="Please select a parameter to view"
         ListPaneBackground="{ThemeResource SystemControlPageBackgroundChromeLowBrush}"
         ListPaneWidth="350"
         SelectionChanged="ListViewParameters_SelectionChanged">

    <controls:ListDetailsView.ItemTemplate>
        <DataTemplate x:DataType="parameters:ParameterSet">
            <!-- item template, works great -->
        </DataTemplate>
    </controls:ListDetailsView.ItemTemplate>

    <controls:ListDetailsView.DetailsTemplate>
        <DataTemplate>
            <ScrollViewer x:Name="ContentScrollViewer">
                <StackPanel x:Name="Details" Margin="{StaticResource MediumLeftMargin}">
                    <!-- this is where problems happen -->
                    <TextBlock Style="{StaticResource TitleTextBlockStyle}"
                              Text="{Binding Name, Mode=OneWay}"
                              x:Name="title"
                              Margin="{StaticResource MediumBottomMargin}"/>
                    <!-- other elements with bindings... -->
                </StackPanel>
            </ScrollViewer>
        </DataTemplate>
   </controls:ListDetailsView.DetailsTemplate>
</controls:ListDetailsView>

My problem appears when the page loads: the first thing bound to the DetailsTemplate appears to be the page's DataContext, which is a ViewModel. I can see this in the output console :

Error: BindingExpression path error: 'Name' property not found on 'MyProject.ParametersViewModel'. BindingExpression: Path='ParameterSet.Name' DataItem='MyProject.Components.ParametersEditor'; target element is 'Windows.UI.Xaml.Controls.TextBox' (Name='null'); target property is 'Text' (type 'String')

Error: BindingExpression path error: 'IsDefault' property not found on 'MyProject.ParametersViewModel'. BindingExpression: Path='ParameterSet.IsDefault' DataItem='MyProject.Components.ParametersEditor'; target element is 'Windows.UI.Xaml.Controls.TextBox' (Name='null'); target property is 'IsEnabled' (type 'Boolean')

...

These binding errors cause some delay during page load (more than 1 second, resulting in a terrible UX), and if I try to set a DataType on the DetailsTemplate, it crashes with a casting exception.

Here's the code behind :

public sealed partial class ParametersPage : Page
{
    public ParametersPage()
    {
        this.InitializeComponent();
        Loaded += ParametersPage_Loaded;
        Unloaded += ParametersPage_Unloaded;
    }

    public ParametersViewModel ViewModel { get; set; } = new ParametersViewModel();

    private async void ParametersPage_Loaded(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadParametersAsync();
    }

    private async void ParametersPage_Unloaded(object sender, RoutedEventArgs e)
    {
        await ViewModel.UpdateParameterSetAsync(ViewModel.Selected);
    }

    private async void ListViewParameters_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.RemovedItems.Count > 0 && e.RemovedItems[0] is ParameterSet parameters)
        {
                await ViewModel.UpdateParameterSetAsync(parameters);
        }
    }
}

And the ParametersViewModel looks like this :

public class ParametersViewModel : Observable
{
    public ParametersViewModel()
    {
        ParametersList = new ObservableCollection<ParameterSet>();
    }

    public ObservableCollection<ParameterSet> ParametersList
    {
        get { return _parameters; }
        set { Set(ref _parameters, value); }
    }

    // some other methods like LoadParametersAsync and UpdateParameterSetAsync


    private ParameterSet _selected;

ParameterSet contains a string property Name and some other stuff.

Is there something wrong with my code? How can I avoid having the DataContext bound to this template?

Note: I also posted a question on the Windows Community Toolkit discussion on GitHub, but there's almost no traffic there.

Update: the crash when adding a DataType was happening because I didn't change {Binding} to {x:Bind}. It doesn't crash anymore, but there still is an exception logged in the debug console :

Exception thrown at 0x00007FF94A6F4B59 (KernelBase.dll) in MyApp.exe: WinRT originate error - 0x80004002 : 'System.InvalidCastException: Unable to cast object of type 'MyProject.ViewModels.ShellViewModel' to type 'MyProject.Core.Models.ParameterSet'.
   at MyProject.Views.ParametersPage.ParametersPage_obj12_Bindings.SetDataRoot(Object newDataRoot)
   at MyProject.Views.ParametersPage.ParametersPage_obj12_Bindings.DataContextChangedHandler(FrameworkElement sender, DataContextChangedEventArgs args)'.

Update 2: I forgot to mention that my project has been generated with Windows Template Studio and ShellViewModel is the general view model. The issue can be reproduced by generating a WTS project with one ListDetails page.

In ListDetailsPage.xaml, change the DetailsTemplate to this:

<DataTemplate x:Key="DetailsTemplate" x:DataType="model:SampleOrder">
    <Grid>
        <views:MasterDetailDetailControl MasterMenuItem="{x:Bind}" />
    </Grid>
</DataTemplate>

A casting exception should be thrown.

c#
xaml
uwp
windows-community-toolkit
asked on Stack Overflow May 7, 2021 by loics2 • edited May 25, 2021 by loics2

1 Answer

1

The official document mentions that “Inside a DataTemplate, the value of Path is not interpreted in the context of the page, but in the context of the data object being templated. When using {x:Bind} in a data template, so that its bindings can be validated at compile-time, the DataTemplate needs to declare the type of its data object using x:DataType.”

<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{x:Bind Title}"/>
     <TextBlock Text="{x:Bind Description}"/>
    </StackPanel>
  </DataTemplate>

As shown above, the example could be used as the ItemTemplate of an items control bound to a collection of SampleDataGroup objects.

In your scenario, you need to specify the x:DataType for the DataTemplate of ListDetailsView.DetailsTemplate, assuming that the type you specify is local:ParaClass class which contains the Name property. Then you need to bind the collection of ParaClass objects to the ListDetailsView ItemSource. Finally, you could use {x:Bind} to bind Name property in DataTemplate.

Update:

I have created a WTS project and reproduced your issue.

<views:ListDetailDetailControl ListMenuItem="{Binding}" />

As you can see, the ListDetailDetailControl binds the SampleOrder object. Therefore, its internal control could use <TextBlock Text="{x:Bind ListMenuItem.OrderDate, Mode=OneWay}" /> to achieve binding.

You changed the original template and placed these internal controls directly in DetailsTemplate, but you don’t set its binding object, so that these internal controls that use {Binding property} can’t find the correct DataContext. Therefore, you need to set DataContext for these internal control, then you could use {Binding property} to set binding for these properties of internal controls directly. Please refer to the following code.

<DataTemplate x:Key="DetailsTemplate">
            <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" DataContext="{Binding}">
                <ScrollViewer..>
                   ……                         
                            <TextBlock Style="{StaticResource DetailBodyBaseMediumStyle}" Text="{Binding Status, Mode=OneWay}" />                  
                            <TextBlock Style="{StaticResource DetailBodyBaseMediumStyle}" Text="{Binding OrderDate, Mode=OneWay}" /> 
<TextBlock Style="{StaticResource DetailBodyBaseMediumStyle}" Text="{Binding Company, Mode=OneWay}" />
……
                </ScrollViewer>
            </Grid>
        </DataTemplate>
answered on Stack Overflow May 10, 2021 by Arya Ding - MSFT • edited May 26, 2021 by Arya Ding - MSFT

User contributions licensed under CC BY-SA 3.0