使用MVVM的WPF ComboBox的双向绑定问题

She*_*dan 12 c# data-binding wpf combobox mvvm

我有一个Activity具有许多属性的对象.其中一个如下:

public ActivityStatus Status
{
    get { return status; }
    set { status = value; NotifyPropertyChanged("Status"); }
}
Run Code Online (Sandbox Code Playgroud)

ActivityStatus班只有两个属性:

public Guid Guid
{
    get { return guid; }
    set { guid = value; NotifyPropertyChanged("Guid"); }
}
public string Name
{
    get { return name; }
    set { name = value; NotifyPropertyChanged("Name"); }
}
Run Code Online (Sandbox Code Playgroud)

Equals方法:

public override bool Equals(object otherObject)
{
    if (!(otherObject is ActivityStatus)) return false;
    return Equals(otherObject as ActivityStatus);
}
public bool Equals(ActivityStatus otherStatus)
{
    if (!(otherStatus is ActivityStatus) || otherStatus == null) return false;
    return Guid == otherStatus.Guid && Name == otherStatus.Name;
}
Run Code Online (Sandbox Code Playgroud)

我有一个ActivityViewModel类作为DataContext一个的ActivityView类.该ActivityViewModel有一个Activity类型的属性Activity等等,并且一个ActivityStatuses类型的属性ObservableCollection<ActivityStatus>.在ActivityView我有一个ComboBox声明如下:

<ComboBox ItemsSource="{Binding ActivityStatuses}" 
          SelectedItem="{Binding Activity.Status, Mode=TwoWay}"
          DisplayMemberPath="Name" />
Run Code Online (Sandbox Code Playgroud)

这让我选择ActivityStatusComboBox这个正确更新Status的财产ActivityActivity该视图模型的财产.问题在于双向绑定...当加载新的时Activity,ComboBox.SelectedItem不会更新以显示Activity.Status属性值.

使用此声明ComboBox,将SelectedItem绑定到该ActivityStatus对象中,Activity并且这是与viewmodel ActivityStatuses属性中具有相同值的对象不同的对象.因此,WPF框架不认为项目是相同的,也不会选择项目中的项目ComboBox.

如果我Activity.Status在加载每个项目后将具有相同值的项目分配给属性Activity,则ComboBox在其ItemsSource集合中查找匹配项并正确设置SelectedItem显示该值的属性.我真的不想这样做,因为我在Activity类中有许多其他类似的属性,我将不得不重复这个代码,我希望双向绑定到ComboBoxes.

所以我也尝试绑定到ActivityStatus.Guid属性如下:

<ComboBox ItemsSource="{Binding ActivityStatuses}" 
          SelectedValue="{Binding Activity.Status.Guid, Mode=TwoWay}"
          SelectedValuePath="Guid" 
          DisplayMemberPath="Name" />
Run Code Online (Sandbox Code Playgroud)

此正确地选择的对象具有相同的Guid作为一个在Activity.Status从属性ComboBox.ItemsSource加载不同的时收集Activity的对象.此方法的问题SelectedValue在于绑定到对象中的ActivityStatus.Guid属性ActivityStatus,因此在更改UI中的值时,只ActivityStatus更新对象的"Guid"属性,保持名称不变.Activity.StatusGuid属性值外,属性中的对象不会更改.

正如您所看到的,我也尝试实现该Equals方法,因为我假设ComboBox将使用它来比较对象,但它没有任何区别.所以最后,我不知所措,并希望找到一个简单的方法来解决这个问题...希望,有一个简单的属性,我错过了ComboBox.

我只是希望能够选择一个项目,ComboBoxActivity.Status相应地更改对象,并Activity.Status从代码中更改属性的值,并相应地ComboBox.SelectedItem更新.我很感激任何建议.

更新>>>

在阅读了Will的回复之后,我在一个新的解决方案中尝试了他的代码示例,并看到它按预期工作.然后我检查他的代码thorouhly一看,原来是和我一样,所以又跑我自己的解决方案(以来这个职位的第一次).令我完全惊讶的是,它没有改变任何代码而按预期工作!

这让我很困惑,我花了一些时间才知道发生了什么.事实证明问题是/是Visual Studio 2010!我已经将Equals方法添加到我的数据类型作为最后一个阶段.出于某种原因,Visual Studio在运行应用程序时未构建数据类型项目.

因此,应用程序必须已使用旧的dll文件,并没有使用我的变化......我不知道为什么在我的破发点Equals的方法从来没有击中.这导致我的假设是实施Equals方法并没有帮助.Visual Studio今天具有相同的行为,这就是我发现发生了什么的方式.

我在我的解决方案中检查了项目构建顺序,但是在顺序中的正确位置列出了数据类型项目.在运行应用程序时,Visual Studio中的"输出"窗口显示以不同顺序加载的项目dll.我不确定为什么运行应用程序不再进行完整的构建,但至少我知道在运行应用程序之前我必须在对其进行更改之后构建该项目.

最终更新>>>

我刚刚发现了为什么我的数据类型项目没有构建...我在Configuration Manager窗口中查看并看到该项目的平台不正确并且Build复选框已取消选中!我不知道这是怎么发生的,但是我终于明白了这个问题.

