WinUI3中GridView使用ItemsWrapGrid时自定义UserControl的x:Bind绑定失效问题
大家好,最近在WinUI3项目里碰到了一个特别诡异的绑定问题,折腾了好久没搞定,想请教下各位:
我用GridView展示一个集合,每个项是自定义的UserHomeView控件,绑定对应的UserHomeViewModel。当GridView的ItemsPanel用StackPanel的时候,一切都正常——UI渲染没问题,ViewModel的属性和逻辑都能正常触发。但只要换成ItemsWrapGrid(想让子项按网格排列),就出问题了:每个项的UserHomeView虽然能看到UI,但它的UserHomeViewModel直接变成了null,整个控件完全没法用,关键是既没有编译错误,也没有运行时的报错信息,完全摸不着头脑。
正常工作的代码
<GridView ItemsSource="{x:Bind WindowConfiguratorViewModel.KioskCollection, Mode=OneWay}"> <GridView.ItemsPanel> <ItemsPanelTemplate> <StackPanel /> <!-- 完全正常 --> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemTemplate> <DataTemplate x:DataType="viewModelsConfigurator:ProfileKioskDesktopViewModel"> <userHomeViews:UserHomeView UserHomeViewModel="{x:Bind UserHomeViewModel}" /> </DataTemplate> </GridView.ItemTemplate> </GridView>
绑定失效的代码
<GridView ItemsSource="{x:Bind WindowConfiguratorViewModel.KioskCollection, Mode=OneWay}"> <GridView.ItemsPanel> <ItemsPanelTemplate> <ItemsWrapGrid /> <!-- 这里就失效了 --> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemTemplate> <DataTemplate x:DataType="viewModelsConfigurator:ProfileKioskDesktopViewModel"> <userHomeViews:UserHomeView UserHomeViewModel="{x:Bind UserHomeViewModel}" /> </DataTemplate> </GridView.ItemTemplate> </GridView>
我还换了其他自定义用户控件测试,结果一模一样——只要用ItemsWrapGrid当ItemsPanel,ViewModel的绑定就会莫名其妙变成null。有没有大佬知道这是啥原因?我甚至已经做了一个最小复现项目来定位问题,但还是没找到根因。
补充:我尝试过的排查方向(可能的解决思路)
后来我查了一些资料,猜测可能和WinUI3里的UI虚拟化有关,因为GridView默认对ItemsWrapGrid开启了虚拟化,而StackPanel作为ItemsPanel时虚拟化行为不一样。针对这个方向我试了几个办法,分享给大家参考:
- 关闭GridView的虚拟化
给GridView加上VirtualizingStackPanel.VirtualizationMode="Disabled"属性,强制关闭虚拟化后,绑定居然正常了!不过这个方法的缺点是如果集合数据量大的话,可能会影响性能,适合数据量小的场景。
<GridView ItemsSource="{x:Bind WindowConfiguratorViewModel.KioskCollection, Mode=OneWay}" VirtualizingStackPanel.VirtualizationMode="Disabled"> <!-- 其余代码不变 --> </GridView>
- 把x:Bind换成Binding
x:Bind是编译时绑定,对上下文的要求更严格,我试着把UserHomeView的绑定改成Binding,居然也正常了:
<DataTemplate x:DataType="viewModelsConfigurator:ProfileKioskDesktopViewModel"> <userHomeViews:UserHomeView UserHomeViewModel="{Binding UserHomeViewModel, Mode=OneWay}" /> </DataTemplate>
应该是Binding作为运行时绑定,会自动处理数据上下文的传递,绕开了虚拟化带来的编译时绑定上下文问题。
- 检查UserHomeView的依赖属性定义
我还特意确认了UserHomeViewModel是正确定义的依赖属性,而不是普通CLR属性——如果是普通属性的话,在虚拟化回收重建控件时,可能没法正确更新值。正确的依赖属性定义大概是这样的:
public static readonly DependencyProperty UserHomeViewModelProperty = DependencyProperty.Register( nameof(UserHomeViewModel), typeof(YourUserHomeViewModelType), typeof(UserHomeView), new PropertyMetadata(null, OnViewModelChanged)); public YourUserHomeViewModelType UserHomeViewModel { get => (YourUserHomeViewModelType)GetValue(UserHomeViewModelProperty); set => SetValue(UserHomeViewModelProperty, value); } private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // 这里可以处理ViewModel变化后的逻辑 var view = (UserHomeView)d; var newVm = e.NewValue as YourUserHomeViewModelType; // ... }
目前这几个办法里,关闭虚拟化和换Binding都解决了我的问题,但还是想搞清楚为什么ItemsWrapGrid会导致x:Bind失效,有没有大佬能解释下底层原因?
备注:内容来源于stack exchange,提问作者Hector Lazarin

