如何在MVVM项目中的用户控件之间切换

时间:2022-01-15 07:26:29

Edit: Although this question has been flagged up by @AbinMathew as a possible duplicate of this, the solution provided to that question didn't explain very well how to relay the command logic. I was able to resolve this with the help of John Smith's article as mentioned in my answer.

编辑:虽然这个问题已被@AbinMathew标记为可能的副本,但提供给该问题的解决方案并没有很好地解释如何中继命令逻辑。在我的回答中提到的John Smith的文章的帮助下,我能够解决这个问题。

I've got this test project which I'm running in order to get my head around MVVM. What I'm trying to achieve: MainWindow has a back button and a ContentControl. On Window_loaded I want to display MainGadget in the ContentControl. When I click MyBtn in MainGadget, I want to then display MyGadget in the ContentControl.

我有一个我正在运行的测试项目,以便了解MVVM。我想要实现的目标:MainWindow有一个后退按钮和一个ContentControl。在Window_loaded上,我想在ContentControl中显示MainGadget。当我在MainGadget中单击MyBtn时,我想在ContentControl中显示MyGadget。

ViewModelBase is a class which is used by MainGadgetVM, MainWindowVM and MyGadgetVM. It implements the INotifyPropertyChanged interface. RelayCommand implements the ICommand interface so I want to use it for executing MyBtn_Click and displaying other UserControls.

ViewModelBase是MainGadgetVM,MainWindowVM和MyGadgetVM使用的类。它实现了INotifyPropertyChanged接口。 RelayCommand实现了ICommand接口,因此我想用它来执行MyBtn_Click并显示其他UserControls。

At the moment, when I run the program only the 'Back' button is displayed. I can't seem to figure out how to display the other UserControls. Any help will be much appreciated.

目前,当我运行程序时,只显示“后退”按钮。我似乎无法弄清楚如何显示其他UserControls。任何帮助都感激不尽。

如何在MVVM项目中的用户控件之间切换

DataTemplates.xaml

DataTemplates.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:vm="clr-namespace:ExampleContentCtrl.VMs"
                    xmlns:view="clr-namespace:ExampleContentCtrl.Panels">
    <DataTemplate DataType="{x:Type vm:MainGadgetVM}">
        <view:MainGadget/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:MyGadgetVM}">
        <view:MyGadget/>
    </DataTemplate>

</ResourceDictionary>

MainWindow.xaml

MainWindow.xaml

<Window x:Class="ExampleContentCtrl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="375" Width="300" Loaded="Window_Loaded">
    <Window.Resources>
        <ResourceDictionary Source="DataTemplates.xaml"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="8*"/>
        </Grid.RowDefinitions>
        <Button x:Name="BckSpace" Content="Back" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0"/>
        <ContentControl Grid.Row="1"/>
    </Grid>
</Window>

MainGadget.xaml

MainGadget.xaml

<UserControl x:Class="ExampleContentCtrl.Panels.MainGadget"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button x:Name="MyBtn" Content="My Gadget" HorizontalAlignment="Center" VerticalAlignment="Top" Grid.Row="1" Command="{Binding MyBtn_Click}"/>
    </Grid>

</UserControl>

MainWindow.xaml.cs

MainWindow.xaml.cs

namespace ExampleContentCtrl
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //Load MainGadgetVM via MainWindowVM.Initialize()
        }

    }
}

MainWindowVM.cs

MainWindowVM.cs

namespace ExampleContentCtrl.VMs
{
    public class MainWindowVM : ViewModelBase
    {
        private RelayCommand _ShowWorkSpace;
        private static MainWindowVM _Instance;
        public static MainWindowVM Instance { get { return _Instance; } }

        public MainWindowVM()
        {
            MainWindowVM._Instance = this;
        }

        public RelayCommand ShowWorkSpace
        {
            get
            {
                if (_ShowWorkSpace == null)
                    _ShowWorkSpace = new RelayCommand(param => { });
                return _ShowWorkSpace;
            }
        }

        public void Initialize()
        {
            //this.ShowWorkSpace.Execute("ExampleContentCtrl.VMs.MainGadgetVM");
        }
    }
}

3 个解决方案

#1


1  

Add a binding to your content control and change the bound value to the view model you want to show.

向内容控件添加绑定,并将绑定值更改为要显示的视图模型。

<Window x:Class="ExampleContentCtrl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="375" Width="300" Loaded="Window_Loaded">
    <Window.Resources>
        <ResourceDictionary Source="DataTemplates.xaml"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="8*"/>
        </Grid.RowDefinitions>
        <Button x:Name="BckSpace" Content="Back" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0"/>
        <ContentControl Grid.Row="1" Content={Binding Path=MyContent}/>
    </Grid>
</Window>


public class MainWindowVM  
{
    //...

    public MainViewModel
    {
        MyContent = new TheViewModelThatShouldBeShownAtStart();
    }

    public object MyContent
    {
        get; private set; // add Notification!
    }


    void FunctionCalledWhenButtonIsPressed()
    {
        if (...) // add your logic here
            MyContent = new VM1();
        else
            MyContent = new VM2();
    }

}

#2


0  

You can use a style inside your content control that will switch its content based on a common bound property within your main ViewModel.

您可以在内容控件中使用一种样式,该样式将根据主ViewModel中的公共绑定属性切换其内容。

Something like:

就像是:

<Window x:Class="ExampleContentCtrl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="375" Width="300" Loaded="Window_Loaded">
    <Window.Resources>
        <ResourceDictionary Source="DataTemplates.xaml"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="8*"/>
        </Grid.RowDefinitions>
        <Button x:Name="BckSpace" Content="Back" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0"/>
        <ContentControl Grid.Row="1">
        <ContentControl.Style>
            <Style>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding UserControlToShow}" Value="MainGadget">
                        <Setter Property="ContentControl.Content" Value="{StaticResource MainGadget}"/>
                    </DataTrigger>
                    <DataTrigger Biniding="{Binding UserControlToShow}" Value="MyGadget">
                        <Setter Property="ContentControl.Content" Value="{StaticResource MyGadget}"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ContentControl.Style>
        </ContentControl>
    </Grid>
</Window>

Then, update your ResourceDictionary so that the DateTemplates have keys:

然后,更新您的ResourceDictionary,以便DateTemplates具有键:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:vm="clr-namespace:ExampleContentCtrl.VMs"
                    xmlns:view="clr-namespace:ExampleContentCtrl.Panels">
    <DataTemplate DataType="{x:Type vm:MainGadgetVM}" x:Key="MainGadget">
        <view:MainGadget/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:MyGadgetVM}" x:Key="MyGadget">
        <view:MyGadget/>
    </DataTemplate>
</ResourceDictionary>

Now, add the property that is used as the trigger to switch content:

现在,添加用作切换内容的触发器的属性:

   public class MainWindowVM : ViewModelBase
    {
        private RelayCommand _ShowWorkSpace;
        private static MainWindowVM _Instance;
        public static MainWindowVM Instance { get { return _Instance; } }

        private string _userControlToShow;
        public string UserControlToShow 
        {
            get { return _userControlToShow; }
            set
            {
                _userControlToShow = value;
                RaisePropertyChanged("UserControlToShow");
            }
        }

        public MainWindowVM()
        {
            MainWindowVM._Instance = this;
        }

        public RelayCommand ShowWorkSpace
        {
            get
            {
                if (_ShowWorkSpace == null)
                    _ShowWorkSpace = new RelayCommand(param => { });
                return _ShowWorkSpace;
            }
        }
    }

#3


0  

After trying out the solutions mentioned above, I fell upon John Smith's MSDN article here. In this article he explains how to apply a view to a viewmodel and how to relay the corresponding command logic. Although this was similar to the link posted by @AbinMathew it was more detailed and actually provided the desired solution.

在尝试了上面提到的解决方案之后,我在这里找到了John Smith的MSDN文章。在本文中,他解释了如何将视图应用于视图模型以及如何中继相应的命令逻辑。虽然这与@AbinMathew发布的链接类似,但它更加详细,实际上提供了所需的解决方案。

