简体   繁体   中英

How can I switch between user controls in my MVVM project

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.

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.

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.

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.

解决方案资源管理器

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

<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

<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

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

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

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

}

You can use a style inside your content control that will switch its content based on a common bound property within your main 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 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;
            }
        }
    }

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.

Grid.Children.Clear();
Grid.Children.Add(new NextUserControl());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM