简体   繁体   English

我如何从 MVVM WPF 中的视图 go 到另一个视图?

[英]How do I go to another view from a view in MVVM WPF?

Here I have a WPF application that is made with the MVVM structure.这里我有一个 WPF 应用程序,它是用 MVVM 结构制作的。 I am fairly new to C# WPF and am not familiar with this concept.我是 C# WPF 的新手,不熟悉这个概念。 I am attempting to switch to another view through a function in one view via the press of a button.我试图通过按下按钮在一个视图中通过 function 切换到另一个视图。

Here is what the application looks like,这是应用程序的样子, 在此处输入图像描述

Once the Login button is pressed a function is triggered that will validate the inputs and if valid switch to another view.按下登录按钮后,将触发 function 以验证输入,如果有效则切换到另一个视图。 Which would look like such,看起来像这样,

在此处输入图像描述

File Structure文件结构

在此处输入图像描述

How can i switch the views?我怎样才能切换视图? Below are some code for reference.下面是一些代码供参考。

MainWindow.xaml主窗口.xaml

<Window x:Class="QuizAppV2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:QuizAppV2"
        xmlns:viewModel="clr-namespace:QuizAppV2.MVVM.ViewModel"
        mc:Ignorable="d"
        Height="600" Width="920"
        WindowStartupLocation="CenterScreen"
        WindowStyle="None"
        ResizeMode="NoResize"
        Background="Transparent"
        AllowsTransparency="True">
    <Window.DataContext>
        <viewModel:MainViewModel/>
    </Window.DataContext>
    <Border Background="#272537"
            CornerRadius="20">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="75"/>
                <RowDefinition/>
                <RowDefinition Height="25"/>
            </Grid.RowDefinitions>

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>

                <TextBlock Text="Online Quiz"
                            Grid.Column="1"
                            FontSize="20"
                            Foreground="White"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"/>
                <StackPanel Grid.Column="2"
                            Margin="30,20"
                            Orientation="Horizontal"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Top">

                    <Button Content="–"
                            Background="#00CA4E"
                            Style="{StaticResource UserControls}"
                            Click="Minimise"/>
                    <Button Content="▢"
                            Background="#FFBD44"
                            Style="{StaticResource UserControls}"
                            Click="Restore"/>
                    <Button Content="X"
                            Background="#FF605C"
                            Style="{StaticResource UserControls}"
                            Click="Exit"/>
                </StackPanel>
            </Grid>
            <ContentControl Grid.Column="1"
                            Grid.Row="1"
                            Margin="20,10,20,50"
                            Content="{Binding CurrentView}"/>
        </Grid>
    </Border>
</Window>

MainViewModel.cs MainViewModel.cs

using QuizAppV2.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QuizAppV2.MVVM.ViewModel
{
    class MainViewModel : ObservableObject
    {

        public RelayCommand LoginViewCommand { get; set; }
        public RelayCommand SubjectSelectionViewCommand { get; set; }
        public RelayCommand QuizViewCommand { get; set; }
        public RelayCommand ResultViewCommand { get; set; }

        public LoginViewModel LoginVM { get; set; }
        public SubjectSelectionViewModel SubjectSelectVM { get; set; }
        public QuizViewModel QuizVM { get; set; }
        public ResultViewModel ResultVM { get; set; }


        private object _currentView;

        public object CurrentView
        {
            get { return _currentView; }
            set
            {
                _currentView = value;
                onPropertyChanged();
            }
        }

        public MainViewModel()
        {
            LoginVM = new LoginViewModel();
            SubjectSelectVM = new SubjectSelectionViewModel();
            QuizVM = new QuizViewModel();
            ResultVM = new ResultViewModel();
            CurrentView = SubjectSelectVM;

            LoginViewCommand = new RelayCommand(o =>
            {
                CurrentView = LoginVM;
            });
            SubjectSelectionViewCommand = new RelayCommand(o =>
            {
                CurrentView = SubjectSelectVM;
            });
        }
    }
}