#1


1  

Add a binding to your content control and change the bound value to the view model you want to show.

向内容控件添加绑定,并将绑定值更改为要显示的视图模型。

<Window x:Class="ExampleContentCtrl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="375" Width="300" Loaded="Window_Loaded">
    <Window.Resources>
        <ResourceDictionary Source="DataTemplates.xaml"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="8*"/>
        </Grid.RowDefinitions>
        <Button x:Name="BckSpace" Content="Back" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0"/>
        <ContentControl Grid.Row="1" Content={Binding Path=MyContent}/>
    </Grid>
</Window>


public class MainWindowVM  
{
    //...

    public MainViewModel
    {
        MyContent = new TheViewModelThatShouldBeShownAtStart();
    }

    public object MyContent
    {
        get; private set; // add Notification!
    }


    void FunctionCalledWhenButtonIsPressed()
    {
        if (...) // add your logic here
            MyContent = new VM1();
        else
            MyContent = new VM2();
    }

}

#2


0  

You can use a style inside your content control that will switch its content based on a common bound property within your main ViewModel.

您可以在内容控件中使用一种样式,该样式将根据主ViewModel中的公共绑定属性切换其内容。

Something like:

就像是:

<Window x:Class="ExampleContentCtrl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="375" Width="300" Loaded="Window_Loaded">
    <Window.Resources>
        <ResourceDictionary Source="DataTemplates.xaml"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="8*"/>
        </Grid.RowDefinitions>
        <Button x:Name="BckSpace" Content="Back" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0"/>
        <ContentControl Grid.Row="1">
        <ContentControl.Style>
            <Style>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding UserControlToShow}" Value="MainGadget">
                        <Setter Property="ContentControl.Content" Value="{StaticResource MainGadget}"/>
                    </DataTrigger>
                    <DataTrigger Biniding="{Binding UserControlToShow}" Value="MyGadget">
                        <Setter Property="ContentControl.Content" Value="{StaticResource MyGadget}"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ContentControl.Style>
        </ContentControl>
    </Grid>
</Window>

Then, update your ResourceDictionary so that the DateTemplates have keys:

然后,更新您的ResourceDictionary,以便DateTemplates具有键:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:vm="clr-namespace:ExampleContentCtrl.VMs"
                    xmlns:view="clr-namespace:ExampleContentCtrl.Panels">
    <DataTemplate DataType="{x:Type vm:MainGadgetVM}" x:Key="MainGadget">
        <view:MainGadget/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:MyGadgetVM}" x:Key="MyGadget">
        <view:MyGadget/>
    </DataTemplate>
</ResourceDictionary>

Now, add the property that is used as the trigger to switch content:

现在,添加用作切换内容的触发器的属性:

   public class MainWindowVM : ViewModelBase
    {
        private RelayCommand _ShowWorkSpace;
        private static MainWindowVM _Instance;
        public static MainWindowVM Instance { get { return _Instance; } }

        private string _userControlToShow;
        public string UserControlToShow 
        {
            get { return _userControlToShow; }
            set
            {
                _userControlToShow = value;
                RaisePropertyChanged("UserControlToShow");
            }
        }

        public MainWindowVM()
        {
            MainWindowVM._Instance = this;
        }

        public RelayCommand ShowWorkSpace
        {
            get
            {
                if (_ShowWorkSpace == null)
                    _ShowWorkSpace = new RelayCommand(param => { });
                return _ShowWorkSpace;
            }
        }
    }

#3


0  

After trying out the solutions mentioned above, I fell upon John Smith's MSDN article here. In this article he explains how to apply a view to a viewmodel and how to relay the corresponding command logic. Although this was similar to the link posted by @AbinMathew it was more detailed and actually provided the desired solution.

在尝试了上面提到的解决方案之后,我在这里找到了John Smith的MSDN文章。在本文中,他解释了如何将视图应用于视图模型以及如何中继相应的命令逻辑。虽然这与@AbinMathew发布的链接类似,但它更加详细,实际上提供了所需的解决方案。