原創作品,允許轉載,轉載時請務必以超連結形式标明文章 原始出處 、作者資訊和本聲明。否則将追究法律責任。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這個關鍵字。請大家看這兩段代碼
- // For UI
- public partial class Car
- {
- Color bodyColor;
- Color windowColor;
- Polygon door;
- Polygon seat;
- }
- // For logic
- publicvoid Accelerate() { /*80, 90... 120, 140....1200...flying...*/}
- publicvoid Break() {/*zizizizizizizizizi....*/ }
- publicclass Car
- // UI
- // logic
實際效果是完全一樣的。隻是前者是把UI和邏輯劈開寫,後者是混在一起寫罷了。
劈開的确是劈開了,但讓設計師用C#代碼去實作UI恐怕不現實——讓Blend直接生成C#不是不可能是事情,隻是C#描述UI太不直覺了。于是,微軟更進一步,把界面描述語言又向設計師方向推進了一層,也就是XAML語言。于是,開發和設計的格局就變成了這樣:
有了XAML和将XAML解析為C#/VB的解析器,設計師們就能以自己最高的工作效率與程式員們合作開發軟體了。目前關于XAML是如何解析成C#/VB的資料非常少。
Name揭秘
下面讓我們把目光集中在XAML->C#的解析上來,看看Name和x:Name的本質是什麼。
讓我們看一段代碼:
- <Windowx:Class="WpfApplication2.Window1"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window1"Height="100"Width="300"Background="SteelBlue">
- <StackPanel>
- <TextBoxName="textBox1"/>
- <TextBoxName="textBox2"/>
- <ButtonContent="Show Name"Click="Button_Click"/>
- </StackPanel>
- <x:Code>
- <![CDATA[
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- Button btn = e.OriginalSource as Button;
- textBox1.Text = btn.Name;
- textBox2.Name = "Made_in_China";
- textBox2.Text = textBox2.Name;
- }
- ]]>
- </x:Code>
- </Window>
運作結果是:
我用XAML定義了三個UI元素,其中兩個TextBox是有Name的。凡是你在XAML代碼裡設定了它的Name,那麼在C#代碼裡就會有一個對應的變量。這可也很好解釋,看看IL程式集就知道了——
不難看出,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屬性的值,那可就要小心了!請看下面的代碼:
- <Window x:Class="WpfApplication2.Window1"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window1" Height="100" Width="300" Background="SteelBlue">
- <StackPanel>
- <TextBox Name="textBox1"/>
- <TextBox Name="textBox2"/>
- <Button Content="Show Name" Click="Button_Click"/>
- </StackPanel>
- <x:Code>
- <![CDATA[
- privatevoid Button_Click(object sender, RoutedEventArgs e)
- //this.RegisterName("Made_in_China", this.textBox2);
- TextBox t = this.FindName("Made_in_China") as TextBox;
- if(t==null)
- {
- return;
- }
- else
- MessageBox.Show("OK");
- </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類的聲明。
- [RuntimeNamePropertyAttribute("Name")]
- [StyleTypedPropertyAttribute(Property = "FocusVisualStyle", StyleTargetType = typeof(Control))]
- [XmlLangPropertyAttribute("Language")]
- publicclass FrameworkElement : UIElement,
- IFrameworkInputElement, IInputElement, ISupportInitialize
- {
- //...
- }
x:Name揭秘
x:Name的x加一個冒号,說明它來自x這個名稱空間。這個名稱空間是定義在XAML的根元素上的。也就是這句:
- 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,
現在我想把MyControl用在我的Window1裡。如果代碼寫成這樣:
- <Window x:Class="WpfApplication.Window1"
- xmlns:local="clr-namespace:WpfApplication"
- Title="Window1" Height="300" Width="300">
- <Grid>
- <local:MyControl Name="myControl"/>
- </Grid>
當編譯的時候,會報出錯誤:
最讓人哭笑不得的原因就是“因為MyControl是在同一個程式集裡,你就得使用x:Name而不是Name!”這算什麼解釋?跟是不是同一個程式集有什麼關系?
TO BE CONTINUE...