# WPF快速入门系列(3)——深入解析WPF事件机制
## 一、引言
WPF除了创建了一个新的依赖属性系统之外,还用更高级的路由事件功能替换了普通的.NET事件。
路由事件是具有更强传播能力的事件——它可以在元素树上向上冒泡和向下隧道传播,并且沿着传播路径被事件处理程序处理。与依赖属性一样,可以使用传统的事件方式使用路由事件。尽管路由事件的使用方式与传统的事件一样,但是理解其工作原理还是相当重要的。
## 二、路由事件的详细介绍
对于.NET中的事件,大家应该在熟悉不过了。事件指的在某个事情发生时,由对象发送用于通知代码的消息。WPF中的路由事件允许事件可以被传递。例如,路由事件允许一个来自工具栏按钮的单击事件,在被处理之前可以传递到工具栏,然后再传递到包含工具栏的窗口。那么现在问题来了,我怎样在WPF中去定义一个路由事件呢?
## 2.1 如何定义路由事件
既然有了问题,自然就要去解决了。在自己定义一个依赖属性之前,首先,我们得学习下WPF框架中是怎么去定义的,然后按照WPF框架中定义的方式去试着自己定义一个依赖属性。下面通过Reflector工具来查看下WPF中[Button](http://msdn.microsoft.com/zh-cn/library/system.windows.controls.button(v=vs.110).aspx)按钮的Click事件的定义方式。
由于Button按钮的Click事件是继承于[ButtonBase](http://msdn.microsoft.com/zh-cn/library/system.windows.controls.primitives.buttonbase(v=vs.110).aspx)基类的,所以我们直接来查看ButtonBase中Click事件的定义。具体的定义代码如下所示:
```
[Localizability(LocalizationCategory.Button), DefaultEvent("Click")]
public abstract class ButtonBase : ContentControl, ICommandSource
{
// 事件定义
**public static readonly RoutedEvent ClickEvent;** // 事件注册
static ButtonBase()
{
**ClickEvent** **= EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof****(ButtonBase));**
CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ButtonBase), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ButtonBase.OnCommandChanged)));
.......
}
// 传统事件包装
** public event RoutedEventHandler Click
{
add
{
base.AddHandler(ClickEvent, value);
}
remove
{
base****.RemoveHandler(ClickEvent, value);
}
}**
.......
}
```
从上面代码可知,路由事件的定义与依赖属性的定义类似,路由事件由只读的静态字段表示,在一个静态构造函数通过[EventManager.RegisterRoutedEvent](http://msdn.microsoft.com/zh-cn/library/system.windows.eventmanager.registerroutedevent(v=vs.110).aspx)函数注册,并且通过一个.NET事件定义进行包装。
现在已经知道了路由事件是如何在WPF框架中定义和实现的了,那要想自己定义一个路由事件也自然不在话下了。
## 2.2 共享路由事件
与依赖属性一样,可以在类之间共享路由事件的定义。即实现路由事件的继承。例如[UIElement](http://msdn.microsoft.com/zh-cn/library/System.Windows.UIElement(v=vs.110).aspx)类和ContentElement类都使用了MouseUp事件,但MouseUp事件是由[System.Windows.Input.Mouse](http://msdn.microsoft.com/zh-cn/library/System.Windows.Input.Mouse(v=vs.110).aspx)类定义的。UIElement类和ContentElement类只是通过[RouteEvent.AddOwner](http://msdn.microsoft.com/zh-cn/library/system.windows.routedevent.addowner(v=vs.110).aspx)方法重用了MouseUp事件。你可以在UIElement类的静态构造函数找到下面的代码:
```
static UIElement()
{
_typeofThis = typeof(UIElement);
PreviewMouseUpEvent = Mouse.PreviewMouseUpEvent.AddOwner(_typeofThis);
MouseUpEvent = **Mouse.MouseUpEvent.AddOwner(_typeofThis)**;
}
```
## 2.3 引发和处理路由事件
尽管路由事件通过传统的.NET事件进行包装,但路由事件并不是通过.NET事件触发的,而是使用RaiseEvent方法触发事件,所有元素都从UIElement类继承了该方法。下面代码是具体ButtonBase类中触发路由事件的代码:
而在WinForm中,[Button](http://msdn.microsoft.com/zh-cn/library/system.windows.forms.button(v=vs.110).aspx)的Click事件是通过调用委托进行触发的,具体的实现代码如下所示:
```
1 protected virtual void OnClick(EventArgs e)
2 {
3 EventHandler handler = (EventHandler)base.Events[EventClick];
4 if (handler != null)
5 {
6 **handler(this, e);** // 直接调用委托进行触发事件
7 }
8 }
```
对于路由事件的处理,与原来WinForm方式一样,你可以在XAML中直接连接一个事件处理程序,具体实现代码如下所示:
```
<TextBlock Margin="3" MouseUp="SomethingClick" Name="tbxTest">
text label
</TextBlock>
// 后台cs代码
private void SomethingClick(object sender, MouseButtonEventArgs e)
{
}
```
同时还可以通过后台代码的方式连接事件处理程序,具体的实现代码如下所示:
## 三、路由事件其特殊性
路由事件的特殊性在于其传递性,WPF中的路由事件分为三种。
* 与普通的.NET事件类似的直接路由事件(Direct event)。它源自一个元素,并且不传递给其他元素。例如,MouseEnter事件(当鼠标移动到一个元素上面时触发)就是一个直接路由事件。
* 在包含层次中**向上传递的冒泡路由事件(Bubbling event)**。例如,MouseDown事件就是一个冒泡路由事件。它首先被单击的元素触发,接下来就是该元素的父元素触发,依此类推,直到WPF到达元素树的顶部为止。
* 在包含层次中**向下传递的隧道路由事件(Tunneling event)**。例如PreviewKeyDown就是一个隧道路由事件。在一个窗口上按下某个键,首先是窗口,然后是更具体的容器,直到到达按下键时具有焦点的元素。
既然,路由事件有三种表现形式,那我们怎么去区别具体的路由事件是属于哪种呢?辨别的方法在于路由事件的注册方法上,当使用[EventManager.RegisterEvent](http://msdn.microsoft.com/zh-cn/library/system.windows.eventmanager.registerroutedevent(v=vs.110).aspx)方法注册一个路由事件时,需要传递一个[RoutingStrategy](http://msdn.microsoft.com/zh-cn/library/system.windows.routingstrategy(v=vs.110).aspx)枚举值来标识希望应用于事件的事件行为。
## 3.1 冒泡路由事件
下面代码演示了事件冒泡过程:
```
<Window x:Class="BubbleLabelClick.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" MouseUp="SomethingClick">
<Grid Margin="3" MouseUp="SomethingClick">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Margin="5" Grid.Row="0" HorizontalAlignment="Left" Background="AliceBlue"
BorderBrush="Black" BorderThickness="2" MouseUp="SomethingClick">
<StackPanel MouseUp="SomethingClick">
<TextBlock Margin="3" MouseUp="SomethingClick" Name="tbxTest">
Image and text label
</TextBlock>
<Image Source="pack://application:,,,/BubbleLabelClick;component/face.png" Stretch="None" MouseUp="SomethingClick"/>
<TextBlock Margin="3" MouseUp="SomethingClick">
Courtest for the StackPanel
</TextBlock>
</StackPanel>
</Label>
<ListBox Grid.Row="1" Margin="3" Name="lstMessage">
</ListBox>
<CheckBox Margin="5" Grid.Row="2" Name="chkHandle">Handle first event</CheckBox>
<Button Click="cmdClear_Click" Grid.Row="3" HorizontalAlignment="Right" Margin="5" Padding="3">Clear List</Button>
</Grid>
</Window>
```
其后台代码为:
```
1 public partial class MainWindow : Window
2 {
3 public MainWindow()
4 {
5 InitializeComponent();
6
7 }
8
9 private int eventCounter = 0;
10
11 private void SomethingClick(object sender, RoutedEventArgs e)
12 {
13 eventCounter++;
14 string message = "#" + eventCounter.ToString() + ":\r\n" + "Sender: " + sender.ToString() + "\r\n" +
15 "Source: " + e.Source + "\r\n" +
16 "Original Source: " + e.OriginalSource;
17 lstMessage.Items.Add(message);
18 e.Handled = (bool)chkHandle.IsChecked;
19 }
20
21 private void cmdClear_Click(object sender, RoutedEventArgs e)
22 {
23 eventCounter = 0;
24 lstMessage.Items.Clear();
25 }
26 }
```
运行之后的效果图如下所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb4157006.png)
单击窗口中的笑脸图像之后,程序的运行结果如下图所示。
![](https://box.kancloud.cn/2016-01-23_56a2eb4169fa9.png)
从上图结果可以发现,MouseUp事件由下向上传递了5级,直到窗口级别结束。另外,如果选择了Handle first event复选框的话,SomethingClicked方法会将RoutedEventArgs.Handled属性设置为true,表示事件已被处理,且该事件将终止向上冒泡。因此,此时列表中只能看到Image的事件,具体运行结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb4182b67.png)
并且在列表框或窗口空白处进行单击,此时也一样只会出现一次MouseUp事件。但单击一个地方例外。当单击Clear List按钮,此时不会引发MouseUp事件。这是因为按钮包含一些特殊的处理代码,这些代码会挂起MouseUp事件(即不会触发MouseUp事件,则相应的事件处理程序也不会被调用),并引发一个更高级的Click事件,同时,Handled标记被设置为true(这里指的在触发Click事件时会把Handled设置为true),从而阻止MouseUp事件继续向上传递。
**通过博友yiifans指出,上面有一点说错了,在设置Handled = true的时候,不管是冒泡还是隧道事件,它还是会继续传播的,只是对应的事件不会再处理了。这里之所以没有删除上面错误解释而是在这里另行说明,是为了强调,因为WPF编程宝典上也是说会阻止传播。如果想继续响应相应事件的话,可以通过[AddHandler](http://msdn.microsoft.com/zh-cn/library/ms598899(v=vs.110).aspx)方法进行注册。此时你可以去掉XAMLStackPanel中MouseUp的注册,而是通过后台代码的方式进行注册MouseUp事件,具体的实现代码如下:**
之所以还是会继续上传,其实通过在SomethingClick事件处理程序中设置一个断点就可以发现其调用堆栈,具体的堆栈调用截图如下所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb4193507.png)
从上图可以知道SomethingClick调用前都执行了哪些操作,其中RoutedEventHandleInfo.InvokeHandler方法的实现代码就是这个问题的关键所在,下面通过Reflector查看下这个方法的源码,具体查看的源码如下所示:
```
internal void InvokeHandler(object target, RoutedEventArgs routedEventArgs)
{
**if (!routedEventArgs.Handled || this****._handledEventsToo)**
{
if (this._handler is RoutedEventHandler)
{
((RoutedEventHandler) this._handler)(target, routedEventArgs);
}
else
{
routedEventArgs.InvokeHandler(this._handler, target);
}
}
}
```
** 在上面代码中,红色标记的就是解释这个问题的关键代码,每当触发事件处理程序之前,都会检查RoutedEventArgs的Handled属性和handleEventsToo字段。这样我们就彻底明白了,当Handle=true时,其实路由事件一样还是会传递,只是传递到对应事件处理程序中时,只是因为Handle为true和_handleEventsToo为false,从而导致事件处理程序没有运行罢了,如果通过AddHandler(RoutedEvent, Delegate, Boolean)注册事件处理程序的话,此时把_handleEventToo显式设置为true了,所以即使Handle为true,该元素的事件处理程序照样会执行,因为此时if条件一样为true的。**
## 3.2 隧道路由事件
**隧道路由事件与冒泡路由事件的工作方式一样,只是方向相反。**即如果上面的例子中,触发的是一个隧道路由事件的话,如果在图像上单击,则首先窗口触发该隧道路由事件,然后才是Grid控件,接下来是StackPanel面板,以此类推,直到到达实际源头,即标签中的图像为止。
看了上面的介绍。隧道路由事件想必是相当好理解吧。它与冒泡路由事件的传递方式相反。但是我们怎样去区别隧道路由事件呢?隧道路由事件的识别相当容易,因为**隧道路由事件都是以单词Preview开头。**并且,WPF一般都成对地定义冒泡路由事件和隧道路由事件。这意味着如果发现一个冒泡的MouseUp事件,则对应的PreviewMouseUp就是一个隧道路由事件。另外,**隧道路由事件总是在冒泡路由事件之前被触发**。
另外需要注意的一点是:如果将隧道路由事件标记为已处理的,那么冒泡路由事件就不会发生。这是因为这两个事件共享同一个RoutedEventArgs类的实例。隧道路由事件对于来执行一些预处理操作非常有用,例如,根据键盘上特定的键执行特定操作,或过滤掉特定的鼠标操作等这样的场景都可以在隧道路由事件处理程序中进行处理。下面的示例演示了PreviewKeyDown事件的隧道过程。XAML代码如下所示。
```
<Window x:Class="TunneleEvent.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" PreviewKeyDown="SomeKeyPressed">
<Grid Margin="3" PreviewKeyDown="SomeKeyPressed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Margin="5" Grid.Row="0" HorizontalAlignment="Left" Background="AliceBlue"
BorderBrush="Black" BorderThickness="2" PreviewKeyDown="SomeKeyPressed">
<StackPanel>
<TextBlock Margin="3" PreviewKeyDown="SomeKeyPressed">
Image and text label
</TextBlock>
<Image Source="face.png" Stretch="None" PreviewMouseUp="SomeKeyPressed"/>
<DockPanel Margin="0,5,0,0" PreviewKeyDown="SomeKeyPressed">
<TextBlock Margin="3"
PreviewKeyDown="SomeKeyPressed">
Type here:
</TextBlock>
<TextBox PreviewKeyDown="SomeKeyPressed" KeyDown="SomeKeyPressed"></TextBox>
</DockPanel>
</StackPanel>
</Label>
<ListBox Grid.Row="1" Margin="3" Name="lstMessage">
</ListBox>
<CheckBox Margin="5" Grid.Row="2" Name="chkHandle">Handle first event</CheckBox>
<Button Click="cmdClear_Click" Grid.Row="3" HorizontalAlignment="Right" Margin="5" Padding="3">Clear List</Button>
</Grid>
</Window>
```
其对应的后台cs代码实现如下所示:
```
1 public partial class MainWindow : Window
2 {
3 public MainWindow()
4 {
5 InitializeComponent();
6 }
7
8 private int eventCounter = 0;
9
10 private void SomeKeyPressed(object sender, RoutedEventArgs e)
11 {
12 eventCounter++;
13 string message = "#" + eventCounter.ToString() + ":\r\n" +
14 " Sender: " + sender.ToString() + "\r\n" +
15 " Source: " + e.Source + "\r\n" +
16 " Original Source: " + e.OriginalSource + "\r\n" +
17 " Event: " + e.RoutedEvent;
18 lstMessage.Items.Add(message);
19 e.Handled = (bool)chkHandle.IsChecked;
20 }
21
22 private void cmdClear_Click(object sender, RoutedEventArgs e)
23 {
24 eventCounter = 0;
25 lstMessage.Items.Clear();
26 }
27 }
```
程序运行后的效果图如下所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb41a2d25.png)
在文本框中按下一个键时,事件首先在窗口触发,然后在整个层次结构中向下传递。具体的运行结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb41b2486.png)
**如果在任何位置将PreviewKeyDown事件标记为已处理,则冒泡的KeyDown事件也就不会触发。**当勾选了Handle first event 复选框时,当在输入框中按下一个键时,listbox中显示的记录只有1条记录,因为窗口触发的PrevieKeyDown事件处理已经把隧道路由事件标识为已处理,所以PreviewKeyDown事件将不会向下传递,所以此时只会显示一条MainWindow触发的记录。并且,此时,你可以注意到,**我们按下的键上对应的字符并没有在输入框中显示,因为此时并没有触发Textbox中的KeyDown事件,因为改变文本框内容的处理是在KeyDown事件中处理的。**具体的运行结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb421e95f.png)
## 3.3 附加事件
在上面例子中,因为所有元素都支持MouseUp和PreviewKeyDown事件。然而,许多控件都有它们自己特殊的事件。例如按钮的的Click事件,其他任何类都有定义该事件。假设有这样一个场景,StackPanel面板中包含了一堆按钮,并且希望在一个事件处理程序中处理所有这些按钮的单击事件。首先想到的办法就是将每个按钮的Click事件关联到同一个事件处理程序。但是Click事件支持事件冒泡,从而有一种更好的解决办法。可以在更高层次元素来关联Click事件来处理所有按钮的单击事件,具体的XAML代码实现如下所示:
```
<Window x:Class="AttachClickEvent.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel Margin="3" **Button.Click="DoSomething"**>
<Button Name="btn1">Button 1</Button>
<Button Name="btn2">Button 2</Button>
<Button Name="btn3">Button 3</Button>
</StackPanel>
</Window>
```
也可以在代码中关联附加事件,但是需要使用UIElement.AddHandle方法,而不能使用+=运算符的方式。具体实现代码如下所示:
## 四、WPF事件生命周期
WPF事件生命周期起始和WinForm中类似。下面详细解释下WPF中事件的生命周期。
FrameworkElement类实现了[ISupportInitialize](http://msdn.microsoft.com/zh-cn/library/system.componentmodel.isupportinitialize(v=vs.110).aspx)接口,该接口提供了两个用于控制初始化过程的方法。第一个是[BeginInit](http://msdn.microsoft.com/zh-cn/library/system.componentmodel.isupportinitialize.begininit(v=vs.110).aspx)方法,在实例化元素后立即调用该方法。BeginInit方法被调用之后,XAML解析器设置所有元素的属性并添加内容。第二个是EndInit方法,当初始化完成后,该方法被调用。此时引发[Initialized](http://msdn.microsoft.com/zh-cn/library/system.windows.frameworkelement.initialized(v=vs.110).aspx)事件。更准确地说,XAML解析器负责调用BeginInit方法和EndInit方法。
当创建窗口时,每个元素分支都以自下而上的方式被初始化。这意味着位于深层的嵌套元素在它们容器之前先被初始化。当引发初始化事件时,可以确保元素树中当前元素以下的元素已经全部完成了初始化。但是,包含当前元素的容器还没有初始化,而且也不能假设窗口的其他部分也已经完成初始化了。在每个元素都完成初始化之后,还需要在它们的容器中进行布局、应用样式,如果需要的话还会进行数据绑定。
一旦初始化过程完成后,就会引发Loaded事件。Loaded事件和Initialized事件的发生过程相反。意思就是说,包含所有元素的窗口首先引发Loaded事件,然后才是更深层次的嵌套元素。当所有元素都引发了Loaded事件之后,窗口就变得可见了,并且元素都已被呈现。下图列出了部分生命周期事件。
![](https://box.kancloud.cn/2016-01-23_56a2eb423025d.png)
## 五、小结
到这里,WPF路由事件的内容就介绍结束了,本文首先介绍了路由事件的定义,接着介绍了三种路由事件,WPF包括直接路由事件、冒泡路由事件和隧道路由事件,最后介绍了WPF事件的生命周期。在后面一篇文章将介绍WPF中的元素绑定。
本文所有源代码下载:[WPFRouteEventDemo.zip](http://files.cnblogs.com/zhili/WPFRouteEventDemo.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结