ListDetailsView DetailsTemplate 中的类型错误
Wrong type in ListDetailsView DetailsTemplate
我正在使用 Windows 社区工具包开发 UWP 应用程序。在某些页面中,我像这样使用 ListDetailsView
(FKA MasterDetailsView
) :
<!-- 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>
页面加载时出现我的问题:绑定到 DetailsTemplate
的第一件事似乎是页面的 DataContext
,即 ViewModel
。我可以在输出控制台中看到这个:
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')
...
这些绑定错误会在页面加载期间造成一些延迟(超过 1 秒,导致糟糕的用户体验),如果我尝试在 DetailsTemplate
上设置 DataType
,它会崩溃铸造异常。
下面是代码:
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);
}
}
}
ParametersViewModel
看起来像这样:
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
包含字符串 属性 Name
和一些其他内容。
我的代码有问题吗?如何避免将 DataContext
绑定到此模板?
注:我也在Windows Community Toolkit discussion on GitHub上发了一个问题,但是那里几乎没有流量
更新: 添加 DataType
时发生崩溃是因为我没有将 {Binding}
更改为 {x:Bind}
。它不再崩溃,但在调试控制台中仍然记录了一个异常:
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)'.
更新二: 忘了说我的项目是用Windows Template Studio生成的,ShellViewModel
是通用视图模型。可以通过生成一个包含一个 ListDetails 页面的 WTS 项目来重现该问题。
在 ListDetailsPage.xaml
中,将 DetailsTemplate
更改为:
<DataTemplate x:Key="DetailsTemplate" x:DataType="model:SampleOrder">
<Grid>
<views:MasterDetailDetailControl MasterMenuItem="{x:Bind}" />
</Grid>
</DataTemplate>
应该抛出转换异常。
The official document 提到“在 DataTemplate 中,Path 的值不是在页面的上下文中解释的,而是在被模板化的数据对象的上下文中解释的。在数据模板中使用 {x:Bind} 时,以便可以在编译时验证其绑定,DataTemplate 需要使用 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>
如上所示,该示例可用作绑定到 SampleDataGroup 对象集合的项目控件的 ItemTemplate。
在您的场景中,您需要为 ListDetailsView.DetailsTemplate 的 DataTemplate 指定 x:DataType
,假设您指定的类型是 local:ParaClass
class 其中包含 Name
属性。然后,您需要将 ParaClass 对象的集合绑定到 ListDetailsView ItemSource。最后,您可以使用 {x:Bind}
绑定 DataTemplate 中的 Name 属性。
更新:
我已经创建了一个 WTS 项目并重现了你的问题。
<views:ListDetailDetailControl ListMenuItem="{Binding}" />
如您所见,ListDetailDetailControl 绑定了 SampleOrder 对象。因此,其内部控件可以使用<TextBlock Text="{x:Bind ListMenuItem.OrderDate, Mode=OneWay}" />
实现绑定。
你改变了原来的模板,将这些内部控件直接放在DetailsTemplate中,但是你没有设置它的绑定对象,导致这些使用{Binding property}
的内部控件找不到正确的DataContext
.
因此,您需要为这些内部控件设置DataContext,然后您可以使用{Binding 属性} 直接为内部控件的这些属性设置绑定。请参考以下代码。
<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>
我正在使用 Windows 社区工具包开发 UWP 应用程序。在某些页面中,我像这样使用 ListDetailsView
(FKA MasterDetailsView
) :
<!-- 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>
页面加载时出现我的问题:绑定到 DetailsTemplate
的第一件事似乎是页面的 DataContext
,即 ViewModel
。我可以在输出控制台中看到这个:
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')
...
这些绑定错误会在页面加载期间造成一些延迟(超过 1 秒,导致糟糕的用户体验),如果我尝试在 DetailsTemplate
上设置 DataType
,它会崩溃铸造异常。
下面是代码:
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);
}
}
}
ParametersViewModel
看起来像这样:
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
包含字符串 属性 Name
和一些其他内容。
我的代码有问题吗?如何避免将 DataContext
绑定到此模板?
注:我也在Windows Community Toolkit discussion on GitHub上发了一个问题,但是那里几乎没有流量
更新: 添加 DataType
时发生崩溃是因为我没有将 {Binding}
更改为 {x:Bind}
。它不再崩溃,但在调试控制台中仍然记录了一个异常:
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)'.
更新二: 忘了说我的项目是用Windows Template Studio生成的,ShellViewModel
是通用视图模型。可以通过生成一个包含一个 ListDetails 页面的 WTS 项目来重现该问题。
在 ListDetailsPage.xaml
中,将 DetailsTemplate
更改为:
<DataTemplate x:Key="DetailsTemplate" x:DataType="model:SampleOrder">
<Grid>
<views:MasterDetailDetailControl MasterMenuItem="{x:Bind}" />
</Grid>
</DataTemplate>
应该抛出转换异常。
The official document 提到“在 DataTemplate 中,Path 的值不是在页面的上下文中解释的,而是在被模板化的数据对象的上下文中解释的。在数据模板中使用 {x:Bind} 时,以便可以在编译时验证其绑定,DataTemplate 需要使用 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>
如上所示,该示例可用作绑定到 SampleDataGroup 对象集合的项目控件的 ItemTemplate。
在您的场景中,您需要为 ListDetailsView.DetailsTemplate 的 DataTemplate 指定 x:DataType
,假设您指定的类型是 local:ParaClass
class 其中包含 Name
属性。然后,您需要将 ParaClass 对象的集合绑定到 ListDetailsView ItemSource。最后,您可以使用 {x:Bind}
绑定 DataTemplate 中的 Name 属性。
更新:
我已经创建了一个 WTS 项目并重现了你的问题。
<views:ListDetailDetailControl ListMenuItem="{Binding}" />
如您所见,ListDetailDetailControl 绑定了 SampleOrder 对象。因此,其内部控件可以使用<TextBlock Text="{x:Bind ListMenuItem.OrderDate, Mode=OneWay}" />
实现绑定。
你改变了原来的模板,将这些内部控件直接放在DetailsTemplate中,但是你没有设置它的绑定对象,导致这些使用{Binding property}
的内部控件找不到正确的DataContext
.
因此,您需要为这些内部控件设置DataContext,然后您可以使用{Binding 属性} 直接为内部控件的这些属性设置绑定。请参考以下代码。
<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>