You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:29:53