WinUI 3中ListView内ComboBox与嵌套INotifyPropertyChanged ViewModel双向绑定问题
问题:ListView中ComboBox双向绑定异常与数据更新问题
我正在尝试用数据绑定替代ComboBox的Loaded事件实现解决方案,场景为:ListView绑定外层ViewModel(TaskViewModel)列表,每个列表项内的ComboBox绑定内层ViewModel(StatusViewModel)列表,同时ComboBox的选中项与TaskViewModel的Status属性双向绑定。
目前遇到的核心问题:
- TaskViewModel.Status的Set方法会被传入null值,堆栈跟踪显示该调用来自外部代码;取消注释Set方法内的null判断代码后程序可运行,但ComboBox绑定完全失效。
- 已确认传入SetProjectTasks()的MyTask列表中,所有任务的Status值均不为null,不清楚null值的来源。
- 后续简化代码(引入CommunityToolkit.Mvvm,移除StatusViewModel直接使用Status模型)后问题依旧:ComboBox初始化无选中项,更改选择不触发任何代码,PropertyChanged事件从未执行,且Status值仍会被设为null。
原始代码
Page.xaml
<ListView x:Name="TasksListView" Grid.Row="1" Grid.ColumnSpan="2" ItemsSource="{x:Bind MyTasks}" SelectionMode="None" IsItemClickEnabled="True" ItemClick="TasksListView_ItemClick"> <ListView.ItemTemplate> <DataTemplate x:DataType="viewmodels:TaskViewModel"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <ComboBox x:Name="StatusComboBox" Tag="{x:Bind ID, Mode=OneWay}" Grid.Column="0" Margin="0,0,10,0" VerticalAlignment="Center" ItemsSource="{Binding Path=ProjectTaskStatuses, ElementName=RootPage}" SelectedValue="{x:Bind Status, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <ComboBox.ItemTemplate> <DataTemplate x:DataType="viewmodels:StatusViewModel"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Rectangle Grid.Column="0" Margin="0,0,10,0" Height="10" Width="10" StrokeThickness="1"> <Rectangle.Fill> <SolidColorBrush Color="{x:Bind Color}"></SolidColorBrush> </Rectangle.Fill> <Rectangle.Stroke> <SolidColorBrush Color="{x:Bind Color}"></SolidColorBrush> </Rectangle.Stroke> </Rectangle> <TextBlock Grid.Column="1" Text="{x:Bind Name}"></TextBlock> </Grid> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <TextBlock Grid.Column="1" Text="{x:Bind Name}"></TextBlock> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView>
Page.xaml.cs
public ObservableCollection<StatusViewModel> ProjectTaskStatuses { get; set; } private ObservableCollection<TaskViewModel> MyTasks { get; set; } public void SetProjectStatuses(List<Status> statuses) { this.ProjectTaskStatuses.Clear(); statuses.ForEach(status => this.ProjectTaskStatuses.Add(new StatusViewModel(status))); } public void SetProjectTasks(List<MyTask> tasks) { this.MyTasks.Clear(); tasks.ForEach(task => this.MyTasks.Add(new TaskViewModel(task))); }
TaskViewModel.cs
public class TaskViewModel : INotifyPropertyChanged { private MyTask _model; public MyTask Model { get => new MyTask(this._model) { Status = this._status.Model }; } public string ID { get => this._model?.ID; set { this._model.ID = value; this.RaisePropertyChanged(nameof(ID)); } } public string Name { get => this._model?.Name; set { this._model.Name = value; this.RaisePropertyChanged(nameof(Name)); } } private StatusViewModel _status; public Status Status { get => this._status?.Model; set { // 测试时注释的代码——此处会出现意料之外的null值 //if (value == null) //{ // System.Diagnostics.Debug.WriteLine("NULL STATUS BEING SET TO - " + this._model.ID + " " + this._model.Name + " " + this._model.Status.Name); // return; //} if (this._status != null) this._status.PropertyChanged -= StatusChanged; this._status = new StatusViewModel(value); if (this._status != null) this._status.PropertyChanged += StatusChanged; this.RaisePropertyChanged(nameof(Status)); void StatusChanged(object sender, PropertyChangedEventArgs e) => this.RaisePropertyChanged(nameof(Status)); } } /// <summary> /// 当ViewModel的可绑定属性变更时触发 /// </summary> public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } public TaskViewModel(MyTask task) { this._model = task; this._status = new StatusViewModel(task.Status); } }
StatusViewModel.cs
public class StatusViewModel : INotifyPropertyChanged { private Status _model; public Status Model { get => new Status(this._model); } public string ID { get => this._model?.ID; set { this._model.ID = value; this.RaisePropertyChanged(nameof(ID)); } } public string Name { get => this._model?.Name; set { this._model.Name = value; this.RaisePropertyChanged(nameof(Name)); } } public Color Color { get => this._model.Color; set { this._model.Color = value; this.RaisePropertyChanged(nameof(Color)); } } /// <summary> /// 当ViewModel的可绑定属性变更时触发 /// </summary> public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } public StatusViewModel(Status status) { this._model = status; } }
更新后的代码
引入CommunityToolkit.Mvvm NuGet包,移除StatusViewModel直接使用Status模型,简化后问题未解决:
后台代码更改
public partial class TaskViewModel : ObservableObject { [ObservableProperty] private MyTask _model; public TaskViewModel(MyTask task) { this._model = task; } } public void SetProjectTasks(List<MyTask> tasks) { MyTasks.Clear(); TaskViewModel taskViewModel = new TaskViewModel(task); taskViewModel.PropertyChanged += this.ViewModel_PropertyChanged; tasks.ForEach(task => this.MyTasks.Add(taskViewModel)); } private void TaskViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) { // 调试确认该事件处理程序从未执行——即使更改ComboBox的选择 TaskViewModel model = sender as TaskViewModel; System.Diagnostics.Debug.WriteLine("Model for task: " + model.Model.Name + " property " + e.PropertyName + " changed to " + model.GetType().GetProperty(e.PropertyName).GetValue(model).ToString()); }
XAML更改
<ComboBox x:Name="StatusComboBox" Tag="{x:Bind Model.ID, Mode=OneWay}" Grid.Column="0" Margin="0,0,10,0" VerticalAlignment="Center" ItemsSource="{Binding Path=ProjectTaskStatuses, ElementName=RootPage}" SelectedValue="{x:Bind Model.Status, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <ComboBox.ItemTemplate> <DataTemplate x:DataType="models:Status"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Rectangle Grid.Column="0" Margin="0,0,10,0" Height="10" Width="10" StrokeThickness="1"> <Rectangle.Fill> <SolidColorBrush Color="{x:Bind Color}"></SolidColorBrush> </Rectangle.Fill> <Rectangle.Stroke> <SolidColorBrush Color="{x:Bind Color}"></SolidColorBrush> </Rectangle.Stroke> </Rectangle> <TextBlock Grid.Column="1" Text="{x:Bind Name}"></TextBlock> </Grid> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox>
当前存在的问题
- ComboBox初始化后无选中项
- 更改ComboBox选择时,未触发任何代码执行
- 数据绑定过程中会将Status值覆盖为null(已确认SetProjectTasks()传入的MyTask.Status均不为null)
内容的提问来源于stack exchange,提问作者Michael Kintscher they-them
相关产品推荐
相关产品推荐

