【问题标题】:Binding a ContentControl to a deep path in WPF将 ContentControl 绑定到 WPF 中的深层路径
【发布时间】:2012-01-31 11:48:37
【问题描述】:

我目前正在编写的应用程序使用 MVVM 和 ViewModel-first 模式。我有类似于以下内容的 XAML:

<ContentControl Content="{Binding FooViewModel.BarViewModel.View, Mode=OneWay}"/>

每个虚拟机都是DependencyObject。每个属性都是DependencyProperty。根据应用程序的状态,FooViewModelBarViewModel 属性的值可以改变,从而改变View 属性的值。不幸的是,发生这种情况时,新视图不会显示,而旧视图仍然存在。

这非常令人沮丧。我认为如果路径表达式的任何部分发生更改,绑定就会更新,但情况似乎并非如此。当我使用较浅的路径表达式(例如 FooViewModel.View)并更改了 FooViewModel 属性的值时,更新了它所绑定的 ContentControl,但未在这种情况。

如果您的解决方案是我首先放弃 ViewModel,这不是一个选择,尽管我很感谢您的建议。我必须让它按原样工作。

澄清

这是一个关于数据绑定的问题,不是关于 MVVM 或如何实现它的问题。如果它可以帮助您思考问题,或者如果您对如何实现 MVVM 有不同的想法,您可以安全地忽略它的 MVVM 方面。这是一个大型的现有项目,其中 MVVM 设计模式无法更改。 (现在为时已晚。)

所以,话虽如此,要回答的正确问题如下:

给定一个绑定路径表达式,其中每个元素都是DependencyProperty,最终属性是绑定到 ContentControl 的视图,为什么路径中间的属性更改不会导致绑定更新?

【问题讨论】:

  • 您应该发布您的 FooViewModel、BarViewModel 和 View。我认为你混淆了一些东西。
  • 您可以使用容器来管理深层路径吗?换句话说,将 ContentControl 上的内容绑定到 View,然后将其包围在一个带有 DC 绑定到 BarViewModel 的网格中,并用一个带有 DC 绑定到 FooViewModel 的网格包围它。我还没有尝试过这种与依赖属性的绑定(因此它是评论而不是答案),但它可以工作。
  • 我在尝试将 xaml 设置为我的 mvvm 上的内部类的路径时遇到了同样的问题。就像在我的 mvvm 中必须 RaisePropertyChange("MvvmClass.InnerClass.PropertyName") 这不起作用。

标签: wpf xaml binding mvvm


【解决方案1】:

虽然我希望这能奏效,但您的方法存在几个问题。

首先,您的视图模型不应使用DependencyObjectDependencyProperty,这会将它们与 WPF 联系起来。他们应该改为实现INotifyPropertyChanged。这使您的视图模型可在 Silverlight 等其他演示技术中重复使用。

其次,您的视图模型不应引用您的视图,因此您的视图模型不应要求 View 属性。

我会认真考虑使用 MVVM 框架进行视图组合 - 例如,Caliburn.Micro 使视图模型优先开发非常简单,并且已经提供了实现 INotifyPropertyChanged 的视图模型基类,以及构建视图的机制有约定的作品。

即您可以拥有一个具有ActiveItem 属性的导体视图模型,并且您只需在视图上放置一个与该属性同名的ContentControl

<ContentControl x:Name="ActiveItem" />

您可以使用ActivateItem() 方法更改当前活动项。

Caliburn.Micro 还具有许多其他功能,例如能够在您的视图上放置带有x:Name="Save"Button 控件,并且您的视图模型上的Save 方法将在按钮时自动调用被点击。

【讨论】:

  • 谢谢,但我对 MVVM 没有任何问题。这是一个现有的大型项目,所有其他方面都运行良好。它是 WPF,没有使用 Silverlight 的计划。这些属性是依赖属性,因为我们的基类使用 CSLA.NET 的 ViewModel,它是一个依赖对象。因此,我们不能接受您的任何建议,必须按原样处理。
  • 是的,我不知道 Rocky 为什么采用这种方法,我们将他的视图模型库作为使用 CSLA 的示例,并编写了我们自己的仅使用 INotifyPropertyChanged。话虽如此,Rocky 的视图模型库也实现了 INotifyPropertyChanged,尽管不是以一种特别优雅的方式,因为他只实现了一个基于字符串的 OnPropertyChanged 辅助方法。因此,您可以尝试将 BarViewModel 和 View 属性实现为 INotifyPropertyChanged 属性,而不是 DependencyProperty,看看是否可行。
  • 我会试试的。顺便说一句,您声明我们的视图模型不应该引用我们的视图,但这不是带有依赖注入的 VM-first 的工作方式。首先在 VM 中,您的视图作为接口注入到构造函数中,然后作为属性公开。这工作正常并且完全可测试,因为没有直接引用视图本身。我没有改变它的计划,因为我发现它是一种非常灵活的方法。
  • 原来INotifyPropertyChanged 是解决方案。迷人。我不知道你可以在同一个对象中混合匹配 INotifyPropertyChanged 和依赖属性。
  • 我支持你,因为 -1 的答案看起来不太好 :)
【解决方案2】:

每个 VM 都是一个 DependencyObject。每个属性都是一个 依赖属性。

为什么? viewmodel 应该是一个带有 INotifyPropertyChanged 的​​简单类,Properties 应该是简单的属性。

如果您希望以不同的方式呈现不同的视图模型 - 您应该使用 DataTemplate。

 <Window>
  <Window.Resources>
    <DataTemplate DataType="{x:Type local:MyViewModelA}>
     <MyViewA/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:MyViewModelB}>
     <MyViewB/>
    </DataTemplate>
   </Windows.Resources>
   <Grid>
     <ContentControl Content="{Binding MyActualVM}"/>
   </Grid>
  </Window>

编辑:顺便说一句,你总是绑定到最后一个属性:FooViewModel.BarViewModel.View --> 所以 INotifyPropertyChanged(如果提出)只适用于 .View

EDIT2:另一种方法是获取内容控件的 BindingExpression 并调用。

System.Windows.Data.BindingExpression expr = //get it from your contentcontrol
expr.UpdateTarget();

EDIT3: 和一个简单的 mvvm 方式 - 只需使用 INotifyPropertyChanged

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.MyFooVM = new FooVM();
        this.MyFooVM.MyBarVM = new BarVM(){View = "erster"};

        this.DataContext = this;

    }

    public FooVM MyFooVM { get; set; }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.MyFooVM.MyBarVM = new BarVM(){View = "zweiter"};
    }
}

public class INPC : INotifyPropertyChanged
{
    #region Implementation of INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropChanged(string property)
    {
        var handler = PropertyChanged;

        if(handler != null)
            handler(this, new PropertyChangedEventArgs(property));
    }

    #endregion
}

public class FooVM:INPC
{
    private BarVM _myBarVm;
    public BarVM MyBarVM
    {
        get { return _myBarVm; }
        set { _myBarVm = value;OnPropChanged("MyBarVM"); }
    }
}

public class BarVM : INPC
{
    private string _view;
    public string View
    {
        get { return _view; }
        set { _view = value;OnPropChanged("View"); }
    }
}

【讨论】:

  • 正如我对 DevDigital 所说,这是一个现有的大型项目,所有其他方面都运行良好。这些属性是依赖属性,因为我们的基类使用 CSLA.NET 的 ViewModel,它是一个依赖对象。因此,我们不能接受您的任何建议,必须按原样处理。我了解数据绑定的工作原理以及您需要绑定的内容。那不是我的问题。请重新阅读我的问题。
  • 关于 mvvm 和 viewmodel 特别 - 我不喜欢 viewmodel 从 system.windows 命名空间继承。没有这个命名空间,单元测试会更干净。但这只是我的想法。我记得有人(也许是 josh smith)在你的使用中写了一个 System.Windows - 它不再是一个干净的视图模型了。
猜你喜欢
  • 1970-01-01
  • 2011-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-26
  • 1970-01-01
相关资源
最近更新 更多