65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MVVMC 导航的 WPF 中的向导应用程序

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.86/5 (6投票s)

2018年12月21日

MIT

6分钟阅读

viewsIcon

27948

downloadIcon

1009

演示了如何使用 Model-View-ViewModel-Controller 导航框架创建两个向导式应用程序

引言

我知道你在想什么——2018 年底,世界需要另一个 WPF 向导应用程序的实现。好吧,本文中提出的解决方案将展示一种构建多屏应用程序的新方法,它具有一些独特的优势。

所解释的实现使用了 MVVMC 模式。这与 MVC 有些相似,并为您的应用程序添加了控制器。我们将看到如何使用 Wpf.MVVMC 库来构建两个向导应用程序。第一个是简单的 4 步向导。第二个具有一些高级功能,如果使用简单的 MVVM 或其他导航框架,这将是一项繁重的工作。您将看到 MVVMC 开发速度快,简单且灵活多变。

完全公开:我是 Wpf.MVVMC 的创建者。

基本向导

Basic Wizard Video

高级向导

Advanced Wizard Video

基本向导教程

首先创建一个常规的 WPF 项目,然后将 Wpf.MVVMC NuGet 包添加到您的项目中。无需进行任何初始化或引导。

我们将遍历所有文件,但为了让您对它的工作原理有一个大致的了解,解决方案资源管理器将如下所示

如您所见,向导的每个步骤都有一个“View”和一个“ViewModel”,除了第一个。还有一个“WizardController”文件,我们很快就会讲到。首先,让我们从 MainWindow.xaml 开始

MainWindow.xaml

<Window x:Class="WizardAppMvvmc.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:mvvmc="clr-namespace:MVVMC;assembly=MVVMC"
        Title="Wizard with MVVMC" Height="450" Width="800"
        Background="#FFDDDDDD">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="180"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Border Background="LightSkyBlue"></Border>
		
        <mvvmc:Region Grid.Column="1" ControllerID="Wizard"></mvvmc:Region>
		
        <Border Grid.Row="1" Grid.ColumnSpan="2" Background="LightGray">
            <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
                <Button Command="{mvvmc:GoBackCommand ControllerID=Wizard}">Back</Button>
                <Button Command="{mvvmc:NavigateCommand Action=Next, ControllerID=Wizard}">
                 Next</Button>
            </StackPanel>
        </Border>
    </Grid>
</Window>

解释

  • mvvmc:Region 是屏幕上的一个区域,由 Controller 控制。它本质上是一个普通的 ContentControl,其内容会根据您的导航而变化。ControllerID=Wizard 属性决定了它与哪个 Controller 相关联。它是基于约定的,因此您必须有一个名为 WizardController 的类,它派生自 MVVMC.Controller(否则将抛出异常)。
  • 第一个 ButtonCommandmvvmc:GoBackCommand。使用 Wpf.MVVMC,每次导航都会保存历史记录,您可以自动返回上一步(或前进)。
  • 第二个按钮的 Commandmvvmc:NavigateCommand,其属性为 Action=Next。这意味着在按钮点击时,WizardController 中的 Next 方法将被调用。在该方法中,我们可以(但不必须)执行导航。

WizardController.cs

public class Model
{
    public string Position { get; set; }
    public int YearsOfExperience { get; set; }
    public string Notes { get; set; }
}

public class WizardController : Controller
{
    private Model _model;

    public override void Initial()
    {
        FirstStep();
    }

    private void FirstStep()
    {
        _model  = new Model();
        ExecuteNavigation();
    }

    private void SecondStep()
    {
        ExecuteNavigation();
    }

    private void ThirdStep()
    {
        ExecuteNavigation();
    }

    private void FourthStep()
    {
        ExecuteNavigation(null, new Dictionary<string, object>()
        {
            { "Position",_model.Position},
            { "YearsOfExperience",_model.YearsOfExperience.ToString()},
            { "Notes",_model.Notes},

        });
    }

