WinUI 3中DataTemplate适配VisualStateGroup的集合绑定报错问题
问题
在WinUI 3(1.1.5)中尝试将VisualStateGroup对象集合传递给ContentControl或ListView等控件时,持续收到“值不在预期范围内”的错误。即使定义了对应DataTemplate也无法解决,但将VisualStateGroup和VisualState的属性复制到自定义密封类Thing1、Thing2后,集合就能正常绑定显示。
错误复现代码
C#代码
public sealed partial class MainPage : Page { public MainViewModel ViewModel { get; } public ShellPage ShellPage { get; } public MainPage() { ViewModel = App.GetService<MainViewModel>(); InitializeComponent(); ShellPage = App.GetService<ShellPage>(); var rootGrid = (ShellPage.IsLoaded) ? VisualTreeHelper.GetChild(ShellPage.NavigationViewControl, 0) as Grid : null; if (rootGrid != null) { var listOfVisualStateGroups = VisualStateManager.GetVisualStateGroups(rootGrid); if (listOfVisualStateGroups?.Count > 0) cControl.Content = listOfVisualStateGroups[0]; } } }
XAML代码
<Page x:Class="TestCollection.Views.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid x:Name="ContentArea"> <ContentControl x:Name="cControl"> <ContentControl.ContentTemplate> <DataTemplate x:DataType="VisualStateGroup"> <TextBlock Text="{x:Bind Name}" /> </DataTemplate> </ContentControl.ContentTemplate> </ContentControl> </Grid> </Page>
可行的封装方案代码
C#代码
public sealed partial class MainPage : Page { public MainViewModel ViewModel { get; } public ShellPage ShellPage { get; } public List<Thing1> Things { get; set; } = new(); public List<Thing2> OtherThings { get; set; } = new(); public MainPage() { ViewModel = App.GetService<MainViewModel>(); InitializeComponent(); ShellPage = App.GetService<ShellPage>(); var rootGrid = (ShellPage.IsLoaded) ? VisualTreeHelper.GetChild(ShellPage.NavigationViewControl, 0) as Grid : null; if (rootGrid != null) { var listOfVisualStateGroups = VisualStateManager.GetVisualStateGroups(rootGrid); foreach (var group in listOfVisualStateGroups) Things.Add(new(group)); if (listOfVisualStateGroups?.Count > 0) foreach (var state in listOfVisualStateGroups[0].States) OtherThings.Add(new(state)); } } } // 复制VisualStateGroup属性的自定义类 public partial class Thing1 { public VisualState CurrentState { get; } public CoreDispatcher Dispatcher { get; } public DispatcherQueue DispatcherQueue { get; } public string Name { get; } public IList<VisualState> States { get; } public IList<VisualTransition> Transitions { get; } public Thing1(VisualStateGroup group) { CurrentState = group.CurrentState; Dispatcher = group.Dispatcher; DispatcherQueue = group.DispatcherQueue; Name = group.Name; States = group.States; Transitions = group.Transitions; } } // 复制VisualState属性的自定义类 public partial class Thing2 { public CoreDispatcher Dispatcher { get; } public DispatcherQueue DispatcherQueue { get; } public string Name { get; } public SetterBaseCollection Setters { get; } public IList<StateTriggerBase> StateTriggers { get; } public Storyboard Storyboard { get; set; } public Thing2(VisualState state) { Dispatcher = state.Dispatcher; DispatcherQueue = state.DispatcherQueue; Name = state.Name; Setters = state.Setters; StateTriggers = state.StateTriggers; Storyboard = state.Storyboard; } }
XAML代码
<Page x:Class="TestCollection.Views.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="using:TestCollection.Views" mc:Ignorable="d"> <Grid x:Name="ContentArea"> <StackPanel Orientation="Horizontal"> <ListView Margin="0,0,20,20" BorderThickness="1" BorderBrush="Black" Header="Things (VisualStateGroups)" ItemsSource="{x:Bind Things}"> <ListView.ItemTemplate> <DataTemplate x:DataType="views:Thing1"> <TextBlock Text="{x:Bind Name}" /> </DataTemplate> </ListView.ItemTemplate> </ListView> <ListView Margin="0,0,20,20" BorderThickness="1" BorderBrush="Black" Header="OtherThings (VisualStates)" ItemsSource="{x:Bind OtherThings}"> <ListView.ItemTemplate> <DataTemplate x:DataType="views:Thing2"> <TextBlock Text="{x:Bind Name}" /> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackPanel> </Grid> </Page>
原因分析
VisualStateGroup的设计定位:它并非为数据绑定场景设计,而是作为Visual State管理的核心对象,内部依赖于与可视化树的强关联,包含大量与控件状态控制相关的内部逻辑和引用,这些内容不适合直接作为绑定源暴露给UI控件。- XAML绑定的类型限制:WinUI 3的XAML绑定系统对作为绑定源的类型有隐性要求——需要类型具备符合绑定规则的属性访问权限、无内部状态冲突,且未被标记为仅用于可视化系统内部的类型。
VisualStateGroup属于UI框架内部使用的类型,其属性可能包含无法被绑定系统正确处理的内部引用(如Dispatcher、CurrentState等与控件生命周期强绑定的成员),导致绑定过程中出现参数越界错误。 - 自定义封装类的适配性:
Thing1、Thing2仅复制了需要展示的属性,剥离了VisualStateGroup/VisualState与原可视化树的关联,仅保留了纯数据属性(如Name),这些属性完全符合绑定系统的要求,因此可以正常被ListView或ContentControl处理。
内容的提问来源于stack exchange,提问作者aturnbul
相关产品推荐
相关产品推荐

