首页 > 其他分享 >WPF MVVM系统入门-下

WPF MVVM系统入门-下

时间:2023-02-18 12:37:50浏览次数:36  
标签:set 入门 MVVM get mainModel Command new WPF public


WPF MVVM系统入门-下

CommandManager

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

public class CommandBase : ICommand
{
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested += value; }
}

public Func<object,bool> DoCanExecute { get; set; }
public bool CanExecute(object? parameter)
{
return DoCanExecute?.Invoke(parameter) == true;
}

public void Execute(object? parameter)
{
DoExecute?.Invoke(parameter);
}
public Action<object> DoExecute { get; set; }
}

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

//Model
public class MainModel : INotifyPropertyChanged
{
public double Value1 { get; set; }
public double Value2 { get; set; }

private double _value3;

public double Value3
{
get { return _value3; }
set
{
_value3 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value3"));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
}
//ViewModel
public class MainViewModel
{
public MainModel mainModel { set; get; } = new MainModel();

public void Add(object obj)
{
mainModel.Value3 = mainModel.Value2 + mainModel.Value1;
}

public bool CanCal(object obj)
{
return mainModel.Value1 != 0;
}

public CommandBase BtnCommand { get; set; }//命令
public MainViewModel()
{
BtnCommand = new CommandBase() {
DoExecute = new Action<object>(Add),
DoCanExecute = new Func<object, bool>(CanCal)
};
}
}

执行效果如下

WPF MVVM系统入门-下_c#

内置命令

上面我们自定义了​​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" />

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

<TextBox Text="{Binding mainModel.Value1, UpdateSourceTrigger=PropertyChanged}">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy" Header="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}" />
<MenuItem Command="ApplicationCommands.Paste" Header="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}" />
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>

WPF MVVM系统入门-下_.net_02

鼠标行为

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

<Button Content="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>
<RoutedUICommand x: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​​​只能对​​KeyBinding​​​和​​MouseBinding​​​进行绑定,但如果我想要其他的事件,比如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:EventTrigger EventName="SelectionChanged">
<Behaviors:InvokeCommandAction Command="{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:EventTrigger EventName="SelectionChanged">
<Behaviors:CallMethodAction MethodName="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:EventTrigger EventName="SelectionChanged">
<ii:CallMethodAction TargetObject="{Binding}"
MethodName="ComboBox_SelectionChanged"/>
</i:EventTrigger>

MVVM中跨模块交互

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

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

V中注册委托,VM中执行

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

public class ActionManager<T>
{
static Dictionary<string, Func<T, bool>> _actions = new Dictionary<string, Func<T, bool>>();

//注册
public static void Register(string name,Func<T,bool> func)
{
if (!_actions.ContainsKey(name))
{
_actions.Add(name, func);
}
}

//执行
public static bool Invoke(string name,T value)
{
if (_actions.ContainsKey(name))
{
return _actions[name].Invoke(value);
}
return false;
}
}

可以在V中注册

ActionManager<object>.Register("ShowSubWin", new Func<object, bool>(_ => {
WindowManager.ShowDialog(typeof(SubWindow).Name,null);
return true;
}));

在VM中执行

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

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

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

public class WindowManager
{
//注册窗口存放
static Dictionary<string, WinEntity> _windows = new Dictionary<string, WinEntity>();

//注册,传入Type类型,因为注册的时候不需要实例,
//但是owner则需要传入Window,因为要设置owner说明已经有了实例
public static void Register(Type type,Window owner)
{
if (!_windows.ContainsKey(type.Name))
{
_windows.Add(type.Name, new WinEntity {Type = type,Owner = owner });
}
}

//使用string类型的winKey,因为调用showDialog方法往往是在VM中,如果使用Type类型,则要在VM中引用View
public static bool ShowDialog(string winKey ,object dataContext)
{
if (_windows.ContainsKey(winKey))
{
Type type = _windows[winKey].Type;
Window? win = (Window)Activator.CreateInstance(type);
win.DataContext = dataContext;
win.Owner = _windows[winKey].Owner;
return win.ShowDialog()==true;
}
return false;
}
}
public class WinEntity
{
public Type Type { get; set; }
public Window Owner { get; set; }
}

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

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

页面切换

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

