C# ContentControl内容属性不随托管内容更改

C# ContentControl内容属性不随托管内容更改,c#,wpf,mvvm,material-design-in-xaml,C#,Wpf,Mvvm,Material Design In Xaml,我试图学习MVVM,但遇到了一个奇怪的障碍。我有一个带抽屉控制的主菜单,它会弹出并显示一个菜单: 在这个抽屉所在的主窗口中,我有一个ContentControl,在那里我用绑定设置了它的内容 <ContentControl x:Name="MainWindowContentControl" Content="{Binding Path=WindowContent}"/> 到目前为止,一切正常。例如,如果我单击“恢复操作”,我会得到以下结果: 恢复操作视图.xaml 在“Reco

我试图学习MVVM,但遇到了一个奇怪的障碍。我有一个带抽屉控制的主菜单,它会弹出并显示一个菜单:

在这个抽屉所在的主窗口中,我有一个
ContentControl
,在那里我用绑定设置了它的内容

<ContentControl x:Name="MainWindowContentControl" Content="{Binding Path=WindowContent}"/>
到目前为止,一切正常。例如,如果我单击“恢复操作”,我会得到以下结果:

恢复操作视图.xaml

在“RecoveryOperationsView.xaml”(这是一个
UserControl
)中,我也从上面这样引用视图模型

<UserControl.DataContext>
    <viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
在我的类中,为了处理命令,我使用如下switch语句根据传递的参数更改内容

ChangeWindowContentCommand.cs

public class MainWindowViewModel: ViewModelBase
{

    private object _content;

    public object WindowContent
    {
        get { return _content; }
        set
        {
            _content = value;
            RaisePropertyChanged(nameof(WindowContent));
        }
    }
    public ICommand SetWindowContent { get; set; }

    public MainWindowViewModel()
    {
        SetWindowContent = new ChangeWindowContentCommand(this);
    }


}
public class ChangeWindowContentCommand : ICommand
{
    private MainWindowViewModel viewModel;
    public ChangeWindowContentCommand(MainWindowViewModel vm)
    {
        this.viewModel = vm;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        switch (parameter)
        {
            case "Home":
                viewModel.WindowContent = new HomeView();
                break;
            case "RecoveryOps":
                viewModel.WindowContent = new RecoveryOperationsView();
                break;
            case "DatabaseRecovery":
                viewModel.WindowContent = new DatabaseRestoreView();
                break;
        }
    }
}

然而,这就是我迷路的地方。。。如果我在这个新窗口中单击某个内容,说“还原数据库”,并用断点检查它,我可以看到属性正在更改,但实际的
ContentControl
Content属性没有更改为我创建的新
UserControl
。。。我可以使用抽屉中的任何内容更改内容,但如果我尝试单击
ContentControl
的托管内容中的按钮,则不会更改任何内容。我遗漏了什么?

如果没有要测试的项目,很难100%确定,但我相当确信至少有一个问题是
UserControl
MainWindow
使用了
MainWindowViewModel
的不同实例。您不需要为用户控件实例化VM,因为它将从
main窗口继承
DataContext
。它在WPF中的工作方式是,如果任何给定的
UIElement
没有显式分配
DataContext
,它将从分配了一个元素的逻辑树上的第一个元素继承它

所以,只要删除这段代码,它至少可以解决这个问题

<UserControl.DataContext>
    <viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
看看这种方法是如何让UI和逻辑保持一定距离的


最后,考虑创建一个非常通用的实现:<代码> ICOMMand <代码>,用于所有的VIEW模型,而不是许多具体的实现。我认为大多数WPF程序员在他们的软件库中或多或少都有这一点。

“我可以看到属性被更改,但实际的窗口内容没有。”。改成什么?您是否偶然在某处创建了一个重复的MainWindowViewModel?“但是,如果我试图在ContentControl的托管内容中使用任何内容,都不会起作用”这是什么“任何内容”?我不知道你这句话是什么意思。请编辑并改进您的问题以澄清此问题。@elgonzo我已尽可能多地更新了我的问题。我不认为我会在任何地方多次引用MainWindowViewModel,除非在XAML中添加datacontext确实做到了这一点。。。在断点中,当我单击“还原数据库”按钮时,我可以看到WindowContent属性更改为DatabaseRestoreView的实例。这绝对是问题所在!我非常感谢您对我的视图模型从我的视图中解耦的帮助和出色的解释!我的方法与您建议的方法非常相似,在这两种方法中,我为每个用户控件创建一个ViewModel。我想我需要在它们之间切换的实现。再次感谢!我的荣幸。很高兴这有帮助。看起来你就快到了,只是概念上的一个小变化。您应该像上面那样使用隐式数据模板,并将切换代码移动到MainViewModel(而不是ICommand类)中,同时使用上面RelayCommand的通用实现。这将完全符合MVVM模式!因此,我实现了您提供的DataTemplate方法,使用一个公开属性
public ViewModelBase CurrentViewModel
来更改当前视图,但是,当我尝试单击UserControl中显示的一个视图中的按钮时,似乎继承的DataContext在VisualTree中丢失了。输出控制台似乎记录了以下内容:`BindingExpression路径错误:`ChangeCurrentViewModel'属性未在'object''RecoveryOperationsViewModel'(HashCode=58033516')上找到。这是因为设置
CurrentViewModel
会更改视图的上下文吗?因此,工作方式是,由于绑定,用户控件将不再从窗口继承数据上下文,而是从
ContentControl
继承数据上下文。此
DataContext
现在将是
recoveryooperatisviewmodel
,而不是定义命令的
MainWindowViewModel
。因此出现了绑定错误。您应该能够使用
RelativeSource
绑定来触发命令。或者将命令移动到子视图模型,并让子视图与父VM通信。我不知道这有多大意义,但今天下班后我可以在我的回答中为大家充实一下。当我第一次学习WPF时,一个简单的概念对我帮助很大:一切都是一棵树!一切都是从逻辑树上继承下来的。这适用于样式、数据模板和任何其他依赖项属性。要跳过逻辑树中的某些级别(即选择正确的datacontext),通常需要使用
RelativeSource
绑定(以及其他技术)。
public class ChangeWindowContentCommand : ICommand
{
    private MainWindowViewModel viewModel;
    public ChangeWindowContentCommand(MainWindowViewModel vm)
    {
        this.viewModel = vm;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        switch (parameter)
        {
            case "Home":
                viewModel.WindowContent = new HomeView();
                break;
            case "RecoveryOps":
                viewModel.WindowContent = new RecoveryOperationsView();
                break;
            case "DatabaseRecovery":
                viewModel.WindowContent = new DatabaseRestoreView();
                break;
        }
    }
}
<UserControl.DataContext>
    <viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
<Window.Resources>
  <DataTemplate DataType = "{x:Type viewmodels:RecoveryOperationsViewModel}">
    <views:RecoveryOperationsView/>
  </DataTemplate> 
  <!-- Now add a template for each of the views-->
</Window.Resources>

<ContentControl x:Name="MainWindowContentControl" Content="{Binding CurrentViewModel}"/>