ContentControl 内容 属性 不随托管内容变化

ContentControl Content Property not changing with hosted content

我正在尝试学习 MVVM,但遇到了一个奇怪的问题。我有一个带有抽屉控件的主菜单,它出现并显示一个菜单:

在这个抽屉所在的主要 window 中,我有一个 ContentControl,我用 Binding 设置它的内容。

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

此 window 的绑定设置为视图模型。

<Window.DataContext>
    <viewmodels:MainWindowViewModel/>
</Window.DataContext>

这是 ViewModel:

MainWindowViewModel.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);
    }


}

到目前为止,一切正常。例如,如果我点击 "Recovery Operations",我会得到:

RecoveryOperationsView.xaml

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

<UserControl.DataContext>
    <viewmodels:MainWindowViewModel/>
</UserControl.DataContext>

并且有一个按钮可以调用命令来更改主 window..

ContentControl 的内容 属性
<Button Grid.Row="2" Content="Restore Database" Width="150" Style="{StaticResource MaterialDesignFlatButton}" Command="{Binding SetWindowContent}" CommandParameter="DatabaseRecovery" >

在我的 class 处理命令中,我使用 switch 语句根据传递的参数更改内容

ChangeWindowContentCommand.cs

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;
        }
    }
}

然而,这就是我迷路的地方...如果我在这个新 window 中单击某些内容,比如 "Restore Database" 并使用断点检查它,我可以看到 属性 正在更改,但实际 ContentControl 内容 属性 不会更改为新的 UserControl 我制作的...我可以更改抽屉中的任何内容,但如果我尝试单击ContentControl 的托管内容中的按钮没有任何变化。我错过了什么?

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

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

<UserControl.DataContext>
    <viewmodels:MainWindowViewModel/>
</UserControl.DataContext>

由于您正在学习 WPF,我觉得有义务提供一些其他技巧。即使您使用的是 ViewModel,您仍然通过创建 ICommand 的非常具体的实现并通过 ViewModel 分配 UI 元素来混合 UI 和逻辑。这打破了 MVVM 模式。我知道 MVVM 需要一点时间来理解,但是一旦你理解了,它就非常容易使用和维护。

为了解决您的问题,我建议您为每个用户控件创建视图模型。请参阅 ,我在其中详细介绍了实现。

要切换不同的视图,您有两种选择。您可以使用 TabControl,或者如果您想使用命令,您可以将单个 ContentControl 绑定到类型为 MainWindowViewModel 的 属性 23=]。我们称它为 CurrentViewModel。然后当命令触发时,您将所需用户控件的视图模型分配给该绑定 属性。您还需要使用 implicit data templates。基本思想是为每个用户控制 VM 类型创建一个模板,它只包含一个视图实例。当您将用户控制 VM 分配给 CurrentViewModel 属性 时,绑定将找到那些数据模板并呈现用户控制。例如:

<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}"/>

看看这种方法如何使 UI 和逻辑保持一定距离?

最后,考虑创建一个非常通用的 ICommand 实现以在所有 ViewModel 中使用,而不是许多特定的实现。我认为大多数 WPF 程序员在他们的武器库中或多或少都有这种 RelayCommand implementation