【问题标题】:Two-way binding problem with WPF ComboBox using MVVM使用 MVVM 的 WPF ComboBox 的双向绑定问题
【发布时间】:2011-09-02 23:35:14
【问题描述】:

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

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 类作为ActivityView 类的DataContextActivityViewModel 有一个Activity 类型为Activity 的属性,除此之外还有一个ActivityStatuses 类型为ObservableCollection<ActivityStatus> 的属性。在ActivityView 我有一个ComboBox 声明如下:

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

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

使用ComboBox 的此声明,SelectedItem 绑定到Activity 中的ActivityStatus 对象,这是与视图模型ActivityStatuses 属性中具有相同值的对象不同的对象。因此 WPF 框架不认为项目相同,不会选择 ComboBox 中的项目。

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

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

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

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

如您所见,我还尝试实现Equals 方法,因为我假设ComboBox 会使用它来比较对象,但它没有任何区别。所以最后,我不知所措并渴望找到一种简单干净的方法来解决这个问题......希望有一个我在ComboBox 上错过的简单属性。

我只是希望能够在ComboBox 中选择一个项目并相应地更改Activity.Status 对象并从代码中更改Activity.Status 属性的值并让ComboBox.SelectedItem 也相应地更新。如有任何建议,我将不胜感激。

更新>>>

阅读 Will 的回复后,我在新解决方案中尝试了他的代码示例,发现它按预期工作。然后我彻底检查了他的代码,发现它和我的一样,所以再次运行了我自己的解决方案(这是自这篇文章以来的第一次)。令我完全惊讶的是,它按预期工作,无需我更改任何代码!

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

所以应用程序一定是在使用旧的 dll 文件,而我的更改没有被使用...我确实想知道为什么我在 Equals 方法上的断点从未被命中。这导致我假设实施Equals 方法没有帮助。 Visual Studio 今天也有同样的行为,这就是我发现发生了什么的方式。

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

最终更新>>>

我刚刚发现为什么我的数据类型项目没有构建...我查看了配置管理器窗口,发现平台对于该项目不正确,并且构建复选框已被取消选中!我不知道这是怎么发生的,但我终于找到了问题的根源,这让我松了一口气。

【问题讨论】:

  • 您在这里遇到的是经典的“项目引用/文件引用”问题。您应该删除解决方案中对其他项目的所有引用,并将它们重新添加为项目引用。文件引用不会在构建或更改构建类型(例如,调试/发布)时自动更新。 永远不要浏览到 /bin 目录来添加引用! 更多关于项目引用的信息check this msdn article
  • 谢谢@Will,但我刚刚发现为什么我的数据类型项目没有构建...我查看了配置管理器窗口,发现平台对于该项目和构建复选框不正确已经变得不受控制了!我不知道这是怎么发生的,但我终于找到了问题的根源,这让我松了一口气。

标签: c# wpf data-binding mvvm combobox


【解决方案1】:

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

我整理了一个快速项目来完成您想做的事情。喜欢在这里看到它。

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

现在打开 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>

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

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

那么为什么这是有效的,但你的代码不是?我不知道。您的问题出在代码的其他地方。

【讨论】:

    【解决方案2】:

    伙计,我不知道我是否完全按照你的问题,但是当你说

    当加载一个新的Activity时,

    您是否将新的Activity 添加到您的ActivityStatuses 集合中?因为如果你不是,那么我很确定绑定不会起作用,因为 SelectedItem 需要在 ItemsSource 中。

    只是一个想法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-23
      • 1970-01-01
      • 2010-10-11
      • 2011-07-05
      • 2015-06-24
      • 1970-01-01
      • 2020-12-04
      相关资源
      最近更新 更多