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

雜七雜八——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.     public partial class Car
  11.     {
  12.         public void Accelerate() { /*80, 90... 120, 140....1200...flying...*/}
  13.         public void Break() {/*zizizizizizizizizi....*/ }
  14.     }
  1.     public  class Car
  2.     {
  3.         // UI
  4.         Color bodyColor;
  5.         Color windowColor;
  6.         Polygon door;
  7.         Polygon seat;
  8.         // logic
  9.         public void Accelerate() { /*80, 90... 120, 140....1200...flying...*/}
  10.         public void Break() {/*zizizizizizizizizi....*/ }
  11.     }
實際效果是徹底同樣的。只是前者是把UI和邏輯劈開寫,後者是混在一塊兒寫罷了。
 
劈開的確是劈開了,但讓設計師用C#代碼去實現UI恐怕不現實——讓Blend直接生成C#不是不多是事情,只是C#描述UI太不直觀了。因而,微軟更進一步,把界面描述語言又向設計師方向推動了一層,也就是XAML語言。因而,開發和設計的格局就變成了這樣:
 
 
有了XAML和將XAML解析爲C#/VB的解析器,設計師們就能以本身最高的工做效率與程序員們合做開發軟件了。目前關於XAML是如何解析成C#/VB的資料很是少。
 
 
Name揭祕
 
下面讓咱們把目光集中在XAML->C#的解析上來,看看Name和x: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.        
  13.         private void Button_Click(object sender, RoutedEventArgs e)
  14.         {
  15.             Button btn = e.OriginalSource as Button;
  16.             textBox1.Text = btn.Name;
  17.             textBox2.Name = "Made_in_China";
  18.             textBox2.Text = textBox2.Name;
  19.         }
  20.      
  21.         ]]>
  22.     </x:Code>
  23. </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屬性的值,那可就要當心了!請看下面的代碼:
 
  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.        
  13.         private void Button_Click(object sender, RoutedEventArgs e)
  14.         {
  15.             textBox2.Name = "Made_in_China";
  16.             //this.RegisterName("Made_in_China", this.textBox2);
  17.             TextBox t = this.FindName("Made_in_China"as TextBox;
  18.             if(t==null)
  19.             {
  20.               return;
  21.             }
  22.             else
  23.             {
  24.               MessageBox.Show("OK");
  25.             }
  26.         }
  27.      
  28.         ]]>
  29.     </x:Code>
  30. </Window>
注意,除非我取消對第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. public class FrameworkElement : UIElement, 
  5.     IFrameworkInputElement, IInputElement, ISupportInitialize
  6. {
  7.     //...
  8. }
x:Name揭祕
 
x:Name的x加一個冒號,說明它來自x這個名稱空間。這個名稱空間是定義在XAML的根元素上的。也就是這句:
 
這個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裏。若是代碼寫成這樣:
 
  1. <Window x:Class="WpfApplication.Window1"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     xmlns:local="clr-namespace:WpfApplication"
  5.     Title="Window1" Height="300" Width="300">
  6.     <Grid>
  7.         <local:MyControl Name="myControl"/>
  8.     </Grid>
  9. </Window>
當編譯的時候,會報出錯誤:
 
 
 
 
最讓人啼笑皆非的緣由就是「由於MyControl是在同一個程序集裏,你就得使用x:Name而不是Name!」這算什麼解釋?跟是否是同一個程序集有什麼關係?
 
TO BE CONTINUE...
相關文章
相關標籤/搜索