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% 确定,但我相当有信心至少有一个问题是您的 UserControl
和 MainWindow
使用不同的实例的 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。
我正在尝试学习 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% 确定,但我相当有信心至少有一个问题是您的 UserControl
和 MainWindow
使用不同的实例的 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。