|
1.開篇前言
首先很高興這個系列能得到大家的關(guān)注和支持,基于對大家負(fù)責(zé)和對自己負(fù)責(zé)的態(tài)度,我會不斷努力寫好這個系列,分享自己的微薄技術(shù)和經(jīng)驗(yàn),希望在幫助別人的同時(shí)也不斷提升自己。由于這篇文章很多(現(xiàn)已拆分成2篇,今天這篇只是其中之一),一共花了幾個個晚上的休息時(shí)間才完成,所以讀者花的時(shí)間長了一些,也希望大家能夠見諒,這個系列以后會每周發(fā)三到四篇左右(主要是寫一篇差不多要花幾晚上,感覺思維比較發(fā)散),除了講WPF技術(shù)本身之外,也會講一些項(xiàng)目具體開發(fā),所以敬請關(guān)注。
本篇文章取名為WPF千年輪回只因?yàn)閮蓚€原因:
- WPF和當(dāng)年Win32、WinForm等的到來頗為相似,只是在功能和體驗(yàn)上上進(jìn)行了提高,所以這是微軟產(chǎn)品上的一個輪回;
- WPF的學(xué)習(xí)過程和其他技術(shù)一樣,譬如ASP.NET,我們在學(xué)習(xí)的時(shí)候會先要了解ASP.NET構(gòu)架(Http請求處理流程)、Pipeline、HttpHandler 和 HttpModule 等內(nèi)容,這和WPF的Application生命周期相對應(yīng),再如WPF的Window生命周期可以和ASP.NET的頁面生命周期相對應(yīng)等。當(dāng)然你也可以拿WinForm或者其他技術(shù)來舉例,這里這是闡述觀點(diǎn)。
在前三篇文章中我們對WPF有了一個比較全面的認(rèn)識,并且也通過一個基本的例子對比了WPF和之前的WinForm程序的區(qū)別和聯(lián)系。那么在本篇文章當(dāng)中,除了講一些理論知識外,更多的是用實(shí)際的代碼來驗(yàn)證這些理論。
2.本文提綱
· 1.開篇前言
· 2.本文提綱
· 3.Application
· 4.Window
· 5.Dispatcher及多線程
· 6.類繼承結(jié)構(gòu)
· 7.WPF的邏輯樹和視覺樹
· 8.本文總結(jié)
. 9.系列進(jìn)度
3.Application
一.介紹
WPF和 傳統(tǒng)的WinForm 類似, WPF 同樣需要一個 Application 來統(tǒng)領(lǐng)一些全局的行為和操作,并且每個 Domain (應(yīng)用程序域)中只能有一個 Application 實(shí)例存在。和 WinForm 不同的是 WPF Application 默認(rèn)由兩部分組成 : App.xaml 和 App.xaml.cs,這有點(diǎn)類似于 Delphi Form(我對此只是了解,并沒有接觸過Delphi ),將定義和行為代碼相分離。當(dāng)然,這個和WebForm 也比較類似。XAML 從嚴(yán)格意義上說并不是一個純粹的 XML 格式文件,它更像是一種 DSL(Domain Specific Language,領(lǐng)域特定語言),它的所有定義都直接映射成某些代碼,只是具體的翻譯工作交給了編譯器完成而已。WPF應(yīng)用程序由System.Windows.Application類來進(jìn)行管理。
二.創(chuàng)建WPF應(yīng)用程序
創(chuàng)建WPF應(yīng)用程序有兩種方式:
1、Visual Studio和Expression Blend默認(rèn)的方式,使用App.xaml文件定義啟動應(yīng)用程序
App.xaml文件的內(nèi)容大致如下所示:
2、可以自已定義類,定義Main方法實(shí)現(xiàn)對WPF應(yīng)用程序的啟動
在項(xiàng)目中添加一個類,類的代碼如下,在項(xiàng)目選項(xiàng)中,設(shè)定此類為啟動項(xiàng)。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
namespace WPFApplications
{
///
/// Interaction logic for App.xaml
///
public partial class App : Application
{
[STAThread]
static void Main()
{
// 定義Application對象作為整個應(yīng)用程序入口
Application app = new Application();
// 方法一:調(diào)用Run方法,參數(shù)為啟動的窗體對象 ,也是最常用的方法
Window2 win = new Window2();
app.Run(win);
// 方法二:指定Application對象的MainWindow屬性為啟動窗體,然后調(diào)用無參數(shù)的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應(yīng)用程序關(guān)閉
OnLastWindowClose(默認(rèn)值): 最后一個窗體關(guān)閉或調(diào)用Application對象的Shutdown() 方法時(shí),應(yīng)用程序關(guān)閉。 OnMainWindowClose 啟動窗體關(guān)閉或調(diào)用Application對象的Shutdown()方法時(shí),應(yīng)用程序關(guān)閉。(和C#的Windows應(yīng)用程序的關(guān)閉模式比較類似) OnExplicitShutdown 只有在調(diào)用Application對象的Shutdown()方法時(shí),應(yīng)用程序才會關(guān)閉。 對關(guān)閉選項(xiàng)更改的時(shí)候,可以直接在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)中進(jìn)行更改,但必須注意這個設(shè)置寫在app.Run()方法之前 ,如下代碼:
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
app.Run(win);四、Application對象的事件
名稱
描述
Activated
Deactivated
DispatcherUnhandledException
在異常由應(yīng)用程序引發(fā)但未進(jìn)行處理時(shí)發(fā)生。
Exit
正好在應(yīng)用程序關(guān)閉之前發(fā)生,且無法取消。
FragmentNavigation
當(dāng)應(yīng)用程序中的導(dǎo)航器開始導(dǎo)航至某個內(nèi)容片斷時(shí)發(fā)生,如果所需片段位于當(dāng)前內(nèi)容中,則導(dǎo)航會立即發(fā)生;或者,如果所需片段位于不同 內(nèi)容中,則導(dǎo)航會在加載了源 XAML 內(nèi)容之后發(fā)生。
LoadCompleted
在已經(jīng)加載、分析并開始呈現(xiàn)應(yīng)用程序中的導(dǎo)航器導(dǎo)航到的內(nèi)容時(shí)發(fā)生。
Navigated
在已經(jīng)找到應(yīng)用程序中的導(dǎo)航器要導(dǎo)航到的內(nèi)容時(shí)發(fā)生,盡管此時(shí)該內(nèi)容可能尚未完成加載。
Navigating
在應(yīng)用程序中的導(dǎo)航器請求新導(dǎo)航時(shí)發(fā)生。
NavigationFailed
在應(yīng)用程序中的導(dǎo)航器在導(dǎo)航到所請求內(nèi)容時(shí)出現(xiàn)錯誤的情況下發(fā)生。
NavigationProgress
在由應(yīng)用程序中的導(dǎo)航器管理的下載過程中定期發(fā)生,以提供導(dǎo)航進(jìn)度信息。
NavigationStopped
在調(diào)用應(yīng)用程序中的導(dǎo)航器的 StopLoading 方法時(shí)發(fā)生,或者當(dāng)導(dǎo)航器在當(dāng)前導(dǎo)航正在進(jìn)行期間請求了一個新導(dǎo)航時(shí)發(fā)生(沒大用到)。
SessionEnding
在用戶通過注銷或關(guān)閉操作系統(tǒng)而結(jié)束 Windows 會話時(shí)發(fā)生。
Startup
在調(diào)用 Application 對象的 Run 方法時(shí)發(fā)生。
應(yīng)用程序的事件處理可以:
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對象作為整個應(yīng)用程序入口
Application app = new Application();
// 方法一:調(diào)用Run方法,參數(shù)為啟動的窗體對象 ,也是最常用的方法
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對象作為整個應(yīng)用程序入口
Application app = new Application();
// 調(diào)用Run方法,參數(shù)為啟動的窗體對象 ,也是最常用的方法
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啟動窗體的話,也會編譯成為為如下的程序,默認(rèn)路徑為Debug文件夾得App.g.cs文件:
public partial class App : System.Windows.Application {
///
/// InitializeComponent
///
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
#line 4 "../../App.xaml"
this.StartupUri = new System.Uri("Window5.xaml", System.UriKind.Relative);
#line default
#line hidden
}
///
/// Application Entry Point.
///
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static void Main() {
WPFApplications.App app = new WPFApplications.App();
app.InitializeComponent();
app.Run();
}
}五、WPF應(yīng)用程序生存周期
當(dāng)然這幅圖也只是簡單的概括了WPF的執(zhí)行順序和生命周期,具體還要細(xì)致研究才是。
4.Window
一、窗體類基本概念
對于WPF應(yīng)用程序,在Visual Studio和Expression Blend中,自定義的窗體均繼承System.Windows.Window類.大家都可能聽說過或者看過Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation這本書,它里面就是用XAML和后臺代碼兩種形式來實(shí)現(xiàn)同一個功能,那么我們這里定義的窗體也由兩部分組成:
1、 XAML文件,在這里面通常全部寫UI的東西(希望大家還記得這兩幅圖)
2、后臺代碼文件
namespace WPFApplications
{
///
/// Interaction logic for Window5.xaml
///
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>
void btnOK_Click(object sender, System.Windows.RoutedEventArgs e)
{
lblHello.Content = "Hello World Changed";
}
]]>
x:Code>StackPanel>
Window>二、窗體的生命周期
1、顯示窗體
- 構(gòu)造函數(shù)
- Show()、ShowDialog()方法:Show()方法顯示非模態(tài)窗口,ShowDialog()方法顯示模態(tài)窗口,這個基本和WinForm類似
- Loaded事件:窗體第一次Show()或ShowDialog()時(shí)引發(fā)的事件,通常在此事件中加載窗體的初始化數(shù)據(jù),但如果用了MVVM模式,基本就不在這里面寫。
2、關(guān)閉窗體
- Close()方法:關(guān)閉窗體,并釋放窗體的資源
- Closing事件、Closed事件:關(guān)閉時(shí)、關(guān)閉后引發(fā)的事件,通常在Closing事件中提示用戶是否退出等信息。
3、窗體的激活
- Activate()方法:激活窗體
- Activated、Deactivated事件:當(dāng)窗體激動、失去焦點(diǎn)時(shí)引發(fā)的事件
4、窗體的生命周期
為了證實(shí)上面的結(jié)論,我們用下面的代碼進(jìn)行測試:
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");
}
}執(zhí)行結(jié)果為:
WPF窗體的詳細(xì)的屬性、方法、事件請參考MSDN,有很多的屬性、方法、事件與Windows應(yīng)用程序中 System.Windows.Forms.Form類頗為相似,其中常用的一些屬性、方法、事件有:
- 窗體邊框模式(WindowStyle屬性)和是否允許更改窗體大小(ResizeMode屬性) 。
- 窗體啟動位置(WindowStartupLocation屬性)和啟動狀態(tài)(WindowState屬性) 等。
- 窗體標(biāo)題(Title屬性)及圖標(biāo) 。
- 是否顯示在任務(wù)欄(ShowInTaskbar)
- 始終在最前(TopMost屬性)
5.Dispatcher及多線程
提到這個UI和后臺線程交互這個問題,大家都可能在WinForm中遇到過,記得幾年前我參加一個外資企業(yè)的面試,公司的其中一道題就是說在WinForm 中如何使用后臺線程來操作UI,所以對這個問題比較記憶猶新。
WPF線程分配系統(tǒng)提供一個Dispatcher屬性、VerifyAccess 和 CheckAccess 方法來操作線程。線程分配系統(tǒng)位于所有 WPF 類中基類,大部分WPF 元素都派生于此類,如下圖的Dispatcher類:
WPF 應(yīng)用程序啟動后,會有兩個線程:
- 一個是用來處理UI呈現(xiàn)(處理UI的請求,比如輸入和展現(xiàn)等操作)。
- 一個用來管理 UI的 (對UI元素及整個UI進(jìn)行管理)。
與 Dispatcher 調(diào)度對象想對應(yīng)的就是 DispatcherObject,在 WPF 中絕大部分控件都繼承自 DispatcherObject,甚至包括 Application。這些繼承自 DispatcherObject 的對象具有線程關(guān)聯(lián)特征,也就意味著只有創(chuàng)建這些對象實(shí)例,且包含了 Dispatcher 的線程(通常指默認(rèn) UI 線程)才能直接對其進(jìn)行更新操作。
當(dāng)我們嘗試從一個非 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()
{
// 模擬一些工作正在進(jìn)行
Thread.Sleep(TimeSpan.FromSeconds(5));
lblHello.Content = "Hello,Dispatcher";
}
}錯誤截圖:
按照 DispatcherObject 的限制原則,我們改用 Window.Dispatcher.Invoke() 即可順利完成這個更新操作。
private void ModifyUINew()
{
// 模擬一些工作正在進(jìn)行
Thread.Sleep(TimeSpan.FromSeconds(5));
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,(ThreadStart)delegate()
{
lblHello.Content = "Hello,Dispatcher";
});
}如果在其他工程或者類中,我們可以用 Application.Current.Dispatcher.Invoke方法來完成同樣的操作,它們都指向 UI Thread Dispatcher這個唯一的對象。
Dispatcher 同時(shí)還支持 BeginInvoke 異步調(diào)用,如下代碼:
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();
}
關(guān)于Dispatcher和WPF多線程,還有很多要講,由于篇幅有限且精力有限,我這里只講一些我們最常見的應(yīng)用,同時(shí)包括Freezable 的處理等問題,大家可以查閱MSDN或者查閱國外相關(guān)的專題。
6.類繼承結(jié)構(gòu)
在WPF中常用的的控件類繼承結(jié)構(gòu)如下圖所示(圖中圓圈的表示抽象類,方框的表示實(shí)體類):
- System.Object 類:大家都知道在.NET中所有類型的根類型,在圖中沒有畫出來,DispatcherObject 就繼承于它,所以它是整個應(yīng)用系統(tǒng)的基類。
- System.Windows.Threading.DispatcherObject 類:WPF 中的絕大多數(shù)對象是從 DispatcherObject 派生的,它提供了用于處理并發(fā)和線程的基本構(gòu)造。WPF 是基于調(diào)度程序?qū)崿F(xiàn)的消息系統(tǒng)。
- System.Windows.DependencyObject類:WPF基本所有的控件都實(shí)現(xiàn)了依賴屬性,它表示一個參與依賴項(xiàng)屬性系統(tǒng)的對象。
- System.Windows.Media.Visual類:為 WPF 中的呈現(xiàn)提供支持,其中包括命中測試、坐標(biāo)轉(zhuǎn)換和邊界框計(jì)算等。
- System.Windows.UIElement 類:UIElement 是 WPF 核心級實(shí)現(xiàn)的基類,該類建立在 Windows Presentation Foundation (WPF) 元素和基本表示特征基礎(chǔ)上。
- System.Windows.FrameworkElement類:為 Windows Presentation Foundation (WPF) 元素提供 WPF 框架級屬性集、事件集和方法集。此類表示附帶的 WPF 框架級實(shí)現(xiàn),它是基于由UIElement定義的 WPF 核心級 API 構(gòu)建的。
- System.Windows.Controls.Control 類:表示 用戶界面 (UI) 元素的基類,這些元素使用 ControlTemplate 來定義其外觀。
- System.Windows.Controls.ContentControl類:表示包含單項(xiàng)內(nèi)容的控件。
- System.Windows.Controls.ItemsControl 類:表示一個可用于呈現(xiàn)項(xiàng)的集合的控件。
- System.Windows.Controls.Panel類:為所有 Panel 元素(布局)提供基類。使用 Panel 元素在 Windows Presentation Foundation (WPF) 應(yīng)用程序中放置和排列子對象。
- System.Windows.Sharps.Sharp類:為 Ellipse、Polygon 和 Rectangle 之類的形狀元素提供基類。
除了上面的圖以外,還有幾個命名空間也很重要,如下:
- System.Windows.Controls.Decorator 類:提供在單個子元素(如 Border 或 Viewbox)上或周圍應(yīng)用效果的元素的基類。
- System.Windows.Controls.Image 類:表示顯示圖像的控件。
- System.Windows.Controls.MediaElement類:表示包含音頻和 /或視頻的控件。
7.WPF的邏輯樹和視覺樹
關(guān)于這部分的內(nèi)容講起來就比較多了,正如上次大家的留言里說的一樣,這個內(nèi)容如果拉開來講肯定就要開幾個篇幅,所以我們今天主要以講清楚概念為重點(diǎn),先看下面的一個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是一個根結(jié)點(diǎn),它有一個子結(jié)點(diǎn)StackPanel,StackPanel有一個子結(jié)點(diǎn)Label。注意Label下還有一個子結(jié)點(diǎn)string(LabelText),它同時(shí)也是一個葉子結(jié)點(diǎn)。這就構(gòu)成了窗口的一個邏輯樹。邏輯樹始終存在于WPF的UI中,不管UI是用XAML編寫還是用代碼編寫。WPF的每個方面(屬性、事件、資源等等)都是依賴于邏輯樹的。
視覺樹基本上是邏輯樹的一種擴(kuò)展。邏輯樹的每個結(jié)點(diǎn)都被分解為它們的核心視覺組件。邏輯樹的結(jié)點(diǎn)對我們來說是不可見的。而視覺樹不同,它暴露了視覺的實(shí)現(xiàn)細(xì)節(jié)。下面是Visual Tree結(jié)構(gòu)就表示了上面四行XAML代碼的視覺樹結(jié)構(gòu)(下面這幅圖片來源于WPF揭秘):
當(dāng)然并不是所有的邏輯樹結(jié)點(diǎn)都可以擴(kuò)展為視覺樹結(jié)點(diǎn)。只有從 System.Windows.Media.Visual或者System.Windows.Media.Visual3D繼承的元素才能被視覺樹所包含。其他的元素不能包含是因?yàn)樗鼈儽旧頉]有自己的提交(Rendering)行為。在Windows Vista SDK Tools當(dāng)中的XamlPad提供查看Visual Tree的功能。需要注意的是XamlPad目前只能查看以Page為根元素,并且去掉了SizeToContent屬性的XAML文檔。如下圖所示:
在visual studio的命令行中輸入xamlpad就可以進(jìn)入如下的界面:
![]()
通過上圖我們可以看到Visual Tree確實(shí)比較復(fù)雜,其中還包含有很多的不可見元素,比如ContentPresenter等。Visual Tree雖然復(fù)雜,但是在一般情況下,我們不需要過多地關(guān)注它。我們在從根本上改變控件的風(fēng)格、外觀時(shí),需要注意Visual Tree的使用,因?yàn)樵谶@種情況下我們通常會改變控件的視覺邏輯。 比如我們在自己寫一些控件的時(shí)候,再比如我們對某些外觀進(jìn)行特別訂制的時(shí)候。
WPF 中還提供了遍歷邏輯樹和視覺樹的輔助類:System.Windows.LogicalTreeHelper和 System.Windows.Media.VisualTreeHelper。注意遍歷的位置,邏輯樹可以在類的構(gòu)造函數(shù)中遍歷。但是,視覺樹必須在經(jīng)過至少一次的布局后才能形成。所以它不能在構(gòu)造函數(shù)遍歷。通常是在OnContentRendered進(jìn)行,這個函數(shù)為在布局發(fā)生后被調(diào)用。
其實(shí)每個Tree結(jié)點(diǎn)元素本身也包含了遍歷的方法。比如,Visual類包含了三個保護(hù)成員方法VisualParent、 VisualChildrenCount、GetVisualChild。通過它們可以訪問Visual的父元素和子元素。而對于 FrameworkElement,它通常定義了一個公共的Parent屬性表示其邏輯父元素。特定的FrameworkElement子類用不同的方式暴露了它的邏輯子元素。比如部分子元素是Children Collection,有是有時(shí)Content屬性,Content屬性強(qiáng)制元素只能有一個邏輯子元素。為了弄清楚這些概念,我們就通過如下代碼作為演示:
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));
}
}
}結(jié)果為:
8.本文總結(jié)
本篇主要對Application、window、多線程、類繼承結(jié)構(gòu)、邏輯樹與可視樹等的理論和實(shí)際Demo進(jìn)行了探討,通過這一篇文章,我們可以大概了解WPF在這些元素上的處理,同時(shí)也給我后面的內(nèi)容奠定了基礎(chǔ),后面會逐漸牽涉到實(shí)際的一些案例和新的概念,所以如果有不熟悉且對這個專題感興趣的朋友可以仔細(xì)看一下這篇文章,在文章后面也會把本文用到的代碼附加上去,大家可以下載下來進(jìn)行測試。
最后圣殿騎士 會盡心盡力寫好這個系列,同時(shí)由于是自己對這些技術(shù)的使用總結(jié)和心得體會,錯誤之處在所難免,懷著技術(shù)交流的心態(tài),在博客園和51CTO發(fā)表出來,所以希望大家能夠多多指點(diǎn),這樣在使一部分人受益的同時(shí)也能糾正我的錯誤觀點(diǎn),以便和各位共同提高,后續(xù)文章敬請關(guān)注!
9.系列進(jìn)度(紅色標(biāo)示已發(fā)布)
· 1. WPF 基礎(chǔ)到企業(yè)應(yīng)用系列1——開篇有益
· 2. WPF 基礎(chǔ)到企業(yè)應(yīng)用系列2——WPF前世今生
· 3. WPF 基礎(chǔ)到企業(yè)應(yīng)用系列3——WPF開發(fā)漫談
· 4. WPF 基礎(chǔ)到企業(yè)應(yīng)用系列4——WPF千年輪回
· 5. 使用面板做布局(幾種布局控件的XAML及CS代碼,綜合布局等)
· 6. 依賴屬性、附加屬性(基本、繼承、元數(shù)據(jù))
· 7. 路由事件、附加事件
· 8. 命令
· 9. WPF控件分類介紹與使用技巧(ContentControl、HeaderedContentControl…… Decorator)
· 10. 尺寸縮放、定位與變換元素
· 11. 資源
· 12. 數(shù)據(jù)綁定(基本、值轉(zhuǎn)換、驗(yàn)證、集合的篩選、排序、分組、主從、數(shù)據(jù)提供者)
· 13. 樣式
· 14. 模板
· 15. 多語言、皮膚和主題
· 16. 2D圖形
· 17. 3D圖形
· 18. 動畫(幾種動畫的應(yīng)用)
· 19. 音頻、視頻、語音
· 20. 文檔、打印、報(bào)表
· 21. 用戶控件和自定義控件
· 22. Win32、Windows Form以及ActiveX之間的互用性
· 23. 構(gòu)建并部署應(yīng)用程序(ClickOnce部署、微軟setup /InstallShield+自動更新組件)
· 24. WPF的模式講解及實(shí)例(MVC Demo)
· 25. WPF的模式講解及實(shí)例(MVP Demo)
· 26. WPF的模式講解及實(shí)例(MVVM Demo)
· 27. 性能優(yōu)化(WPF項(xiàng)目的瓶頸)
· 28.一個完整WPF項(xiàng)目(普通架構(gòu)版)
· 39. 一個完整WPF項(xiàng)目(MVVM架構(gòu)版)
· 30. WPF 4.0新功能
NET技術(shù):WPF 基礎(chǔ)到企業(yè)應(yīng)用系列4——WPF千年輪回,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時(shí)間聯(lián)系我們修改或刪除,多謝。