XAML 列表框中的WPF绑定数据未更新

cmssoen2  于 12个月前  发布在  其他
关注(0)|答案(2)|浏览(153)

我试图创建WPF应用程序以下MVVM模式everithing是好的,直到我开始绑定命令更改数据。我不明白为什么后,命令执行信息在我的列表框不更新。
下面是我的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 => 
        {
                        //this method return new ObservableCollection
            Materials = GetMaterialsAfterNormalBatch(Materials, (o as DetailScheme).Materials);
                Text = Materials[0].ToString();
        });
        
    }

字符串
材料类别:

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}";
        }
    }


和视图:

<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>


我添加了TextBlock来查看单击按钮后信息是否更新,是的,每次单击后TextBlock数据都会更改,但ListBox保持不变。Material类实现了INotifyPropertyChanged。

syqv5f0l

syqv5f0l1#

我认为问题在于你没有直接绑定到Material项的属性,而是让ToString()来完成这项工作,这是直接显示数据,但缺乏更改通知。一个只使用XAML的解决方案,保持C#代码不变,将是:

<ListBox ItemsSource="{Binding Materials}" 
                     Background="Transparent" 
                     Foreground="white"
                     BorderThickness="0"                     
                     IsHitTestVisible="False">
    <ListBox.ItemTemplate>
        <DataTemplate>
              <StackPanel Orientation="Horizontal">
                  <!-- ToDo tweak Widths by yourself according to you
                   material name and amout values widths, so that they look nicely aligned -->
                  <TextBlock MinWidth="150" Text={Binding Name, Mode=OneWay}" />
                  <TextBlock MinWidth="100" Text={Binding Amount, Mode=OneWay, StringFormat=N0}" 
                             TextAlignment="Right" />
              </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

字符串
你也可以添加一个“DisplayText”字符串属性到Material类,让它像ToString现在一样返回Name + Amount,并将其用作ListBox的DisplayMemberPath
另一种解决方案是在GetMaterialsAfterNormalBatch中创建一个new ObservableCollection,并且不返回传入的allMaterials集合。然后UI更新将通过CollectionChanged进行。但实际上,如果只有项目成员数据更改,则不建议重新创建新集合。

ttcibm8c

ttcibm8c2#

根据注解中的附加说明,我猜您需要使用如下代码:

public ObservableCollection<Material> Materials {get;} = new();

    public RelayCommand ProduceCommand { get;}

    public FirstVM () 
    {
        _detailsScheme = GetDetailsScheme("D:/ DetailsScheme.json");
        _materials = GetMaterials("D:/ Materials.json");
        _detail = GetDetails("D:/ Details.json");
        ProduceCommand = new RelayCommand(o => 
        {
                        //this method return new ObservableCollection
            _materials = GetMaterialsAfterNormalBatch(Materials, (o as DetailScheme).Materials);
            Materials.Clear();
            foreach(var m in _materials)
            {
                Materials.Add(m);
            }
            Text = Materials[0].ToString();
        });
        
    }

字符串

**P.S.**但是我仍然对“Get”方法有很大的怀疑。根据参数和名称判断,你在其中得到了Materials集合的一个投影。下一次调用它时,它将是一个投影的投影,然后投影链将不断增长。

如果你能解释一下这个方法背后的逻辑,也许我能提出一个更好的解决方案来实现它。

在添加物料分类代码后添加到答案。

1.取Mshu基类代码BaseInpc and RelayCommand classes.
1.加入性质以显示“材料”显示视图,并在其变更时显示通知。

public class Material : BaseInpc
    {
        private string _name = string.Empty;
        private int _amount;

        public int Amount { get => _amount; set => Set(ref _amount, value); }
        public string Name { get => _name; set => Set(ref _name, value ?? string.Empty); }

        public Material(string name, int amount)
        {
            Name = name;
            Amount = amount;
        }
        public override string ToString()
        {
            return Name + $" : {Amount}";
        }

        public string DisplayName => ToString();

        protected override void OnPropertyChanged(string propertyName, object? oldValue, object? newValue)
        {
            base.OnPropertyChanged(propertyName, oldValue, newValue);
            RaisePropertyChanged(nameof(DisplayName));
        }


1.“GetMaterialsAfterNormalBatch”方法不应返回集合,它只是修改传递给它的集合。

public static void GetMaterialsAfterNormalBatch(IEnumerable<Material> allMaterials, IEnumerable<Material> requiredMaterials)
        {
            foreach (Material  material in allMaterials)
            {
                foreach (var req in requiredMaterials)
                {
                     if (material.Name == req.Name)
                        material.Amount -= req.Amount;
                }
            }
        }

  1. ListBox默认输出ToString()值,该值不更新,因此需要设置要与更改通知一起显示的属性值。
<ListBox ItemsSource="{Binding Materials}" 
             Background="Transparent" 
             Foreground="white"
             BorderThickness="0"
             IsHitTestVisible="False"
             DisplayMemberPath="DisplayName"/>


1.该命令不需要更改集合本身。您只需要更改其元素的属性值:

ProduceCommand = new RelayCommand<DetailScheme>(ds => 
        {
                        //this method return new ObservableCollection
            GetMaterialsAfterNormalBatch(Materials, ds.Materials);
            Text = Materials[0].ToString();
        });

  1. Text属性对我来说也是不必要的。这个值可以直接在XAML中获得:
<TextBlock Foreground="DarkGray"
               Text="{Binding Materials[0].DisplayName}"/>

相关问题