天天看點

【WPF學習】第四章 加載和編譯XAML

  前面已經介紹過,盡管XAML和WPF這兩種技術具有互相補充的作用,但他們也是互相獨立的。是以,完全可以建立不使用XAML和WPF應用程式。

  總之,可使用三種不同的編碼方式來建立WPF應用程式:

  • 隻使用代碼。這是在Visual Studio中為Windows窗體應用程式使用的傳統方法。它通過代碼語句生成使用者界面。
  • 使用代碼和未經編譯的标記(XAML)。這種具體方式對于某些特殊情況是很有意義的,例如建立高度動态化的使用者界面。這種方式在運作時使用System.Windows.Markup名稱空間中的XamlReader類,從XAML檔案中加載部分使用者界面。
  • 使用代碼和編譯過的标記(BAML)。對于WPF而言這是一種更好的方式,也是Visual Studio支援的一種方式。這種方式為每個視窗建立了一個XAML模闆,這個XAML模闆被編譯為BAML,并嵌入到最終的程式集中。編譯過的BAML在運作時被提取出來,用于重新生成使用者界面。

一、隻使用代碼

  對于編寫WPF應用程式,隻使用代碼進行開發而不适用任何XAML的做法并不常見(但是仍然完全支援)。隻使用代碼進行開發的明顯缺點在于,可能會使編寫WPF應用程式成為極端乏味的工作。WPF控件沒有包含參數化的構造函數,是以即使為視窗添加一個簡單按鈕也需要編寫幾行代碼。

  隻使用代碼進行開發的一個潛在的有點是可以随意定制應用程式。例如,可根據資料庫記錄中的資訊生成充滿輸入控件的窗體,或可根據目前的使用者酌情添加或替換控件。需要的所有内容隻不過是少量的條件邏輯。相比之下,如果使用XAML文檔,它們隻能作為固定不變的資源嵌入到程式集中。

  下面代碼用于生成一個普通窗體,該窗體包含一個按鈕和一個時間處理程式。在建立視窗時,構造函數調用InitializeComponent()方法,該方法執行個體化并配置這個按鈕和窗體,并連接配接(hook up)事件處理程式.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;

namespace WPFCompileByCode
{
    public class Window1:Window
    {
        private Button button1;
        public Window1()
        {
            InitializeComponent();
        }
        private void InitializeComponent()
        {
            this.Width = 300;
            this.Height = 300;
            this.Left = 100;
            this.Top = 100;
            this.Title = "Code Only Window";

            DockPanel panel = new DockPanel();
            button1 = new Button();
            button1.Content = "Click Me";
            button1.Margin = new Thickness(30);
            button1.Click += button1_Click;
            IAddChild container = panel;
            container.AddChild(button1);

            container = this;
            container.AddChild(panel);
        }

        void button1_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Click Me");
        }
    }
}      

  從概念上講,本例中的Windows1類更像傳統的Windows窗體硬功程式中的窗體。它繼承自Window基類,并為每個控件添加一個私有程式變量。為了清晰起見,該類在專門的InitializeComponent()方法中執行初始化操作。

  為啟動該應用程式,可在Main()方法中添加如下代碼:

public class Program:Application
    {
        [STAThread]
        public static void Main()
        {
            Program app = new Program();
            app.MainWindow = new Window1();
            app.MainWindow.ShowDialog();
        }
    }      

  運作效果圖如下所示:

【WPF學習】第四章 加載和編譯XAML

 二、使用代碼和未編譯的XAML

   使用XAML最有趣的方式之一是使用XamlReader類随時解析它。例如,假設建立一個Window1.xaml的檔案,且内容如下所示:

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <Button Name="button1" Margin="30">Click Me</Button>
</DockPanel>      

   編寫一個類,用來加載xaml檔案。如下代碼所示:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;

namespace WPFCompileByXaml
{
    public class Window1:Window
    {
        private Button button1;
        public Window1(string xamlFile)
        {
            this.Width = 300;
            this.Height = 300;
            this.Left = 100;
            this.Top = 100;

            DependencyObject rootElement;
            using (FileStream fs = new FileStream(xamlFile, FileMode.Open))
            {
                rootElement = (DependencyObject)XamlReader.Load(fs);
            }

            this.Content = rootElement;

            button1 = (Button)LogicalTreeHelper.FindLogicalNode(rootElement, "button1");

            /*
            FrameworkElement frameworkElement = (FrameworkElement)rootElement;
            button1 = (Button)frameworkElement.FindName("button1");
             */

            button1.Click += button1_Click;
        }

        void button1_Click(object sender, RoutedEventArgs e)
        {
            //MessageBox.Show("Hello ,you click me!");
            button1.Content = "Thank you!";
        }
    }
}      

  在此,構造函數接收XAML檔案名作為參數。然後構造函數打開一個FileStream對象,并使用XamlReader.Load()方法将這個檔案中的内容轉換為DependencyObject對象,DependencyObject是所有WPF控件繼承的基類。DependencyObject對象可放在任意類型的容器中,但在這個示例中它被用作整個窗體的内容。

  為操作元素——如Windows1.xaml檔案中的按鈕,需要在動态加載的内容中查找相應的控件對象。LogicalTreeHelper類可達到該目的,因為它具有查找一顆完整控件對象的能力,它可以查找所需的許多層,直到找到具有指定名稱的對象。然後将一個事件處理程式關聯到Button.Click事件。

  另外一種方法是使用FrameworkElement.FindName()方法,在這個示例中,根元素是DockPanel對象,與WPF視窗中的所有控件一樣,DockPanel類繼承自FrameworkElment類,這意味着可使用如下等效的方法:

FrameworkElement frameworkElement = (FrameworkElement)rootElement;
button1 = (Button)frameworkElement.FindName("button1");      

代替下面的代碼:

button1 = (Button)LogicalTreeHelper.FindLogicalNode(rootElement, "button1");      

  在這個示例中,Window1.xaml檔案和可執行的應用程式位于同一檔案夾中,并一同釋出。然而,盡管該檔案沒有被編譯為應用程式的一部分,但仍可以将其添加到Visual Studio項目中。這樣可以更友善地管理檔案,并使用Visual Studio設計使用者界面(假定使用.xaml檔案擴充名,進而使用Visual Studio能夠識别出該文檔是XAML文檔)。

  如果使用這種情況,確定松散的XAML檔案不會像傳統的XAML檔案那樣被編譯或嵌入到項目中。将檔案添加到項目後,在解決方案中選中該檔案,然後使用屬性視窗,将Build Action設定為None,并将Copy to Output Directory 設定為Copy Always。

  顯然,先将XAML編譯為BAML,再在運作時加載BAML,比動态加載XAML的效率高,當使用者界面比較複雜時尤其如此。然而,這種編碼模式為建構動态的使用者界面提供了多種可能。例如,可建立通用的檢測應用程式,從Web服務中讀取窗體檔案,然後顯示相應的檢測控件(标簽、文本框和複選框等)。窗體檔案可以是具有WPF标簽的普通XML檔案,使用XamlReader類将該文檔加載到一個已經存在的窗體中。檢測之後,為了收集結果,隻需要枚舉所有輸入控件并提取他們的内容即可。

三、使用代碼和編譯過的XAML

  當編譯WPF應用程式時,Visual Studio使用分為兩個階段的編譯處理過程。第一階段将XAML檔案編譯為BAML。例如,如果項目中包含名為Window1.xaml的檔案,編譯器将建立名為Window1.baml的臨時檔案,并将該檔案放在項目檔案夾的obj/Debug字檔案夾中。同時,使用選擇的語言為視窗建立部分類。例如,如果使用C#語言,編譯器将在obj/Debug檔案夾中建立名為Window1.g.cs的檔案。g代表生産的(generated)。

  部分類包括如下三部分内容:

  • 視窗中所有控件的字段。
  • 從程式集中加載BAML的代碼,由此建立對象樹。當構造函數調用InitializeComponent()方法時将發生這種情況。
  • 将恰當的控件對象指定給各個字段以及連接配接所有事件處理程式的代碼。該過程是在名為Connect()的方法中完成,BAML解析器在每次發現一個已經命名的對象時調用該方法一次。

  部分類不包含執行個體化和初始化控件的代碼,因為這項任務由WPF引擎在使用Application.LoadComponent()方法處理BAML時執行。

  具體執行個體,可以檢視Visual Studio編譯後的WPF應用程式。

作者:Peter Luo

出處:https://www.cnblogs.com/Peter-Luo/

本文版權歸作者和部落格園共有,歡迎轉載,但必須給出原文連結,并保留此段聲明,否則保留追究法律責任的權利。