小智 12

我有一些坏消息要告诉你.它应该工作.在其他地方存在导致问题的错误/意外副作用.

我把一个快速的项目拼凑起来做你想要做的事情.喜欢在这里看到它.

创建一个名为NestedProperties的新WPF项目.向根添加一个新类并粘贴以下代码(我删除了很多东西,所以它有点难看):

public sealed class ViewModel : DependencyObject
{
    public ObservableCollection<Activity> Activities 
           { get; private set; }
    public ObservableCollection<ActivityStatus> Statuses 
           { get; private set; }

    public static readonly DependencyProperty 
        SelectedActivityProperty =
        DependencyProperty.Register(
            "SelectedActivity",
            typeof(Activity),
            typeof(ViewModel),
            new UIPropertyMetadata(null));
    public Activity SelectedActivity
    {
        get { return (Activity)GetValue(SelectedActivityProperty); }
        set { SetValue(SelectedActivityProperty, value); }
    }

    public ViewModel()
    {
        Activities = new ObservableCollection<Activity>();
        Statuses = new ObservableCollection<ActivityStatus>();

        // NOTE!  Each Activity has its own ActivityStatus instance.
        // They have the same Guid and name as the instances in
        // Statuses!!
        for (int i = 1; i <= 4; i++)
        {
            var id = Guid.NewGuid();
            var aname = "Activity " + i;
            var sname = "Status " + i;
            Activities.Add(new Activity
            {
                Name = aname,
                Status = new ActivityStatus
                {
                    Name = sname,
                    Id = id,
                    InstanceType = "Activity"
                }
            });
            Statuses.Add(new ActivityStatus
            {
                Name = sname,
                Id = id,
                InstanceType = "Collection"
            });
        }
    }
}

public sealed class Activity : DependencyObject
{
    public static readonly DependencyProperty NameProperty =
        DependencyProperty.Register(
            "Name",
            typeof(string),
            typeof(Activity),
            new UIPropertyMetadata(null));
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
    public static readonly DependencyProperty StatusProperty =
        DependencyProperty.Register(
            "Status",
            typeof(ActivityStatus),
            typeof(Activity),
            new UIPropertyMetadata(null));
    public ActivityStatus Status
    {
        get { return (ActivityStatus)GetValue(StatusProperty); }
        set { SetValue(StatusProperty, value); }
    }
}
public sealed class ActivityStatus
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    /// <summary>
    /// indicates if this instance came from 
    /// the ComboBox or from the Activity
    /// </summary>
    public string InstanceType { get; set; }
    public ActivityStatus()
    {
        Id = Guid.NewGuid();
    }
    public override bool Equals(object otherObject)
    {
        if (!(otherObject is ActivityStatus)) return false;
        return Equals(otherObject as ActivityStatus);
    }
    public bool Equals(ActivityStatus otherStatus)
    {
        if (!(otherStatus is ActivityStatus) ||
            otherStatus == null) return false;
        return Id == otherStatus.Id &&
            Name == otherStatus.Name;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在打开MainWindow并将其粘贴到:

<Window
    x:Class="NestedProperties.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"
    xmlns:t="clr-namespace:NestedProperties"
    SizeToContent="Height"
    MaxHeight="350"
    Width="525">
    <Window.DataContext>
        <t:ViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition
                Height="auto" />
            <RowDefinition
                Height="auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Label>Select an Activity:</Label>
        <ComboBox
            Grid.Row="1"
            ItemsSource="{Binding Activities}"
            SelectedItem="{Binding SelectedActivity}"
            DisplayMemberPath="Name" />
        <Label
            Grid.Column="1">Select a Status</Label>
        <ComboBox
            Grid.Row="1"
            Grid.Column="1"
            ItemsSource="{Binding Statuses}"
            SelectedItem="{Binding SelectedActivity.Status}"
            DisplayMemberPath="Name" />
        <ContentControl
            Grid.Row="2"
            Grid.ColumnSpan="2"
            Content="{Binding SelectedActivity}">
            <ContentControl.ContentTemplate>
                <DataTemplate>
                    <StackPanel>
                        <Label>Selected Activity:</Label>
                        <TextBlock
                            Text="{Binding Name}" />
                        <Label>Activity Status</Label>
                        <TextBlock
                            Text="{Binding Status.Name}" />
                        <Label>Status Id</Label>
                        <TextBlock
                            Text="{Binding Status.Id}" />
                        <Label>Status came from</Label>
                        <TextBlock
                            Text="{Binding Status.InstanceType}" />
                    </StackPanel>
                </DataTemplate>
            </ContentControl.ContentTemplate>
        </ContentControl>
    </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

当你运行它时,你会发现你有四个活动和四个状态.如果翻阅"活动"组合,您将看到每个"状态"都标记为" 活动",这意味着它是ViewModel构造函数中为"活动"指定的实例. 您还将看到状态组合框随着活动的更改而更改,这意味着该Equals方法正在运行.

接下来,更改每个活动的状态.您将看到状态的类型更改为Collection,这意味着此实例已创建并添加到构造函数中的Statuses集合中.

那么为什么这样可行,但你的代码不是?我不确定.您的问题出在代码的其他地方.