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

时间:2018-11-19 23:00:34

标签: c# wpf mvvm material-design-in-xaml

我正在尝试学习MVVM,并且遇到了奇怪的问题。我有一个带有抽屉控件的主菜单,出来并显示一个菜单: enter image description here

在此抽屉所在的主窗口中,我有一个ContentControl,在其中使用绑定设置其内容。

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

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

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


}

到目前为止,一切正常。因此,例如,如果我单击“恢复操作”,则会得到以下信息:

RecoveryOperationsView.xaml enter image description here

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

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

并有一个按钮来调用该命令,以从主窗口更改ContentControl的Content属性。

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

在我的类中处理命令,我像这样使用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;
        }
    }
}

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

1 个答案:

答案 0 :(得分:3)

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

因此,只需删除此代码,它至少可以解决该问题。

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

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

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

要切换不同的视图,您有两个选择。您可以使用TabControl,或者如果您想使用命令,则可以将单个ContentControl绑定到MainWindowViewModel类型为ViewModelBase的属性。我们称之为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