LoginView.xaml登录查看.xaml

using QuizAppV2.MVVM.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace QuizAppV2.MVVM.View
{
    /// <summary>
    /// Interaction logic for LoginView.xaml
    /// </summary>
    public partial class LoginView : UserControl
    {
        public LoginView()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (UsrId.Text == "" || UsrName.Text == "")
            {
                UsrIDErrMsg.Visibility = Visibility.Visible;
                UsrNameErrMsg.Visibility = Visibility.Visible;
            }
            else
            {
                UsrIDErrMsg.Visibility = Visibility.Hidden;
                UsrNameErrMsg.Visibility = Visibility.Hidden;
                MainWindow.currentUser = new Student(UsrId.Text, UsrName.Text);
                
            }
        }
    }
}

Thank you谢谢

I suggest using "Datatemplate".我建议使用“Datatemplate”。 Put in the main window resources the following:放入主要的 window 资源如下:

<DataTemplate DataType="{x:Type viewmodel:QuizViewModel}">
            <local:QuizView/>
 </DataTemplate>
<DataTemplate DataType="{x:Type viewmodel:LoginViewModel}">
            <local:LoginView/>
 </DataTemplate>

and so on with the others... WPF is doing all the work for you, it examine the "CurrentView" prroperty and select how to view it according the the suitable DataTemplate.等等... WPF 正在为您完成所有工作,它检查“CurrentView”属性和 select 如何根据合适的 DataTemplate 查看它。

Navigation is a tricky topic there are few ways to do this but since you are new to WPF I tried to outline a simple technique, along the lines of the examples you've provided requirement is have to go from page to page, a simple idea would be to swap out the contents.导航是一个棘手的话题,有几种方法可以做到这一点,但由于您是 WPF 的新手,我试图概述一种简单的技术,根据您提供的示例,要求必须逐页 go,这是一个简单的想法将是换出内容。 What I mean by that is when the user clicks "Login" we authenticate the user and swap the LoginPage with some other page, in your case a quiz page, when the user selection any option we swap out the view with the next view and so on.我的意思是,当用户单击“登录”时,我们对用户进行身份验证并将 LoginPage 与其他页面交换,在您的情况下是测验页面,当用户选择任何选项时,我们将视图换成下一个视图等等在。

I've coded up a simple solution with Shell mechanism.我用 Shell 机制编写了一个简单的解决方案。 Essentially we create a empty shell in MainWindow (ie it has no UI) and we load pages into this empty shell using a NavigationService/Helper.本质上,我们在 MainWindow 中创建一个空的 shell(即它没有 UI),我们使用 NavigationService/Helper 将页面加载到这个空的 shell 中。 I've gone with a singleton class here just for simplicity, there are 3 main Methods in this,为了简单起见,我在这里使用了 singleton class,其中有 3 种主要方法,

RegisterShell: This has to be the Window where the swapping will happen, this ideally needs to be set once. RegisterShell:这必须是交换将发生的 Window,理想情况下需要设置一次。

Load View: Method which Swaps out old view with the new one, I have gone with user control for this as most of the sub views can be user control in WPF.加载视图:用新视图交换旧视图的方法,我为此使用了用户控件,因为大多数子视图都可以是 WPF 中的用户控件。

LoadViewWithCustomData: Similar to above but has more flexibilty since it allows you to supply extra data. LoadViewWithCustomData:与上面类似,但具有更多的灵活性,因为它允许您提供额外的数据。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace Navigation
{
    class NavigationService
{
    /// <summary>
    /// Singleton so we keep on shell which views can use this to navigate to different pages.
    /// </summary>
    public static NavigationService Instance = new NavigationService();
    private MainWindow myShell;


    private NavigationService()
    {
    }

    /// <summary>
    /// Register the main shell so this service know where to swap the data out and in of
    /// </summary>
    /// <param name="theShell"></param>
    public void RegisterShell(MainWindow theShell)
    {
        this.myShell = theShell;
    }

    /// <summary>
    /// Swaps out any view to the shell.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public void LoadView<T>() where T : UserControl, new()
    {
        myShell.TheShell = new T();
    }

    /// <summary>
    /// Swaps out any view to the shell with custom data, here the user responsible to create UserControl with all the reqired data for the view.
    /// We can automate this via reflection if required.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="theNewControl"></param>
    public void LoadViewWithCustomData<T>(UserControl theNewControl) where T : UserControl, new()
    {
        myShell.TheShell = theNewControl;
    }
}

Now here's how my LoginPage looks, the important line here is NavigationService.Instance.LoadView<_4OptionQuizPage>() this essentially sends the user to _4OptionQuizPage.现在这是我的登录页面的样子,这里重要的一行是NavigationService.Instance.LoadView<_4OptionQuizPage>()这实质上将用户发送到 _4OptionQuizPage。

    public partial class LoginPage : UserControl
        {
            public ICommand LoginClicked { get; }
            public LoginPage()
            {
                InitializeComponent();
                this.DataContext = this;
                LoginClicked = new SimpleCommand(OnLoginClicked);
            }
    
            private void OnLoginClicked()
            {
                // TODO : Authenticate user here.
    
                // Send the user to Quiz Page
                NavigationService.Instance.LoadView<_4OptionQuizPage>();
            }
        }

And in the _4OptionQuizPage we can have something like this, this is where the bulk of business logic may reside, I have 4 buttons here, 2 of them show message box but Button 1 sends you back to LoginPage and Button 2 reloads the same page with different data (ie sending the user to next question)在 _4OptionQuizPage 中,我们可以有这样的东西,这是大部分业务逻辑可能驻留的地方,我在这里有 4 个按钮,其中 2 个显示消息框,但按钮 1 将您发送回登录页面,按钮 2 重新加载同一页面不同的数据(即将用户发送到下一个问题)

public partial class _4OptionQuizPage : UserControl, INotifyPropertyChanged
    {
        public ICommand Option1Clicked { get; }
        public ICommand Option2Clicked { get; }
        public ICommand Option3Clicked { get; }
        public ICommand Option4Clicked { get; }
        private string myQuestion;
        public event PropertyChangedEventHandler PropertyChanged;
        public string Question
        {
            get { return myQuestion; }
            set 
            {
                myQuestion = value;
                NotifyPropertyChanged();
            }
        }
        public _4OptionQuizPage() : this($"Question Loaded At {DateTime.Now}, this can be anything.")
        {
        }
        public _4OptionQuizPage(string theCustomData)
        {
            InitializeComponent();
            Question = theCustomData; 
            this.DataContext = this;
            this.Option1Clicked = new SimpleCommand(OnOption1Clicked);
            this.Option2Clicked = new SimpleCommand(OnOption2Clicked);
            this.Option3Clicked = new SimpleCommand(OnOption3Clicked);
            this.Option4Clicked = new SimpleCommand(OnOption4Clicked);
        }
        private void OnOption4Clicked()
        {
            MessageBox.Show("Option 4 selected, Store the results");
        }
        private void OnOption3Clicked()
        {
            MessageBox.Show("Option 3 selected, Store the results");
        }
        private void OnOption1Clicked()
        {
            NavigationService.Instance.LoadView<LoginPage>();
        }
        private void OnOption2Clicked()
        {
            NavigationService.Instance.LoadViewWithCustomData<LoginPage>(new _4OptionQuizPage("A custom question to emulate custom data"));
        }
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Finally your MainWindow would be registering the shell and sending the user to LoginPage, and it's XAML file should not have anything in it最后,您的 MainWindow 将注册 shell 并将用户发送到 LoginPage,它的 XAML 文件中不应包含任何内容

    public partial class MainWindow : Window, INotifyPropertyChanged
{
    private object myShell;

    public object TheShell
    {
        get { return myShell; }

        set 
        { 
            myShell = value;
            this.NotifyPropertyChanged();
        }
    }

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
        NavigationService.Instance.RegisterShell(this);
        NavigationService.Instance.LoadView<LoginPage>();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

}

MainWindow.xaml should be empty, essentially a shell for everything else. MainWindow.xaml 应该是空的,本质上是一个 shell 用于其他所有内容。

<Window x:Class="Navigation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Navigation"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Content="{Binding TheShell}">
</Window>

There are many ways how to allow a view model to participate in page navigation.如何让视图 model 参与页面导航有很多方法。
In general, each class that participates in navigation has to have access to your navigation API.一般情况下,每个参与导航的class都必须访问你的导航API。
For example, you could move the navigation logic to a dedicated class NavigationService and provide a shared reference to every class that should be able to navigate to a different view.例如,您可以将导航逻辑移动到专用的 class NavigationService并提供对每个应该能够导航到不同视图的 class 的共享引用。

Alternatively (and recommended), you can use routed commands that you handle on the MainWindow , which then delegates the command to the MainViewModel .或者(推荐),您可以使用您在MainWindow上处理的路由命令,然后将命令委托给MainViewModel
In this scenario each button would have to pass the destination as CommandParameter .在这种情况下,每个按钮都必须将目标作为CommandParameter传递。 This solution allows the particular view models to not directly participate in the navigation.该解决方案允许特定视图模型不直接参与导航。 You don't need to pollute your view model classes with navigation details.您不需要用导航详细信息污染您的视图 model 类。

The following example shows how to navigate from the QuizView to the ResultView using a RoutedCommand .以下示例显示如何使用RoutedCommandQuizView导航到ResultView

MainViewModel.cs MainViewModel.cs
The MainViewModel is the only view model class that knows how to navigate and about the related details. MainViewModel是唯一知道如何导航和了解相关细节的视图 model class。
This enables extensibility while keeping the implementation of the view model classes simple.这实现了可扩展性,同时保持视图 model 类的实现简单。

In general, to enable data validation let the view models implement INotifyDataErrorInfo .通常,要启用数据验证,让视图模型实现INotifyDataErrorInfo
You can then query the INotifyDataErrorInfo.HasErrors property before allowing to leave a page.然后,您可以在允许离开页面之前查询INotifyDataErrorInfo.HasErrors属性。

class MainViewModel : ObservableObject
{
  public object CurrentView { get; set; }
  private Dictionary<Type, INotifyPropertyChanged> ViewModelMap { get; }

  public MainViewModel()
  {
    this.ViewModelMap = new Dictionary<Type, INotifyPropertyChanged>
    {
      { typeof(QuizVm),  new QuizVm() },
      { typeof(ResultVm),  new ResultVm() },
    };
  }

  // Check if destination type is valid.
  // In case the navigation source implements INotifyDataErrorInfo,
  // check if the source is in a valid state (INotifyDataErrorInfo.HasEWrrors returns 'false').
  // This method is called by the view. It will delegate its ICommand.CanExecute to this method
  // If this method returns 'false' the command source e.g. Button will be disabled.
  public bool CanNavigate(Type navigationSourceType, Type navigationDestinationType)
    => CanNavigateAwayFrom(navigationSourceType) 
      && CanNavigateTo(navigationDestinationType);

  private bool CanNavigateAwayFrom(Type navigationSourceType) 
    => this.ViewModelMap.TryGetValue(navigationSourceType, out INotifyPropertyChanged viewModel)
      && viewModel is INotifyDataErrorInfo notifyDataErrorInfo
        ? !notifyDataErrorInfo.HasErrors
        : true;

  private bool CanNavigateTo(Type navigationDestinationType)
    => this.ViewModelMap.ContainsKey(navigationDestinationType);

  // This method is called by the view. It will delegate its ICommand.Execute to this method
  public void NavigateTo(Type destinationType)
  {
    if (this.ViewModelMap.TryGetValue(destinationType, out INotifyPropertyChanged viewModel))
    {
      this.CurrentView = viewModel;
    }
  }
}

MainWindow.xaml.cs MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static RoutedCommand NavigateCommand { get; } = new RoutedUICommand(
    "Navigate to view command", 
    nameof(NavigateCommand), 
    typeof(MainWindow));

  private MainViewModel MainViewModel { get; }

  public MainWindow()
  {   
    InitializeComponent();

    this.MainViewModel = new MainViewModel();
    this.DataContext = this.MainViewModel;

    var navigateCommandBinding = new CommandBinding(MainWindow.NavigateCommand, ExecuteNavigateCommand, CanExecuteNavigateCommand);
    this.CommandBindings.Add(navigateCommandBinding);
  }

  private void CanExecuteNavigateCommand(object sender, CanExecuteRoutedEventArgs e)
  {
    if (e.Source is not FrameworkElement commandSource)
    {
      return;
    }

    Type navigationSourceType = commandSource.DataContext.GetType();
    Type navigationDestinationType = (Type)e.Parameter;
    e.CanExecute = this.MainViewModel.CanNavigate(navigationSourceType, navigationDestinationType);
  }

  private void ExecuteNavigateCommand(object sender, ExecutedRoutedEventArgs e)
  {
    var destinationViewModelType = (Type)e.Parameter;
    this.MainViewModel.NavigateTo(destinationViewModelType);
  }
}

MainWindow.xaml主窗口.xaml
To actually render the views (for example a custom Control) you need to define an implicit DataTemplate (without the x:Key directive) that has the associated view model class as DataType .要实际呈现视图(例如自定义控件),您需要定义一个隐式DataTemplate (没有x:Key指令),它具有关联视图 model class 作为DataType The ContentControl will then automatically pick the correct one that matches the type of the ContentControl.Content property value.然后, ContentControl将自动选择与ContentControl.Content属性值的类型相匹配的正确类型。

<Window>
  <Window.Resources>
    <DataTemplate DataType="{x:Type local:QuizVM}">
      <QuizView />
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:ResultVM}">
      <ResultView />
    </DataTemplate>
  </Window.Resources>

  <ContentControl Content="{Binding CurrentView}" />
