WPF学习第九集-深入浅出话命令
WPF为我们准备了完善的命令系统,你可能会问:“有了路由事件为什么还需要命令系统呢?”。事件的作用是发布、传播一些消息,消息传达到了接收者,事件的指令也就算完成了,至于如何响应事件送来的消息事件并不做任何限制,每个接收者可已用自己的行为来响应事件。也就是说,事件不具有约束力。命令和事件的区别就在于命令具有约束力。
的确,在实际编程工作中,即使只用事件不用命令程序的逻辑一样被驱动的很好,但我们不能够阻止程序员按照自己的习惯去编写代码。比如保存事件的处理器,程序员可以写Save()、SaveHandle()、SaveDocument()... 这些都符合代码规范。但迟早有一天整个项目会变的让人无法读懂,新来的程序员或修改bug的程序员会很抓狂。如果使用命令,情况就会好很多----当Save命令到达某个组件的时候,命令会自动去调用组件的Save方法。而这个方法可能定义在基类或者接口里(即保证了这个方法是一定存在的),这就在代码结构和命名上做了约束。不但如此,命令还可控制接收者“先做校验,再保存,最后退出”,也就是说命令除了可以约束代码,还可以约束步骤逻辑,让新来的程序员想犯错都难,也让那个修改Bug的程序员容易找到规律,容易上手。
1.1 命令系统的基本元素和关系
- WPF的命令系统由几个基本要素构成,它们是:
- 命令(Command):WPF的命令实际上就是实现了ICommand接口的类,平时使用最多的就是RoutedCommand类。我们还会学习使用自定义命令。
- 命令源(Command Source):即命令的发送者,是实现了ICommandSource接口的类。很多界面元素都实现了这个接口,其中包括Button,ListBoxItem,MenuItem等。
- 命令目标(Command Target):即命令发送给谁,或者说命令作用在谁的身上。命令目标必须是实现了IInputElement接口的类。
- 命令关联(Command Binding):负责把一些外围逻辑和命令关联起来,比如执行之前对命令是否可以执行进行判断、命令执行之后还有哪些后续工作等。
![](https://img-my.csdn.net/uploads/201211/05/1352102845_8922.png)
- <Window x:Class="WpfApplication1.Window28"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window28" Height="300" Width="300" WindowStyle="ToolWindow">
- <StackPanel Background="LightBlue" x:Name="sp1">
- <Button Content="Send Command" x:Name="btn1" Margin="5"></Button>
- <TextBox x:Name="txtA" Margin="5,0" Height="200"></TextBox>
- </StackPanel>
- </Window>
后台代码为:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Data;
- using System.Windows.Documents;
- using System.Windows.Input;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- using System.Windows.Shapes;
- namespace WpfApplication1
- {
- /// <summary>
- /// Window28.xaml 的交互逻辑
- /// </summary>
- public partial class Window28 : Window
- {
- public Window28()
- {
- InitializeComponent();
- InitializeCommand();
- }
- //声明并定义命令
- private RoutedCommand rouutedCommand = new RoutedCommand("Clear",typeof(Window28));
- private void InitializeCommand()
- {
- //把命令赋值给命令源,并定义快捷键
- this.btn1.Command = rouutedCommand;
- this.rouutedCommand.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt));
- //指定命令目标
- this.btn1.CommandTarget = txtA;
- //创建命令关联
- CommandBinding commandBinding = new CommandBinding();
- commandBinding.Command = rouutedCommand;//只关注与rouutedCommand相关的命令
- commandBinding.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);
- commandBinding.Executed += new ExecutedRoutedEventHandler(cb_Execute);
- //把命令关联安置在外围控件上
- this.sp1.CommandBindings.Add(commandBinding);
- }
- //当命令到达目标之后,此方法被调用
- private void cb_Execute(object sender, ExecutedRoutedEventArgs e)
- {
- txtA.Clear();
- //避免事件继续向上传递而降低程序性能
- e.Handled = true;
- }
- //当探测命令是否可执行的时候该方法会被调用
- private void cb_CanExecute(object sender,CanExecuteRoutedEventArgs e)
- {
- if (string.IsNullOrEmpty(txtA.Text))
- {
- e.CanExecute = false;
- }
- else
- {
- e.CanExecute = true;
- }
- //避免事件继续向上传递而降低程序性能
- e.Handled = true;
- }
- }
- }
运行程序,在TextBox中输入内容之后,Button在命令可执行状态下变为可用,此时单击按钮或者按Alt+C,TextBox就会被清空,效果如下图:
![](https://img-my.csdn.net/uploads/201211/05/1352105090_4044.png)
![](https://img-my.csdn.net/uploads/201211/05/1352105168_3789.png)
- private RoutedCommand rouutedCommand = new RoutedCommand("Clear",typeof(Window28));
命令具有一处声明,处处使用的特点,比如Save命令,在程序的任何地方它都表示要求命令目标保存数据。因此,微软在WPF类库里面准备了一些便捷的命令库,这些命令库包括:
![](https://img-my.csdn.net/uploads/201211/05/1352106710_9096.png)
- <Window x:Class="WpfApplication1.Window29"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window29" Height="278" Width="398">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="24" />
- <RowDefinition Height="4" />
- <RowDefinition Height="24" />
- <RowDefinition Height="4" />
- <RowDefinition Height="24" />
- <RowDefinition Height="4" />
- <RowDefinition Height="*" />
- </Grid.RowDefinitions>
- <!--命令和命令参数-->
- <TextBlock HorizontalAlignment="Left" Name="textBlock1" Text="Name:" VerticalAlignment="Center" Grid.Row="0"/>
- <TextBox x:Name="txtName" Margin="60,0,0,0" Grid.Row="0"></TextBox>
- <Button Content="New Teacher" Grid.Row="2" Command="New" CommandParameter="Teacher"></Button>
- <Button Content="New Student" Grid.Row="4" Command="New" CommandParameter="Student"></Button>
- <ListBox Grid.Row="6" x:Name="lbInfos">
- </ListBox>
- </Grid>
- <!--为窗体添加CommandBinding-->
- <Window.CommandBindings>
- <CommandBinding Command="New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed">
- </CommandBinding>
- </Window.CommandBindings>
- </Window>
以上代码有两个地方需要注意:
- private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
- {
- if (string.IsNullOrEmpty(txtName.Text))
- {
- e.CanExecute = false;
- }
- else
- {
- e.CanExecute = true;
- }
- //路由终止,提高系统性能
- e.Handled = true;
- }
- private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
- {
- if (e.Parameter.ToString() == "Student")
- {
- this.lbInfos.Items.Add(string.Format("New Student:{0} 好好学习,天天向上。",txtName.Text));
- }
- else if(e.Parameter.ToString()=="Teacher")
- {
- this.lbInfos.Items.Add(string.Format("New Teacher:{0} 学而不厌,诲人不倦。", txtName.Text));
- }
- //路由终止,提高系统性能
- e.Handled = true;
- }
运行程序,当TextBox中没有内容的时候,两个按钮都不可用;当输入文字后按钮变为可用,单击按钮,ListBox中会添加不同的条目,效果如下图:
![](https://img-my.csdn.net/uploads/201211/05/1352108654_7429.png)
![](https://img-my.csdn.net/uploads/201211/05/1352108669_9830.png)
- <Button x:Name="cmdBtn" Command="{Binding Path=ppp, Source=sss}" Content="Command"></Button>
不过话又说回来了,因为大多数命令按钮都有相对应的图标来表示固定的含义,所以日常工作中一个控件的命令一经确定很少改变。
![](https://img-my.csdn.net/uploads/201211/05/1352106710_9096.png)
![](https://img-my.csdn.net/uploads/201211/06/1352165163_6074.png)
![](https://img-my.csdn.net/uploads/201211/06/1352165210_2335.png)
![](https://img-my.csdn.net/uploads/201211/06/1352165952_3028.png)
![](https://img-my.csdn.net/uploads/201211/06/1352166438_2987.png)
- public interface IView
- {
- //属性
- bool IsChanged { get; set; }
- //方法
- void SetBinding();
- void Refresh();
- void Clear();
- void Save();
- }
并且要求每个接收命令的组件必须实现这个接口,这样可以确保命令可以对其进行操作。
- /// <summary>
- ///自定义命令
- /// </summary>
- public class ClearCommand:ICommand
- {
- //用来判断命令是否可以执行
- public bool CanExecute(object parameter)
- {
- throw new NotImplementedException();
- }
- //当命令可执行状态发送改变时,应当被激发
- public event EventHandler CanExecuteChanged;
- //命令执行时,带有与业务相关的Clear逻辑
- public void Execute(object parameter)
- {
- IView view = parameter as IView;
- if(view!=null)
- {
- view.Clear();
- }
- }
- }
命令实现了ICommand接口并继承了CanExecuteChanged事件、CanExecute方法、Execute方法。目前这个命令比较简单,只用到了Execute方法。在实现这个方法时,我们将这个方法唯一的参数作为命令的目标,如果目标是IView接口的派生类则调用其Clear方法---显然我们已经把程序的业务逻辑引入到了命令的Execute方法中。
- /// <summary>
- /// MyCommandSource.xaml 的交互逻辑
- /// </summary>
- public partial class MyCommandSource : UserControl,ICommandSource
- {
- /// <summary>
- /// 继承自ICommand的3个属性
- /// </summary>
- public ICommand Command
- {
- get;
- set;
- }
- public object CommandParameter
- {
- get;
- set;
- }
- public IInputElement CommandTarget
- {
- get;
- set;
- }
- //在命令目标上执行命令,或者说让命令作用于命令目标
- protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
- {
- base.OnMouseLeftButtonDown(e);
- if(this.CommandTarget!=null)
- {
- this.Command.Execute(CommandTarget);
- }
- }
- }
ICommand接口只包含Command,CommandParameter,CommandTarget 3个属性,至于这3个属性直接有什么样的关系就看我们要怎么去实现了。在本例中CommandParameter完全没有被用到,而CommandTarget作为参数传递给了Command的Execute方法。命令不会自己被发出,所以一定要为命令的执行选择一个好的时机,本例中我们在控件左单击的时候执行命令。
- <UserControl x:Class="WpfApplication1.MniView"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- mc:Ignorable="d"
- d:DesignHeight="300" d:DesignWidth="300">
- <Border CornerRadius="5" BorderBrush="GreenYellow" BorderThickness="2">
- <StackPanel>
- <TextBox Margin="5" x:Name="txt1"></TextBox>
- <TextBox Margin="5" x:Name="txt2"></TextBox>
- <TextBox Margin="5" x:Name="txt3"></TextBox>
- <TextBox Margin="5" x:Name="txt4"></TextBox>
- </StackPanel>
- </Border>
- </UserControl>
它的后台代码部分如下:
- /// <summary>
- /// MniView.xaml 的交互逻辑
- /// </summary>
- public partial class MniView : UserControl,IView
- {
- //构造器
- public MniView()
- {
- InitializeComponent();
- }
- //继承自IView的成员们
- public bool IsChanged
- {
- get
- {
- throw new NotImplementedException();
- }
- set
- {
- throw new NotImplementedException();
- }
- }
- public void SetBinding()
- {
- throw new NotImplementedException();
- }
- public void Refresh()
- {
- throw new NotImplementedException();
- }
- /// <summary>
- /// 用于清除内容的业务逻辑
- /// </summary>
- public void Clear()
- {
- this.txt1.Clear();
- this.txt2.Clear();
- this.txt3.Clear();
- this.txt4.Clear();
- }
- public void Save()
- {
- throw new NotImplementedException();
- }
- }
因为我们只演示Clear方法被调用,所以其它几个方法没有具体实现。当Clear方法被调用的时候,它的几个TextBox会被清空。
- <Window x:Class="WpfApplication1.Window30"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window30" Height="300" Width="300" xmlns:my="clr-namespace:WpfApplication1">
- <StackPanel>
- <my:MyCommandSource x:Name="myCommandSource1">
- <TextBlock Text="清除" Width="80" FontSize="16" TextAlignment="Center" Background="LightGreen"></TextBlock>
- </my:MyCommandSource>
- <my:MniView x:Name="mniView1" />
- </StackPanel>
- </Window>
本例中使用简单的文本作为命令源的显示内容,实际工作中可以使用图标,按钮或者更复杂的内容来填充它,但要适当更改激发命令的方法。不然你打算在里面放置一个按钮,那么就不要用OnMouseLeftButtonDown的方法来执行命令了,而应该捕获button的Click事件并在事件处理器中执行方法(Mouse事件会被Button吃掉)。
- /// <summary>
- /// Window30.xaml 的交互逻辑
- /// </summary>
- public partial class Window30 : Window
- {
- public Window30()
- {
- InitializeComponent();
- ClearCommand clearCommand = new ClearCommand();
- this.myCommandSource1.Command = clearCommand;
- this.myCommandSource1.CommandTarget = mniView1;
- }
- }
我们首先创建了一个ClearCommand实例并把它赋值给自定义命令源的Command属性,自定义命令源的CommandTarget属性目标是MiniView的实例。提醒一句:为了讲解清晰才把命令放在这里,正规的方法应该是把命令声明为静态全局的地方供所有对象调用。运行程序,在TextBox里输入然后再单击清除控件,效果如下图:
![](https://img-my.csdn.net/uploads/201211/06/1352172301_5165.png)
![](https://img-my.csdn.net/uploads/201211/06/1352172316_4463.png)
至此,一个简单的自定义命令就完成了,若想通过Command的CanExecute方法来影响命令源的状态,还需要使用到ICommand和ICommandSource接口的成员组成更复杂的逻辑。
WPF学习第九集-深入浅出话命令相关推荐
- 深入浅出话命令(Command)-笔记(-)
深入浅出话命令(Command)-笔记(-) 一 基本概念 命令的基本元素: 命令(Command):实现了ICommand接口的类,平常使用最多的是RoutedCommand类. 命令源(Comma ...
- WPF学习之深入浅出话命令
WPF为我们准备了完善的命令系统,你可能会问:"有了路由事件为什么还需要命令系统呢?".事件的作用是发布.传播一些消息,消息传达到了接收者,事件的指令也就算完成了,至于如何响应事件 ...
- 《深入浅出WPF》学习笔记之深入浅出话属性
依赖属性是一种可以从父级元素继承,并且可以通过Binding从数据源获取,当从父级继承时不占用内存的属性系统.拥有依赖属性的对象称为依赖对象.WPF允许在创建对象时并不分配用于存储数据的空间,而在需要 ...
- WPF学习第二集-XMAL概览
微软为了把开发模式从网络开发移植到桌面开发和富媒体网络程序的开发上,微软创造了一种新的开发语言------XMAL(读作ZAML),XAML全称Extensible Application MarkU ...
- WPF学习之深入浅出话模板
图形用户界面应用程序较之控制台界面应用程序最大的好处就是界面友好.数据显示直观.CUI程序中数据只能以文本的形式线性显示,GUI程序则允许数据以文本.列表.图形等多种形式立体显示. 用户体验在GUI程 ...
- WPF 学习总结归纳之发布订阅与代理模式以及命令的投石问路(一)
1.X:Name与Name的区别 一些WPF框架级别的应用程序可能能够避免使用x:Name属性,因为WPF命名空间中为几个重要基类(例如FrameworkElement / FrameworkCont ...
- .NET-7.WPF学习2. 知识总结
WPF学习2. 知识总结 前言 一.面试 二.代码片段 三.查看链接 前言 对wpf 的知识总结. 一.面试 1. 跨线程操作(Dispatcher)2. template(模板类型[控件模板.数据模 ...
- 【转载】wpf学习笔记1
http://blog.csdn.net/fantasiax/article/details/4575968 深入浅出WPF(7)--数据的绿色通道,Binding(上) 小序: 怎么直接从2蹦到7啦 ...
- web前端学习1-45集
web前端1-45集 ==第一集== 课程划分 1.HTML+CSS系列教程1之拨云见日 2.HTML+CSS系列教程2之溯本求源 3.HTML+CSS系列教程3之风声水起 4.HTML+CSS系列教 ...
最新文章
- 数据库-sql-面试-rank
- 速领!抗疫大礼包(含QQ音乐、全民K歌、网易云音乐等等)
- hadoop hdfs访问权限更新延迟问题
- 【PAT甲级 最长公共子串】1007 Maximum Subsequence Sum (25 分) C++ 全部AC
- 如何成为一个搞垮公司的程序员?
- 真正的创业者和伪创业者的区别在哪里?
- 开源的仓库管理软件——Sonatype Nexus
- 设计模式——行为型模式
- U956(MTK6589系列)移植乐蛙教程
- 芭蕉树上第十五根芭蕉-- qt帮助文档使用
- linux防火墙_专业的linux web应用防火墙国内排名推荐
- 2016年民营企业500强榜单(全国工商联发布)
- 1972年发射失败的苏联金星探测器可能今年坠落地球
- html5 竖线的实现,border 实现竖线
- Sushi的MISO:不断扩展的DeFi边界
- 南大通用GBase XDM支持的操作平台
- e的近似求解方法matlab,3X^2-E^X并用matlab切线法求出所有实根的近似值,源程序
- 《CCNA学习指南:Cisco网络设备互连(ICND2)(第4版)》——1.3节通过EtherChannel 改进冗余及增加带宽...
- 分享复习经验和后台开发面经,已拿offer入职
- 8通道同步并行数据采集PCI模块的设计
热门文章
- c语言switch和break用法,c语言里面你不知道的break与switch,contiune的用法
- 聊聊如何制作自定义ArcGIS Python工具箱
- 地球固体潮汐和海洋潮汐改正
- 【SystemVerilog基础】合并数组与非合并数组深入探究
- CPU卡程序设计实例(二十三)卡和ESAM之间内部认证
- 学了go语言再学java容易吗_年纪大了还想转行当程序员,现在学java还来得及吗-Go语言中文社区...
- SU+GIS,让SketchUp模型在地图上活起来
- 华为认证考试难吗?怎样才能通过?
- mbps和MB/s是怎么换算的
- 使用宝塔创建PHP网站,出现“no input file specified“错误的解决方案。