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

WPF MVVM模式下ListBox绑定数据执行命令后不更新问题

WPF MVVM中ListBox绑定数据不更新问题解决

问题描述

基于MVVM模式开发WPF应用,执行命令修改Material数据后,ListBox中的内容无法更新,但用于验证的TextBlock显示正常。Material类已实现INotifyPropertyChanged接口。

代码片段

ViewModel代码

class FirstVM : ObservableObject
{
    private ObservableCollection<DetailScheme> _detailsScheme;
    private string _text;
    private ObservableCollection<Material> _materials;
    private ObservableCollection<Detail> _detail;

    public string Text
    {
        get { return _text; }
        set
        {
            _text = value;
            OnPropertyChanged();
        }
    }
    public ObservableCollection<DetailScheme> DetailScheme 
    { 
        get { return _detailsScheme; } 
    }
    
    public ObservableCollection<Material> Materials 
    {
        get { return _materials; } 
        set { 
                _materials = value;
                OnPropertyChanged();
            }
    }
    public ObservableCollection<Detail> Details 
    { 
        get { return _detail; } 
        set { 
                _detail = value; 
                OnPropertyChanged(); 
            }
    }
    public RelayCommand ProduceCommand { get; set; }

    public FirstVM () 
    {
        _detailsScheme = GetDetailsScheme("D:/ DetailsScheme.json");
        _materials = GetMaterials("D:/ Materials.json");
        _detail = GetDetails("D:/ Details.json");
        ProduceCommand = new RelayCommand(o => 
        {
            Materials = GetMaterialsAfterNormalBatch(Materials, (o as DetailScheme).Materials);
            Text = Materials[0].ToString();
        });
    }
}

Material类代码

class Material : ObservableObject
{
    private string _name;
    private int _amount;

    public int Amount { 
        get { return _amount; }
        set { 
                _amount = value;
                OnPropertyChanged();
            }  
        }

    public string Name { 
        get { return _name; }
        set { _name = value; }
    }

    public Material(string name, int amount)
    {
        _name = name;
        _amount = amount;
    }
    
    public static ObservableCollection<Material> GetMaterials(string path)
    {
        ObservableCollection<Material> materials;
        using (StreamReader sr = new StreamReader(path))
        {
            materials = JsonConvert.DeserializeObject<ObservableCollection<Material>>(sr.ReadToEnd());
        }
        return materials;
    }
    public static ObservableCollection<Material> GetMaterialsAfterNormalBatch(ObservableCollection<Material> allMaterials, ObservableCollection<Material> requiredMaterials)
    {
        for (int i = 0; i < allMaterials.Count; i++)
            for (int j = 0; j < requiredMaterials.Count; j++)
                if (allMaterials[i].Name == requiredMaterials[j].Name)
                    allMaterials[i].Amount -= requiredMaterials[j].Amount;
        return allMaterials;
    }
    public override string ToString()
    {
        return Name+$" : {Amount}";
    }
}

View代码

<UserControl x:Class="Storage_Manager.MVVM.View.Task_1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Storage_Manager.MVVM.View"
             xmlns:VM="clr-namespace:Storage_Manager.MVVM.ViewModel"
             xmlns:View="clr-namespace:Storage_Manager.MVVM.View"
             mc:Ignorable="d" 
             d:DesignHeight="515" d:DesignWidth="700">
    <UserControl.DataContext>
        <VM:FirstVM/>
    </UserControl.DataContext>
    <Grid>
        <ComboBox x:Name="BoxOfDetails"
                  Height="50" 
                  Width="250"  
                  SelectedIndex="0"
                  Margin="10,10,440,455"
                  ItemsSource="{Binding DetailScheme}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Text="{Binding Name}" FontSize="18" />
                        <TextBlock Text="{Binding }" FontSize="9" Margin="0,7,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <Button Content ="Produce"
                Width="200"
                Margin="60,460,440,10"
                Command="{Binding ProduceCommand}"
                CommandParameter="{Binding ElementName=BoxOfDetails, Path=SelectedItem}"/>
        <ListBox ItemsSource="{Binding Materials}" 
                 Background="Transparent" 
                 Foreground="white"
                 BorderThickness="0"
                 IsHitTestVisible="False"/>
        <TextBlock Foreground="DarkGray"
                   Text="{Binding Text}"/>
    </Grid>
</UserControl>

问题原因

  1. ListBox默认显示逻辑限制:当前ListBox未自定义ItemTemplate,默认调用Material.ToString()显示内容。但ToString()仅在对象初始化或显式调用时生成字符串,Amount属性变化不会自动触发ToString()重新计算,因此ListBox不会刷新显示。
  2. 不必要的集合引用替换:ProduceCommand中重新赋值Materials属性为同一个集合实例,虽然触发了OnPropertyChanged,但WPF绑定检测到集合引用未变化,不会强制重新渲染,且这一步操作完全多余——因为GetMaterialsAfterNormalBatch已经直接修改了原集合内的对象属性。

解决方案

1. 为ListBox添加自定义ItemTemplate

显式绑定到Material的Name和Amount属性,这样当属性变化时,WPF绑定系统会自动更新UI:

<ListBox ItemsSource="{Binding Materials}" 
         Background="Transparent" 
         Foreground="white"
         BorderThickness="0"
         IsHitTestVisible="False">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}" Margin="0,0,5,0"/>
                <TextBlock Text=":" Margin="0,0,5,0"/>
                <TextBlock Text="{Binding Amount}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

2. 优化ViewModel中的命令逻辑

移除不必要的Materials属性赋值,直接修改集合内的对象即可:

ProduceCommand = new RelayCommand(o => 
{
    GetMaterialsAfterNormalBatch(Materials, (o as DetailScheme).Materials);
    Text = Materials[0].ToString();
});

说明

修改后,Material.Amount属性变化会通过INotifyPropertyChanged通知绑定系统,ListBox的ItemTemplate会自动更新对应项的显示;同时避免了多余的集合引用赋值,提升代码效率。

内容的提问来源于stack exchange,提问作者zahar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 20:44:52