</Window>

If a view needs to navigate, it must use the static routed command (defined and handled in the MainWindow ) and pass the Type of the destination view model as CommandParameter .如果视图需要导航,它必须使用 static 路由命令(在MainWindow中定义和处理)并将目标视图 model 的Type作为CommandParameter传递。
This way, navigation will not pollute the view models and stays within the view.这样,导航就不会污染视图模型并停留在视图内。

QuizView.xaml QuizView.xaml

<QuizView>
  <Button Content="Next"
          Command="{x:Static local:MainWindow.NextPageCommand}"
          CommandParameter="{x:Type local:ResultVM}"/>
</QuizView>

ResultView.xaml结果视图.xaml

<ResultView>
  <Button Content="Back"
          Command="{x:Static local:MainWindow.NextPageCommand}"
          CommandParameter="{x:Type local:QuizVM}"/>
</ResultView>

Because the view model classes generally don't directly participate in the navigation,因为视图model类一般不直接参与导航,
they don't have to implement any related commands or depend on any navigation service.他们不必执行任何相关命令或依赖任何导航服务。
Navigation is completely controlled by the MainWindow and its MainViewModel .导航完全由MainWindow及其MainViewModel控制。

For optional data validation let them implement INotifyDataErrorInfo .对于可选的数据验证,让他们实施INotifyDataErrorInfo