    public void Next()
    {
        if (this.GetCurrentPageName() + "View" == nameof(FirstStepView))
        {
            SecondStep();
        }
        else if (this.GetCurrentViewModel() is SecondStepViewModel secondStepViewModel)
        {
            _model.Position = GetPosition(secondStepViewModel);
            ThirdStep();
        }
        else if (this.GetCurrentViewModel() is ThirdStepViewModel thirdStepViewModel)
        {
            _model.YearsOfExperience = thirdStepViewModel.YearsOfExperience;
            _model.Notes = thirdStepViewModel.Notes;
            FourthStep();
            
        }
        else // From fourth step
        {
            ClearHistory();
            FirstStep();
        }
    }

    private string GetPosition(SecondStepViewModel secondStepViewModel)
    {
        if (secondStepViewModel.IsQAEngineer)
            return "QA Engineer";
        else if (secondStepViewModel.IsSoftwareEngineer)
            return "Software Engineer";
        else
            return "Team Leader";
    }
}

如前所述,Wpf.MVVMC 是基于约定的。所以当我们在 XAML 中写入 <mvvmc:Region ControllerID="Wizard"/> 时,我们必须有一个名为 WizardController 的类,它派生自 MVVMC.Controller。除此之外,RegionController 之间的配对是自动的。

解释

  • Model 是一个用于在步骤之间保存数据的小类。
  • 在每个 Controller 中,我们必须重写 Initial(),它决定了 Region 的初始 Content。您可以在该方法中不做任何事情,在这种情况下 Region 将保持为空。我们正在调用 FirstStep() 方法 - 请参阅下文。
  • 控制器中的每个步骤方法 FirstStepSecondStepThirdStepFourthStep 都调用了 protected 方法 ExecuteNavigationExecuteNavigation() 取决于调用方法名。例如,当从“FirstStep()”调用时,它将导航到“FirstStep”页面。这意味着它将创建 FirstStepViewFirstStepViewModel 实例,并连接它们进行绑定。您不必拥有 ViewModel
  • FourthStep() 中,我们正在向 ExecuteNavigation 传递参数。这就是 ViewBag,您可能从 MVC 中了解它。它的作用方式类似——它允许从 XAML 轻松绑定,您将在后面看到。
  • Next 方法是从 XAML 自动调用的,使用 Command="{mvvmc:NavigateCommand Action=Next, ControllerID=Wizard}"。它检查我们当前在哪个步骤,然后调用下一个步骤。如您所见,这是在代码中实现的,并且非常灵活。您可以选择跳过步骤、添加步骤、回退一些步骤或不执行任何操作。
  • 您的 ViewViewModelController 应位于同一 namespace 中。这样,您可以在同一项目中拥有同名步骤。

至此,代码中最难的部分就结束了。所有 Views 都是一个简单的 UserControl。所有 ViewModel 都应该派生自 MVVMC.MVVMCViewModel。在 MVVMC 术语中,一对 ViewViewModel 被称为 Page。让我们看看其中一个页面的代码。

页面示例:SecondStep

View 是一个普通的 UserControlSecondStepView.xaml

<UserControl x:Class="WizardAppMvvmc.Wizard.SecondStepView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <StackPanel Margin="50">
      <TextBlock FontSize="25" >Second Step - Start recruitment</TextBlock>
      <TextBlock FontSize="18" >Who do you want to recruit to your team?</TextBlock>
      <RadioButton IsChecked="{Binding IsQAEngineer}">QA Engineer</RadioButton>
      <RadioButton IsChecked="{Binding IsSoftwareEngineer}">Software Engineer</RadioButton>
      <RadioButton IsChecked="{Binding IsTeamLeader}">Team Leader</RadioButton>
    </StackPanel>
</UserControl>

ViewModel 应派生自 MVVMC.MVVMCViewModel

public class SecondStepViewModel : MVVMCViewModel
{
    private bool _isQAEngineer;
    public bool IsQAEngineer
    {
        get { return _isQAEngineer; }
        set
        {
            _isQAEngineer = value;
            OnPropertyChanged();
        }
    }

    private bool _isSoftwareEngineer;
    public bool IsSoftwareEngineer
    {
        get { return _isSoftwareEngineer; }
        set
        {
            _isSoftwareEngineer = value;
            OnPropertyChanged();
        }
    }

    private bool _isTeamLeader;
    public bool IsTeamLeader
    {
        get { return _isTeamLeader; }
        set
        {
            _isTeamLeader = value;
            OnPropertyChanged();
        }
    }
}

如您所见,ViewViewModel 与您的常规 ViewViewModel 完全相同。这里唯一的例外是 FourthStep,我们在其中使用了 ViewBag。代码如下

使用 ViewBag - FourthStep

FourthStepView.xaml:

<UserControl x:Class="WizardAppMvvmc.Wizard.FourthStepView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:mvvmc="clr-namespace:MVVMC;assembly=MVVMC">
  <StackPanel >
      <TextBlock FontSize="25" >Finished, Recruitment is on the way</TextBlock>
      <UniformGrid Columns="2">
          <TextBlock>Position:</TextBlock>
        <TextBlock Text="{mvvmc:ViewBagBinding Path=Position}"></TextBlock>
          <TextBlock>Years of Experience:</TextBlock>
        <TextBlock Text="{mvvmc:ViewBagBinding Path=YearsOfExperience}"></TextBlock>
          <TextBlock>Notes:</TextBlock>
        <TextBlock Text="{mvvmc:ViewBagBinding Path=Notes}"></TextBlock>
      </UniformGrid>
  </StackPanel>
</UserControl>

FourthStepViewModel.cs:

public class FourthStepViewModel : MVVMCViewModel
{
}

您可以使用 mvvmc:ViewBagBinding 自动绑定到您在 ExecuteNavigation 中的 ViewBag 中传递的值。使用 ViewBag 时,即使它是一个空 ViewModel,您也必须拥有一个 ViewModel

基本向导总结

我想您可以看到 MVVMC 和 MVC 之间的相似之处。Navigation “请求”发送到 ControllerController 具有内部逻辑,决定要导航到的“Page”。这实现了 View/ViewModel 与导航逻辑之间的关注点分离。基于约定的方法也受 MVC 的启发,在我看来,它节省了您否则必须编写的大量样板代码。

所有 Navigation “请求”都在 View 中,带有 mvvmc:NavigateCommand。这只是一种方式。ViewModel 也可以通过获取 Controller 对象并调用其方法来启动 navigation(本文末尾附近有一个示例)。

高级向导教程

高级”向导(其实并没有那么高级)展示了 Wpf.MVVMC 的更多功能。以下是一些它简单实现但否则需要大量工作的功能

  • 您可以拥有嵌套导航。就像主向导中的子向导一样。
  • 导航按钮可以位于“动态”内容内部或外部。
  • 向导不必有线性步骤“1、2、3...”。相反,它可以根据用户所做的选择,拥有您选择的任何步骤逻辑。

向导的源代码可供下载。我将向您展示其中一些更有趣的部分。让我们从嵌套导航开始。

嵌套区域

在视频中,当点击“软件工程师”时,您将被引导至一个子向导,您可以在其中选择技术(前端)和框架(Angular、React)。这是 SoftwareEngineerView 的代码

<UserControl x:Class="AdvancedWizard.Wizard.SoftwareEngineerView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mvvmc="clr-namespace:MVVMC;assembly=MVVMC">
    <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="100"></RowDefinition>
          <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Border Background="WhiteSmoke">
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="18">
             Software Engineer Recruitment</TextBlock>
        </Border>
        <mvvmc:Region ControllerID="SoftwareEngineer" Grid.Row="1"></mvvmc:Region>
    </Grid>
</UserControl>

SoftwareEngineerView 是“”向导中的一个步骤。在其中,您可以拥有更多由另一个 Controller 控制的 Region。在我们的例子中,是 SoftwareEngineerController

