在主窗口和用户控件之间共享DataContext

3
我想知道是否可以在C# / WPF中的Windows和UserControl之间共享DataContext。
我有一个主窗口,像这样(未完成):
MainWindow.xaml:
<Window x:Class="MyProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyProject"
        xmlns:v="clr-namespace:MyProject.Views"
        mc:Ignorable="d"
        Title="MyProject" >
    <Window.DataContext>
        <local:MainViewModel/>        
    </Window.DataContext>
    <Grid>
        <v:GenerateView/>
        <v:ReadView/>
    </Grid>
</Window>

MainViewModel.cs:

public class MainViewModel : ViewModelBase
{
    #region Properties
    #endregion

    #region Fields
    #endregion

    #region Constructor
    public MainViewModel()
        : base()
    {
    }
    #endregion

    #region Methods
    #endregion

    #region Commands
    #endregion
}

根据未来的参数,我将显示我的视图 GenerateView 或 ReadView。实际上,我正在开发 UserControl GenerateView,但我想知道是否可以使用相同的 Datacontext。
根据 这篇文章,我从这里开始:
<UserControl x:Class="MyProject.Views.GenerateView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:MyProject.Views"
         xmlns:p="clr-namespace:MyProject.Properties"
         xmlns:MyProject="clr-namespace:MyProject" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyProject:MainWindow}}}">
    <Grid>
    </Grid>
</UserControl>

但是当我尝试在GenerateView中访问Datacontext时,它无法工作,Datacontext为null。

编辑:

我忘记了代码的一部分:

public partial class GenerateView : UserControl
{
    private MainViewModel Context
    {
        get
        {
            return DataContext as MainViewModel;
        }
    }

    public GenerateView()
    {
        InitializeComponent();
        Context.PropertyChanged += Context_PropertyChanged;
    }

    private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        //Action to perform
    }
}

这行代码 Context.PropertyChanged += Context_PropertyChanged; 报错是因为Datacontext为空。


5
如果未明确设置,DataContext将被继承到所有子控件。尝试从您的UserControl中删除它,只在Window中设置它。 - Felix D.
此外,在构造函数被调用之后,DataContext 将不会被设置。使用 DataContextChanged 事件来知道何时设置它。 - Andrei Tătar
@Nudity 我已经从UserControls和测试中删除了它。在GenerateView的构造函数中,DataContext为null,但是在MainViewModel的InitializeComponent之后,两者的DataContext都是正确的。但是我无法在我的UserControl上设置Context.PropertyChanged。@Andrew 完美! - A.Pissicat
4个回答

3
重新使用窗口和其子用户控件的视图模型的主要要求是什么?它没有意义,两者是否完全相同?
我认为应该创建一个MainWindowViewModel并创建SubViewModel用于用户控件。在MainWindowViewModel中创建子视图模型的实例,并使用DataContext.SubViewModel访问它们。
这样做可以维护代码和应用程序,并保留编码标准,同时拥有一个无复杂性的视图模型。如果您混合所有内容以实现可重用性,则可能会违反MVVM模式。让不同的视图/窗口拥有自己的视图模型,因为它们完全不同。
如果两者相似,则可以使用依赖属性创建可重用控件。

我的MainWindows是一个空窗口。我的应用程序用于生成或读取图像。我有一个带有IHM的UserControl用于读取(ReadView),另一个用于生成(GenerateView)。两个UserControls使用相同的字段/属性和一些公共方法。我刚开始设计我的应用程序,我可以改变它。这就是为什么我问了我的问题,我想尊重MVVM模式,你的答案似乎非常有趣。是否有一种适当的方式来创建类似于:MainWindowViewModel具有公共字段/方法,并且GenerateViewModelReadViewModel都从主类继承? - A.Pissicat
@A.Pissicat:是的,这显然是可能的。这就是我所说的。你可以有一个父视图模型。但你不能继承视图模型。也许如果你有两个视图共同使用依赖属性来实现可重用的视图。这样当你想要更改数据时,你可以更新这些属性。而不是拥有2个视图,如果所有属性都相同,你可以使用1个视图并相应地更改数据。 - ViVi

1

我通常在我的Window中设置DataContext

public class MainWindow : Window
{
   InitializeComponent();
   ViewModel vm = new ViewModel();
   this.DataContext = vm;
}

有时候会更加高级:
我向我的ViewModel添加了一个静态属性:
public static ViewModel Instance {get; set;}

public class MainWindow : Window
{
   InitializeComponent();
   if(ViewModel.Instance == null)
   {
       ViewModel.Instance = new ViewModel();           
   }
   this.DataContext = ViewModel.Instance;
}

我在想,把ViewModel创建成单例模式是否更容易一些呢? - A.Pissicat
@A.Pissicat 这取决于您的用例。我更喜欢始终使用相同的实例,而不是每次创建新的虚拟机。当涉及到大量绑定时,我不想使用默认值获取所有绑定 - 相反,我获取虚拟机上次拥有的值。 - Felix D.

0
根据安德鲁的评论,我已经找到了解决我的问题的方法: public partial class GenerateView : UserControl { private MainViewModel Context { get { return DataContext as MainViewModel; } } }
public GenerateView()
{
    InitializeComponent();
    DataContextChanged += GenerateView_DataContextChanged;
}

private void GenerateView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (Context != null)
        Context.PropertyChanged += Context_PropertyChanged;
}
private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    //Action to perform
}

}

我从我的用户控件中删除了DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyProject:MainWindow}}}"


0

可以使用视图优先的方法。 首先为设计目的定义设计上下文。

<UserControl x:Class="MyProject.Views.CustomView"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:local="clr-namespace:MyProject.Views"
     xmlns:p="clr-namespace:MyProject.Properties"
     xmlns:MyProject="clr-namespace:MyProject" 
     mc:Ignorable="d"
     d:DataContext="{d:DesignInstance Type=vm:MyViewModel}"
     d:DesignHeight="300" d:DesignWidth="300">
  <Grid>
     .....
  </Grid>
</UserControl>

然后将用户控件绑定到主窗口的数据上下文

<Window x:Class="MyProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:MyProject"
    xmlns:v="clr-namespace:MyProject.Views"
    mc:Ignorable="d"
    Title="MyProject" >
<Window.DataContext>
    <local:MyViewModel/>        
</Window.DataContext>
<Grid>
    <v:CustomView/>
</Grid>
</Window>

然后,用户控件将自动继承其父级的数据上下文。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接