QuizVM.cs QuizVM.cs

class QuizVM : INotifyPropertyChnaged, INotifyDataErrorInfo
{
}

ResultVM.cs结果VM.cs

class ResultVM : INotifyPropertyChnaged, INotifyDataErrorInfo
{
}

This sample demonstrates two approaches to navigation.此示例演示了两种导航方法。 Often useful since you say want to start by logging in but not show any menus etc until the user is logged in. Then once they log in you want some sort of menu or list of views they can navigate to which remains static.通常很有用,因为您说要从登录开始,但在用户登录之前不显示任何菜单等。然后一旦他们登录,您需要某种菜单或视图列表,他们可以导航到仍然是 static。

My mainwindow is purely a shell to contain everything.我的主窗口纯粹是一个包含所有内容的 shell。

It's markup is:它的标记是:

 <Window ......
    Title="{Binding Title}" 
    Content="{Binding}"
    />

This sample uses viewmodel first for all navigation.此示例首先对所有导航使用 viewmodel。 Viewmodels are templated out into UI.视图模型被模板化到 UI 中。

There is more in the code behind.后面的代码还有更多。

public partial class LoginNavigationWindow : Window
{
    public Type ParentViewModel
    {
        get { return (Type)GetValue(ParentViewModelProperty); }
        set { SetValue(ParentViewModelProperty, value); }
    }

