使用MVVM的WPF组合框的双向绑定问题

问题描述:

我有一个具有许多属性的Activity对象。其中之一是:使用MVVM的WPF组合框的双向绑定问题

public ActivityStatus Status 
{ 
    get { return status; } 
    set { status = value; NotifyPropertyChanged("Status"); } 
} 

ActivityStatus类只有两个属性:

public Guid Guid 
{ 
    get { return guid; } 
    set { guid = value; NotifyPropertyChanged("Guid"); } 
} 
public string Name 
{ 
    get { return name; } 
    set { name = value; NotifyPropertyChanged("Name"); } 
} 

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

我有一个ActivityViewModel类为一体的DataContext一个类。 ActivityViewModelActivity类型Activity财产,其中ActivityStatuses类型ObservableCollection<ActivityStatus>类型的财产。在我有一个ComboBox声明如下:

<ComboBox ItemsSource="{Binding ActivityStatuses}" 
      SelectedItem="{Binding Activity.Status, Mode=TwoWay}" 
      DisplayMemberPath="Name" /> 

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

使用ComboBox的该声明,SelectedItem势必在ActivityActivityStatus对象,这是一个不同的目的是在一个与所述视图模型ActivityStatuses属性相同的值。因此,WPF框架不认为这些项目是相同的,并且不会选择ComboBox中的项目。

如果我每次加载后Activity从分配与到Activity.Status属性相同的值集合中的项目,则ComboBox找到匹配它ItemsSource收集并正确设置SelectedItem属性显示的值。我不想这样做,但因为我在Activity类中有许多其他类似的属性,所以我将不得不在任何地方重复此代码,以便双向绑定到ComboBox es。

所以我也试过如下结合ActivityStatus.Guid属性:

<ComboBox ItemsSource="{Binding ActivityStatuses}" 
      SelectedValue="{Binding Activity.Status.Guid, Mode=TwoWay}" 
      SelectedValuePath="Guid" 
      DisplayMemberPath="Name" /> 

加载不同Activity对象时,这正确选择的对象具有相同Guid作为一个从ComboBox.ItemsSource集合Activity.Status财产。此方法的问题是SelectedValue绑定到ActivityStatus对象中的ActivityStatus.Guid属性,因此当更改UI中的值时,只有ActivityStatus对象的'Guid'属性会更新,而名称保持不变。除Guid属性的值外,Activity.Status属性中的对象不会更改。

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

我只是希望能够选择在ComboBox一个项目,因此有Activity.Status对象的变化和改变从代码Activity.Status属性的值,并有ComboBox.SelectedItem也相应更新。我会很感激任何建议。

UPDATE >>>

读数的响应后,我尽了代码样本中的新的解决方案,并看到它的工作如预期。然后我仔细检查了他的代码,发现它和我的代码一样,所以再次运行我自己的解决方案(这是本文以来的第一次)。令我惊讶的是,它没有改变任何代码就按预期工作!

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

因此,应用程序必须使用一个较旧的dll文件,我的更改没有被使用......我确实想知道为什么我的断点在Equals方法从未被击中。这导致了我的假设,即实施Equals方法无济于事。 Visual Studio今天有同样的行为,这就是我发现发生了什么。

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

最后更新>>>

我刚刚发现,为什么我的数据类型项目没有建设......我看着在配置管理器窗口,只见那莫名其妙的平台是不正确的这一项目,并生成复选框已经变得无法检查!我不知道这是怎么发生的,但是我终于明白了问题的根源让我感到宽慰。

+0

这里有什么是经典的“项目参考/文件参考”问题。您应该删除对解决方案中其他项目的所有引用,并将它们重新添加为**项目引用**。在构建或更改构建类型(如调试/发布)时,文件引用不会自动更新。 *不要浏览到/ bin目录以添加引用!*有关** Project References ** [请检查此msdn文章]的更多信息(http://msdn.microsoft.com/zh-cn/library/ez524kew.aspx ) – Will 2011-05-30 17:42:54

+1

谢谢@威尔,但我刚刚发现为什么我的数据类型项目不在构建中......我查看了Configuration Manager窗口,发现该平台对于该项目来说不正确,并且Build复选框未被选中!我不知道这是怎么发生的,但是我终于明白了问题的根源让我感到宽慰。 – Sheridan 2011-06-04 00:04:02

我有一些坏消息给你。它应该工作。在其他地方有一个错误/意外的副作用导致您的问题。

我把一个快速项目放在一起,做你想做的事情。喜欢在这里看到它。

创建一个名为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; 
    } 
} 

现在打开主窗口并粘贴此在:

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

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

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

那么为什么这个工作,但你的代码不是?我不确定。你的问题在于你的代码的其他地方。

人,如果我跟着你的问题正是我不知道,但是当你说

加载一个新的活动时,

你添加新的ActivityActivityStatuses收藏?因为如果你不是那么我很确定绑定不起作用,因为SelectedItem需要在ItemsSource中。

只是一个想法。