一,摘要
這篇課程主要是對上幾次課程的回顧和簡單深化,所以沒有講什麼比較新的概念,不過掌握好了這篇,對後面的很多文章都有幫助,同時這一篇文章做Demo、構思、研究等也花費了不少時間,所以希望對大家有所幫助。
二,本文提綱
· 1.摘要
· 2.本文提綱
· 3.前篇回顧
· 4.Xaml基礎
· 5.脫離VS工具CSC編譯WPF
· 6.XamlReader與XamlWriter
· 7.本文總結
. 8.系列進度
三,前篇回顧
在我們日常的開發中,軟件企業的開發人員一般會有兩種類型的工作:
1,一類是用戶界面設計人員,他們關心的是軟件和用戶之間的交互,就是如何讓用戶體驗更好;
2,另一類是軟件開發人員,他們關心的是軟件的架構設計、業務邏輯的處理和軟件功能的實現;
在BS中,用戶界面設計人員使用HTML及其工具來設計界面,開發人員使用Java,C#,VB或其他語言來實現其中的邏輯,HTML網頁可以用到最終的產品中。
在CS中,過去我們一直沒有分開這兩種不同性質的工作。用戶界面設計人員通常和開發人員使用不同的工具,當界面設計人員設計好用戶界面時,他們的工作並沒有用到最終的產品中,而只是用來展現某種概念或工作流程。
XAML實現了互聯網應用程序和桌面應用程序的統一,界面設計人員可以使用XAML或基於XAML的工具(如微軟的Design和 Blend) 來設計CS或BS應用程序的界面。程序開發人員則可以在此基礎上使用C#或VB.NET等來開發相應的功能,這樣,界面設計人員的工作便自然過渡到最終產品中。
在XAML中,用戶界面用XML的元素或屬性來表示。WPF引擎把XAML描述的UI元素解釋為相應的.NET對象,從而在桌面程序或Silverlight網頁上創建相應的控件。如下圖所示:
上面這副就是傳統的WinForm開發模式,這兩種人沒有分離開來,所以在很多企業裡就形成了開發人員既要做UI也要做程序的境地。
上圖就是現在的WPF和Silverlight程序的開發模式,這兩類人可以分開來工作,他們都可以對Window1.xaml進行修改和加載,所以這樣就使分工更專業了,由於大家專注於某一個方面,分工協作的同時,質量和效率也逐漸提高了。
前幾篇介紹了一些基礎知識,那麼這篇也簡單的回顧一下,下面第一幅圖是WPF的執行順序,第二副圖是WPF的一個項目的構成,第三幅圖是WPF所對應的IL代碼(這些圖處理得不好,還望各位見諒)。
WPF的執行順序
WPF的一個項目的構成
WPF所對應的IL代碼,通過Reflector查看
四,Xaml基礎
這個部分要講的東西就太多了,由於這篇文章篇幅有限,同時我覺得用代碼诠釋能讓大家可以更清晰地理解,所以就講得隨意一些,通過一個Demo介紹 WPF對資源、類、控件的調用和處理,對Dictionary資源、Application資源、window資源以及控件資源的應用等,如下圖所示(本篇所有代碼在評論的第一條):
由於這些概念比較簡單並且較多,如果全部寫完,也得專門寫一長篇,還好大家都喜歡看代碼,所以我就不花費大的篇幅來講它們,感興趣或者對這些知識還有不清楚的朋友可以下載這個Demo進行查看或調試,我覺得對初學者很有幫助。
五,脫離VS工具CSC編譯WPF
為了更好的認識WPF的編譯和執行過程,我們可以暫時棄用我們熟悉的VS工具,選用記事本寫如下的代碼:
using System;
using System.Windows;
namespace KnightsWarrior.HelloWorld
{
class HelloWorld
{
[STAThread]
public static void Main()
{
Window win = new Window();
win.Height = 300;
win.Width = 400;
win.Title = "Hello,KnightsWarrior!";
win.Show();
Application app = new Application();
app.Run();
}
}
}
然後保存到D:\HelloWorld.cs 這個位置,通過CMD或者VS cmomand Line中輸入以下編譯命令:
csc.exe /out:D:\HelloWorld.exe D:\HelloWorld.cs /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\presentationframework.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\windowsbase.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\presentationcore.dll"
然後就可以手動編譯成功了。
那麼通過Reflector可以查看到它的IL代碼,如果感興趣的朋友也可以進行詳細的分析。
如果對MSIL比較熟悉的朋友,也可以用記事本寫同樣功能的IL代碼,由於沒有對WPF窗體的IL做具體研究,所以用Console程序代替,等過一段時間再研究WPF控件的IL代碼.
.assembly extern mscorlib { auto }
.assembly HelloApp {}
.module HelloApp.exe
.namespace HelloApp
{
.class public Program extends [mscorlib]System.Object
{
.method static private void Main(string[] args)
{
.entrypoint
ldstr "Hello, KnightsWarrior!"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}
}
然後打開 Visual Studio Command Prompt,使用 ILASM 開始編譯,
這樣你就更能看清楚編譯器背後的秘密,同時也能跟蹤每一步執行的操作,同時對一些簡單的內存洩露問題也比較容易察覺到。當然現在也有很多工具可以跟蹤這些問題,我這裡只是寫一種思路,大家可以根據自己的愛好取捨。
六,XamlReader與XamlWriter
System.Windows.Markup 命名空間中提供了 XamlReader、XamlWriter 兩個類型,允許我們手工操控 XAML和BAML 文件。
XamlReader類除了定義Load的實時加載之外,也定義了異步方法,可以異步解析XAML中的內容。我們可以在XamlReader對象的實例裡調用它們。如果在讀取一個大文件時要保持用戶UI的響應性,就可以使用異步讀取的方法。和異步讀取方法匹配的還有一個CancelAsync方法,用於停止讀取操作。XamlReader 還定義了LoadCompleted事件,在讀取完成後會觸發該事件,那麼我們就可以把讀完後要做的事情都在這裡進行處理。
XamlWriter 供一個靜態 Save 方法(多次重載),該方法可用於以受限的 XAML 序列化方式,將所提供的運行時對象序列化為 XAML 標記。這句話似乎有點難懂,其實簡單的說就是把它序列化為我們需要的類型。
具體功能代碼如下:
通過XamlReader 動態構建並實例化一個Window
//XamlReader
StringBuilder strXMAL = new StringBuilder("<Window ");
strXMAL.Append("xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" ");
strXMAL.Append("xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" ");
strXMAL.Append("Title=\"Window2\" Height=\"600\" Width=\"600\">");
strXMAL.Append("</Window>");
var window = (Window)XamlReader.Parse(strXMAL.ToString());
window.ShowDialog();
同時我們還可以從文件流中讀取並操作。
//XamlReader
using (var stream = new FileStream(@"Window2.xaml", FileMode.Open))
{
var window2 = (Window)XamlReader.Load(stream);
var button = (Button)window2.FindName("btnTest");
button.Click += (x, y) => MessageBox.Show("Knights Warrior");
window2.ShowDialog();
}
Window2.xaml 的代碼:
<Window x:Class="XamlReaderWriterDemo.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">
<Grid>
<Button Height="23" Name="btnTest" Margin="98,72,105,0" VerticalAlignment="Top">Button</Button>
</Grid>
</Window>
這裡我們需要特別注意的是 XamlReader 載入的 XAML 代碼不能包含任何類型(x:Class)以及事件代碼(x:Code),也就是說要XAML自身的代碼才受支持(這個也在WPF揭秘這本書講到過)。那麼我們可以用 XamlWriter 將一個編譯的 BAML 還原成 XAML了,具體代碼如下:
//XamlWriter
var xaml = XamlWriter.Save(new Window2());
MessageBox.Show(xaml);
輸出的Message如下(為了效果更好看一些,我粘貼到了VS):
<Window2 Title="Window2" Width="300" Height="300" xmlns="clr-namespace:XamlReaderWriterDemo;assembly=XamlReaderWriterDemo" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<av:Grid>
<av:Button Name="btnTest" Height="23" Margin="98,72,105,0" VerticalAlignment="Top">Button</av:Button>
</av:Grid>
</Window2>
XAML 的動態載入在使用動態換膚以及運行時加載等場景頗為有用,以後也會慢慢接觸。
由於使用XamlReader和XamlWriter有很多限制,比如我想把一批Baml轉化為Xaml,再比如我想指定Baml的路徑,然後通過 Load的方式載入,那麼這些場景就無法通過XamlReader和XamlWriter完成了,這個讓我也做過不少的Demo,也跟蹤了很長時間的IL 代碼,在百思不得其解之後和周永恆、Virus等討論了一下,最後終於找到了一個方案,如下代碼所示:
public static class BamlWriter
{
public static void Save(object obj, Stream stream)
{
string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(path);
try
{
string xamlFile = Path.Combine(path, "input.xaml");
string projFile = Path.Combine(path, "project.proj");
using (FileStream fs = File.Create(xamlFile))
{
XamlWriter.Save(obj, fs);
}
Engine engine = new Engine();
engine.BinPath = RuntimeEnvironment.GetRuntimeDirectory();
Project project = engine.CreateNewProject();
BuildPropertyGroup pgroup = project.AddNewPropertyGroup(false);
pgroup.AddNewProperty("AssemblyName", "temp");
pgroup.AddNewProperty("OutputType", "Library");
pgroup.AddNewProperty("IntermediateOutputPath", ".");
pgroup.AddNewProperty("MarkupCompilePass1DependsOn", "ResolveReferences");
BuildItemGroup igroup = project.AddNewItemGroup();
igroup.AddNewItem("Page", "input.xaml");
igroup.AddNewItem("Reference", "WindowsBase");
igroup.AddNewItem("Reference", "PresentationCore");
igroup.AddNewItem("Reference", "PresentationFramework");
project.AddNewImport(@"$(MSBuildBinPath)\Microsoft.CSharp.targets", null);
project.AddNewImport(@"$(MSBuildBinPath)\Microsoft.WinFX.targets", null);
project.FullFileName = projFile;
if (engine.BuildProject(project, "MarkupCompilePass1"))
{
byte[] buffer = new byte[1024];
using (FileStream fs = File.OpenRead(Path.Combine(path, "input.baml")))
{
int read = 0;
while (0 < (read = fs.Read(buffer, 0, buffer.Length)))
{
stream.Write(buffer, 0, read);
}
}
}
else
{
throw new System.Exception("Baml compilation failed.");
}
}
finally
{
Directory.Delete(path, true);
}
}
}
public static class BamlReader
{
public static object Load(Stream stream)
{
ParserContext pc = new ParserContext();
return typeof(XamlReader)
.GetMethod("LoadBaml", BindingFlags.NonPublic | BindingFlags.Static)
.Invoke(null, new object[] { stream, pc, null, false });
}
}
上面的代碼,大家可以試一下運行效果。或者有更好的方式也請告知。
七,本文總結
本篇主要對前幾次的課程進了一些簡單的回顧,同時用一個比較全的Demo介紹了Xaml中引用各種控件和類等,另外對脫離VS工具CSC編譯WPF以及 XamlReader與XamlWriter 做了比較詳細的介紹。下篇我們將進入WPF布局的世界進行漫游,爭取和布局控件及應用來一個全接觸!
出處:http://www.cnblogs.com/KnightsWarrior/
本文配套源碼