    public static readonly DependencyProperty ParentViewModelProperty =
        DependencyProperty.Register(name: "ParentViewModel",
        propertyType: typeof(Type),
        ownerType: typeof(LoginNavigationWindow),
        typeMetadata: new FrameworkPropertyMetadata(
            defaultValue: null,
        propertyChangedCallback: new PropertyChangedCallback(ParentViewModelChanged)
      ));
    private static void ParentViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var vm = Activator.CreateInstance((Type)e.NewValue);
        ((Window)d).DataContext = vm;
        Task.Run(((IInitiatedViewModel)vm).Initiate);
    }

    public LoginNavigationWindow()
    {
        InitializeComponent();
        WeakReferenceMessenger.Default.Register<ParentNavigate>(this, (r, pn) =>
        {
            this.SetValue(LoginNavigationWindow.ParentViewModelProperty, pn.ParentViewModelType);
        });
    }

The messenger registration will switch out the window's datacontext using a dependency property. Messenger 注册将使用依赖属性切换出窗口的数据上下文。 The message is just a class with a property to pass a Type该消息只是一个 class 具有传递类型的属性

public class ParentNavigate
{
    public Type ParentViewModelType { get; set; }
}

The callback ParentViewModelChanged takes a type, instantiates it and sets datacontext on the window.回调 ParentViewModelChanged 采用一个类型,将其实例化并在 window 上设置数据上下文。

Usually, you're not interested in retaining state of a window or parent level piece of view.通常,您对保留 window 或父级视图的 state 不感兴趣。 You already logged in. If you wanted to log back in again then you would start again and input name and password.您已经登录。如果您想重新登录,您将重新开始并输入用户名和密码。

The entrypoint is slightly unusual since I handle application startup and rely on that dependency property callback.入口点有点不寻常,因为我处理应用程序启动并依赖于该依赖属性回调。

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        var mw = new LoginNavigationWindow();
        mw.Show();
        mw.SetValue(LoginNavigationWindow.ParentViewModelProperty, typeof(LoginViewModel));
    }

Instead of a mainwindow full of menus etc I have of course got nothing.而不是一个充满菜单等的主窗口,我当然什么也没有。

I have a LoginUC is the first thing you will see on start up.我有一个 LoginUC 是您将在启动时看到的第一件事。 This is just illustrative.这只是说明性的。

We will get input from the user and validate it before navigating in a real app.在真正的应用程序中导航之前,我们将从用户那里获取输入并对其进行验证。 We're just interested in that navigation here so this version just has a button to navigate to MainViewModel:我们只是对这里的导航感兴趣,所以这个版本只有一个按钮可以导航到 MainViewModel:

