使用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
一个类。 ActivityViewModel
有Activity
类型Activity
财产,其中ActivityStatuses
类型ObservableCollection<ActivityStatus>
类型的财产。在我有一个ComboBox
声明如下:
<ComboBox ItemsSource="{Binding ActivityStatuses}"
SelectedItem="{Binding Activity.Status, Mode=TwoWay}"
DisplayMemberPath="Name" />
这让我选择从ComboBox
的ActivityStatus
,这在视图模型的Activity
属性正确更新Activity
的Status
财产。问题在于双向绑定...当加载新的Activity
时,ComboBox.SelectedItem
不会更新以显示Activity.Status
属性值。
使用ComboBox
的该声明,SelectedItem
势必在Activity
的ActivityStatus
对象,这是一个不同的目的是在一个与所述视图模型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。我不确定为什么运行应用程序不再执行完整的构建,但至少我知道在运行应用程序之前必须先修改该项目。
最后更新>>>
我刚刚发现,为什么我的数据类型项目没有建设......我看着在配置管理器窗口,只见那莫名其妙的平台是不正确的这一项目,并生成复选框已经变得无法检查!我不知道这是怎么发生的,但是我终于明白了问题的根源让我感到宽慰。
我有一些坏消息给你。它应该工作。在其他地方有一个错误/意外的副作用导致您的问题。
我把一个快速项目放在一起,做你想做的事情。喜欢在这里看到它。
创建一个名为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集合中。
那么为什么这个工作,但你的代码不是?我不确定。你的问题在于你的代码的其他地方。
人,如果我跟着你的问题正是我不知道,但是当你说
加载一个新的活动时,
你添加新的Activity
您ActivityStatuses
收藏?因为如果你不是那么我很确定绑定不起作用,因为SelectedItem需要在ItemsSource中。
只是一个想法。
这里有什么是经典的“项目参考/文件参考”问题。您应该删除对解决方案中其他项目的所有引用,并将它们重新添加为**项目引用**。在构建或更改构建类型(如调试/发布)时,文件引用不会自动更新。 *不要浏览到/ bin目录以添加引用!*有关** Project References ** [请检查此msdn文章]的更多信息(http://msdn.microsoft.com/zh-cn/library/ez524kew.aspx ) – Will 2011-05-30 17:42:54
谢谢@威尔,但我刚刚发现为什么我的数据类型项目不在构建中......我查看了Configuration Manager窗口,发现该平台对于该项目来说不正确,并且Build复选框未被选中!我不知道这是怎么发生的,但是我终于明白了问题的根源让我感到宽慰。 – Sheridan 2011-06-04 00:04:02