原創(chuàng)|其它|編輯:郝浩|2010-07-16 13:57:23.000|閱讀 2618 次
概述:主要對Application、window、多線程、類繼承結(jié)構(gòu)、邏輯樹與可視樹等的理論和實際Demo進(jìn)行了探討,通過這一篇文章,我們可以大概了解WPF在這些元素上的處理,同時也給我后面的內(nèi)容奠定了基礎(chǔ),后面會逐漸牽涉到實際的一些案例和新的概念
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
本文系轉(zhuǎn)載 來自 博客園 本站作了適當(dāng)編輯和點評
WPF和 傳統(tǒng)的WinForm 類似, WPF 同樣需要一個 Application 來統(tǒng)領(lǐng)一些全局的行為和操作,并且每個 Domain (應(yīng)用程序域)中只能有一個 Application 實例存在。和 WinForm 不同的是 WPF Application 默認(rèn)由兩部分組成 : App.xaml 和 App.xaml.cs,這有點類似于 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)用程序有兩種方式:
1、Visual Studio和Expression Blend默認(rèn)的方式,使用App.xaml文件定義啟動應(yīng)用程序
App.xaml文件的內(nèi)容大致如下所示:
2、可以自已定義類,定義Main方法實現(xiàn)對WPF應(yīng)用程序的啟動
在項目中添加一個類,類的代碼如下,在項目選項中,設(shè)定此類為啟動項。
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對象作為整個應(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();
} } }
OnLastWindowClose(默認(rèn)值): | 最后一個窗體關(guān)閉或調(diào)用Application對象的Shutdown() 方法時,應(yīng)用程序關(guān)閉。 |
OnMainWindowClose | 啟動窗體關(guān)閉或調(diào)用Application對象的Shutdown()方法時,應(yīng)用程序關(guān) 閉。(和C#的Windows應(yīng)用程序的關(guān)閉模式比較類似) |
OnExplicitShutdown | 只有在調(diào)用Application對象的Shutdown()方法時,應(yīng)用程序才會關(guān)閉。 |
對關(guān)閉選項更改的時候,可以直接在App.xaml中更改:
<Application x:Class="WPFApplications.App"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//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);
名稱 |
描述 |
Activated |
當(dāng)應(yīng)用程序成為前臺應(yīng)用程序時發(fā)生,即獲取焦點。 |
Deactivated |
當(dāng)應(yīng)用程序停止作為前臺應(yīng)用程序時發(fā)生,即失去焦點。 |
DispatcherUnhandledException |
在異常由應(yīng)用程序引發(fā)但未進(jìn)行處理時發(fā)生。 |
Exit |
正好在應(yīng)用程序關(guān)閉之前發(fā)生,且無法取消。 |
FragmentNavigation |
當(dāng)應(yīng)用程序中的導(dǎo)航器開始導(dǎo)航至某個內(nèi)容片斷時發(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)容時發(fā)生。 |
Navigated |
在已經(jīng)找到應(yīng)用程序中的導(dǎo)航器要導(dǎo)航到的內(nèi)容時發(fā)生,盡管此時該內(nèi)容可能尚未完成加載。 |
Navigating |
在應(yīng)用程序中的導(dǎo)航器請求新導(dǎo)航時發(fā)生。 |
NavigationFailed |
在應(yīng)用程序中的導(dǎo)航器在導(dǎo)航到所請求內(nèi)容時出現(xiàn)錯誤的情況下發(fā)生。 |
NavigationProgress |
在由應(yīng)用程序中的導(dǎo)航器管理的下載過程中定期發(fā)生,以提供導(dǎo)航進(jìn)度信息。 |
NavigationStopped |
在調(diào)用應(yīng)用程序中的導(dǎo)航器的 StopLoading 方法時發(fā)生,或者當(dāng)導(dǎo)航器在當(dāng)前導(dǎo)航正在進(jìn)行期間請求了一個新導(dǎo)航時發(fā)生(沒大用到)。 |
SessionEnding |
在用戶通過注銷或關(guān)閉操作系統(tǒng)而結(jié)束 Windows 會話時發(fā)生。 |
Startup |
在調(diào)用 Application 對象的 Run 方法時發(fā)生。 |
應(yīng)用程序的事件處理可以:
1、在App.xaml中做事件的綁定,在App.xaml.cs文件中添加事件的處理方法
在App.xaml文件中:
<Application x:Class="WPFApplications.App"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//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 { /// <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(); } }
當(dāng)然這幅圖也只是簡單的概括了WPF的執(zhí)行順序和生命周期,具體還要細(xì)致研究才是。
對于WPF應(yīng)用程序,在Visual Studio和Expression Blend中,自定義的窗體均繼承System.Windows.Window類.大家都可能聽說過或者看過Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation這本書,它里面就是用XAML和后臺代碼兩種形式來實現(xiàn)同一個功能,那么我們這里定義的窗體也由兩部分組成:
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="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//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、顯示窗體
2、關(guān)閉窗體
3、窗體的激活
4、窗體的生命周期
為了證實上面的結(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類頗為相似,其中常用的一些屬性、方法、事件有:
到這個UI和后臺線程交互這個問題,大家都可能在WinForm中遇到過,記得幾年前我參加一個外資企業(yè)的面試,公司的其中一道題就是說在 WinForm 中如何使用后臺線程來操作UI,所以對這個問題比較記憶猶新。
WPF線程分配系統(tǒng)提供一個Dispatcher屬性、VerifyAccess 和 CheckAccess 方法來操作線程。線程分配系統(tǒng)位于所有 WPF 類中基類,大部分WPF 元素都派生于此類,如下圖的Dispatcher類:
WPF 應(yīng)用程序啟動后,會有兩個線程:
與 Dispatcher 調(diào)度對象想對應(yīng)的就是 DispatcherObject,在 WPF 中絕大部分控件都繼承自 DispatcherObject,甚至包括 Application。這些繼承自 DispatcherObject 的對象具有線程關(guān)聯(lián)特征,也就意味著只有創(chuàng)建這些對象實例,且包含了 Dispatcher 的線程(通常指默認(rèn) UI 線程)才能直接對其進(jìn)行更新操作。
當(dāng)我們嘗試從一個非 UI 線程更新一個UI元素,會看到如下的異常錯誤。
XAML代碼:
<Window x:Class="WPFApplications.Window2"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//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 同時還支持 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)用,同時包括Freezable 的處理等問題,大家可以查閱MSDN或者查閱國外相關(guān)的專題。
在WPF中常用的的控件類繼承結(jié)構(gòu)如下圖所示(圖中圓圈的表示抽象類,方框的表示實體類):
除了上面的圖以外,還有幾個命名空間也很重要,如下:
關(guān)于這部分的內(nèi)容講起來就比較多了,正如上次大家的留言里說的一樣,這個內(nèi)容如果拉開來講肯定就要開幾個篇幅,所以我們今天主要以講清楚概念為重 點,先看下面的一個XAML代碼的例子:
<Window x:Class="WPFApplications.Window1"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<Label>Hello,World!</Label>
</StackPanel>
</Window>上面這個UI非常的簡單,Window是一個根結(jié)點,它有一個子結(jié)點StackPanel,StackPanel有一個子結(jié)點Label。注意 Label下還有一個子結(jié)點string(LabelText),它同時也是一個葉子結(jié)點。這就構(gòu)成了窗口的一個邏輯樹。邏輯樹始終存在于WPF的UI 中,不管UI是用XAML編寫還是用代碼編寫。WPF的每個方面(屬性、事件、資源等等)都是依賴于邏輯樹的。
視覺樹基本上是邏輯樹的一種擴(kuò)展。邏輯樹的每個結(jié)點都被分解為它們的核心視覺組件。邏輯樹的結(jié)點對我們來說是不可見的。而視覺樹不同,它暴露了視覺 的實現(xiàn)細(xì)節(jié)。下面是Visual Tree結(jié)構(gòu)就表示了上面四行XAML代碼的視覺樹結(jié)構(gòu)(下面這幅圖片來源于WPF揭秘):
![]()
當(dāng)然并不是所有的邏輯樹結(jié)點都可以擴(kuò)展為視覺樹結(jié)點。只有從 System.Windows.Media.Visual或者System.Windows.Media.Visual3D繼承的元素才能被視覺樹所包 含。其他的元素不能包含是因為它們本身沒有自己的提交(Rendering)行為。在Windows Vista SDK Tools當(dāng)中的XamlPad提供查看Visual Tree的功能。需要注意的是XamlPad目前只能查看以Page為根元素,并且去掉了SizeToContent屬性的XAML文檔。如下圖所示:
在visual studio的命令行中輸入xamlpad就可以進(jìn)入如下的界面:
通過上圖我們可以看到Visual Tree確實比較復(fù)雜,其中還包含有很多的不可見元素,比如ContentPresenter等。Visual Tree雖然復(fù)雜,但是在一般情況下,我們不需要過多地關(guān)注它。我們在從根本上改變控件的風(fēng)格、外觀時,需要注意Visual Tree的使用,因為在這種情況下我們通常會改變控件的視覺邏輯。 比如我們在自己寫一些控件的時候,再比如我們對某些外觀進(jìn)行特別訂制的時候。
WPF 中還提供了遍歷邏輯樹和視覺樹的輔助類:System.Windows.LogicalTreeHelper和 System.Windows.Media.VisualTreeHelper。注意遍歷的位置,邏輯樹可以在類的構(gòu)造函數(shù)中遍歷。但是,視覺樹必須在經(jīng) 過至少一次的布局后才能形成。所以它不能在構(gòu)造函數(shù)遍歷。通常是在OnContentRendered進(jìn)行,這個函數(shù)為在布局發(fā)生后被調(diào)用。
其 實每個Tree結(jié)點元素本身也包含了遍歷的方法。比如,Visual類包含了三個保護(hù)成員方法VisualParent、 VisualChildrenCount、GetVisualChild。通過它們可以訪問Visual的父元素和子元素。而對于 FrameworkElement,它通常定義了一個公共的Parent屬性表示其邏輯父元素。特定的FrameworkElement子類用不同的方式 暴露了它的邏輯子元素。比如部分子元素是Children Collection,有是有時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é)果為:
篇主要對Application、window、多線程、類繼承結(jié)構(gòu)、邏輯樹與可視樹等的理論和實際Demo進(jìn)行了探討,通過這一篇文章,我們可以大概了 解WPF在這些元素上的處理,同時也給我后面的內(nèi)容奠定了基礎(chǔ),后面會逐漸牽涉到實際的一些案例和新的概念,所以如果有不熟悉且對這個專題感興趣的朋友可 以仔細(xì)看一下這篇文章,在文章后面也會把本文用到的代碼附加上去,大家可以下載下來進(jìn)行測試。
正如本文作者講述的一樣,隨著電腦硬件設(shè)備的高速更新,特別是圖形處理系統(tǒng)GPU的飛速發(fā)展,硬件系統(tǒng)已經(jīng)不再成為制約軟件性能的主要因素,越來越 多的軟件開發(fā)商開始選擇WPF,越來越多的用WPF開發(fā)的效果絢麗,超強(qiáng)的用戶體驗,簡單便捷部署方式的軟件應(yīng)用到生活和工作中。
與此同時各大控件開發(fā)商也在WPF開發(fā)方面推出自己的WPF控件,下面就為大家推薦幾款非常棒的WPF的控件。
1、最早,最有名氣的當(dāng)屬美國ActiproSoftware公司出品的:WPF Studio WPF studio 包含12個WPF子控件,囊括了,圖表,條碼,表格,編輯器,預(yù)覽,元素庫等等WPF開發(fā)中最有用的控件。根據(jù)慧都控件網(wǎng)測試和客戶反饋來看,WPF Studio是功能最強(qiáng)大,效果最好的WPF控件。
2、傳統(tǒng)WinForm老大,DevExpress 開始發(fā)力,在最新版的DEV2010中同步推出DXperience™ WPF Subscription 控件包,包括了表格、圖表、工具條、打印輸出、數(shù)據(jù)編輯、導(dǎo)航面板、頁面布局等10個子控件,以DevExpress的業(yè)界老大的實力,這款DXperience™ WPF Subscription 絕對是性價比和功能強(qiáng)大的象征。
3、來自加拿大的ComponentArt公司,同樣在2010推出控件套包 包含16個功能子控件,
功能十分強(qiáng)大,加上其比較便宜的價格,性價比尤其突出。
4、ComponentOne公司的 ComponentOne Studio for WPF 2010 v1 ,套包中包含21個功能子控件,除了包含常見的圖表,報表,編輯器,工具條等,甚至包含了媒體播放器,顏色編輯器等等特別功能。ComponentOne 更新比較快,從其Rodemap看,其2010 V2版本,將新增日歷,地圖,停靠面板等新功能。因此它適合功能需求全面的用戶,價格相對偏高,適合大中型軟件企業(yè)。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:博客園