天天看點

WPF中Name與x:Name的關系

原創作品,允許轉載,轉載時請務必以超連結形式标明文章 原始出處 、作者資訊和本聲明。否則将追究法律責任。http://liutiemeng.blog.51cto.com/120361/121354

雜七雜八——Name與x:Name的關系

小序:

如果想用Google搜包含冒号的内容怎麼辦?比如我想搜x:Name這個字元串……

原來,應該是這樣——x::Name

這世道,連搜尋也要加轉義,全民程式員,要不要人活了?

正文:

從第一天學習XAML語言開始,我就一直沒厘清為什麼對于一個XAML标簽既可以設定它的Name又可以設定它的x:Name。問過一些同僚,大家好像對這種比較孔乙己的問題不太感興趣。今天花了些時間看了看,收獲還挺多的。與大家分享一下。

首先,讓我們剖析一下XAML代碼與C#代碼之間的關系。

大家都知道,XAML是“用來設計UI”的,設計師用XAML設計出來的UI其背景代碼(程式邏輯)可以由程式員用C#或者VB去寫——這叫做Code-behind。實際上,設計師用XAML和程式用C#都是在建構同一個類,換句話說就是:把一個類劈成兩半,與UI相關的那半由設計師用XAML寫,與邏輯相關的那半由程式員用C#寫。

.NET之是以支援這種劈開寫的功能,得益于partial這個關鍵字。請大家看這兩段代碼

  1. // For UI
  2. public partial class Car
  3.    {
  4.        Color bodyColor;
  5.        Color windowColor;
  6.        Polygon door;
  7.        Polygon seat;
  8.    }
  9. // For logic
  10. publicvoid Accelerate() { /*80, 90... 120, 140....1200...flying...*/}
  11. publicvoid Break() {/*zizizizizizizizizi....*/ }
  1. publicclass Car
  2. // UI
  3. // logic

實際效果是完全一樣的。隻是前者是把UI和邏輯劈開寫,後者是混在一起寫罷了。

劈開的确是劈開了,但讓設計師用C#代碼去實作UI恐怕不現實——讓Blend直接生成C#不是不可能是事情,隻是C#描述UI太不直覺了。于是,微軟更進一步,把界面描述語言又向設計師方向推進了一層,也就是XAML語言。于是,開發和設計的格局就變成了這樣:

WPF中Name與x:Name的關系

有了XAML和将XAML解析為C#/VB的解析器,設計師們就能以自己最高的工作效率與程式員們合作開發軟體了。目前關于XAML是如何解析成C#/VB的資料非常少。

Name揭秘

下面讓我們把目光集中在XAML->C#的解析上來,看看Name和x:Name的本質是什麼。

讓我們看一段代碼:

  1. <Windowx:Class="WpfApplication2.Window1"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="Window1"Height="100"Width="300"Background="SteelBlue">
  5. <StackPanel>
  6. <TextBoxName="textBox1"/>
  7. <TextBoxName="textBox2"/>
  8. <ButtonContent="Show Name"Click="Button_Click"/>
  9. </StackPanel>
  10. <x:Code>
  11. <![CDATA[
  12.        private void Button_Click(object sender, RoutedEventArgs e)
  13.        {
  14.            Button btn = e.OriginalSource as Button;
  15.            textBox1.Text = btn.Name;
  16.            textBox2.Name = "Made_in_China";
  17.            textBox2.Text = textBox2.Name;
  18.        }
  19.        ]]>
  20. </x:Code>
  21. </Window>

運作結果是:

WPF中Name與x:Name的關系

我用XAML定義了三個UI元素,其中兩個TextBox是有Name的。凡是你在XAML代碼裡設定了它的Name,那麼在C#代碼裡就會有一個對應的變量。這可也很好解釋,看看IL程式集就知道了——

WPF中Name與x:Name的關系

不難看出,XAML解析器會為XAML代碼中設定了Name的元素聲明同名的引用變量,而且設定Name的元素則不會有引用變量生成(不過這個元素對應的對象是存在的,并且是VisualTree/LogicalTree上的結點)。

通過上面的代碼,我看可以看出,Name的作用有兩個:

1. 告訴XAML解析器為設定了Name的元素聲明對應的引用變量(本例中是textBox1和textBox2),變量名使用Name的值。

2. 将XAML元素對應的對象(本例中是兩個TextBox的執行個體)的Name屬性設定為Name的值。

注意,引用變量一旦聲明之後名字就不能改了,但對象的Name屬性仍然可以改(示例中我就把由textBox2變量引用着的執行個體的Name屬性改成Made_in_China了。)

