我正在尝试学习MVVM,并且遇到了奇怪的问题。我有一个带有抽屉控件的主菜单,出来并显示一个菜单:
在此抽屉所在的主窗口中,我有一个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 ”(这是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中的按钮,则没有任何更改。我想念什么?
答案 0 :(得分:3)
要确保没有要测试的项目很难100%地确定,但是我相当有信心至少一个问题是您的UserControl
和MainWindow
使用了不同的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。