组合框绑定ItemSource到自定义列表并将SelectedItem绑定到该列表的实例无效。

5

我尝试了很多方法来让我的组合框工作,但我还是卡住了 :(

这是我的应用程序的一个非常简化版本:(刚刚编辑过,对错误感到抱歉)

<ListView ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}"/>
<ComboBox ItemsSource="{Binding Grades}" SelectedItem="{Binding SelectedPerson.MyGrade}" 
     DisplayMemberPath="Name"/>

并且代码背后:

public class Person
{
    private string name;
    public string Name
    {
        get { return name; }
        set
        {
            if (name != value)
            {
                name = value;
                NotifyPropertyChanged("Name");
            }
        }
    }

    private Grade myGrade;
    public Grade MyGrade
    {
        get { return myGrade; }
        set
        {
            if (myGrade != value)
            {
                myGrade = value;
                NotifyPropertyChanged("MyGrade");
            }
        }

    }

    //-- INotifyPropertyChanged implementation
}
public class Grade
{
    private string name;
    public string Name
    {
        get { return name; }
        set
        {
            if (name != value)
            {
                name = value;
                NotifyPropertyChanged("Name");
            }
        }
    }

    private int prop;
    public int Prop
    {
        get { return prop; }
        set
        {
            if (prop != value)
            {
                prop = value;
                NotifyPropertyChanged("Prop");
            }
        }

    }

    //-- INotifyPropertyChanged implementation
}
public partial class MainWindow : Window
{
    public ObservableCollection<Person> People { get; set; }
    public ObservableCollection<Grade> Grades { get; set; }

    private Person selectedPerson;
    public Person SelectedPerson
    {
        get { return selectedPerson; }
        set
        {
            if (selectedPerson != value)
            {
                selectedPerson = value;
                NotifyPropertyChanged("SelectedPerson");
            }
        }
    }

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;

        People = new ObservableCollection<Person>();
        Grades = new ObservableCollection<Grade>();

        Grades.Add(new Grade() { Name = "Grade 1", Prop = 1 });
        Grades.Add(new Grade() { Name = "Grade 2", Prop = 2 });

        People.Add(new Person() { Name = "guy 1", MyGrade = Grades[0] });
        People.Add(new Person() { Name = "guy 2", MyGrade = Grades[0] });
        People.Add(new Person() { Name = "guy 3", MyGrade = Grades[1] });
    }

    //-- INotifyPropertyChanged implementation
}

问题在于当我在listview中选择项目时,combobox仍然为空。 Itemsource是正确的(如果我点击combobox,我可以看到“grade 1”和“grade 2”)。 我认为需要一些东西来告诉“Person.Grade是Grades列表的一部分”,但我找不到它的位置。
希望你能帮助我;)

1
“SelectedItem”是否与“ItemsSource”中的项目是相同的内存引用?默认情况下,WPF将通过引用比较项目,因此这可能是您的问题所在。如果它们不同,则建议使用“SelectedValue”或可能覆盖“.Equals()”。请参见此处以获取示例 :) - Rachel
我认为问题可能出在这样的地方,我觉得所选项和项源不是来自同一个引用。今晚我会测试一下并告诉你 ;) - anthoLB29
非常感谢!!equals重写方法救了我 :) :) - anthoLB29
很高兴那对你有用 :) 我已经在下面发布了它作为答案,以防其他人也想解决同样的问题。 - Rachel
3个回答

8

SelectedItemItemsSource中的项在内存中是否是完全相同的引用?

默认情况下,WPF会按照引用比较SelectedItemItemsSource中的项,如果它们在内存中不是相同的引用,则不会返回匹配的项。

如果您无法在代码中执行此操作,则最常见的解决方法是:

  • Bind the SelectedValue to a value type instead of a reference type, and set the SelectedValuePath

    <ComboBox ItemsSource="{Binding Grades}" 
              SelectedValue="{Binding SelectedPerson.MyGrade.GradeId}" 
              SelectedValuePath="GradeId"
              DisplayMemberPath="Name"/>
    
  • Or override the .Equals() to ensure the two objects are considered equal when specific properties match, as opposed to being considered equal when the reference in memory matches.

    public override bool Equals(object obj) 
    { 
        if (obj == null || !(obj is Grade)) 
            return false; 
    
        return ((Grade)obj).GradeId == this.GradeId); 
    }
    

2

首先,你绑定的是 Grade,但你的属性名叫做 MyGrade。其次,在 Grades 列表中实例化了不同的 grade 对象,并使用不同的对象分配给每个人,如果你想要正确映射它们,应该使用相同的对象,就像这样:

Grades.Add(new Grade() { Name = "Grade 1", Prop = 1 });
Grades.Add(new Grade() { Name = "Grade 2", Prop = 2 });

People.Add(new Person() { Name = "guy 1", MyGrade = Grades[0] });
People.Add(new Person() { Name = "guy 2", MyGrade = Grades[0] });
People.Add(new Person() { Name = "guy 3", MyGrade = Grades[1] });

最后,如果您将Grades集合绑定到UI,则可能也希望将其设置为ObservableCollection。此外,您的RaisePropertyChanged没有使用属性名称似乎有点奇怪。
可能还有其他错误,但这是我立即注意到的。

是的,你需要将 MainWindow 中的 Grades 设为 ObservableCollection。此外,你还需要为它引发 INotifyPropertyChanged 事件。虽然 Grades 属性已经被设置了,但绑定并没有被通知去寻找新的集合。 - Erik
谢谢指出错误 :) 我写的“简化应用程序”太快了...不幸的是,问题仍然存在。我将编辑我的帖子并进行更正。 - anthoLB29

0
请确保首先没有绑定错误。您可以通过在Visual Studio中打开输出窗口并检查是否有与绑定表达式相关的错误消息来验证此操作。
您应该能够轻松地发现这些,因为错误消息将包含以下文本:BindingExpression path error 作为替代方法,为什么不尝试直接将ComboBox选定项与ListView中的选定项绑定呢?
以下是一个示例片段,说明如何实现此操作:
<ListView
    x:Name="listView"
    ItemsSource="{Binding People}"
    DisplayMemberPath="Name" />
<ComboBox 
    ItemsSource="{Binding Grades}" 
    SelectedItem="{Binding SelectedItem.MyGrade, ElementName=listView}" 
    DisplayMemberPath="Name"/>

谢谢您的回答,但我没有绑定错误。 我不明白直接绑定listview项的意义。 - anthoLB29
之前的回复是尝试完全绕过属性并检查当从列表视图中选择项目时,组合框是否会更新。如果是这样,那么ViewModel中就会有问题。我使用提供的代码成功地复制了您的场景。您在类定义中省略了接口INotifyPropertyChanged。我不知道您是否为简洁而省略了这些内容,但如果删除它们,您将遇到确切的问题。 - Richard Vella
将您的类定义更改如下:public class Person : INotifyPropertyChanged public class Grade : INotifyPropertyChanged public partial class MainWindow : Window, INotifyPropertyChanged - Richard Vella

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