簡體   English   中英

我如何從 MVVM WPF 中的視圖 go 到另一個視圖?

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

這里我有一個 WPF 應用程序,它是用 MVVM 結構制作的。 我是 C# WPF 的新手,不熟悉這個概念。 我試圖通過按下按鈕在一個視圖中通過 function 切換到另一個視圖。

這是應用程序的樣子, 在此處輸入圖像描述

按下登錄按鈕后,將觸發 function 以驗證輸入,如果有效則切換到另一個視圖。 看起來像這樣,

在此處輸入圖像描述

文件結構

在此處輸入圖像描述

我怎樣才能切換視圖? 下面是一些代碼供參考。

主窗口.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

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

登錄查看.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);
                
            }
        }
    }
}

謝謝

我建議使用“Datatemplate”。 放入主要的 window 資源如下:

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

等等... WPF 正在為您完成所有工作,它檢查“CurrentView”屬性和 select 如何根據合適的 DataTemplate 查看它。

導航是一個棘手的話題,有幾種方法可以做到這一點,但由於您是 WPF 的新手,我試圖概述一種簡單的技術,根據您提供的示例,要求必須逐頁 go,這是一個簡單的想法將是換出內容。 我的意思是,當用戶單擊“登錄”時,我們對用戶進行身份驗證並將 LoginPage 與其他頁面交換,在您的情況下是測驗頁面,當用戶選擇任何選項時,我們將視圖換成下一個視圖等等在。

我用 Shell 機制編寫了一個簡單的解決方案。 本質上,我們在 MainWindow 中創建一個空的 shell(即它沒有 UI),我們使用 NavigationService/Helper 將頁面加載到這個空的 shell 中。 為了簡單起見,我在這里使用了 singleton class,其中有 3 種主要方法,

RegisterShell:這必須是交換將發生的 Window,理想情況下需要設置一次。

加載視圖:用新視圖交換舊視圖的方法,我為此使用了用戶控件,因為大多數子視圖都可以是 WPF 中的用戶控件。

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

現在這是我的登錄頁面的樣子,這里重要的一行是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>();
            }
        }

在 _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));
        }
    }

最后,您的 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 應該是空的,本質上是一個 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>

如何讓視圖 model 參與頁面導航有很多方法。
一般情況下,每個參與導航的class都必須訪問你的導航API。
例如,您可以將導航邏輯移動到專用的 class NavigationService並提供對每個應該能夠導航到不同視圖的 class 的共享引用。

或者(推薦),您可以使用您在MainWindow上處理的路由命令,然后將命令委托給MainViewModel
在這種情況下,每個按鈕都必須將目標作為CommandParameter傳遞。 該解決方案允許特定視圖模型不直接參與導航。 您不需要用導航詳細信息污染您的視圖 model 類。

以下示例顯示如何使用RoutedCommandQuizView導航到ResultView

MainViewModel.cs
MainViewModel是唯一知道如何導航和了解相關細節的視圖 model class。
這實現了可擴展性,同時保持視圖 model 類的實現簡單。

通常,要啟用數據驗證,讓視圖模型實現INotifyDataErrorInfo
然后,您可以在允許離開頁面之前查詢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

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

主窗口.xaml
要實際呈現視圖(例如自定義控件),您需要定義一個隱式DataTemplate (沒有x:Key指令),它具有關聯視圖 model class 作為DataType 然后, 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>

如果視圖需要導航,它必須使用 static 路由命令(在MainWindow中定義和處理)並將目標視圖 model 的Type作為CommandParameter傳遞。
這樣,導航就不會污染視圖模型並停留在視圖內。

QuizView.xaml

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

結果視圖.xaml

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

因為視圖model類一般不直接參與導航,
他們不必執行任何相關命令或依賴任何導航服務。
導航完全由MainWindow及其MainViewModel控制。

對於可選的數據驗證,讓他們實施INotifyDataErrorInfo

QuizVM.cs

class QuizVM : INotifyPropertyChnaged, INotifyDataErrorInfo
{
}

結果VM.cs

class ResultVM : INotifyPropertyChnaged, INotifyDataErrorInfo
{
}

此示例演示了兩種導航方法。 通常很有用,因為您說要從登錄開始,但在用戶登錄之前不顯示任何菜單等。然后一旦他們登錄,您需要某種菜單或視圖列表,他們可以導航到仍然是 static。

我的主窗口純粹是一個包含所有內容的 shell。

它的標記是:

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

此示例首先對所有導航使用 viewmodel。 視圖模型被模板化到 UI 中。

后面的代碼還有更多。

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

Messenger 注冊將使用依賴屬性切換出窗口的數據上下文。 該消息只是一個 class 具有傳遞類型的屬性

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

回調 ParentViewModelChanged 采用一個類型,將其實例化並在 window 上設置數據上下文。

通常,您對保留 window 或父級視圖的 state 不感興趣。 您已經登錄。如果您想重新登錄,您將重新開始並輸入用戶名和密碼。

入口點有點不尋常,因為我處理應用程序啟動並依賴於該依賴屬性回調。

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

而不是一個充滿菜單等的主窗口,我當然什么也沒有。

我有一個 LoginUC 是您將在啟動時看到的第一件事。 這只是說明性的。

在真正的應用程序中導航之前,我們將從用戶那里獲取輸入並對其進行驗證。 我們只是對這里的導航感興趣,所以這個版本只有一個按鈕可以導航到 MainViewModel:

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

我的 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
    }
}

基礎父視圖模型

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

    virtual public async Task Initiate() { }
}

界面

public interface IInitiatedViewModel
{
    Task Initiate();
}

此接口的目的是為我們提供一種通用方法,讓任何視圖模型都能獲取所需的任何數據。 通過設置數據上下文,然后啟動一個后台線程來獲取該數據,視圖將快速出現,然后填充它需要的任何數據。 如果獲取該數據需要一段時間,那么至少在任務仍在繼續工作的同時視圖是“向上”並且快速可見的。

在一個更完整的示例中,我們將在基礎視圖模型中使用 IsBusy,它將從 true 開始並更改為 false。 這將在視圖中驅動一個“throbber”或總線指示器。

資源字典使用數據類型將視圖模型數據模板與用戶控件相關聯:

<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>

即合並到 app.xaml

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

登錄后,window 的全部內容將被替換。 數據上下文從 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";
    }
}

當然,您可能希望有更多視圖並選擇一個最初顯示,這將在 Initiate 中設置。

主要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>

在視圖中,您會在左列中獲得一個按鈕列表,這些按鈕將允許在右列中導航。 但當然要保留 MainUC。

這可以是菜單或選項卡控件,而不是列表框。

單擊按鈕會調用 MainViewModel 中的命令,並將 ChildViewModel 的實例作為參數傳遞。

然后用於實例化視圖模型、設置 CurrentChildViewmodel 並緩存實例。

CurrentChildViewmodel 本身當然會被模板化到 MainUC 中的用戶控件中。

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

    public Type ViewModelType { get; set; }

    public object Instance { get; set; }
}

這是一種相當簡單的方法,在現實世界中,您可能需要依賴注入、工廠等大量應用程序。 但這已經是 Stack Overflow 答案的相當多的代碼了。

其余的視圖模型和視圖只是簡單的實現,以證明它一切正常。 例如

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM