# 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 }
```
運行之后的效果圖如下所示:

單擊窗口中的笑臉圖像之后,程序的運行結果如下圖所示。

從上圖結果可以發現,MouseUp事件由下向上傳遞了5級,直到窗口級別結束。另外,如果選擇了Handle first event復選框的話,SomethingClicked方法會將RoutedEventArgs.Handled屬性設置為true,表示事件已被處理,且該事件將終止向上冒泡。因此,此時列表中只能看到Image的事件,具體運行結果如下圖所示:

并且在列表框或窗口空白處進行單擊,此時也一樣只會出現一次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事件處理程序中設置一個斷點就可以發現其調用堆棧,具體的堆棧調用截圖如下所示:

從上圖可以知道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 }
```
程序運行后的效果圖如下所示:

在文本框中按下一個鍵時,事件首先在窗口觸發,然后在整個層次結構中向下傳遞。具體的運行結果如下圖所示:

**如果在任何位置將PreviewKeyDown事件標記為已處理,則冒泡的KeyDown事件也就不會觸發。**當勾選了Handle first event 復選框時,當在輸入框中按下一個鍵時,listbox中顯示的記錄只有1條記錄,因為窗口觸發的PrevieKeyDown事件處理已經把隧道路由事件標識為已處理,所以PreviewKeyDown事件將不會向下傳遞,所以此時只會顯示一條MainWindow觸發的記錄。并且,此時,你可以注意到,**我們按下的鍵上對應的字符并沒有在輸入框中顯示,因為此時并沒有觸發Textbox中的KeyDown事件,因為改變文本框內容的處理是在KeyDown事件中處理的。**具體的運行結果如下圖所示:

## 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事件之后,窗口就變得可見了,并且元素都已被呈現。下圖列出了部分生命周期事件。

## 五、小結
到這里,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 領域驅動設計實戰系列總結