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>
问题原因
- ListBox默认显示逻辑限制:当前
ListBox未自定义ItemTemplate,默认调用Material.ToString()显示内容。但ToString()仅在对象初始化或显式调用时生成字符串,Amount属性变化不会自动触发ToString()重新计算,因此ListBox不会刷新显示。 - 不必要的集合引用替换:
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
相关产品推荐
相关产品推荐

