1.開篇前言
本篇文章取名為WPF千年輪回只因為兩個原因:
WPF和當年Win32、WinForm等的到來頗為相似,只是在功能和體驗上上進行了提高,所以這是微軟產品上的一個輪回;
WPF的學習過程和其他技術一樣,譬如ASP.NET,我們在學習的時候會先要了解Asp.Net構架(Http請求處理流程)、 Pipeline、HttpHandler 和 HttpModule 等內容,這和WPF的Application生命周期相對應,再如WPF的Window生命周期可以和ASP.NET的頁面生命周期相對應等。當然你也可以拿WinForm或者其他技術來舉例,這裡這是闡述觀點。
在前三篇文章中我們對WPF有了一個比較全面的認識,並且也通過一個基本的例子對比了WPF和之前的WinForm程序的區別和聯系。那麼在本篇文章當中,除了講一些理論知識外,更多的是用實際的代碼來驗證這些理論。
2.本文提綱
· 1.開篇前言
· 2.本文提綱
· 3.Application
· 4.Window
· 5.Dispatcher及多線程
· 6.類繼承結構
· 7.WPF的邏輯樹和視覺樹
· 8.本文總結
. 9.系列進度
3.Application 一.介紹WPF和 傳統的WinForm 類似, WPF 同樣需要一個 Application 來統領一些全局的行為和操作,並且每個 Domain (應用程序域)中只能有一個 Application 實例存在。和 WinForm 不同的是 WPF Application 默認由兩部分組成 : App.xaml 和 App.xaml.cs,這有點類似於 Delphi Form(我對此只是了解,並沒有接觸過Delphi ),將定義和行為代碼相分離。當然,這個和WebForm 也比較類似。XAML 從嚴格意義上說並不是一個純粹的 XML 格式文件,它更像是一種 DSL(Domain Specific Language,領域特定語言),它的所有定義都直接映射成某些代碼,只是具體的翻譯工作交給了編譯器完成而已。WPF應用程序由 System.Windows.Application類來進行管理。二.創建WPF應用程序
創建WPF應用程序有兩種方式:
1、Visual Studio和Expression Blend默認的方式,使用App.xaml文件定義啟動應用程序
App.xaml文件的內容大致如下所示:
2、可以自已定義類,定義Main方法實現對WPF應用程序的啟動
在項目中添加一個類,類的代碼如下,在項目選項中,設定此類為啟動項。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
namespace WPFApplications
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
[STAThread]
static void Main()
{
// 定義Application對象作為整個應用程序入口
Application app = new Application();
// 方法一:調用Run方法,參數為啟動的窗體對象,也是最常用的方法
Window2 win = new Window2();
app.Run(win);
// 方法二:指定Application對象的MainWindow屬性為啟動窗體,然後調用無參數的Run方法
//Window2 win = new Window2();
//app.MainWindow = win;
//win.Show();
// win.Show()是必須的,否則無法顯示窗體
//app.Run();
// 方法三:通過Url的方式啟動
//app.StartupUri = new Uri("Window2.xaml", UriKind.Relative);
//app.Run();
}
}
}
三、Application應用程序關閉
OnLastWindowClose(默認值): 最後一個窗體關閉或調用Application對象的Shutdown() 方法時,應用程序關閉。 OnMainWindowClose 啟動窗體關閉或調用Application對象的Shutdown()方法時,應用程序關閉。(和C#的Windows應用程序的關閉模式比較類似) OnExplicitShutdown 只有在調用Application對象的Shutdown()方法時,應用程序才會關閉。
對關閉選項更改的時候,可以直接在App.xaml中更改:
<Application x:Class="WPFApplications.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window2.xaml"
ShutdownMode="OnExplicitShutdown">
<Application.Resources>
</Application.Resources>
</Application>
同樣你也可以在代碼文件(App.xaml.cs)中進行更改,但必須注意這個設置寫在app.Run()方法之前,如下代碼:
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
app.Run(win);
四、Application對象的事件
名稱 描述 Activated 當應用程序成為前台應用程序時發生,即獲取焦點。 Deactivated 當應用程序停止作為前台應用程序時發生,即失去焦點。 DispatcherUnhandledException 在異常由應用程序引發但未進行處理時發生。 Exit 正好在應用程序關閉之前發生,且無法取消。 FragmentNavigation 當應用程序中的導航器開始導航至某個內容片斷時發生,如果所需片段位於當前內容中,則導航會立即發生;或者,如果所需片段位於不同 內容中,則導航會在加載了源 XAML 內容之後發生。 LoadCompleted 在已經加載、分析並開始呈現應用程序中的導航器導航到的內容時發生。 Navigated 在已經找到應用程序中的導航器要導航到的內容時發生,盡管此時該內容可能尚未完成加載。 Navigating 在應用程序中的導航器請求新導航時發生。 NavigationFailed 在應用程序中的導航器在導航到所請求內容時出現錯誤的情況下發生。 NavigationProgress 在由應用程序中的導航器管理的下載過程中定期發生,以提供導航進度信息。 NavigationStopped 在調用應用程序中的導航器的 StopLoading 方法時發生,或者當導航器在當前導航正在進行期間請求了一個新導航時發生(沒大用到)。 SessionEnding 在用戶通過注銷或關閉操作系統而結束 Windows 會話時發生。 Startup 在調用 Application 對象的 Run 方法時發生。
應用程序的事件處理可以:
1、在App.xaml中做事件的綁定,在App.xaml.cs文件中添加事件的處理方法
在App.xaml文件中:
<Application x:Class="WPFApplications.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml"
Startup="Application_Startup"
Exit="Application_Exit"
DispatcherUnhandledException="Application_DispatcherUnhandledException">
<Application.Resources>
</Application.Resources>
</Application>
在App.xaml.cs文件中:
public partial class App : Application
{
[STAThread]
static void Main()
{
Application app = new Application();
Window2 win = new Window2();
app.Run(win);
}
private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.
DispatcherUnhandledExceptionEventArgs e)
{ }
private void Application_Exit(object sender, ExitEventArgs e)
{ }
}
2、在自定義的類中可以做正常的C#的事件綁定:
public partial class App : Application
{
[STAThread]
static void Main()
{
// 定義Application對象作為整個應用程序入口
Application app = new Application();
// 調用Run方法,參數為啟動的窗體對象 ,也是最常用的方法
Window2 win = new Window2();
app.Startup += new StartupEventHandler(app_Startup);
app.DispatcherUnhandledException += new System.Windows.Threading.
DispatcherUnhandledExceptionEventHandler(app_DispatcherUnhandledException);
app.Run(win);
}
static void app_DispatcherUnhandledException(object sender, System.Windows.Threading.
DispatcherUnhandledExceptionEventArgs e)
{
throw new NotImplementedException();
}
static void app_Startup(object sender, StartupEventArgs e)
{
throw new NotImplementedException();
}
}
如果通過XAML啟動窗體的話,也會編譯成為為如下的程序,默認路徑為Debug文件夾得App.g.cs文件:
public partial class App : System.Windows.Application {
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
#line 4 "..\..\App.xaml"
this.StartupUri = new System.Uri("Window5.xaml", System.UriKind.Relative);
#line default
#line hidden
}
/// <summary>
/// Application Entry Point.
/// </summary>
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static void Main() {
WPFApplications.App app = new WPFApplications.App();
app.InitializeComponent();
app.Run();
}
}
五、WPF應用程序生存周期
當然這幅圖也只是簡單的概括了WPF的執行順序和生命周期,具體還要細致研究才是。
4.Window 一、窗體類基本概念
對於WPF應用程序,在Visual Studio和Expression Blend中,自定義的窗體均繼承System.Windows.Window類.大家都可能聽說過或者看過Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation這本書,它裡面就是用XAML和後台代碼兩種形式來實現同一個功能,那麼我們這裡定義的窗體也由兩部分組成:
1、 XAML文件,在這裡面通常全部寫UI的東西(希望大家還記得這兩幅圖)
2、後台代碼文件
namespace WPFApplications
{
/// <summary>
/// Interaction logic for Window5.xaml
/// </summary>
public partial class Window5 : Window
{
public Window5()
{
InitializeComponent();
}
private void btnOK_Click(object sender, RoutedEventArgs e)
{
lblHello.Content = "Hello World Changed";
}
}
}
也可以將後台代碼放在XAML文件中,上面的例子可以改寫為:
<Window x:Class="WPFApplications.Window5"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window5" Height="300" Width="300">
<StackPanel>
<Label x:Name="lblHello">Hello,World!</Label>
<Button x:Name="btnOK" Width="88" Height="22" Content="Click"
Click="btnOK_Click"/>
<x:Code>
<![CDATA[
void btnOK_Click(object sender, System.Windows.RoutedEventArgs e)
{
lblHello.Content = "Hello World Changed";
}
]]>
</x:Code>
</StackPanel>
</Window>
二、窗體的生命周期
1、顯示窗體
構造函數
Show()、ShowDialog()方法:Show()方法顯示非模態窗口,ShowDialog()方法顯示模態窗口,這個基本和WinForm類似
Loaded事件:窗體第一次Show()或ShowDialog()時引發的事件,通常在此事件中加載窗體的初始化數據,但如果用了MVVM模式,基本就不在這裡面寫。
2、關閉窗體
Close()方法:關閉窗體,並釋放窗體的資源
Closing事件、Closed事件:關閉時、關閉後引發的事件,通常在Closing事件中提示用戶是否退出等信息。
3、窗體的激活
Activate()方法:激活窗體
Activated、Deactivated事件:當窗體激動、失去焦點時引發的事件
4、窗體的生命周期
為了證實上面的結論,我們用下面的代碼進行測試:
public partial class Window3 : Window
{
public Window3()
{
this.Activated += new EventHandler(Window1_Activated);
this.Closing += new System.ComponentModel.CancelEventHandler(Window1_Closing);
this.ContentRendered += new EventHandler(Window1_ContentRendered);
this.Deactivated += new EventHandler(Window1_Deactivated);
this.Loaded += new RoutedEventHandler(Window1_Loaded);
this.Closed += new EventHandler(Window1_Closed);
this.Unloaded += new RoutedEventHandler(Window1_Unloaded);
this.SourceInitialized += new EventHandler(Window1_SourceInitialized);
InitializeComponent();
}
void Window1_Unloaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Unloaded");
}
void Window1_SourceInitialized(object sender, EventArgs e)
{
Debug.WriteLine("SourceInitialized");
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Loaded");
}
void Window1_Deactivated(object sender, EventArgs e)
{
Debug.WriteLine("Deactivated");
}
void Window1_ContentRendered(object sender, EventArgs e)
{
Debug.WriteLine("ContentRendered");
}
void Window1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Debug.WriteLine("Closing");
MessageBoxResult dr = MessageBox.Show("Cancel the window?",
"Answer", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (dr == MessageBoxResult.No)
{
e.Cancel = true;
}
}
void Window1_Closed(object sender, EventArgs e)
{
Debug.WriteLine("Closed");
}
void Window1_Activated(object sender, EventArgs e)
{
Debug.WriteLine("Activated");
}
}
執行結果為:
WPF窗體的詳細的屬性、方法、事件請參考MSDN,有很多的屬性、方法、事件與Windows應用程序中 System.Windows.Forms.Form類頗為相似,其中常用的一些屬性、方法、事件有:
窗體邊框模式(WindowStyle屬性)和是否允許更改窗體大小(ResizeMode屬性) 。
窗體啟動位置(WindowStartupLocation屬性)和啟動狀態(WindowState屬性) 等。
窗體標題(Title屬性)及圖標 。
是否顯示在任務欄(ShowInTaskbar)
始終在最前(TopMost屬性)
5.Dispatcher及多線程
提到這個UI和後台線程交互這個問題,大家都可能在WinForm中遇到過,記得幾年前我參加一個外資企業的面試,公司的其中一道題就是說在WinForm 中如何使用後台線程來操作UI,所以對這個問題比較記憶猶新。
WPF線程分配系統提供一個Dispatcher屬性、VerifyAccess 和 CheckAccess 方法來操作線程。線程分配系統位於所有 WPF 類中基類,大部分WPF 元素都派生於此類,如下圖的Dispatcher類:
WPF 應用程序啟動後,會有兩個線程:
一個是用來處理UI呈現(處理UI的請求,比如輸入和展現等操作)。
一個用來管理 UI的 (對UI元素及整個UI進行管理)。
與 Dispatcher 調度對象想對應的就是 DispatcherObject,在 WPF 中絕大部分控件都繼承自 DispatcherObject,甚至包括 Application。這些繼承自 DispatcherObject 的對象具有線程關聯特征,也就意味著只有創建這些對象實例,且包含了 Dispatcher 的線程(通常指默認 UI 線程)才能直接對其進行更新操作。
當我們嘗試從一個非 UI 線程更新一個UI元素,會看到如下的異常錯誤。
XAML代碼:
<Window x:Class="WPFApplications.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300">
<StackPanel>
<Label x:Name="lblHello">Hello,World!</Label>
</StackPanel>
</Window>
後台代碼:
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
Thread thread = new Thread(ModifyUI);
thread.Start();
}
private void ModifyUI()
{
// 模擬一些工作正在進行
Thread.Sleep(TimeSpan.FromSeconds(5));
lblHello.Content = "Hello,Dispatcher";
}
}
錯誤截圖:
按照 DispatcherObject 的限制原則,我們改用 Window.Dispatcher.Invoke() 即可順利完成這個更新操作。
private void ModifyUINew()
{
// 模擬一些工作正在進行
Thread.Sleep(TimeSpan.FromSeconds(5));
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,(ThreadStart)delegate()
{
lblHello.Content = "Hello,Dispatcher";
});
}
如果在其他工程或者類中,我們可以用 Application.Current.Dispatcher.Invoke方法來完成同樣的操作,它們都指向 UI Thread Dispatcher這個唯一的對象。
Dispatcher 同時還支持 BeginInvoke 異步調用,如下代碼:
private void btnHello_Click(object sender, RoutedEventArgs e)
{
new Thread(() =>
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(5));
this.lblHello.Content = DateTime.Now.ToString();
}));
}).Start();
}
關於Dispatcher和WPF多線程,還有很多要講,由於篇幅有限且精力有限,我這裡只講一些我們最常見的應用,同時包括Freezable 的處理等問題,大家可以查閱MSDN或者查閱國外相關的專題。6.類繼承結構
在WPF中常用的的控件類繼承結構如下圖所示(圖中圓圈的表示抽象類,方框的表示實體類):
System.Object 類:大家都知道在.Net中所有類型的根類型,在圖中沒有畫出來,DispatcherObject 就繼承於它,所以它是整個應用系統的基類。
System.Windows.Threading.DispatcherObject 類:WPF 中的絕大多數對象是從 DispatcherObject 派生的,它提供了用於處理並發和線程的基本構造。WPF 是基於調度程序實現的消息系統。
System.Windows.DependencyObject類:WPF基本所有的控件都實現了依賴屬性,它表示一個參與依賴項屬性系統的對象。
System.Windows.Media.Visual類:為 WPF 中的呈現提供支持,其中包括命中測試、坐標轉換和邊界框計算等。
System.Windows.UIElement 類:UIElement 是 WPF 核心級實現的基類,該類建立在 Windows Presentation Foundation (WPF) 元素和基本表示特征基礎上。
System.Windows.FrameworkElement類:為 Windows Presentation Foundation (WPF) 元素提供 WPF 框架級屬性集、事件集和方法集。此類表示附帶的 WPF 框架級實現,它是基於由UIElement定義的 WPF 核心級 API 構建的。
System.Windows.Controls.Control 類:表示 用戶界面 (UI) 元素的基類,這些元素使用 ControlTemplate 來定義其外觀。
System.Windows.Controls.ContentControl類:表示包含單項內容的控件。
System.Windows.Controls.ItemsControl 類:表示一個可用於呈現項的集合的控件。
System.Windows.Controls.Panel類:為所有 Panel 元素(布局)提供基類。使用 Panel 元素在 Windows Presentation Foundation (WPF) 應用程序中放置和排列子對象。
System.Windows.Sharps.Sharp類:為 Ellipse、Polygon 和 Rectangle 之類的形狀元素提供基類。
除了上面的圖以外,還有幾個命名空間也很重要,如下:
System.Windows.Controls.Decorator 類:提供在單個子元素(如 Border 或 Viewbox)上或周圍應用效果的元素的基類。
System.Windows.Controls.Image 類:表示顯示圖像的控件。
System.Windows.Controls.MediaElement類:表示包含音頻和 /或視頻的控件。
7.WPF的邏輯樹和視覺樹
關於這部分的內容講起來就比較多了,正如上次大家的留言裡說的一樣,這個內容如果拉開來講肯定就要開幾個篇幅,所以我們今天主要以講清楚概念為重點,先看下面的一個XAML代碼的例子:
<Window x:Class="WPFApplications.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<Label>Hello,World!</Label>
</StackPanel>
</Window>
上面這個UI非常的簡單,Window是一個根結點,它有一個子結點StackPanel,StackPanel有一個子結點Label。注意 Label下還有一個子結點string(LabelText),它同時也是一個葉子結點。這就構成了窗口的一個邏輯樹。邏輯樹始終存在於WPF的UI 中,不管UI是用XAML編寫還是用代碼編寫。WPF的每個方面(屬性、事件、資源等等)都是依賴於邏輯樹的。
視覺樹基本上是邏輯樹的一種擴展。邏輯樹的每個結點都被分解為它們的核心視覺組件。邏輯樹的結點對我們來說是不可見的。而視覺樹不同,它暴露了視覺的實現細節。下面是Visual Tree結構就表示了上面四行XAML代碼的視覺樹結構(下面這幅圖片來源於WPF揭秘):
當然並不是所有的邏輯樹結點都可以擴展為視覺樹結點。只有從 System.Windows.Media.Visual或者System.Windows.Media.Visual3D繼承的元素才能被視覺樹所包含。其他的元素不能包含是因為它們本身沒有自己的提交(Rendering)行為。在Windows Vista SDK Tools當中的XamlPad提供查看Visual Tree的功能。需要注意的是XamlPad目前只能查看以Page為根元素,並且去掉了SizeToContent屬性的XAML文檔。如下圖所示:
在visual studio的命令行中輸入xamlpad就可以進入如下的界面:
通過上圖我們可以看到Visual Tree確實比較復雜,其中還包含有很多的不可見元素,比如ContentPresenter等。Visual Tree雖然復雜,但是在一般情況下,我們不需要過多地關注它。我們在從根本上改變控件的風格、外觀時,需要注意Visual Tree的使用,因為在這種情況下我們通常會改變控件的視覺邏輯。比如我們在自己寫一些控件的時候,再比如我們對某些外觀進行特別訂制的時候。
WPF 中還提供了遍歷邏輯樹和視覺樹的輔助類:System.Windows.LogicalTreeHelper和 System.Windows.Media.VisualTreeHelper。注意遍歷的位置,邏輯樹可以在類的構造函數中遍歷。但是,視覺樹必須在經過至少一次的布局後才能形成。所以它不能在構造函數遍歷。通常是在OnContentRendered進行,這個函數為在布局發生後被調用。
其實每個Tree結點元素本身也包含了遍歷的方法。比如,Visual類包含了三個保護成員方法VisualParent、 VisualChildrenCount、GetVisualChild。通過它們可以訪問Visual的父元素和子元素。而對於 FrameworkElement,它通常定義了一個公共的Parent屬性表示其邏輯父元素。特定的FrameworkElement子類用不同的方式暴露了它的邏輯子元素。比如部分子元素是Children Collection,有是有時Content屬性,Content屬性強制元素只能有一個邏輯子元素。
為了弄清楚這些概念,我們就通過如下代碼作為演示:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
PrintLogicalTree(0, this);
}
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
PrintVisualTree(0, this);
}
void PrintLogicalTree(int depth, object obj)
{
// 打印空格,方便查看
Debug.WriteLine(new string(' ', depth) + obj);
// 如果不是DependencyObject,如string等類型
if (!(obj is DependencyObject)) return;
// 遞歸打印邏輯樹
foreach (object child in LogicalTreeHelper.GetChildren(
obj as DependencyObject))
{
PrintLogicalTree(depth + 1, child);
}
}
void PrintVisualTree(int depth, DependencyObject obj)
{
//打印空格,方便查看
Debug.WriteLine(new string(' ', depth) + obj);
// 遞歸打印視覺樹
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
}
}
}
結果為:
8.本文總結
本篇主要對Application、window、多線程、類繼承結構、邏輯樹與可視樹等的理論和實際Demo進行了探討,通過這一篇文章,我們可以大概了解WPF在這些元素上的處理,同時也給我後面的內容奠定了基礎,後面會逐漸牽涉到實際的一些案例和新的概念,所以如果有不熟悉且對這個專題感興趣的朋友可以仔細看一下這篇文章,在文章後面也會把本文用到的代碼附加上去,大家可以下載下來進行測試。
出處:http://www.cnblogs.com/KnightsWarrior/
本文配套源碼