<Grid>
    <StackPanel>
        <TextBlock Text="Log in"/>
        <Button Content="Go"
                Command="{Binding LoadMainCommand}"/>
    </StackPanel>
</Grid>
</UserControl>

My LoginViewModel has a command, title and a task.我的 LoginViewModel 有命令、标题和任务。

public partial class LoginViewModel : BaseParentViewModel
{
    [RelayCommand]
    private async Task LoadMain()
    {
        var pn = new ParentNavigate{ ParentViewModelType = typeof(MainViewModel) };
        WeakReferenceMessenger.Default.Send(pn);
    }
    public LoginViewModel()
    {
        Title = "Please Log In first";
    }

    public override async Task Initiate()
    {
        // Get any data for login here
    }
}

BaseParentViewModel基础父视图模型

public partial class BaseParentViewModel : ObservableObject, IInitiatedViewModel
{
    [ObservableProperty]
    private string title = string.Empty;

    virtual public async Task Initiate() { }
}

Interface界面

public interface IInitiatedViewModel
{
    Task Initiate();
}

The purpose of this interface is to give us a generic way for any viewmodel to get any data it requires.此接口的目的是为我们提供一种通用方法,让任何视图模型都能获取所需的任何数据。 By setting datacontext and then starting up a background thread to get that data the view will appear quickly and then fill with any data it needs.通过设置数据上下文,然后启动一个后台线程来获取该数据,视图将快速出现,然后填充它需要的任何数据。 If getting that data takes a while then at least the view is "up" and visible quickly whilst the task still carries on working.如果获取该数据需要一段时间,那么至少在任务仍在继续工作的同时视图是“向上”并且快速可见的。

In a fuller example we would have IsBusy in a base viewmodel which would start off true and be changed to false.在一个更完整的示例中,我们将在基础视图模型中使用 IsBusy,它将从 true 开始并更改为 false。 That would drive a "throbber" or busing indicator in the view.这将在视图中驱动一个“throbber”或总线指示器。

A resource dictionary associates viewmodel datatemplates with usercontrols using datatype:资源字典使用数据类型将视图模型数据模板与用户控件相关联:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:LoginNavigation"
                    >
    <DataTemplate DataType="{x:Type local:MainViewModel}">
        <local:MainUC/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:LoginViewModel}">
        <local:LoginUC/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:SubjectsViewModel}">
        <local:SubjectsView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:ResultViewModel}">
        <local:ResultView/>
    </DataTemplate>
</ResourceDictionary>

That is merged in app.xaml即合并到 app.xaml

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                 <ResourceDictionary  Source="/Resources/ViewDataTemplates.xaml"/>
             </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Once you login, the entire content of the window is replaced.登录后,window 的全部内容将被替换。 The datacontext is changed from LoginViewModel to MainViewModel, that is then templated out into MainUC:数据上下文从 LoginViewModel 更改为 MainViewModel,然后模板化到 MainUC 中:

public partial class MainViewModel : BaseParentViewModel
{

    [ObservableProperty]
    private object currentChildViewModel;

    [ObservableProperty]
    private List<ChildViewModel> childViewModelList;

    [RelayCommand]
    private async Task ChildNavigation(ChildViewModel cvm)
    {
        if (cvm.Instance == null)
        {
            cvm.Instance = Activator.CreateInstance(cvm.ViewModelType);
            if (cvm.Instance is IInitiatedViewModel)
            {
                Task.Run(((IInitiatedViewModel)cvm.Instance).Initiate);
            }
        }
        CurrentChildViewModel = cvm.Instance;
    }

    public override async Task Initiate()
    {
        ChildViewModelList = new List<ChildViewModel>()
        {
            new ChildViewModel{ DisplayName="Subjects", ViewModelType= typeof(SubjectsViewModel) },
            new ChildViewModel{ DisplayName="Results", ViewModelType= typeof(ResultViewModel) }
        };
    }
    public MainViewModel()
    {
        Title = "Quiz";
    }
}

