【问题标题】:WPF: Change databinding while using MVVMWPF:使用 MVVM 时更改数据绑定
【发布时间】:2013-06-27 12:08:17
【问题描述】:

以下是我真正想做的简化示例,但我的问题是一样的。

假设我有两个对象,男人和女人,它们都具有相同的属性(年龄、身高和体重),但它们是两个不同的对象。我无法改变这一点。

现在假设我有一个 WPF 面板,它使用 MVVM 原理在文本框中显示某个人的年龄。为此,我使用 Text="{Binding Path=OnePerson.Age}" ,其中 OnePerson 是视图模型中定义的 Man 类型的对象。

这很好用,但我想要一个类似的页面来显示这个女人的信息。理想情况下,我只想使用与以前相同的视图和视图模型。但这很棘手,因为数据绑定指向 Man-object OnePerson。我可以以编程方式更改数据绑定(如WPF Binding Programatically 中所述),但我只能从视图的代码隐藏中这样做。我不允许这样做,因为我们使用的是 MVVM 模型。

我想让 OnePerson 引用一个 Man 或 Woman 对象,但我不知道这样做的好方法。它们是不同的类型,所以我不能只使用 if 语句来分配男人或女人。我可以将 OnePerson 声明为对象而不是类型,但是我不能再如此轻松地访问 Age、Height 和 Weight 属性。或者我可以制作一个完全不同的 ViewModel,其中一个将 OnePerson 声明为男人,另一个声明为女人,并为他们两个使用相同的 View。我认为这应该可行,但是对于一个视图有两个视图模型似乎有点奇怪。添加我自己的 Person 类并在它与 Man 或 Woman 之间进行转换可能只会使整个视图模型变得相当复杂,当我开始添加诸如添加新的 Man/Woman 或编辑现有的功能时,我可能会好吧,复制粘贴 Man 视图和视图模型,然后仅将 OnePerson 对象更改为 Woman。

我的问题是,在这种情况下,是否有一种干净简单的方法可以使用单个 View 和 Viewmodel 来显示男人或女人的信息。还是我不应该为这些案例烦恼并单独制作页面?

希望这已经足够清楚了。

【问题讨论】:

    标签: c# wpf xaml data-binding mvvm


    【解决方案1】:

    我认为这是ViewModel 上的问题,而不是视图或绑定。 View 只是ViewModel 的可视化表示,听起来你的问题需要在ViewModel 中解决,而不是View

    WPF 的绑定系统仅在绑定无效时抛出警告,并且它不关心 DataContext 的数据类型。无论OnePersonMan 还是Woman 对象,您的问题({Binding OnePerson.Age}) 中显示的绑定都将正确评估,并将显示该对象上任何Age 属性的值。

    因此,最好的解决方案是将OnePerson 属性设置为可以是ManWoman 的类型。一个包含所有共享属性的接口将是理想的,因为这样代码就可以在不强制转换的情况下访问它的属性,并且您可以保留所有已有的绑定。

    IPerson OnePerson { get; set; }
    

    如果不能使用共享接口而不是使用object,但是您需要记住将OnePerson 对象转换为ManWoman 类,然后再引用它的属性代码。

    object OnePerson { get; set; }
    
    ...
    
    if (((Man)OnePerson).Age < 0) ...
    

    第二种选择是创建两个单独的属性,一个用于Man,一个用于Woman。然后,您可以从ViewModel 轻松访问您正在使用的任何项目的属性,并且您可以为 ViewModel 使用单个视图。

    Man SomeMan { get; set; }
    Woman SomeWoman { get; set; }
    

    您可能需要一些标志来识别哪个是填充对象,并使用此标志来更新对象的属性以及何时确定视图的DataContext

    这是一个使用标志来确定DataContext的示例

    <Style x:Key="MyContentControlStyle">
        <Setter Property="Content" Value="{Binding SomeMan}" />
        <Style.Triggers>
            <DataTrigger Property="{Binding SelectedPersonType}" Value="Woman">
                <Setter Property="Content" Value="{Binding SomeWoman}" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
    

    当然,如果您确实使用单独的ViewModel 或想要为每种对象类型使用单独的模板,您可以使用隐式DataTemplates 之类的方法轻松地做到这一点,以确定如何绘制每个对象。

    <DataTemplate DataType="{x:Type myModels:Man}">
        <myViews:ManUserControl />
    </DataTemplate>
    <DataTemplate DataType="{x:Type myModels:Woman}">
        <myViews:WomanUserControl />
    </DataTemplate>
    
    <ContentPresenter Content="{Binding SelectedPerson}" />
    

    总的来说,我建议您尝试使OnePerson 成为两个对象共享的数据类型,或者为这两个对象创建一个单独的ViewModel。听起来您的两个类都非常相似,因此您甚至可以使用某种通用的ViewModel,例如PersonViewModel&lt;T&gt;,在其中将ManWoman 传递为T

    【讨论】:

    • 感谢您的详尽解释。我自己考虑过 DataTriggers,但是一旦我进入一个更复杂的面板,它看起来很快就会变得很麻烦。但是 Datatemplate 示例看起来不错。我明天试试看。
    【解决方案2】:

    您是否考虑过基于接口对象制作对象。该接口基本上创建了一个合同,说明从它派生的任何东西都必须至少具有它所声明的任何东西......派生控件可以有更多,但至少是你想要的东西。例如。

    public interface IPersonProperties
    {
       string PersonName { get; set; }
       int Age { get; set; }
    
       // if you want a function that is common between them too
       bool SomeCommonFunction(string whateverParms);
    
       etc...
    }
    
    public class Man : IPersonProperties
    {
       // these required as to support the IPersonProperties
       public string PersonName { get; set; }
       public int Age { get; set; }
    
       public bool SomeCommonFunction(string whateverParms)
       {  doSomething;
          return true;
       }
    
    
       // you can still have other stuff specific to man class definition
       public string OtherManBasedProperty { get; set;}
    
       public void SomeManFunction()
       {  // do something specific for man here }
    }
    
    public class Woman : IPersonProperties
    {
       // these required as to support the IPersonProperties
       public string PersonName { get; set; }
       public int Age { get; set; }
    
       public bool SomeCommonFunction(string whateverParms)
       {  doSomething;
          return false;
       }
    
       // you can still have other stuff specific to WOMAN class definition
       public string OtherWOMANBasedProperty { get; set;}
    
       public void SomeWomanFunction()
       {  // do something specific for man here }
    
    }
    

    然后,在您的 MVVM 中,您可以公开的“对象”是 IPersonProperties 的对象,例如

    public class YourMVVM
    {
       public IPersonProperties BindToMe{ get; set }
    
       public YourMVVM()
       {
          BindToMe = new Man();
          // OR... BindToMe = new Woman();
       }
    }
    

    然后,但是/在您创建对象的 MVVM 对象中的任何位置,都这样做。您的绑定将是相同的,但可以是任何一种性别。如果它们之间通过接口有共同点,您可以简单地通过

    if( BindToMe.SomeCommonFunction( "testing"))
       blah blah.
    

    但是,如果在某些方法中您需要根据特定性别做某事,您可以这样做...

    if( BindToMe is Man )
       ((Man)BindToMe).SomeManFunction();
    else
       ((Woman)BindToMe).SomeWOMANFunction();
    

    希望这为您在实施选择上打开了一些大门。

    【讨论】:

    • 感谢您的建议。不幸的是,对于如何定义我在实际案例中使用的对象,我无能为力。 (它们是 AchterstandswijkRootCollection 和 GrenswijkRootCollection。你可以看到为什么我在我的示例中更喜欢更简单的男人和女人。)它们是现有项目的一部分,我不能与它们混为一谈。虽然它们非常深入地基于相同的接口对象,但我必须经历很多奇怪的步骤,以至于我无法从通用接口对象中提取任何有意义的信息。
    • @user2527902,您总是可以添加另一个接口并将它们添加到现有的类定义中。没有其他人知道他们,它不应该崩溃任何东西。您只是添加到它,然后绑定到您想要的那些新简化的属性...这可行吗?
    • 可能。但在我开始编辑这些东西之前,我必须征得许可。这基本上是我的第一个真正的任务,所以我还没有信心开始自己改变框架的东西,因为这本质上是一个可选的快捷方式。
    • 不过,您的建议还是不错的。如果可以的话,我会支持它,但我的代表太低了。
    【解决方案3】:

    如果您只想使用一个适合您年龄的 xaml 控件,您可以这样做

    年龄用户控制

      <TextBlock Text="{Binding Path=Age}" />
    

    个人观点

       <local:AgeUserControl DataContext="{Binding Path=MyObjectTypePersonProperty} />
    

    【讨论】:

    • 这当然只适用于每个 MyObjectTypePersonProperty 都有一个 Age 属性
    猜你喜欢
    • 2015-05-24
    • 1970-01-01
    • 1970-01-01
    • 2019-06-23
    • 1970-01-01
    • 2017-08-20
    • 2010-11-11
    • 1970-01-01
    相关资源
    最近更新 更多