文章目录

  • 总目录
  • 前言
  • 一、准备工作
  • 1.搭建一个简单的mvvm项目结构
  • 2.实现ICommand 和 INotifyPropertyChanged接口
  • 二、页面切换
  • 1.使用Frame控件的方式实现
  • 2.使用反射的方式实现
  • 3.实现效果
  • 三、Window的跳转切换
  • 1、原生的跳转切换
  • 2、修改应用程序关闭方式实现窗体切换
  • 3、MVVM中Window的切换(初级)
  • 4、MVVM中Window的交互
  • 总结



前言

本文主要讲述如何在同一个窗体内,实现不同功能模块的页面切换。


一、准备工作

1.搭建一个简单的mvvm项目结构

Wpf引入Rubyer wpf interaction_xml


首先搭建一个简单的项目框架,然后有红和绿两个页面,ViewModels中的Base 中简单实现了ICommand 和 INotifyPropertyChanged接口

2.实现ICommand 和 INotifyPropertyChanged接口

public class CommandBase : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public CommandBase(Action executeAction, Func<bool> canExcuteFunc = null)
        {
            this.ExecuteAction = executeAction;
            this.CanExecuteFunc = canExcuteFunc;
        }

        public CommandBase(Action<object> executeParaAction, Func<object, bool> canExecuteParaFunc = null)
        {
            this.ExecuteParaAction = executeParaAction;
            this.CanExecuteParaFunc = canExecuteParaFunc;
        }

        public bool CanExecute(object parameter=null)
        {
            if (parameter==null)
            {
                return this.CanExecuteFunc == null ? true : CanExecuteFunc.Invoke();
            }
            return CanExecuteParaFunc == null ? true : CanExecuteParaFunc.Invoke(parameter);
        }

        public void Execute(object parameter = null)
        {
            if (parameter == null)
            {
                ExecuteAction?.Invoke();
            }
            else
            {
                ExecuteParaAction?.Invoke(parameter);
            }
        }

        public Action ExecuteAction { get; set; }
        public Func<bool> CanExecuteFunc { get; set; }
        public Action<object> ExecuteParaAction { get; set; }
        public Func<object,bool> CanExecuteParaFunc { get; set; }
    }
public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged([CallerMemberName]string name="")
        {
            PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(name));
        }
    }

二、页面切换

1.使用Frame控件的方式实现

利用Frame的Source 属性加载内部的控件,使用Frame的时候,用于切换的页面可以是UserControl 或者Page,如案例中使用的就是Page

实现代码如下:

<Window x:Class="WpfApp2.Views.MainView"
        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:WpfApp2.Views"
        xmlns:vm="clr-namespace:WpfApp2.ViewModels"
        mc:Ignorable="d"
        Title="MainView" Height="450" Width="800">
    <Window.DataContext>
        <vm:MainViewModel></vm:MainViewModel>
    </Window.DataContext>
    <DockPanel Grid.Column="0">
        <StackPanel Background="LightBlue">
            <RadioButton IsChecked="True" Command="{Binding ChangePageCommand}" CommandParameter="PageRedView.xaml" Content="红色" Margin="10"></RadioButton>
            <RadioButton Command="{Binding ChangePageCommand}" CommandParameter="PageGreenView.xaml" Content="绿色" Margin="10"></RadioButton>
        </StackPanel>
        <Frame NavigationUIVisibility="Hidden" Source="{Binding PageName}"/>
    </DockPanel>
</Window>

注意:这里的CommandParameter传入的是PageRedView.xaml文件

public class MainViewModel:ViewModelBase
    {
        private string _pageName;

        public string PageName
        {
            get { return _pageName; }
            set { _pageName = value; OnPropertyChanged(); }
        }

        public ICommand ChangePageCommand { get; set; }

        public MainViewModel()
        {
            ChangePageCommand = new CommandBase(ChangePage);
            ChangePage("UserControlRed.xaml");//默认选中红色界面
        }

        private void ChangePage(object obj)
        {
            PageName = obj.ToString();
        }
    }

2.使用反射的方式实现

使用反射+ContentControl 的方式也可使用页面切换,不过该方式下ContentControl 的Content不可以承接Page,但是可以承接UserControl。(Page只有Frame 和Window可以承接,)
首先将红色和绿色两个界面修改为UserControl并命名为UserControlRed和UserControlGreen ,然后修改代码如下:

<DockPanel Grid.Column="0">
        <StackPanel Background="LightBlue">
            <RadioButton IsChecked="True" Command="{Binding ChangePageCommand}" CommandParameter="UserControlRed" Content="红色" Margin="10"></RadioButton>
            <RadioButton Command="{Binding ChangePageCommand}" CommandParameter="UserControlGreen" Content="绿色" Margin="10"></RadioButton>
        </StackPanel>
        <ContentControl Content="{Binding MainContent}"/>
    </DockPanel>
public class MainViewModel:ViewModelBase
    {
        private FrameworkElement mainContent;

        public FrameworkElement MainContent
        {
            get { return mainContent; }
            set { mainContent = value; OnPropertyChanged(); }
        }


        public ICommand ChangePageCommand { get; set; }

        public MainViewModel()
        {
            ChangePageCommand = new CommandBase(ChangePage);
            ChangePage("UserControlRed");//默认选中红色界面
        }

        private void ChangePage(object obj)
        {
        	//【 * 】这里需要拼接路径
            Type type = Type.GetType("WpfApp2.Views." + obj.ToString());
            MainContent = (FrameworkElement)System.Activator.CreateInstance(type);
        }
    }

3.实现效果

Wpf引入Rubyer wpf interaction_wpf_02

三、Window的跳转切换

1、原生的跳转切换

Wpf引入Rubyer wpf interaction_应用程序_03


上面的代码运行后会发现:

  • 仅仅弹出子窗口的时候发现,以ShowDialog()打开的窗体,要等窗体关闭后才能操作其他窗体.而Show()则不受此限制.(见Button_Click)
  • 在尝试Show() 和ShowDialog() 来跳转窗口的时候,Show 可行,ShowDialog不可行,因为Close方法会将两个窗口均关闭
  • Show 和Close 的配合可以做一个简单的Window跳转切换,实现登录窗口 跳转到 主窗口的小功能

2、修改应用程序关闭方式实现窗体切换

  • 1 在App.xaml中修改如下:去掉StartupUri 的设置,然后将ShutdownMode 设置为OnExplicitShutdown

OnExplicitShutdown 表示只有手动调用Shutdown方法的时候,应用程序才会关闭,否则应用程序不会关闭,多用于 用户登录窗口, 过度到引用的主界面中

<Application x:Class="WpfApp2.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp2"
             ShutdownMode="OnExplicitShutdown">
    <Application.Resources>
         
    </Application.Resources>
</Application>
  • 2 在App.xaml.cs文件中重写OnStartup方法
protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            new MainView().ShowDialog();
            new TestView().ShowDialog();
            App.Current.Shutdown();
        }

上面代码的意思,就是启动的时候 先启动并展示MainView这个窗口,然后当MainView的窗口关闭的时候,就展示TestView这个窗口,当TestView窗口关闭的时候,整个应用程序就会关闭。

这里就涉及到一个知识点:ShowDialog()和Show()的区别

  • ShowDialog就是模式窗体,使用ShowDialog()后,代码会”卡“在这里,后面的代码不会执行;直到使用ShowDialog显示的窗体被关闭,后面的代码才会执行。
  • 关闭窗体方式1:直接点击使用ShowDialog显示的窗体关闭按钮,关闭当前窗体,则可继续
  • 关闭窗体方式2:只要设置了窗体的DialogResult属性(true或false),窗体就会关闭退出
  • Show就是非模式的窗体,显示窗体后不论窗体是否关闭都执行Show后面的语句。
  • ShowDialog() 的返回值DialogResult 为 bool?类型,如果通过关闭按钮,关闭窗体,默认返回值为false
  • Show() 的返回值为void ,只管执行即可
  • 3 优化在App.xaml.cs文件中重写的OnStartup方法
    从扩展的【ShowDialog()和Show()的区别】知识点中我们知道,当使用关闭按钮,关闭窗体,默认返回值DialogResult为false,而通过设置DialogResult的值,无论true/false ,都可关闭窗体,因此我们要区分这两个场景:
    就比如当我们点击登录窗体的关闭窗口的时候,我们需要真正关闭窗口和应用程序;但是当我们点击登录成功的时候,我们需要的是关闭登录窗口,显示主窗口,因此可优化如下:
protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            //增加一层判断,表示如果是点击窗体关闭按钮,则直接退出应用程序
            //另外这也要求我们在手动设置DialogResult的时候,需要设置为true,才可以将这个场景予以区分
            if (new MainView().ShowDialog()==true)
            {
                new TestView().ShowDialog();
            }
            App.Current.Shutdown();
        }
  • 4 在需要跳转的功能处设置DialogResult为true

3、MVVM中Window的切换(初级)

还是借用【2、修改应用程序关闭方式实现窗体切换】+MVVM的方式,适用于登录界面过度到主窗口

Wpf引入Rubyer wpf interaction_xml_04

4、MVVM中Window的交互

在这以上1-3小节介绍的都是,关闭一个窗口后,然后又需要展示一个窗口,多用于登录窗口到主窗口的过渡场景。但是在平常的开发不仅仅只有这一种场景,还有一种常见场景是在主窗口 点击某个信息列表的按钮,然后打开详情信息的窗口(主窗口不可关闭),此场景如果使用原生代码直接new xxxWindow().ShowDialog();即可。但是如果在MVVM解耦思想的项目下,在ViewModel中直接使用这样的代码,就达不到解耦的目的了。因此我们需要一个中间层来管理窗口和处理窗体间信息的传递。
由此我们可以定义一个WindowManager类,

public class WindowManager
    {
        private static Dictionary<string, Action<object>> _dir = new Dictionary<string, Action<object>>();
        public static void Register(string key,Action<object> action)
        {
            if (!string.IsNullOrEmpty(key)&&!_dir.ContainsKey(key))
            {
                _dir.Add(key,action);
            }
        }

        public static void DoAction(string key,object obj)
        {
            if (!string.IsNullOrEmpty(key) && _dir.ContainsKey(key))
            {
                _dir[key]?.Invoke(obj);
            }
        }
    }

WindowManager 可以根据项目需求,做不同的扩展和兼容(如我们定义有返回值的委托Func,或者用泛型让该类更加通用),由上面的代码可知:该类的主要作用就是做了一个 处理窗体间传递信息用的集合,由于WindowManager第三方的身份,我们可以在任意地方使用Register 注册一个委托,然后使用DoAction执行委托,这样足以满足我们降低耦合的目的。

另外我们使用MVVM+WindowManager 写了一个增删改查的小案例,先看下实现的效果:

Wpf引入Rubyer wpf interaction_xml_05


主要代码如下:

Wpf引入Rubyer wpf interaction_xml_06


Wpf引入Rubyer wpf interaction_应用程序_07