文章目录
- 总目录
- 前言
- 一、准备工作
- 1.搭建一个简单的mvvm项目结构
- 2.实现ICommand 和 INotifyPropertyChanged接口
- 二、页面切换
- 1.使用Frame控件的方式实现
- 2.使用反射的方式实现
- 3.实现效果
- 三、Window的跳转切换
- 1、原生的跳转切换
- 2、修改应用程序关闭方式实现窗体切换
- 3、MVVM中Window的切换(初级)
- 4、MVVM中Window的交互
- 总结
前言
本文主要讲述如何在同一个窗体内,实现不同功能模块的页面切换。
一、准备工作
1.搭建一个简单的mvvm项目结构
首先搭建一个简单的项目框架,然后有红和绿两个页面,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.实现效果
三、Window的跳转切换
1、原生的跳转切换
上面的代码运行后会发现:
- 仅仅弹出子窗口的时候发现,以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的方式,适用于登录界面过度到主窗口
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 写了一个增删改查的小案例,先看下实现的效果:
主要代码如下: