WPF MVVM系统入门-下

news2024/9/23 1:25:18

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)
        };
    }
}

执行效果如下

img

内置命令

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

img

鼠标行为

一般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只能对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: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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/351146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Verilog硬件描述语言]程序设计语句

目录一、数据流建模二、行为级建模2.1 应用场景2.2 initial过程语句2.3 always过程语句2.3.1 电平敏感信号&#xff1a;2.3.2 边沿敏感信号&#xff1a;2.3.3 initial和always语句使用注意2.4 例题&#xff1a;用always过程语句描述4选1数据选择器2.5 例题&#xff1a; 用alway…

2023-02-16:干活小计

数学公式表示学习&#xff1a; 大约耗时&#xff1a;2 hours 在做了一些工作后重读论文&#xff1a;MathBERT: A Pre-Trained Model for Mathematical Formula Understanding 这是本篇论文最重要的idea&#xff1a;Current pre-trained models neglect the structural featu…

魔百和M401A刷入Armbian系统EMMC开启wifi

文章目录一、Armbian系统写入U盘二、U盘内uEnv.txt文件修改三、盒子从U盘进行启动四、设置用户名和密码五、Armbian系统写入EMMC六、 重启系统reboot(不可以拔U盘)七、盒子关机拔出U盘八、插入USB无线网卡&#xff0c;连接wifi上次盒子刷了5.15版本的armbian系统&#xff0c;可…

C++ map和set

目录 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 3.1 set 3.1.1 set的介绍 3.1.2 set的使用 3.2 map 3.2.1 map的介绍 3.2.2 map的使用 3.3 multiset 3.3.1 multiset的介绍 3.3.2 multiset的使用 3.4 multimap 3.4.1 multimap的介绍 3.5 在OJ中的使用 4.…

Android框架源码分析-浅析OkHttp3

浅析OkHttp3 这篇文章主要用来回顾Okhttp3源码中&#xff0c;同步异步请求的区别、拦截器的责任链模式、连接池管理以及探讨socket通信到底在哪里实现。 列出的代码可能删掉了非核心部分的展示&#xff0c;如果有异议请查看源码 连接池涉及知识&#xff1a;可能根据 IP 地址…

iis7.5应用程序池的启动模式设置

最近发现公司的网站第一次登录时比较慢&#xff0c;甚至有超时的时候&#xff0c;当我检查应用程序池(IIS 7.5)时&#xff0c;应用程序池正常启动&#xff0c;但有时候处于停止状态&#xff0c;停止原因未知。所以必须第一时间重新启动它&#xff0c;以保证网站能被正常访问。于…

kubeadm Dashboard harbor

主机名IP地址安装组件master01192.168.186.10docker、kubeadm、kubelet、kubectl、flannelnode01192.168.186.20docker、kubeadm、kubelet、kubectl、flannelnode02192.168.186.30docker、kubeadm、kubelet、kubectl、flannelharbor192.168.186.40docker、docker-compose、harb…

python语言基础(最详细版)

文章目录一、程序的格式框架缩进1、定义2、这里就简单的举几个例子注释二、语法元素的名称三、数据类型四、数值运算符五、关系运算六、逻辑运算七、运算符的结合性八、字符串一、程序的格式框架 缩进 1、定义 &#xff08;1&#xff09;python中通常用缩进来表示代码包含和…

Python迭代器、生成器和装饰器

一、迭代器 1、迭代器简介 迭代操作是访问集合元素的一种方式&#xff0c;是 Python最强大的功能之一。 迭代器是用来迭代取值的工具&#xff0c;是一个可以记住遍历的位置的对象。 迭代器对象从集合的第一个元素开始访问&#xff0c;直到所有的元素被访问完结束。迭代器只能…

QT项目_RPC(进程间通讯)

QT项目_RPC(进程间通讯) 前言&#xff1a; 两个进程间通信、或是说两个应用程序之间通讯。实际情况是在QT开发的一个项目中&#xff0c;里面包含两个子程序&#xff0c;子程序有单独的界面和应用逻辑&#xff0c;这两个子程序跑起来之后需要一些数据的交互&#xff0c;例如&…

GEE学习笔记 八十四:【GEE之Python版教程十四】矢量数据(ee.feature)

上一节讲了几何图形Geometry&#xff0c;这一节讲矢量数据&#xff08;ee.feature&#xff09;&#xff0c;它的构成也就是几何图形以及属性字典。 1、API定义 首先看一下GEE的python版API&#xff08;Welcome to GEE-Python-API’s documentation! — GEE-Python-API 1.0 do…

【论文】智能隧道检测车的现状及改进策略

本文转载自《智慧城轨》2022年第11期 作者&#xff1a;黄丹樱1,韦强1,朱椰毅2,范骁1,林浩立1 单位&#xff1a;1 浙江师范大学工学院&#xff1b;2 浙江金温铁道开发有限公司 声明&#xff1a;本文仅用于学术分享&#xff0c;不做商业用途&#xff0c;如有侵权&#xff0c;联…

从实现到原理,聊聊Java中的SPI动态扩展

原创&#xff1a;微信公众号 码农参上&#xff0c;欢迎分享&#xff0c;转载请保留出处。 八股文背多了&#xff0c;相信大家都听说过一个词&#xff0c;SPI扩展。 有的面试官就很喜欢问这个问题&#xff0c;SpringBoot的自动装配是如何实现的&#xff1f; 基本上&#xff0c…

Redis第二讲

二、Redis02 2.1 发布和订阅 Redis 发布订阅 (pub/sub) 是一种消息通信模式&#xff1a;发送者 (pub) 发送消息&#xff0c;订阅者 (sub) 接收消息。 Redis 客户端可以订阅任意数量的频道。 发布订阅的实现 1、打开一个客户端订阅channel1 127.0.0.1:6379> subscribe ch…

红黑树的原理+实现

文章目录红黑树定义性质红黑树的插入动态效果演示代码测试红黑树红黑树 定义 红黑树是一个近似平衡的搜索树&#xff0c;关于近似平衡主要体现在最长路径小于最短路径的两倍&#xff08;我认为这是红黑树核心原则&#xff09;&#xff0c;为了达到这个原则&#xff0c;红黑树所…

LeetCode刷题--- 面试题 01.07. 旋转矩阵(原地旋转+翻转替旋转)

&#x1f48c; 所属专栏&#xff1a;【LeetCode题解&#xff08;持续更新中&#xff09;】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;…

【C++之容器篇】二叉搜索树的理论与使用

目录前言一、二叉搜索树的概念二、二叉搜素树的模拟实现&#xff08;增删查非递归实现&#xff09;1. 二叉搜素树的结点2. 二叉搜索树的实现&#xff08;1&#xff09;. 二叉搜索树的基本结构&#xff08;2&#xff09;构造函数&#xff08;3&#xff09;查找函数&#xff08;4…

1225057-68-0,Alkyne PEG4 TAMRA-5,四甲基罗丹明-四聚乙二醇-炔基TAMRA红色荧光染料连接剂

中英文别名&#xff1a;CAS号&#xff1a;1225057-68-0 | 英文名&#xff1a;5-TAMRA-PEG4-Alkyne |中文名&#xff1a;5-四甲基罗丹明-四聚乙二醇-炔基物理参数&#xff1a;CASNumber&#xff1a;1225057-68-0Molecular formula&#xff1a;C36H41N3O8Molecular weight&#x…

P16 激活函数与Loss 的梯度

参考&#xff1a;https://www.ngui.cc/el/507608.html?actiononClick这里面简单回顾一下PyTorch 里面的两个常用的梯度自动计算的APIautoGrad 和 Backward, 最后结合 softmax 简单介绍一下一下应用场景。目录&#xff1a;1 autoGrad2 Backward3 softmax一 autoGrad输入 x输出损…

buu [UTCTF2020]basic-crypto 1

题目描述&#xff1a; 01010101 01101000 00101101 01101111 01101000 00101100 00100000 01101100 01101111 01101111 01101011 01110011 00100000 01101100 01101001 01101011 01100101 00100000 01110111 01100101 00100000 01101000 01100001 01110110 01100101 00100000 0…