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>


相关文章
|
3月前
|
C# 开发者 Windows
WPF 应用程序开发:一分钟入门
本文介绍 Windows Presentation Foundation (WPF),这是一种用于构建高质量、可缩放的 Windows 桌面应用程序的框架,支持 XAML 语言,方便 UI 设计与逻辑分离。文章涵盖 WPF 基础概念、代码示例,并深入探讨常见问题及解决方案,包括数据绑定、控件样式与模板、布局管理等方面,帮助开发者高效掌握 WPF 开发技巧。
174 65
|
3月前
|
设计模式 前端开发 C#
WPF 项目中 MVVM模式 的简单例子说明
本文通过WPF项目中的加法操作示例,讲解了MVVM模式的结构和实现方法,包括数据模型、视图、视图模型的创建和数据绑定,以及命令的实现和事件通知机制。
|
4月前
|
前端开发 C# 开发者
WPF开发者必读:MVVM模式实战,轻松构建可维护的应用程序,让你的代码更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,MVVM(Model-View-ViewModel)模式通过分离关注点,提高了代码的可维护性和可扩展性。本文详细介绍了MVVM模式的三个核心组件:Model(数据模型)、View(用户界面)和ViewModel(处理数据绑定与逻辑),并通过示例代码展示了如何在WPF项目中实现MVVM模式。通过这种模式,开发者可以更高效地构建桌面应用程序。希望本文能帮助你在WPF开发中更好地应用MVVM模式。
268 1
|
4月前
|
开发框架 缓存 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
|
4月前
|
C# 开发者 Windows
全面指南:WPF无障碍设计从入门到精通——让每一个用户都能无障碍地享受你的应用,从自动化属性到焦点导航的最佳实践
【8月更文挑战第31天】为了确保Windows Presentation Foundation (WPF) 应用程序对所有用户都具备无障碍性,开发者需关注无障碍设计原则。这不仅是法律要求,更是社会责任,旨在让技术更人性化,惠及包括视障、听障及行动受限等用户群体。
96 0
|
4月前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
129 0
|
4月前
|
前端开发 开发者 C#
WPF开发者必读:MVVM模式实战,轻松实现现代桌面应用架构,让你的代码更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,MVVM(Model-View-ViewModel)模式通过分离应用程序的逻辑和界面,提高了代码的可维护性和可扩展性。本文介绍了MVVM模式的三个核心组件:Model(数据模型)、View(用户界面)和ViewModel(处理数据绑定和逻辑),并通过示例代码展示了如何在WPF项目中实现MVVM模式。通过这种方式,开发者可以构建更加高效和可扩展的桌面应用程序。
225 0
|
4月前
|
C# Windows IDE
WPF入门实战:零基础快速搭建第一个应用程序,让你的开发之旅更上一层楼!
【8月更文挑战第31天】在软件开发领域,WPF(Windows Presentation Foundation)是一种流行的图形界面技术,用于创建桌面应用程序。本文详细介绍如何快速搭建首个WPF应用,包括安装.NET Framework和Visual Studio、理解基础概念、创建新项目、设计界面、添加逻辑及运行调试等关键步骤,帮助初学者顺利入门并完成简单应用的开发。
165 0
|
4月前
|
C# Windows 开发者
超越选择焦虑:深入解析WinForms、WPF与UWP——谁才是打造顶级.NET桌面应用的终极利器?从开发效率到视觉享受,全面解读三大框架优劣,助你精准匹配项目需求,构建完美桌面应用生态系统
【8月更文挑战第31天】.NET框架为开发者提供了多种桌面应用开发选项,包括WinForms、WPF和UWP。WinForms简单易用,适合快速开发基本应用;WPF提供强大的UI设计工具和丰富的视觉体验,支持XAML,易于实现复杂布局;UWP专为Windows 10设计,支持多设备,充分利用现代硬件特性。本文通过示例代码详细介绍这三种框架的特点,帮助读者根据项目需求做出明智选择。以下是各框架的简单示例代码,便于理解其基本用法。
239 0
|
4月前
|
设计模式 前端开发 C#
WPF/C#:理解与实现WPF中的MVVM模式
WPF/C#:理解与实现WPF中的MVVM模式
279 0