  1. 定义菜单模型
public class MenuModel
{
public string MenuIcon { get; set; }
public string MenuHeader { get; set; }
public string TargetView { get; set; }
}
  1. 定义MainModel
public class MainModel : INotifyPropertyChanged
{
public List<MenuModel> MenuList { get; set; }
/// <summary>
/// 当前点击的页面实例
/// </summary>
private object _page;

public object Page
{
get => _page;
set
{
_page = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Page"));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
}
  1. MainViewModel
public class MainViewModel
{
public MainModel mainModel { get; set; }
public MainViewModel()
{
mainModel = new MainModel();
mainModel.MenuList = new List<MenuModel>();
mainModel.MenuList.Add(new MenuModel
{
MenuIcon = "\ue643",// 如果存在数据库的话: e643 这个字符的编号
MenuHeader = "Dashboard",
TargetView = "MvvmDemo.Views.DashboardPage",// 反射 新建一个UserControl名字为DashboardPage
});
mainModel.PageTitle = mainModel.MenuList[0].MenuHeader;
ShowPage(mainModel.MenuList[0].TargetView);
}
private void ShowPage(string target)
{
var type = this.GetType().Assembly.GetType(target);
this.MainModel.Page = Activator.CreateInstance(type);
}

//定义命令
public CommandBase MenuItemCommand
{
get => new CommandBase
{
// obj希望传进来的一个TargetView
DoExecute = new Action<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>


标签:set,入门,MVVM,get,mainModel,Command,new,WPF,public
From: https://blog.51cto.com/u_15943685/6065288

相关文章

  • WPF MVVM系统入门-上
    WPFMVVM系统入门-上Models:存放数据的模型,实体对象Views:可视化界面ViewModels:业务逻辑。ViewModels与Models的联系会更紧密,而Views页面会主动绑定ViewModels中的数据,原则......
  • Solidity极简入门#17. 库合约
    这一讲,我们用ERC721的引用的库合约String为例介绍solidity中的库合约(library),并总结了常用的库函数。库函数库函数是一种特殊的合约,为了提升solidity代码的复用性和减少gas而......
  • Vue急速入门-4
    组件其他根组件和组件的一些问题归总: 1.newVue()el:'xx'管理的Div就算根组件 2.父子组件的数据是无法共享的 3.组件有自己的html,css,js 4.在组件中,this代指当......
  • MyBatisPlus--入门
    入门案例MyBatisPlus(MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率.1、新建springboot项目(版本2.5.0),仅保留JDBC 添加mybatisplus起步依赖和drui......
  • 嵌入式Linux入门级板卡的神经网络框架ncnn移植与测试-米尔i.MX6UL开发板
    本篇测评由电子发烧友的优秀测评者“ALSET”提供。米尔MYD-Y6ULX-V2开发板,基于NXPi.MX6UL/i.MX6ULL处理器,该开发板被米尔称之为经典王牌产品。本次测试目标是在此开发......
  • 万字长文带你入门增量学习
    前言本文介绍了引入增量学习的必要性,继而引出最新的三种能够有效利用有效标签数据范式的增量学习方法。其次,针对这三大范式进行一个全面的调研,让大家对这个领域的整个发......
  • [WPF]MVVM的数据绑定
    啥是MVVM?我理解的MVVM是Model(数据),View(界面),ViewModel(数据与界面之间的桥梁)的缩写,是一种编程模式。前期需要多花一些时间去编辑绑定,在后期维护方便。只需要关注数据即可。如......
  • Blender 图像软件入门教程 导出glb ,gltf格式文件 blender导入glb格式文件
    环境:3.4.1实现功能:导出glb格式文件点击视图左上方文件按钮,点击导出,点击gltf2.0(.glb/gltf)  blender导入glb格式文件视图右上方,选择Collection,鼠标右键,点击删除......
  • WPF中MVVM模式下loaded无法触发问题
    经过实践检测,当时View上设置了Window.SizeToContent="WidthAndHeight"时候<i:Interaction.Triggers><i:EventTriggerEventName="Loaded"><i:Invoke......
  • 【AI入门】C++构建BP神经网络,并实现手写数字识别
    目录BP神经网络的基本原理BP神经网络的C++实现将BP神经网络应用于手写数字识别存在的疑惑BP神经网络的基本原理参考资料:机器学习(西瓜书)-周志华如图所示,一个简单的BP......