WPF MVVM系统入门-下

简介: 本文详细讲解WPF,MVVM开发,实现UI与逻辑的解耦。

WPF MVVM系统入门-下

CommandManager

接上文WPF MVVM系统入门-上,我们想把Command放在ViewModel中,而不是Model中,可以将CommandBase类改为

publicclassCommandBase : ICommand

{

   publiceventEventHandler?CanExecuteChanged

   {

       add { CommandManager.RequerySuggested+=value; }

       remove { CommandManager.RequerySuggested+=value; }

   }

   publicFunc<object,bool>DoCanExecute { get; set; }

   publicboolCanExecute(object?parameter)

   {

      returnDoCanExecute?.Invoke(parameter) ==true;

   }

   

   publicvoidExecute(object?parameter)

   {

       DoExecute?.Invoke(parameter);

   }

   publicAction<object>DoExecute { get; set; }

}

利用了CommandManager的静态事件RequerySuggested,该事件当检测到可能改变命令执行条件时触发(实际上是一直不断的触发)。此时Model和ViewModel分别是

//Model

publicclassMainModel : INotifyPropertyChanged

{

   publicdoubleValue1 { get; set; }

   publicdoubleValue2 { get; set; }

   privatedouble_value3;

   publicdoubleValue3

   {

       get { return_value3; }

       set

       {

           _value3=value;

           PropertyChanged?.Invoke(this, newPropertyChangedEventArgs("Value3"));

       }

   }

   publiceventPropertyChangedEventHandler?PropertyChanged;

}

//ViewModel

publicclassMainViewModel

{

   publicMainModelmainModel { set; get; } =newMainModel();

   publicvoidAdd(objectobj)

   {

       mainModel.Value3=mainModel.Value2+mainModel.Value1;

   }

   publicboolCanCal(objectobj)

   {

       returnmainModel.Value1!=0;

   }

   publicCommandBaseBtnCommand { get; set; }//命令

   publicMainViewModel()

   {

       BtnCommand=newCommandBase() {

           DoExecute=newAction<object>(Add),

           DoCanExecute=newFunc<object, bool>(CanCal)

       };

   }

}

执行效果如下

内置命令

上面我们自定义了CommandBase类,但其实WPF已经预定义了很多常用的命令

MediaCommands(24个) Play、Stop、Pause.......ApplicationCommands(23个) New、Open、Copy、Cut、Print.........NavigationCommands(16个) GoToPage、LastPage、Favorites...ComponentCommands(27个) ScrollByLine、MoveDown、ExtendSelectionDown.........EditingCommands(54个) Delete、ToggleUnderline、ToggleBold.........

命令绑定一般是这样做,此时使用预定义的命令,但是Execute等事件需要写在内置类中,不符合MVVM的宗旨。

<Window.CommandBindings>

   <CommandBinding

       CanExecute="CommandBinding_CanExecute"

       Command="ApplicationCommands.Open"

       Executed="CommandBinding_Executed"/>

</Window.CommandBindings>

<!--使用-->

<!--RoutedUICommand-->

<Button

   Command="ApplicationCommands.Open"

   CommandParameter="123"

   Content="Ok"/>

但是经常使用复制、粘贴等内置命令

<TextBoxText="{Binding mainModel.Value1, UpdateSourceTrigger=PropertyChanged}">

   <TextBox.ContextMenu>

       <ContextMenu>

           <MenuItemCommand="ApplicationCommands.Copy"Header="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>

           <MenuItemCommand="ApplicationCommands.Paste"Header="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>

       </ContextMenu>

   </TextBox.ContextMenu>

</TextBox>

鼠标行为

一般Command都有默认触发的行为,如Button的默认触发行为是单机,那如果我想改成双击触发,那要如何实现?使用InputBindings可以修改触发行为。

<ButtonContent="Ok">

   <Button.InputBindings>

       <MouseBinding

           Command="ApplicationCommands.Open"

           CommandParameter="123"

           MouseAction="LeftDoubleClick"/>

       <KeyBinding

           Key="O"

           Command="ApplicationCommands.Open"

           CommandParameter="123"

           Modifiers="Ctrl"/>

   </Button.InputBindings>

</Button>

上面的案例可以实现双击按钮和Ctrl+o触发ApplicationCommands.Open命令。

自定义RoutedUICommand命令的用法:

<!--定义命令资源-->

<Window.Resources>

   <RoutedUICommandx:Key="myCommand"Text="我的命令"/>

</Window.Resources>

<!--定义命令快捷键-->

<Window.InputBindings>

   <KeyBinding

       Key="Enter"

       Command="{StaticResource myCommand}"

       Gesture="Ctrl"/>

</Window.InputBindings>

<!--定义命令-->

<Window.CommandBindings>

   <CommandBinding

       CanExecute="CommandBinding_CanExecute_1"

       Command="{StaticResource myCommand}"

       Executed="CommandBinding_Executed_1"/>

</Window.CommandBindings>

<!--使用命令-->

<Button

   Command="{StaticResource myCommand}"

   CommandParameter="123"

   Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>

任意事件的绑定

InputBindings只能对KeyBindingMouseBinding进行绑定,但如果我想要其他的事件,比如ComboBox的SelectionChanged,此时可以使用System.Windows.Interactivity

  1. 使用行为需要nuget安装Microsoft.Xaml.Behaviors.Wpf,FrameWork版本安装System.Windows.Interactivity.WPF
  2. xaml中引用命名空间xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors"

<ComboBox

   DisplayMemberPath="Value1"

   ItemsSource="{Binding list}"

   SelectedValuePath="Value2">

   <Behaviors:Interaction.Triggers>

       <Behaviors:EventTriggerEventName="SelectionChanged">

           <Behaviors:InvokeCommandActionCommand="{StaticResource myCommand}"CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=SelectedValue}"/>

       </Behaviors:EventTrigger>

   </Behaviors:Interaction.Triggers>

</ComboBox>

上面的的用法需要绑定命令,也可以直接绑定方法使用

<ComboBox

   DisplayMemberPath="Value1"

   ItemsSource="{Binding list}"

   SelectedValuePath="Value2">

   <Behaviors:Interaction.Triggers>

       <Behaviors:EventTriggerEventName="SelectionChanged">

           <Behaviors:CallMethodActionMethodName="ComboBox_SelectionChanged"TargetObject="{Binding}"/>

       </Behaviors:EventTrigger>

   </Behaviors:Interaction.Triggers>

</ComboBox>

这样可以直接绑定ViewModel中定义的方法

本案例使用.net core进行测试,如果使用FrameWork,则这样使用

<!--引用命名空间-->

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

xmlns:ii="http://schemas.microsoft.com/expression/2010/interactions"

<!--使用-->

<i:EventTriggerEventName="SelectionChanged">  

   <ii:CallMethodActionTargetObject="{Binding}"

                        MethodName="ComboBox_SelectionChanged"/>

</i:EventTrigger>

MVVM中跨模块交互

跨模块交互经常会涉及到VM与V之间的交互,通常V绑定VM中的数据是非常简单的,直接使用Bind就可以

但是有时V中需要定义一些方法,让VM去触发,如果互相引用则违背了MVVM的原则(VM不要引用V),此时就需要一个管理类。

V中注册委托,VM中执行

写一个ActionManager,该类具有注册委托和执行委托方法

publicclassActionManager<T>

{

   staticDictionary<string, Func<T, bool>>_actions=newDictionary<string, Func<T, bool>>();

   

   //注册

   publicstaticvoidRegister(stringname,Func<T,bool>func)

   {

       if (!_actions.ContainsKey(name))

       {

           _actions.Add(name, func);

       }

   }

   //执行

   publicstaticboolInvoke(stringname,Tvalue)

   {

       if (_actions.ContainsKey(name))

       {

           return_actions[name].Invoke(value);

       }

       returnfalse;

   }

}

可以在V中注册

ActionManager<object>.Register("ShowSubWin", newFunc<object, bool>(_=> {

   WindowManager.ShowDialog(typeof(SubWindow).Name,null);

   returntrue;

}));

在VM中执行

ActionManager<object>.Invoke("ShowSubWin", null);

V中注册子窗口,VM中打开

可以写一个WindowManager类,该类中可以注册窗口和打开窗口

publicclassWindowManager

{

   //注册窗口存放

   staticDictionary<string, WinEntity>_windows=newDictionary<string, WinEntity>();

   //注册,传入Type类型,因为注册的时候不需要实例,

   //但是owner则需要传入Window,因为要设置owner说明已经有了实例

   publicstaticvoidRegister(Typetype,Windowowner)

   {

       if (!_windows.ContainsKey(type.Name))

       {

           _windows.Add(type.Name, newWinEntity {Type=type,Owner=owner });

       }

   }

   //使用string类型的winKey,因为调用showDialog方法往往是在VM中,如果使用Type类型,则要在VM中引用View

   publicstaticboolShowDialog(stringwinKey ,objectdataContext)

   {

       if (_windows.ContainsKey(winKey))

       {

           Typetype=_windows[winKey].Type;

           Window?win= (Window)Activator.CreateInstance(type);

           win.DataContext=dataContext;

           win.Owner=_windows[winKey].Owner;

           returnwin.ShowDialog()==true;

       }

       returnfalse;

   }

}

publicclassWinEntity

{

   publicTypeType { get; set; }

   publicWindowOwner { get; set; }

}

此时在主窗口的View中对子窗口进行注册WindowManager.Register(typeof(SubWindow), this);

在VM中打开子窗口WindowManager.ShowDialog("SubWindow", null);

页面切换

在单页面应用中,点击不同的菜单项会跳转到不同的页面,如何利用MVVM来实现该功能?

  1. 定义菜单模型

publicclassMenuModel

{

   publicstringMenuIcon { get; set; }

   publicstringMenuHeader { get; set; }

   publicstringTargetView { get; set; }

}

  1. 定义MainModel

publicclassMainModel : INotifyPropertyChanged

{

   publicList<MenuModel>MenuList { get; set; }

   /// <summary>

   /// 当前点击的页面实例

   /// </summary>

   privateobject_page;

   publicobjectPage

   {

       get=>_page;

       set

       {

           _page=value;

           PropertyChanged?.Invoke(this, newPropertyChangedEventArgs("Page"));

       }

   }

   publiceventPropertyChangedEventHandler?PropertyChanged;

}

  1. MainViewModel

publicclassMainViewModel

{

   publicMainModelmainModel { get; set; }

   publicMainViewModel()

   {

       mainModel=newMainModel();

       mainModel.MenuList=newList<MenuModel>();

       mainModel.MenuList.Add(newMenuModel

       {

           MenuIcon="\ue643",// 如果存在数据库的话: e643    这个字符的编号

           MenuHeader="Dashboard",

           TargetView="MvvmDemo.Views.DashboardPage",// 反射 新建一个UserControl名字为DashboardPage

       });

       mainModel.PageTitle=mainModel.MenuList[0].MenuHeader;

       ShowPage(mainModel.MenuList[0].TargetView);

   }

   privatevoidShowPage(stringtarget)

   {

       vartype=this.GetType().Assembly.GetType(target);

       this.MainModel.Page=Activator.CreateInstance(type);

   }

   

   //定义命令

   publicCommandBaseMenuItemCommand

   {

       get=>newCommandBase

       {

           // obj希望传进来的一个TargetView

           DoExecute=newAction<object>(obj=>

           {

               ShowPage(obj.ToString());

           })

       };

   }

}

  1. View绑定MenuItemCommand

<!--ContentControl显示page页面-->

<ContentControl

           Grid.Row="1"

           Grid.Column="1"

           Content="{Binding MainModel.Page}"/>

<!--GroupName是为了互斥-->

<ItemsControl

   ItemsSource="{Binding MainModel.MenuList}">

   <ItemsControl.ItemTemplate>

       <DataTemplate>

           <RadioButton

               Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.MenuItemCommand}"

               CommandParameter="{Binding TargetView}"

               Content="{Binding MenuHeader}"

               GroupName="menu"

               Tag="{Binding MenuIcon}"/>

       </DataTemplate>

   </ItemsControl.ItemTemplate>

</ItemsControl>


相关文章
|
前端开发 Ubuntu Linux
【.NET6+Avalonia】开发支持跨平台的仿WPF应用程序以及基于ubuntu系统的演示
随着跨平台越来越流行,.net core支持跨平台至今也有好几年的光景了。但是目前基于.net的跨平台,大多数还是在使用B/S架构的跨平台上;至于C/S架构,大部分人可能会选择QT进行开发,或者很早之前还有一款Mono可以支持.NET开发者进行开发跨平台应用。
849 0
【.NET6+Avalonia】开发支持跨平台的仿WPF应用程序以及基于ubuntu系统的演示
|
4月前
|
人工智能 机器人 C#
Windows编程课设(C#)——基于WPF和.net的即时通讯系统(仿微信)
一款参考QQ、微信的即时通讯软件。采用CS结构,客户端基于.Net与WPF开发,服务端使用Java开发。
|
7月前
|
设计模式 开发框架 前端开发
深入理解WPF中MVVM的设计思想
近些年来,随着WPF在生产,制造,工业控制等领域应用越来越广发,很多企业对WPF开发的需求也逐渐增多,使得很多人看到潜在机会,不断从Web,WinForm开发转向了WPF开发,但是WPF开发也有很多新的概念及设计思想,如:数据驱动,数据绑定,依赖属性,命令,控件模板,数据模板,MVVM等,与传统WinForm,ASP.NET WebForm开发,有很大的差异,今天就以一个简单的小例子,简述WPF开发中MVVM设计思想及应用。
61 0
|
8月前
|
前端开发
WPF-Binding问题-MVVM中IsChecked属性CommandParameter转换值类型空异常
WPF-Binding问题-MVVM中IsChecked属性CommandParameter转换值类型空异常
86 0
|
9月前
|
前端开发 算法 JavaScript
走进WPF之MVVM完整案例
走进WPF之MVVM完整案例
152 0
|
11月前
|
Oracle 数据管理 关系型数据库
WPF实验室信息系统源码,LIS源码
检验报告集中管理: 主要包含申请单详细信息、申请检验组合信息、申请单列表数据、以及结构数据等4四功能模块。可在此模块中查看检验结果,进行检验报告预览。
WPF实验室信息系统源码,LIS源码
|
前端开发 C# 图形学
【.NET6+WPF】WPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入
前言:在C/S架构上,WPF无疑已经是“桌面一霸”了。在.NET生态环境中,很多小伙伴还在使用Winform开发C/S架构的桌面应用。但是WPF也有很多年的历史了,并且基于MVVM的开发模式,受到了很多开发者的喜爱。
561 0
【.NET6+WPF】WPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入
|
19天前
|
C# 开发者 Windows
基于Material Design风格开源、易用、强大的WPF UI控件库
基于Material Design风格开源、易用、强大的WPF UI控件库
|
4月前
|
C#
浅谈WPF之装饰器实现控件锚点
使用过visio的都知道,在绘制流程图时,当选择或鼠标移动到控件时,都会在控件的四周出现锚点,以便于修改大小,移动位置,或连接线等,那此功能是如何实现的呢?在WPF开发中,想要在控件四周实现锚点,可以通过装饰器来实现,今天通过一个简单的小例子,简述如何在WPF开发中,应用装饰器,仅供学习分享使用,如有不足之处,还请指正。
65 1
|
8月前
|
C# Windows
WPF技术之图形系列Polygon控件
WPF Polygon是Windows Presentation Foundation (WPF)框架中的一个标记元素,用于绘制多边形形状。它可以通过设置多个点的坐标来定义多边形的形状,可以绘制任意复杂度的多边形。
460 0