WPF高级编程的目录
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF高级编程的目录相关的知识,希望对你有一定的参考价值。
参考技术A第1章 WPF概述 1
1.1 WindowsAPI的简要历史 1
1.1.1 平台演化 2
1.1.2 .NETFramework3.0介绍 2
1.2 初识WindowsPresentationFoundation 3
1.2.1 设计指导原则 3
1.2.2 WPF体系结构 5
1.3 XAML 13
1.4 可视化设计工具 16
1.4.1 XamlPad 16
1.4.2 MicrosoftExpressionBlend 17
1.4.3 WPF可视化设计器 18
1.4.4 ElectricRainZAM3D 19
1.4.5 MobiformAurora 19
1.5 小结 20
第2章 WPF与.NET编程 23
2.1 开始 23
2.1.1 需要安装的软件 24
2.1.2 WPF应用程序类型 24
2.1.3 第一个WPF应用程序 25
2.2 WPF开发概念 27
2.2.1 XAML 27
2.2.2 Application对象 28
2.2.3 创建用户界面 30
2.2.4 事件处理 32
2.2.5 使用控件 38
2.2.6 触发器 47
2.2.7 语言支持 55
2.2.8 部署 57
2.3 小结 57
第3章 WPF应用程序深入剖析 59
3.1 项目组成 59
3.1.1 App.Manifest 61
3.1.2 AssemblyInfo.cs 61
3.1.3 Resources.resx 63
3.1.4 Settings.settings 63
3.1.5 MyApp.xaml 65
3.2 .NETFramework3.0Windows应用程序 66
3.3 XAML浏览器应用程序 68
3.4 WCF服务库 70
3.5 WPF自定义控件库 73
3.6 小结 77
第4章 使用MicrosoftExpression
Blend构建富用户界面:
第一部分 79
4.1 概述 79
4.2 设计环境 80
4.2.1 工作空间面板 81
4.2.2 艺术板 82
4.2.3 配置环境 83
4.3 项目结构 84
4.3.1 添加一个新项目文件 84
4.3.2 构建与部署一个项目 85
4.4 创建向量对象 85
4.4.1 形状 86
4.4.2 路径 87
4.5 使用属性面板操作对象 88
4.5.1 变换面板 88
4.5.2 画刷面板 88
4.5.3 透明性、透明度与可见度 90
4.6 操作文本 91
4.7 管理布局 92
4.7.1 用户界面布局面板 93
4.7.2 其他布局控件 95
4.7.3 嵌套布局面板 96
4.8 动画 96
4.8.1 时间线子面板 96
4.8.2 关键帧 98
4.8.3 为对象的属性添加动画 98
4.8.4 运动路径 98
4.8.5 触发器 99
4.8.6 创建控件 100
4.8.7 模板 102
4.8.8 样式 103
4.8.9 状态 103
4.8.10 导入音频/视频媒体 104
4.9 小结 104
第5章 使用MicrosoftExpressionBlend构建富用户界面:
第二部分 105
5.1 ExpressionBlend工作流 105
5.2 处理用户输入 108
5.2.1 键盘和鼠标类 108
5.2.2 事件与事件处理 108
5.2.3 定位 110
5.2.4 命中测试 114
5.3 WPF动画API 124
5.3.1 动画类 124
5.3.2 使用代码通过编程创建动态动画 124
5.3.3 程序动画 127
5.4 使用故事板进行交互 132
5.5 WPF绘图API 134
5.5.1 Geometry类 134
5.5.2 Shape类 134
5.5.3 画刷 138
5.6 小结 141
第6章 特殊效果 143
6.1 画刷 144
6.1.1 SolidColorBrash 144
6.1.2 GradientBrush 150
6.1.3 ImageBrush 153
6.1.4 DrawingBrush 155
6.1.5 VisualBrush 156
6.2 位图效果 158
6.3 变换 163
6.3.1 TranslateTransform类 165
6.3.2 ScaleTransform类 168
6.3.3 SkewTransform类 171
6.3.4 RotateTransform类 173
6.3.5 透明掩码 176
6.4 综合运用--组合效果 178
6.4.1 具有反射效果的跳跃
小球示例程序 178
6.4.2 动画图像观察器示例程序 184
6.5 小结 190
第7章 自定义控件 191
7.1 概述 191
7.2 控件基类 192
7.2.1 UserControl类 193
7.2.2 创建用户控件 193
7.3 WPF中的数据绑定 202
7.3.1 绑定标记扩展 202
7.3.2 绑定模式 202
7.3.3 数据模板 204
7.3.4 数据转换 205
7.4 创建和编辑样式 207
7.4.1 为样式指定目标类型 208
7.4.2 继承和覆盖样式 209
7.4.3 样式触发器 210
7.5 使用模板定制已有的控件 212
7.6 小结 215
第8章 WPF企业开发 217
8.1 WPF应用程序模型 218
8.1.1 单机应用程序 219
8.1.2 基于浏览器的应用程序 221
8.1.3 安全考虑 230
8.2 状态管理 231
8.2.1 Application对象 231
8.2.2 独立存储 233
8.2.3 状态管理示例程序 235
8.3 导航 241
8.3.1 导航元素 241
8.3.2 结构化导航 242
8.3.3 导航拓扑 252
8.4 应用程序本地化 269
8.4.1 自动布局指导原则 270
8.4.2 使用网格以达到灵活性 272
8.4.3 本地化属性和注释 273
8.5 WPF部署模型 274
8.5.1 生成应用程序 275
8.5.2 部署单机Windows
应用程序 276
8.5.3 部署XAML浏览器应用程序 277
8.5.4 部署.NETFramework3.0运行库 278
8.6 小结 280
第9章 安全性 281
9.1 WPF安全模型 281
9.2 受信任的应用程序与Express应用程序 282
9.3 核心操作系统安全性 282
9.3.1 LUA 282
9.3.2 虚拟化 283
9.3.3 沙箱 283
9.3.4 下一代密码 283
9.4 CLR安全性 284
9.4.1 代码访问安全性 284
9.4.2 临界代码方法 291
9.4.3 验证 292
9.5 MicrosoftInternetExplorer的安全性 293
9.5.1 区域限制 293
9.5.2 XBAP沙箱工作区 294
9.5.3 XAML浏览器应用程序的安全性 294
9.6 ClickOnce安全性 301
9.6.1 受信任的发布者 301
9.6.2 个人证书文件 302
9.7 .NET3.0安全性实用程序 305
9.8 小结 305
第10章 WPF与Win32交互 307
10.1 Win32用户界面概述 307
10.2 WPF和HWND如何交互操作 308
10.3 在WPF中使用Win32HWND 309
10.4 在Win32应用程序中使用WPF 312
10.5 为WPF添加Windows窗体控件 314
10.5.1 在代码中添加WindowsFormsHost 314
10.5.2 在XAML代码中添加HwndHost 316
10.5.3 为WPF添加ActiveX控件 316
10.5.4 在XAML代码中添加ActiveX控件 321
10.5.5 为Windows窗体添加WPF控件 322
10.5.6 影响控件属性 326
10.6 小结 328
第11章 高级开发主题 329
11.1 WPF体系结构 330
11.1.1 核心子系统 331
11.1.2 WPF线程模型 334
11.1.3 桌面窗口管理器 335
11.2 WPF框架 336
11.2.1 Dispatcher对象 337
11.2.2 DependencyObject/DependencyProperty类 337
11.2.3 Application类 338
11.2.4 Freezable类 339
11.2.5 Visual类 339
11.2.6 UIElement类 345
11.2.7 FrameworkElement类 345
11.2.8 Control类 346
11.3 XAML深入分析 347
11.3.1 XAML的后台工作 348
11.3.2 动态操作XAML 352
11.3.3 解析Window1.xaml 356
11.3.4 XAML标记扩展 360
11.3.5 XAML与自定义类型 360
11.4 WPF多线程 362
11.4.1 单线程应用程序模型 363
11.4.2 线程关联度与DispatcherObject 363
11.4.3 WPF分发器 364
11.4.4 使用单线程应用程序 365
11.4.5 异步线程 367
11.5 Windows通信基础WCF 372
11.5.1 服务定向 372
11.5.2 WCF体系结构 373
11.5.3 WCF基础 374
11.5.4 构建一个WCF服务 376
11.6 Windows工作流基础 383
11.6.1 工作流定义 384
11.6.2 WF体系结构 384
11.6.3 WF基础 385
11.7 小结 399
……
WPF学习第三十三章 高级命令
原文:【WPF学习】第三十三章 高级命令
前面两章介绍了命令的基本内容,可考虑一些更复杂的实现了。接下来介绍如何使用自己的命令,根据目标以不同方式处理相同的命令以及使用命令参数,还将讨论如何支持基本的撤销特性。
一、自定义命令
在5个命令类(ApplicationCommands、NavigationCommands、EditingCommands、ComponentCommands以及MediaCommands)中存储的命令,显然不会为应用程序提供所有可能需要的命令。幸运的是,可以很方便地自定义命令,需要做的全部工作就是实例化一个新的RoutedUiCommand对象。
RoutedUICommand类提供了几个构造函数。虽然可创建没有任何附加信息的RoutedUICommand对象,但几乎总是希望提供命令名、命令文本以及所属类型。此外,可能希望为InputGestures集合提供快捷键。
最佳设计方式是遵循WPF库中的范例,并通过静态属性提供自定义命令。下面的示例定义了名为Requery的命令:
public class DataCommands { private static RoutedUICommand requery; static DataCommands() { InputGestureCollection collection = new InputGestureCollection(); collection.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R")); requery = new RoutedUICommand("Requery", "Requery", typeof(DataCommands), collection); } public static RoutedUICommand Requery { get { return requery; } set { requery = value; } } }
一旦定义了命令,就可以在命令绑定中使用它,就像使用WPF提供的所有预先构建好的命令那样。但仍存在一个问题。如果希望在XAML中使用自定义的命令,那么首先需要将.NET名称空间映射为XML名称空间。例如,如果自定义的命令类位于Commands名称空间中(对于名为Commands的项目,这是默认的名称空间),那么应添加如下名称空间映射:
xmlns:local="clr-namespace:Commands"
这个示例使用local作为名称空间的别名。也可使用任意希望使用的别名,只要在XAML文件中保持一致就可以了。
现在,可通过local名称空间访问命令:
<CommandBinding Command="local:DataCommands.Requery" Executed="CommandBinding_Executed"> </CommandBinding>
下面是一个完整示例,在该例中有一个简单的窗口,该窗口包含一个触发Requery命令的按钮:
<Window x:Class="Commands.CustomCommand" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Commands" Title="CustomCommand" Height="300" Width="300"> <Window.CommandBindings> <CommandBinding Command="local:DataCommands.Requery" Executed="CommandBinding_Executed"> </CommandBinding> </Window.CommandBindings> <Grid> <Button Margin="5" Command="local:DataCommands.Requery">Requery</Button> </Grid> </Window>
为完成该例,只需要在代码中实现CommandBinding_Executed()事件处理程序即可。还可以使用CanExecute事件酌情启用或禁用该命令。
二、在不同位置使用相同的命令
在WPF命令模型中,一个重要概念是范围(scope)。尽管每个命令仅有一份副本,但使用命令的效果却会根据触发命令的位置而异。例如,如果有两个文本框,它们都支持Cut、Copy和Paste命令,操作只会在当前具有焦点的文本框中发生。
至此,我们还没有学习如何对自己关联的命令实现这种效果。例如,设想创建了一个具有两个文档的控件的窗口,如下图所示。
如果使用Cut、Copy和Paste命令,就会发现他们能够在正确的文本框中自动工作。然而,对于自己实现的命令——New、Open以及Save命令——情况就不同了。问题在于当为这些命令中的某个命令触发Executed事件时,不知道该事件是属于第一个文本框还是第二个文本框。尽管ExecuteRoutedEventArgs对象提供了Source属性,但该属性反映的是具有命令绑定的元素(像sender引用)。而到目前为止,所有命令都被绑定到了容器窗口。
解决这个问题的方法是使用文本框的CommandBindings集合分别为每个文本框绑定命令。下面是一个示例:
<TextBox Margin="5" Grid.Row="3" TextWrapping="Wrap" AcceptsReturn="True" TextChanged="txt_TextChanged"> <TextBox.CommandBindings> <CommandBinding Command="ApplicationCommands.Save" Executed="SaveCommand" /> </TextBox.CommandBindings> </TextBox>
现在文本框处理Executed事件。在事件处理程序中,可使用这一信息确保保存正确的信息:
private void SaveCommand(object sender, ExecutedRoutedEventArgs e) { string text = ((TextBox)sender).Text; MessageBox.Show("About to save: " + text); isDirty= false; }
上面的实现存在两个小问题。首先,简单的isDirty标记不在能满足需要,因此现在需要跟踪两个文本框。有几种解决这个问题的方法。可使用TextBox.Tag属性存储isDirty标志——使用该方法,无论何时调用CanExecuteSave()方法,都可以查看sender的Tag属性。也可创建私有的字典集合来保存isDirty值,按照控件引用编写索引。当触发CanExecuteSave()方法时,查找属于sender的isDirty值。下面是需要使用的完整代码:
private Dictionary<Object, bool> isDirty = new Dictionary<Object, bool>(); private void txt_TextChanged(object sender, RoutedEventArgs e) { isDirty[sender] = true; } private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { if (isDirty.ContainsKey(sender) && isDirty[sender] == true) { e.CanExecute = true; } else { e.CanExecute = false; } }
当前实现的另一个问题是创建了两个命令绑定,而实际上只需要一个。这会是XAML文件更加混乱,维护起来更难。如果在这两个文本框之间又大量的共享的命令,这个问题尤其明显。
解决方法是创建命令绑定,并向两个文本框的CommandBindings集合中添加同一个绑定。使用代码可很容易地完成该工作。如果希望使用XAML,需要使用WPF资源。在窗口的顶部添加一小部分标记,创建需要使用的Command Binding对象,并为之指定键名:
<Window.Resources> <CommandBinding x:Key="binding" Command="ApplicationCommands.Save" Executed="SaveCommand" CanExecute="SaveCommand_CanExecute"> </CommandBinding> </Window.Resources>
为在标记的另一个位置插入该对象,可使用StaticResource标记扩展并提供键名:
<TextBox.CommandBindings> <StaticResource ResourceKey="binding"></StaticResource> </TextBox.CommandBindings>
该示例的完整代码如下所示:
<Window x:Class="Commands.TwoDocument" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="TwoDocument" Height="300" Width="300"> <Window.Resources> <CommandBinding x:Key="binding" Command="ApplicationCommands.Save" Executed="SaveCommand" CanExecute="SaveCommand_CanExecute"> </CommandBinding> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition ></RowDefinition> <RowDefinition ></RowDefinition> </Grid.RowDefinitions> <Menu Grid.Row="0"> <MenuItem Header="File"> <MenuItem Command="New"></MenuItem> <MenuItem Command="Open"></MenuItem> <MenuItem Command="Save"></MenuItem> <MenuItem Command="SaveAs"></MenuItem> <Separator></Separator> <MenuItem Command="Close"></MenuItem> </MenuItem> </Menu> <ToolBarTray Grid.Row="1"> <ToolBar> <Button Command="New">New</Button> <Button Command="Open">Open</Button> <Button Command="Save">Save</Button> </ToolBar> <ToolBar> <Button Command="Cut">Cut</Button> <Button Command="Copy">Copy</Button> <Button Command="Paste">Paste</Button> </ToolBar> </ToolBarTray> <TextBox Margin="5" Grid.Row="2" TextWrapping="Wrap" AcceptsReturn="True" TextChanged="txt_TextChanged"> <TextBox.CommandBindings> <StaticResource ResourceKey="binding"></StaticResource> </TextBox.CommandBindings> <!--<TextBox.CommandBindings> <CommandBinding Command="ApplicationCommands.Save" Executed="SaveCommand" /> </TextBox.CommandBindings>--> </TextBox> <TextBox Margin="5" Grid.Row="3" TextWrapping="Wrap" AcceptsReturn="True" TextChanged="txt_TextChanged"> <TextBox.CommandBindings> <StaticResource ResourceKey="binding"/> </TextBox.CommandBindings> <!--<TextBox.CommandBindings> <CommandBinding Command="ApplicationCommands.Save" Executed="SaveCommand" /> </TextBox.CommandBindings>--> </TextBox> </Grid> </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 Commands { /// <summary> /// TwoDocument.xaml 的交互逻辑 /// </summary> public partial class TwoDocument : Window { public TwoDocument() { InitializeComponent(); } private void SaveCommand(object sender, ExecutedRoutedEventArgs e) { string text = ((TextBox)sender).Text; MessageBox.Show("About to save: " + text); isDirty[sender] = false; } private Dictionary<Object, bool> isDirty = new Dictionary<Object, bool>(); private void txt_TextChanged(object sender, RoutedEventArgs e) { isDirty[sender] = true; } private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { if (isDirty.ContainsKey(sender) && isDirty[sender] == true) { e.CanExecute = true; } else { e.CanExecute = false; } } } }
三、使用命令参数
上面所有的示例都没有使用命令参数来传递额外信息。然而,有些命令总需要一些额外信息。例如,NavigationCommands.Zoom命令需要用于缩放的百分数。类似地,可设想在特定情况下,前面使用过的一些命令可能也需要额外信息。例如,上节示例所示的两个文本框编辑器使用Save命令,当保存文档时需要知道使用哪个文件。
解决方法是设置CommandParameter属性。可直接为ICommandSource控件设置该属性(甚至可使用绑定表达式从其他控件获取值)。例如,下面的代码演示了如何通过从另一个文本框中读取数值,为链接到Zoom命令的按钮设置缩放百分比:
<Button Command="NavigationCommands.Zoom" CommandParater="{Binding ElementName=txtZoom,Path=Text"}> Zoom To Value </Button>
但该方法并不总是有效。例如,在具有两个文件的文本编辑器中,每个文本框重用同一个Save按钮,但每个文本框需要使用不同的文件名。对于此类情况,必须在其他地方存储信息(例如,在TextBox.Tag属性或在为区分文本框而索引文件名称的单独集合中存储信息),或者需要通过代码触发命令,如下所示:
ApplicationCommands.New.Execute(theFileName,(Button)sender);
无论使用哪种方法,都可以在Executed事件处理程序中通过ExecutedRoutedEventArgs.Parameter属性获取参数。
四、跟踪和翻转命令
WPF命令模型缺少的一个特性是翻转命令。尽管提供了ApplicationCommands.Undo命令,但该命令通常用于编辑控件(如TextBox控件)以维护它们自己的Undo历史。如果希望支持应用程序范围内的Undo特性,需要在内部跟踪以前的状态,并且触发Undo命令时还原该状态。
遗憾的是,扩展WPF命令系统并不容易。相对来说没几个入口点用于连接自定义逻辑,并且对于可用的几个入口点也没有提供说明文档。为创建通用的、可重用的Undo特性,需要创建一组全新的“能够撤销的”命令类,以及一个特定类型的命令绑定。本质上,必须使用自己创建的新命令系统替换WPF命令系统。
更好的解决方案是设计自己的用于跟踪和翻转命令的系统,但使用CommandManager类保存命令历史。下图显示了一个这方面的例子。在该例中,窗口包含两个文本框和一个列表框,可以自由地再这两个文本框中输入内容,而列表框则一直跟踪在这两个文本框中发生的所有命令。可通过单击Reverse Last Command按钮翻转最后一个命令。
为构建这个解决方案,需要使用几项新技术。第一细节是用于跟踪命令历史的类。为构建保存最近命令的撤销系统,肯恩共需要用到这样的类(甚至可能喜欢创建派生的ReversibleCommand类,提供诸如Unexecute()的方法来翻转以前的任务)。但该系统不能工作,因为所有WPF命令都是唯一的。这意味着在应用程序中每个命令只有一个实例。
为理解该问题,假设提供EditingCommands.Backspace命令,而且用户在一行中回退了几个空格。可通过向最近命令堆栈中添加Backspace命令来记录这一操作,但实际上每次添加的是相同的命令对象。因此,没有简单的方法用于存储命令的其他信息,例如刚刚删除的字符。如果希望存储该状态,需要构建自己的数据结构。该例使用名为CommandHistoryItem的类。
每个CommandHistoryItem对象跟踪以下几部分信息:
- 命令名称
- 执行命令的元素。在该例中,有两个文本框,所以可以是其中的任意一个。
- 在目标元素中被改变的属性。在该例中是TextBox类的Text属性。
- 可用于保存受影响元素以前状态的对象(例如,执行命令之前文本框中的文本)。
CommandHistoryItem类还提供了通用的Undo()方法。该方法使用反射为修改过的属性应用以前的值,用于恢复TextBox控件中的文本。但对于更复杂的应用程序,需要使用CommandHistoryItem类的层次结构,每个类都可以使用不同方式翻转不同类型的操作。
下面是CommandHistoryItem类的完整代码。
public class CommandHistoryItem { public string CommandName { get; set; } public UIElement ElementActedOn { get; set; } public string PropertyActedOn { get; set; } public object PreviousState { get; set; } public CommandHistoryItem(string commandName) : this(commandName, null, "", null) { } public CommandHistoryItem(string commandName, UIElement elementActedOn, string propertyActedOn, object previousState) { CommandName = commandName; ElementActedOn = elementActedOn; PropertyActedOn = propertyActedOn; PreviousState = previousState; } public bool CanUndo { get { return (ElementActedOn != null && PropertyActedOn != ""); } } public void Undo() { Type elementType = ElementActedOn.GetType(); PropertyInfo property = elementType.GetProperty(PropertyActedOn); property.SetValue(ElementActedOn, PreviousState, null); } }
需要的下一个要素是执行应用程序范围内Undo操作的命令。ApplicationCommands.Undo命令时不适合的,原因是为了达到不同的目的,它已经被用于单独的文本框控件(翻转最后的编辑变化)。相反,需要创建一个新命令,如下所示:
private static RoutedUICommand applicationUndo; public static RoutedUICommand ApplicationUndo { get { return applicationUndo; } } static MonitorCommands() { applicationUndo = new RoutedUICommand("ApplicationUndo", "Application Undo", typeof(MonitorCommands)); }
在该例中,命令时在名为MonitorCommands的窗口类中定义的。
到目前为止,出了执行Undo操作的反射代码比较有意义外,其他代码没有什么值得注意的地方。更困难的部分是将该命令历史集成进WPF命令模型中。理想的解决方案是使用能跟踪任意命令的方式完成该任务,而不管命令是是被如何触发和绑定的。相对不理想的解决方案是,强制依赖与一整套全新的自定义命令对象(这一逻辑功能内置到这些自定义命令对象中),或手动处理每个命令的Executed事件。
响应特定的命令是非常简单的,但当执行任何命令时如何进行响应呢?技巧是使用CommandManager类,该类提供了几个静态事件。这些事件包括CanExecute、PreviewCanExecute、Executed以及PreviewExecuted。在该例中,Executed和PreviewExecuted事件最有趣,因为每当执行任何一个命令时都会引发他们。
尽管CommandManager类关起了Executed事件,但仍可使用UIElement.AddHandler()方法关联事件处理程序,并为可选的第三个参数传递true值。这样将允许接收事件,即使事件已经被处理过也同样如此。然而,Executed事件是在命令执行完之后被触发的,这时已经来不及在命令历史中保存呗影响的控件的状态了。相反,需要响应PreviewExecuted事件,该事件在命令执行前一刻被触发。
下面的代码在窗口的构造函数中关联PreviewExecuted事件处理程序,并当关闭窗口时解除关联:
public MonitorCommands() { InitializeComponent(); this.AddHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandExecuted)); } private void window_Unloaded(object sender, RoutedEventArgs e) { this.RemoveHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandExecuted)); }
当触发PreviewExecuted事件时,需要确定准备执行的命令是否是我们所关心的。如果是,可创建CommandHistoryItem对象,并将其添加到Undo堆栈中。还需要注意两个潜在的问题。第一个问题是,当单击工具栏按钮以在文本框上执行命令时,CommandExecuted事件被引发了两次——一次是针对工具栏按钮,另一次时针对文本框。下面的代码通过忽略发送者是ICommandSource的命令,避免在Undo历史中重复条目。第二个问题是,需要明确忽略不希望添加到Undo历史中的命令。例如ApplicationUndo命令,通过该命令可翻转上一步操作。
private void CommandExecuted(object sender, ExecutedRoutedEventArgs e) { // Ignore menu button source. if (e.Source is ICommandSource) return; // Ignore the ApplicationUndo command. if (e.Command == MonitorCommands.ApplicationUndo) return; // Could filter for commands you want to add to the stack // (for example, not selection events). TextBox txt = e.Source as TextBox; if (txt != null) { RoutedCommand cmd = (RoutedCommand)e.Command; CommandHistoryItem historyItem = new CommandHistoryItem( cmd.Name, txt, "Text", txt.Text); ListBoxItem item = new ListBoxItem(); item.Content = historyItem; lstHistory.Items.Add(historyItem); // CommandManager.InvalidateRequerySuggested(); } }
该例在ListBox控件中存储所有CommandHistoryItem对象。ListBox控件的DisplayMember属性被设置为true,因而会显示每个条目的CommandHistoryItem.Name属性。上面的代码只为由文本框引发的命令提供Undo特性。然而,处理窗口中的任何文本框通常就足够了。为了支持其他控件和属性,需要对代码进行扩展。
最后一个细节是直线应用程序中范围内Undo操作的代码。使用CanExecute事件处理程序,可确保只有当在Undo历史中至少有一项时,才能执行此代码:
private void ApplicationUndoCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { if (lstHistory == null || lstHistory.Items.Count == 0) e.CanExecute = false; else e.CanExecute = true; }
为恢复最近的修改,只需要调用CommandHistoryItem对象的Undo方法。然后从列表中删除该项即可:
private void ApplicationUndoCommand_Executed(object sender, RoutedEventArgs e) { CommandHistoryItem historyItem = (CommandHistoryItem)lstHistory.Items[lstHistory.Items.Count - 1]; if (historyItem.CanUndo) historyItem.Undo(); lstHistory.Items.Remove(historyItem); }
到此,该示例的所有涉及细节都已经处理完成,该应用程序具有几个完全支持Undo特性的控件,但要在实际应用程序中使用这一方法,还需要进行许多改进。例如,需要耗费大量时间改进CommandManager.PreviewExecuted事件的处理程序,以忽略那些明星不需要跟踪的命令(当前,诸如使用键盘选择文本的事件已经单击空格键引发的命令等)。类似地,可能希望为那些不是由命令表示的但应当被翻转的操作添加CommandHistoryItem对象。例如,输入一些文本,然后导航到其他控件等。
本实例完整代码如下所示:
<Window x:Class="Commands.MonitorCommands" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Commands" Title="MonitorCommands" Height="300" Width="329.323" Unloaded="window_Unloaded"> <Window.CommandBindings> <CommandBinding Command="local:MonitorCommands.ApplicationUndo" Executed="ApplicationUndoCommand_Executed" CanExecute="ApplicationUndoCommand_CanExecute"></CommandBinding> </Window.CommandBindings> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <ToolBarTray Grid.Row="0"> <ToolBar> <Button Command="ApplicationCommands.Cut">Cut</Button> <Button Command="ApplicationCommands.Copy">Copy</Button> <Button Command="ApplicationCommands.Paste">Paste</Button> <Button Command="ApplicationCommands.Undo">Undo</Button> </ToolBar> <ToolBar Margin="0,0,-23,0"> <Button Command="local:MonitorCommands.ApplicationUndo">Reverse Last Command</Button> </ToolBar> </ToolBarTray> <TextBox Margin="5" Grid.Row="1" TextWrapping="Wrap" AcceptsReturn="True"> </TextBox> <TextBox Margin="5" Grid.Row="2" TextWrapping="Wrap" AcceptsReturn="True"> </TextBox> <ListBox Grid.Row="3" Name="lstHistory" Margin="5" DisplayMemberPath="CommandName"></ListBox> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; 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 Commands { /// <summary> /// MonitorCommands.xaml 的交互逻辑 /// </summary> public partial class MonitorCommands : Window { private static RoutedUICommand applicationUndo; public static RoutedUICommand ApplicationUndo { get { return applicationUndo; } } static MonitorCommands() { applicationUndo = new RoutedUICommand("ApplicationUndo", "Application Undo", typeof(MonitorCommands)); } public MonitorCommands() { InitializeComponent(); this.AddHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandExecuted)); } private void window_Unloaded(object sender, RoutedEventArgs e) { this.RemoveHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandExecuted)); } private void CommandExecuted(object sender, ExecutedRoutedEventArgs e) { // Ignore menu button source. if (e.Source is ICommandSource) return; // Ignore the ApplicationUndo command. if (e.Command == MonitorCommands.ApplicationUndo) return; // Could filter for commands you want to add to the stack // (for example, not selection events). TextBox txt = e.Source as TextBox; if (txt != null) { RoutedCommand cmd = (RoutedCommand)e.Command; CommandHistoryItem historyItem = new CommandHistoryItem( cmd.Name, txt, "Text", txt.Text); ListBoxItem item = new ListBoxItem(); item.Content = historyItem; lstHistory.Items.Add(historyItem); // CommandManager.InvalidateRequerySuggested(); } } private void ApplicationUndoCommand_Executed(object sender, RoutedEventArgs e) { CommandHistoryItem historyItem = (CommandHistoryItem)lstHistory.Items[lstHistory.Items.Count - 1]; if (historyItem.CanUndo) historyItem.Undo(); lstHistory.Items.Remove(historyItem); } private void ApplicationUndoCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { if (lstHistory == null || lstHistory.Items.Count == 0) e.CanExecute = false; else e.CanExecute = true; } } public class CommandHistoryItem { public string CommandName { get; set; } public UIElement ElementActedOn { get; set; } public string PropertyActedOn { get; set; } public object PreviousState { get; set; } public CommandHistoryItem(string commandName) : this(commandName, null, "", null) { } public CommandHistoryItem(string commandName, UIElement elementActedOn, string propertyActedOn, object previousState) { CommandName = commandName; ElementActedOn = elementActedOn; PropertyActedOn = propertyActedOn; PreviousState = previousState; } public bool CanUndo { get { return (ElementActedOn != null && PropertyActedOn != ""); } } public void Undo() { Type elementType = ElementActedOn.GetType(); PropertyInfo property = elementType.GetProperty(PropertyActedOn); property.SetValue(ElementActedOn, PreviousState, null); } } }
以上是关于WPF高级编程的目录的主要内容,如果未能解决你的问题,请参考以下文章