C# 如何在MVVM-WPF应用程序中实现用户控件之间的通信

C# 如何在MVVM-WPF应用程序中实现用户控件之间的通信,c#,wpf,xaml,mvvm,user-controls,C#,Wpf,Xaml,Mvvm,User Controls,我想创建一个应用程序,左边有一个菜单,可以更改右边的内容。 为此,我有一个带有两个ContentControl的主窗口(一个包含UserControl“菜单”,另一个包含所选UserControl“红色”或“绿色” 问题是右边的内容没有改变 我做了一些研究,看到了一些概念,如依赖注入、委托、事件、消息总线、ViewModelLocator。。。 但我不知道在这种情况下哪一个最合适,以及如何实现它(我不想使用MVVMLight或任何类似的插件) 为了您的利益,请提前通知我们 为此,我使用MVVM模

我想创建一个应用程序,左边有一个菜单,可以更改右边的内容。 为此,我有一个带有两个ContentControl的主窗口(一个包含UserControl“菜单”,另一个包含所选UserControl“红色”或“绿色”

问题是右边的内容没有改变

我做了一些研究,看到了一些概念,如依赖注入、委托、事件、消息总线、ViewModelLocator。。。 但我不知道在这种情况下哪一个最合适,以及如何实现它(我不想使用MVVMLight或任何类似的插件)

为了您的利益,请提前通知我们

为此,我使用MVVM模式和DataTemplate绑定:

App.xaml

<Application.Resources>
    <DataTemplate DataType="{x:Type viewModel:MainViewModel}">
        <view:MainWindow />
    </DataTemplate>
    <DataTemplate DataType="{x:Type viewModelMenu:LeftViewModel}">
        <viewMenu:Left />
    </DataTemplate>
    <DataTemplate DataType="{x:Type viewModelContent:RedViewModel}">
        <viewContent:Red />
    </DataTemplate>
</Application.Resources>
LeftViewModel.cs

public abstract class ViewModel : INotifyPropertyChanged
{
    #region Properties

    private ViewModel _mainContent;

    public ViewModel MainContent
    {
        get => _mainContent;
        set
        {
            _mainContent = value;
            OnPropertyChanged();
            MessageBox.Show(nameof(MainContent));
        }
    }

    #endregion Properties

    public ViewModel()
    {
        InitCommands();
    }

    protected abstract void InitCommands();

    #region Factory Method - CreateCommand

    protected ICommand CreateCommand(Action execute, Func<bool> canExecute)
    {
        return new RelayCommand(
            execute: execute,
            canExecute: canExecute
            );
    }

    protected ICommand CreateCommand<T>(Action<T> execute, Predicate<T> canExecute)
    {
        return new RelayCommand<T>(
            execute: execute,
            canExecute: canExecute
            );
    }

    #endregion Factory Method - CreateCommand

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
internal class MainViewModel : ViewModel
{
    private ViewModel _leftMenu;
    public ViewModel LeftMenu
    {
        get => _leftMenu;
        set
        {
            _leftMenu = value;
            OnPropertyChanged();
        }
    }

    public MainViewModel()
    {
        LeftMenu = new LeftViewModel();
    }

    protected override void InitCommands()
    {
    }
internal class LeftViewModel : ViewModel
{
    public ICommand ChangeContentToRed { get; set; }
    public ICommand ChangeContentToGreen { get; set; }

    protected override void InitCommands()
    {
        ChangeContentToRed = new RelayCommand(
            execute: () => MainContent = new RedViewModel(),
            canExecute: () => !(MainContent is RedViewModel)
            );

        ChangeContentToGreen = new RelayCommand(
            execute: () => MainContent = new GreenViewModel(),
            canExecute: () => !(MainContent is GreenViewModel)
            );
    }
}
RedViewModel和GreenViewModel为空,因此我不显示代码,而是扩展ViewModel

Window.xaml

 <Window.DataContext>
    <viewModel:MainViewModel />
</Window.DataContext>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="3*" />
    </Grid.ColumnDefinitions>

    <ContentControl Grid.Column="0" Content="{Binding Path=LeftMenu}" />
    <ContentControl Grid.Column="1" Content="{Binding Path=MainContent}" />
</Grid>

Left.xaml

<UserControl.DataContext>
    <viewModel:LeftViewModel />
</UserControl.DataContext>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Button
        Grid.Row="0"
        Command="{Binding Path=ChangeContentToRed}"
        Content="Red" />
    <Button
        Grid.Row="1"
        Command="{Binding Path=ChangeContentToGreen}"
        Content="Green" />
</Grid>


红色和绿色是仅有的两个用户控件,具有红色和绿色网格

<DataTemplate DataType="{x:Type viewModelMenu:LeftViewModel}">
    <viewMenu:Left />
</DataTemplate>

然后将LeftViewModel类型的值指定给ContentControl的Content属性,如

<ContentControl Content="{Binding Path=LeftMenu}"/>

DataTemplate被分配给ContentControl的ContentTemplate,实例化的DataTemplate中的元素(即您的UserControl)继承ContentControl的ControlTemplate中ContentPresenter的DataContext,后者随后保存内容值

但是,只有当您没有显式分配UserControl的DataContext并因此中断DataContext属性的值继承时,这才有效

必须从UserControl中删除显式DataContext分配,即:

<UserControl.DataContext>
    <viewModel:LeftViewModel />
</UserControl.DataContext>


UserControls从来没有自己的私有视图模型对象。请从UserControls的XAML中删除DataContext分配,并将其绑定到常见MainViewModel的属性(您显然已经这样做了)。Thx用于快速回复。我是否必须删除所有VM(RedVM、GreenVM、LeftVM)除了MainViewModel和ViewModel。所以所有内容都引用MainVM?或者不引用?因为如果我这样做,我必须更改属性类型(MainContent for ex)从ViemModel到UserControl…可以吗?因为如果我只删除UserControl.DataContext,main内容不会更新…不确定您到底指的是什么,但是如果您有“sub”视图模型在主视图模型中,如果您的应用程序逻辑需要,其中一个视图模型中属性的更改当然应该传递给另一个子视图模型。最好从单个视图模型开始,即从UserControls的XAML绑定到MainViewModel的属性。如果我将所有视图引用到MainViewModel,它会工作。。。但我希望每个UserControl都引用自己的VM(例如,我更喜欢将LeftMenu的所有特定命令都放在LeftMenuViewModel中,而不是放在MainViewModel中)。问题是,在这种情况下,MainContent不会刷新,而是显示已更改的内容。如上所述,您需要在主视图模型和子视图模型之间进行某种通信。例如,一个vm可能订阅另一个vm的PropertyChanged事件。