您可以从任何 Controller 请求任何导航。在我们的例子中,SoftwareEngineerController 对自身和“main”Controller 都进行导航。这是 FrontEndView.xaml

<UserControl x:Class="AdvancedWizard.Wizard.SoftwareEngineer.FrontEndView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mvvmc="clr-namespace:MVVMC;assembly=MVVMC">
  <StackPanel>
    <TextBlock Margin="5">What framework do you like best?</TextBlock>
    <Button Margin="5" Command="{mvvmc:NavigateCommand ControllerID=SoftwareEngineer, 
                                 Action=Angular1}">Angular 1</Button>
    <Button Margin="5" Command="{mvvmc:NavigateCommand ControllerID=Wizard, 
                                 Action=Finish}">Angular 2+</Button>
    <Button Margin="5" Command="{mvvmc:NavigateCommand ControllerID=Wizard, 
                                 Action=Finish}">React</Button>
    <Button Margin="5" Command="{mvvmc:NavigateCommand ControllerID=Wizard, 
                                 Action=Finish}">Vue.js</Button>
  </StackPanel>
</UserControl>

这是 Angular1View.xaml

<UserControl x:Class="AdvancedWizard.Wizard.SoftwareEngineer.Angular1View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mvvmc="clr-namespace:MVVMC;assembly=MVVMC">
    <StackPanel>
      <TextBlock Margin="5" FontSize="18">Angular 1, Really?</TextBlock>
      <Button Margin="5" Command="{mvvmc:NavigateCommand ControllerID=Wizard, 
                                   Action=Finish}">Yes</Button>
      <Button Margin="5" Command="{mvvmc:GoBackCommand ControllerID=SoftwareEngineer}">No</Button>
    </StackPanel>
</UserControl>

我想展示的另一个例子是从 ViewModel 进行导航。以下是 StartViewModel.cs

using MVVMC;

namespace AdvancedWizard.Wizard
{
    public class StartViewModel : MVVMCViewModel<WizardController>
    {
        private bool _isQA;
        public bool IsQA
        {
            get { return _isQA; }
            set
            {
                _isQA = value; 
                OnPropertyChanged();
                NextCommand.RaiseCanExecuteChanged();
            }
        }

        private bool _isSoftwareEngineer;
        public bool IsSoftwareEngineer
        {
            get { return _isSoftwareEngineer; }
            set
            {
                _isSoftwareEngineer= value; 
                OnPropertyChanged();
                NextCommand.RaiseCanExecuteChanged();
            }
        }

        private bool _isTeamLeader;
        public bool IsTeamLeader
        {
            get { return _isTeamLeader; }
            set
            {
                _isTeamLeader = value;
                OnPropertyChanged();
                NextCommand.RaiseCanExecuteChanged();
            }
        }

        public ICommand _nextCommand;
        public ICommand NextCommand
        {
            get
            {
                if (_nextCommand == null)
                {
                    _nextCommand = new DelegateCommand(
                        () =>
                        {
                            var controller = GetExactController();
                            if (IsQA)
                                controller.QA();
                            else if (IsSoftwareEngineer)
                                controller.SoftwareEngineer();
                            else
                                controller.TeamLeader();
                        },
                        // can execute
                        () => IsQA || IsSoftwareEngineer || IsTeamLeader);
                }

                return _nextCommand;
            }
        }
    }
}

解释

ViewModel 派生自 MVVMCViewModel<WizardController>。这允许使用 GetExactController() 方法,该方法返回 Controller 实例。从那里,导航就像调用方法一样简单。例如,controller.SoftwareEngineer()

摘要

我希望我能让您相信 MVVMC 的强大之处。每当您有一个多屏应用程序时,这都是一个不错的选择。它无疑是一个有主见的库,但通常是最好的,因为它为您提供了结构和更多时间来处理应用程序的功能,而不是导航。

源代码和文档可在 GitHub 上获取,NuGet 包位于此处

© . All rights reserved.