You would probably want to have more views of course and pick one to show initially which would be setup in Initiate.当然,您可能希望有更多视图并选择一个最初显示,这将在 Initiate 中设置。

MainUC:主要UC:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <ListBox ItemsSource="{Binding ChildViewModelList}"
             HorizontalContentAlignment="Stretch">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Button Content="{Binding DisplayName}"
                        Command="{Binding DataContext.ChildNavigationCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
                        CommandParameter="{Binding}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <ContentPresenter Content="{Binding CurrentChildViewModel}"
                      Grid.Column="1"/>
</Grid>
</UserControl>

In the view you get a list of buttons in a left column which will allow navigation in the right column.在视图中,您会在左列中获得一个按钮列表,这些按钮将允许在右列中导航。 But retaining MainUC of course.但当然要保留 MainUC。

Instead of a listbox this could be a menu or maybe a tabcontrol.这可以是菜单或选项卡控件,而不是列表框。

Clicking on a button calls a command in MainViewModel and passes the instance of ChildViewModel as a parameter.单击按钮会调用 MainViewModel 中的命令,并将 ChildViewModel 的实例作为参数传递。

That is then used to instantiate a viewmodel, set CurrentChildViewmodel and cache the instance.然后用于实例化视图模型、设置 CurrentChildViewmodel 并缓存实例。

CurrentChildViewmodel will of course itself be templated out into a usercontrol within MainUC. CurrentChildViewmodel 本身当然会被模板化到 MainUC 中的用户控件中。

public partial class ChildViewModel : ObservableObject
{
    public string DisplayName { get; set; }

    public Type ViewModelType { get; set; }

    public object Instance { get; set; }
}

This is rather a simplistic approach and in a real world substantial app you would want dependency injection, factories and the like.这是一种相当简单的方法,在现实世界中,您可能需要依赖注入、工厂等大量应用程序。 But this is already quite a bit of code for a Stack Overflow answer as it is.但这已经是 Stack Overflow 答案的相当多的代码了。

The remaining viewmodels and views are just simplistic implementations to prove it all works.其余的视图模型和视图只是简单的实现,以证明它一切正常。 eg例如

public partial class SubjectsViewModel : ObservableObject, IInitiatedViewModel
{
    public async Task Initiate()
    {
        // Get any data for Subjects here
    }
}

and

    <Grid>
        <TextBlock Text="Subjects"/>
    </Grid>
</UserControl>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在WPF中使用MVVM从另一个视图打开一个视图 - How to open one view from another view using MVVM in WPF 在WPF MVVM中从一个视图导航到另一个视图 - Navigation from one view to another in WPF MVVM MVVM-WPF如何将我的视图绑定到我的Viewmodel? - MVVM - WPF How do i bind my View to my Viewmodel? 如何在应用程序的框架中使用视图? (WPF MVVM) - How can I use a view from a framework in an application? (WPF MVVM) 如何使用Caliburn.Micro MVVM在WPF中的同一视图上从另一个视图模型更新list &lt;&gt;? - How to update list<> from another View model on same View in WPF using Caliburn.Micro MVVM? 在 WPF Prism MVVM 中,如何从子视图调整父视图的大小 - In WPF Prism MVVM, How to resize Parent view from Child View 如何使用单击处理程序和命令在WPF MVVM中打开另一个视图? (我的解决方案合理吗?) - How can I open another view in WPF MVVM using click handlers and commands? (Is my solution reasonable?) WPF查看谁使用MVVM导致另一个人 - WPF view who leads to another using MVVM 在 MVVM WPF 中从另一个视图模型更改视图模型中的值 - Change a value from a view model from another view model in MVVM WPF WPF/MVVM:如何从 ItemsControl 将模型绑定到视图/视图模型 - WPF/MVVM : How to bind model to view/viewmodel from ItemsControl
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM