当属性更改时,ListBox项目未更新

8

当集合的属性更改(添加/删除项目)时,我遇到了更新包含ObservableCollection的Listbox的问题:

Listbox设置为ItemsSource="{Binding Path=AllPerson}",代码后台中的数据上下文设置如下:this.DataContext = allPersonClass;

allPersonClass包含ObservableCollection<Person> allPerson

Person包含像Name等属性。

我重写了person的ToString方法,以返回Name属性,因此listBox显示有效数据。

我尝试让Person实现INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(object sender, string propertyName) {
    if (this.PropertyChanged != null) {
        PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
    }
}

public string Name {
     get { return name; }
     set {
        name = value;
        onPropertyChanged(this, "allPersonClass");
     }
}

每个属性的设置器都有 onPropertyChanged(this, "propertyName"); ,这个语句会被执行,但listBox从未更新已创建的项。
你知道可能有什么问题吗?
以下是包含listBox XAML的窗口:
     <Button x:Name="btnDetail" Content="Detail" HorizontalAlignment="Left" Margin="361,249,0,0" VerticalAlignment="Top" Width="75" Click="ButtonDetailClick"/>
     <ListBox x:Name="listPerson" ItemsSource="{Binding Path=AllPerson}" HorizontalAlignment="Left" Height="170" Margin="33,29,0,0" VerticalAlignment="Top" Width="155" IsSynchronizedWithCurrentItem="True"/>
     <Button x:Name="btnLoad" Content="Load" HorizontalAlignment="Left" Margin="58,249,0,0" VerticalAlignment="Top" Width="75" Click="btnLoad_Click"/>
     <Button x:Name="btnSave" Content="Save" HorizontalAlignment="Left" Margin="138,249,0,0" VerticalAlignment="Top" Width="75" Click="ButtonSaveClick"/>

这是DetailView窗口的一部分,用于进行修改(绑定到Person)

<TextBox Text="{Binding Path=Name}" Height="23" HorizontalAlignment="Left" Margin="118,20,0,0" Name="txtName" VerticalAlignment="Top" Width="141" />

这里是AllPersonClass的一部分:

public class AllPersonClass {
  private ObservableCollection<Person> allPerson;

public AllPersonClass() {
     allPerson = new ObservableCollection<Person>();
  }

public ObservableCollection<Person> AllPerson {
     get { return allPerson; }
     set { allPerson = value; }
  }

 public void addPerson(Person newPerson) {
     allPerson.Add(newPerson);
  }


public Person getPerson(int personIndex) {
     return allPerson[personIndex];
  }
}

编辑

这里是保存详细视图更改的相关方法部分。

private void OnBtnSaveClick(object sender, RoutedEventArgs e) {
     person.Name = txtName.Text;
     person.SurName = txtSurName.Text;
}

请注意,更改只在“ObservableCollection allPerson”中进行,listBox仍显示旧数据。

2
为了帮助解决问题,您需要发布一份Xaml示例以及显示属性更改通知的ViewModel实现示例。 - cunningdave
请提供您的allPersonClass类的实现。 - Jehof
1
我想我需要一些睡眠,然后我会研究它并发布我的进展。 - Abdul
那个属性更改引发器让我紧张不安。幸运的是,至少有一个答案解决了这个问题。 - Meirion Hughes
4个回答

12

如果你想在更改Person属性时更新UI,你需要通知该属性已更改,而不是通知整个类别

public string Name 
{
     get { return name; }
     set 
     {
        name = value;
        onPropertyChanged(this, "allPersonClass");
     }
}

<TextBox Text="{Binding Path=Name}" .....

应该是这样的

public string Name 
{
     get { return name; }
     set 
     {
        name = value;
        onPropertyChanged(this, "Name");
     }
}

<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" .....

你不需要在Person模型上重写ToString()方法来在ListBox中正确显示,你可以设置ListBoxDisplayMemberPath属性来显示你想要在ListBox中显示的属性。

  <ListBox ItemsSource="{Binding Path=AllPerson}" DisplayMemberPath="Name" />

编辑:回答你的评论问题:

public string Name 
{
     get { return name; }
     set 
     {
        name = value;
        onPropertyChanged(this, "Name");
        onPropertyChanged(this, "Display");
     }
}

public string Surname
{
     get { return surname; }
     set 
     {
        surname= value;
        onPropertyChanged(this, "Surname");
        onPropertyChanged(this, "Display");
     }
}

public string Display
{
     get { return Name + " " + Surname; }
}

这有一点帮助,但并不能满足我的需求:使用DisplayMemberPath="Name"可以正常工作,但在我的列表中只显示名称(并且在更改后正确更新)。当我尝试显示更多信息,例如´listPerson.DisplayMemberPath = "Display";´其中´Display´是´Person´的属性并返回´Name + " " + Surname´时,它再次无法正常工作。 - Abdul
@Abdul,如果您更改了“Display”中使用的任何属性,请确保调用“onPropertyChanged(this,“Display”);”,您必须告诉UI该属性已更改,因此在“Name”和“Surname”中,您将调用“onPropertyChanged”两次,一次是为了属性,一次是为了“Display”。 - sa_ddam213
1
@Abdul,在答案中添加了示例。 - sa_ddam213
它可以使用´listPerson.DisplayMemberPath = "Display";´,但它会立即更改listBox的值(或在textBox失去焦点后),我希望只有在详细窗口中按下保存按钮并手动更改Person类后才对listBox进行更改。 - Abdul
这是一个非常流行的问题,而且在MVVM和WPF中并不容易解决。我建议您提出一个新的SO问题,或者查看现有的问题是否指向了正确的方向。 - sa_ddam213
显示剩余2条评论

5
在您的DetailView中,您需要定义一个双向绑定来更新您的person类的属性名称。
<TextBox Text="{Binding Path=Name, Mode=TwoWay}" Height="23" HorizontalAlignment="Left" Margin="118,20,0,0" Name="txtName" VerticalAlignment="Top" Width="141" />

如果您希望在每次编辑文本时更新属性,还需要使用 PropertyChanged 定义 UpdateSourceTrigger。否则,当文本框失去焦点时才会更新该属性。

<TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="118,20,0,0" Name="txtName" VerticalAlignment="Top" Width="141" />

在“Person”类中的属性在点击保存按钮后需要手动更新(请参见EDIT),ListBox可能无法接收到属性更改事件。 - Abdul

4
你的问题是你的集合从未被通知到你的属性更改。以下内容可能会对你有所帮助。
ObservableCollection<INotifyPropertyChanged> items = new ObservableCollection<INotifyPropertyChanged>();
    items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(items_CollectionChanged);


    static void items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        foreach (INotifyPropertyChanged item in e.OldItems)
            item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);

        foreach (INotifyPropertyChanged item in e.NewItems)
            item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
    }

    static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

请参考此链接


在每个循环之前添加if (e.OldItems != null)或if (e.NewItems != null)会导致抛出NotImplementedException - 现在我正在尝试根据您链接中的答案进行实现。 - Abdul
1
@Abdul,也许这个云可以帮助你 [LINK] (https://dev59.com/sW455IYBdhLWcg3wAvRI) - WiiMaxx
您没有取消订阅事件。 - klm_
@klm_,你在CollectionChanged事件上需要做什么?这只是为了表明他需要绑定到此事件,他如何实现订阅和取消订阅将成为他代码的一部分。 - WiiMaxx

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接