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類的聲明。

 

[RuntimeNamePropertyAttribute("Name")][StyleTypedPropertyAttribute(Property = "FocusVisualStyle", StyleTargetType = typeof(Control))][XmlLangPropertyAttribute("Language")]public class FrameworkElement : UIElement,     IFrameworkInputElement, IInputElement, ISupportInitialize{    //...}

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!”這算什麼解釋?跟是不是同一個程序集有什麼關係?

 

=====================================

崔維福的補充:

"跟是不是同一個程序集有什麼關係" 具體來說還是有關係的。你一定知道Name不能和x:Name同時用,因爲在build的時候他知道這兩個做了相同的工作,什麼工作呢,就是做在“類名.g.cs”,中添加了後臺代碼,x:Name是Attribute,而Name是DP,編譯器可以挖掘Attribute來爲自己的後臺添加內容,但是不能用Name來添加,因爲自己還沒有被構造完全呢。所以呢就是告訴你,要用x:Name,要不就把這個contro放到別的project下,它好能把它構造出來!希望我解釋的夠清楚!

 

作者:

感謝崔先生的批註,您對技術精益求精的精神讓我非常景仰,向您學習、致敬!

=====================================

 

TO BE CONTINUE...

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章