讓我們再挖深點兒——TextBox的Name屬性是從哪兒繼承來的呢?查一查MSDN,原來是從FrameworkElement那兒繼承來的。這個Name屬性是非常重要的——如果你想在一棵“樹”上查找叫某個名字的元素,調用“樹根”的FindName方法就可以做到了。特别需要注意的是——FindName所使用的參數是對象Name屬性的值而不是引用着這個對象的變量的名字。如果你的程式裡隻在XAML裡設定了一次Name,那麼引用變量的名字和對象Name屬性的值恰好一樣。但如果你改變了對象Name屬性的值,那可就要小心了!請看下面的代碼:

  1. <Window x:Class="WpfApplication2.Window1"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.    Title="Window1" Height="100" Width="300" Background="SteelBlue">
  5.    <StackPanel>
  6.        <TextBox Name="textBox1"/>
  7.        <TextBox Name="textBox2"/>
  8.        <Button Content="Show Name" Click="Button_Click"/>
  9.    </StackPanel>
  10.    <x:Code>
  11.        <![CDATA[
  12. privatevoid Button_Click(object sender, RoutedEventArgs e)
  13. //this.RegisterName("Made_in_China", this.textBox2);
  14.            TextBox t = this.FindName("Made_in_China") as TextBox;
  15. if(t==null)
  16.            {
  17. return;
  18.            }
  19. else
  20.              MessageBox.Show("OK");
  21.    </x:Code>

注意,除非我取消對第17行的注釋,不然,盡管我已經把textBox2.Name改成了Made_in_China,但由于這個新名字還沒有被注冊(即沒有使用RegisterName方法将Made_in_China和textBox2所引用的對象關聯起來),我們仍然不能通過FindName找到它。

我知道這段話挺拗口,不過有一點你想通過某種方法查找由DataTemplate自動生成的UI元素時,或許應該跑來讀一讀這段繞密碼:P

最後再啰嗦一句:為什麼這個Name屬性可以起到在運作時被當作查找辨別呢?是因為FrameworkElement被一個名為RuntimeNamePropertyAttribute的attribute所修飾。這個attribute明确指定,FrameworkElement的Name屬性具備了作為查找辨別的資格。TextBox等類派生自FrameworkElement,自然也有這個功能。下面是FrameworkElement類的聲明。

  1. [RuntimeNamePropertyAttribute("Name")]
  2. [StyleTypedPropertyAttribute(Property = "FocusVisualStyle", StyleTargetType = typeof(Control))]
  3. [XmlLangPropertyAttribute("Language")]
  4. publicclass FrameworkElement : UIElement,
  5.    IFrameworkInputElement, IInputElement, ISupportInitialize
  6. {
  7. //...
  8. }

x:Name揭秘

x:Name的x加一個冒号,說明它來自x這個名稱空間。這個名稱空間是定義在XAML的根元素上的。也就是這句:

  1. xmlns:x=[url]http://schemas.microsoft.com/winfx/2006/xaml[/url]

這個x就是XAML的字頭了。這個名稱空間的本意就是告訴我們——這個名稱空間裡所裝的元素都與XAML解析有關。比如,我在代碼裡還使用了x:Code,把本來應該呆在C#代碼裡的内容請到XAML裡來了。

可見,x:Name與Name根本不是一個層面上的東西——Name是直接與元素和面向對象程式設計相關的東西;x:Name是XAML語言解析層面上的東西。

如果我們把上面代碼中的所有Name都改成x:Name,所有效果都是一樣的。

不知道XAML中标有x:的内容是不是會被“預處理”一下。

Name與x:Name關系揭秘

不過,如果你的邏輯感比較強,你會發現這樣一個問題——為一個XAML元素聲明對應的引用變量,這不是面向對象程式設計層面的東西而是XAML解析層的東西。而且,如果Name在語義學上“恪守本分”的話,它應該隻去設定一下對象的Name屬性值而不去管是不是聲明變量的事兒。

大膽設想一下,你會猜到,當XAML解析器發現一個元素的Name被設定了,就會去調用x:Name的那套機制。也就是說,引用變量是在x:Name機制被調用的時候聲明的。同樣,如果你設定的是元素的x:Name,XAML解析器會在聲明變量之後再去給執行個體的Name屬性設定值。

這樣的猜想能夠得到證明嗎?讓我們在MSDN裡搜刮一下。

在x:Name的注釋裡,我們能找到這段話:

Under the standard build configuration for a WPF application project that uses XAML, partial classes, and code-behind, the specified x:Name becomes the name of a field that is created in the underlying code when XAML is processed, and that field holds a reference to the object.

而在FrameworkElement.Name屬性的文檔裡,又能找到這句話:

This property essentially provides a WPF framework-level convenience property to set the XAML x:Name Attribute.

也就是說,Name的确會去調x:Name那套機制。為什麼這麼做?可能是為了寫起來友善。不過,我真不太喜歡這種攪和在一起的風格。我甯可使用Name去給對象的Name屬性指派而使用x:Name去聲明變量。

貌似“Under the standard build configuration ”這句話有點玄機。不知道非standard編譯配置會有什麼樣的效果,怎樣才能自定義編譯配置呢?

不喜歡這種風格的原因還在于:Name和x:Name互相調用會在某些邏輯下出問題,特别是“先有雞還是先有蛋”這種情況下。

關于在XAML中使用同一個程式集中的User Control

說到“先有雞還是先有蛋”的問題,讓我想起了另一個困擾自己很久的問題。請看下面的代碼:

假設我有這樣一個project,

WPF中Name與x:Name的關系

現在我想把MyControl用在我的Window1裡。如果代碼寫成這樣:

  1. <Window x:Class="WpfApplication.Window1"
  2.    xmlns:local="clr-namespace:WpfApplication"
  3.    Title="Window1" Height="300" Width="300">
  4.    <Grid>
  5.        <local:MyControl Name="myControl"/>
  6.    </Grid>

當編譯的時候,會報出錯誤:

WPF中Name與x:Name的關系

最讓人哭笑不得的原因就是“因為MyControl是在同一個程式集裡,你就得使用x:Name而不是Name!”這算什麼解釋?跟是不是同一個程式集有什麼關系?

TO BE CONTINUE...