天天看點

[WPF 容易忽視的細節] —— x:Name與Name屬性

WPF使用XAML來對界面進行編寫,界面與背景邏輯分離。我們也可以寫Style、Trigger來實作一些界面效果,

這些都是通過Name來定位控件的,例如Setter.TargetName、Trigger.SourceName和Binding的ElementName等。

而這些Name都是通過設定控件的x:Name來定義的.

但是,XAML中有x:Name和Name這兩個屬性,究竟它們有什麼差別呢?

本專題就來探究一下x:Name和Name的差別,它們的本質又是什麼?

一、前言

而這些Name都是通過設定控件的x:Name來定義的,如<Button x:Name="Button1" />

二、XAML與Code-Behind

在編寫WPF程式時,通常需要分别編寫前台XAML代碼和背景Code-Behind代碼(不使用MVVM時)。

WPF通過一個partial關鍵字,将一個類的定義切分為兩部分:XAML和Code-Behind。

其中XAML交給設計師設計,Code-Behind交給程式員寫業務邏輯,進而實作分離(雖然大部分時候全部都是程式員完成的)。

我們在XAML上寫标簽,其實與背景寫代碼是等效的。隻要你想,完全可以隻使用XAML或者隻是用Code-Behind來寫程式。

示例:

[WPF 容易忽視的細節] —— x:Name與Name屬性
[WPF 容易忽視的細節] —— x:Name與Name屬性
<!--  隻使用xaml編寫一個窗體  -->
<!--  隻使用一個單獨的xaml檔案 -->
<Window x:Class="Cnblog.OnlyXaml"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="OnlyXaml"
        Width="300"
        Height="300">
    <Grid>
        <Button Width="100"
                Height="100"
                Click="ButtonClick">
            Button
        </Button>
    </Grid>
    <x:Code>
        private void ButtonClick(object sender, RoutedEventArgs e)
        {
        MessageBox.Show(&quot;Button Click&quot;);
        }
    </x:Code>
</Window>      

隻使用xaml編寫一個窗體

[WPF 容易忽視的細節] —— x:Name與Name屬性
[WPF 容易忽視的細節] —— x:Name與Name屬性
namespace Cnblog
{
    // 隻使用Code-Behind編寫一個窗體
    // 隻使用一個單獨的OnlyCode.cs檔案
    public class OnlyCode :Window
    {
        public OnlyCode()
        {
            // button
            var button = new Button { Content = "Button",Width = 100, Height = 100};
            button.Click += ButtonClick;

            // grid
            var grid = new Grid();
            grid.Children.Add(button);

            this.Width = 300;
            this.Height = 300;
            this.Title = "OnlyCode";
            this.Content = grid;
        }

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

隻使用Code-Behind編寫一個窗體

 上面例子,分别隻使用XAML和Code-Behind來定義一個窗體,但是最終的效果都是一樣的。

 當然,如果單獨使用其中一種,必然讓程式設計變得很痛苦,這裡我隻是想表明有這個可能性(因為下面會用到),實際中不會這麼做。

結論:雖然編碼方式不一樣,但是效果是一樣的,編譯器其實對XAML進行編譯生成BAMP,根據标簽建立相應對象。

這個與本專題無關,但是下面要将要用到相關内容,先說明一下。

三、XAML中x:Name和Name最終效果相同

如果你在xaml中建立一個控件,并同時對x:Name和Name兩個屬性進行指派,那麼編譯器就會提醒你:Name被設定了多次。

[WPF 容易忽視的細節] —— x:Name與Name屬性

當然,如果你覺得這個不夠有說服力,那麼下面這段程式可能也能夠佐證:

[WPF 容易忽視的細節] —— x:Name與Name屬性
[WPF 容易忽視的細節] —— x:Name與Name屬性
<!-- 兩個Button分别使用x:Name和Name -->
<Window x:Class="Cnblog.SetName"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SetName"
        Width="300"
        Height="300">
    <StackPanel>
        <Button x:Name="Button1" Loaded="ButtonLoaded" />
        <Button Name="Button2" Loaded="ButtonLoaded" />
    </StackPanel>
    <x:Code>
        private void ButtonLoaded(object sender, RoutedEventArgs e)
        {
        var button = (Button)sender;
        button.Content = button.Name;
        }
    </x:Code>
</Window>      

兩個Button分别使用x:Name和Name

效果圖:

[WPF 容易忽視的細節] —— x:Name與Name屬性

下面是用IL的截圖,兩者無差別。

[WPF 容易忽視的細節] —— x:Name與Name屬性

結論:

XAML中使用Name其實被映射到了x:Name,x:Name才是XAML中唯一的辨別,是以它們效果相同。

四、不同于控件的Name屬性

在WPF中,很多控件擁有Name屬性,例如上面我們使用Button的Name來設定Content,button.Content=button.Name。

是因為它們的父類FrameworkElement中都定義了Name屬性,以下用SomeWpfType來代替這些類型(便于表述)。

下面,我們不使用XAML和x:Name,使用Code-Behind和SomeWpfType.Name來測試一下。

[WPF 容易忽視的細節] —— x:Name與Name屬性
[WPF 容易忽視的細節] —— x:Name與Name屬性
namespace Cnblog
{
    // 使用Button的Name屬性
    public class SetNameByCodeBehind : Window
    {
        public SetNameByCodeBehind()
        {
            // Buttons
            var button1 = new Button { Name = "Button1" };
            button1.Loaded += ButtonLoaded;
            var button2 = new Button { Name = "Button2" };
            button2.Loaded += ButtonLoaded;

            // StackPanel
            var contentPanel = new StackPanel();
            contentPanel.Children.Add(button1);
            contentPanel.Children.Add(button2);

            // Window
            this.Title = "Set Name By Code-Behind";
            this.Height = 100;
            this.Width = 300;
            this.Content = contentPanel;
        }

        void ButtonLoaded(object sender, RoutedEventArgs e)
        {
            var button = (Button)sender;
            button.Content = button.Name;
        }
    }
}      

使用Button的Name屬性

[WPF 容易忽視的細節] —— x:Name與Name屬性

下面,差別終于有了,我們再來看IL的内容:

[WPF 容易忽視的細節] —— x:Name與Name屬性

因為不是使用XAML書寫的,是以缺少一些函數和對象,但是更重要的是:缺少兩個Button對象的定義。

如果我們修改一下代碼,再來看一下,就能清楚的知道原因了!

[WPF 容易忽視的細節] —— x:Name與Name屬性
[WPF 容易忽視的細節] —— x:Name與Name屬性
namespace Cnblog
{
    public class SetNameByCodeBehind : Window
    {
        // 修改為internal的對象,取代之前的局部變量
        internal Button Button1;
        internal Button Button2;

        public SetNameByCodeBehind()
        {
            // Buttons
            Button1 = new Button { Name = "Button1" };
            Button1.Loaded += ButtonLoaded;
            Button2 = new Button { Name = "Button2" };
            Button2.Loaded += ButtonLoaded;

            // StackPanel
            var contentPanel = new StackPanel();
            contentPanel.Children.Add(Button1);
            contentPanel.Children.Add(Button2);

            // Window
            this.Title = "Set Name By Code-Behind";
            this.Height = 100;
            this.Width = 300;
            this.Content = contentPanel;
        }

        void ButtonLoaded(object sender, RoutedEventArgs e)
        {
            var button = (Button)sender;
            button.Content = button.Name;
        }
    }
}      

使用Button的Name屬性2

再來看一下IL的内容:

[WPF 容易忽視的細節] —— x:Name與Name屬性

x:Name不是SomeWpfType.Name,當我們設定了x:Name後(假設為ElementName),

其實做了兩件事情:

1. 建立一個internal的對象,對象名字為ElementName,它其實是一個對此SomeWpfType類型對象的引用;

internal SomeWpfType ElementName = new SomeWpfType();// 假設SomeWpfType為我們定義的類型。

2. 設定此對象的Name屬性,

ElementName.Name = "ElementName";。

五、為什麼XAML中Name與x:Name效果相同

上面,我們分析SomeWpfType.Name和x:Name有很大差別,而且XAML中的設定Name不就是SomeWpfType.Name嗎?

同Width,在XAML中設定Width,就是設定SomeWpfType.Width。

那麼我們就去翻一下FrameworkElement的源碼看看吧。

[WPF 容易忽視的細節] —— x:Name與Name屬性
[WPF 容易忽視的細節] —— x:Name與Name屬性
namespace System.Windows
{
[StyleTypedProperty(Property = "FocusVisualStyle", StyleTargetType = typeof(Control))]
    [XmlLangProperty("Language")] 
    [UsableDuringInitialization(true)] 
    public partial class FrameworkElement : UIElement, IFrameworkInputElement, ISupportInitialize, IHaveResources, IQueryAmbient
    { /// <summary>
        ///     The DependencyProperty for the Name property. 
        /// </summary>
        [CommonDependencyProperty] 
        public static readonly DependencyProperty NameProperty = 
                    DependencyProperty.Register(
                                "Name", 
                                typeof(string),
                                _typeofThis,
                                new FrameworkPropertyMetadata(
                                    string.Empty,                           // defaultValue 
                                    FrameworkPropertyMetadataOptions.None,  // flags
                                    null,                                   // propertyChangedCallback 
                                    null,                                   // coerceValueCallback 
                                    true),                                  // isAnimationProhibited
                                new ValidateValueCallback(System.Windows.Markup.NameValidationHelper.NameValidationCallback)); 

        /// <summary>
        ///     Name property.
        /// </summary> 
        [Localizability(LocalizationCategory.NeverLocalize)]
        [MergableProperty(false)] 
        [DesignerSerializationOptions(DesignerSerializationOptions.SerializeAsAttribute)] 
        public string Name
        { 
            get { return (string) GetValue(NameProperty); }
            set { SetValue(NameProperty, value);  }
        }
        
         // a lot of code
    }
}


namespace System.Windows
{ 
    [RuntimeNamePropertyAttribute("Name")]
    public partial class FrameworkElement 
    {
          // a lot of code...  
    }
}      

FrameworkElement

Name屬性上貌似沒有什麼特别,但是另外一個局部類的定義中找到了個名為RuntimeNameProperty的特性。

我們就來看下它的代碼吧。

[WPF 容易忽視的細節] —— x:Name與Name屬性
[WPF 容易忽視的細節] —— x:Name與Name屬性
namespace System.Windows.Markup
{
  [AttributeUsage(AttributeTargets.Class)]
  [TypeForwardedFrom("WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
  public sealed class RuntimeNamePropertyAttribute : Attribute
  {
    private string _name;

    public string Name
    {
      [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get
      {
        return this._name;
      }
    }

    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public RuntimeNamePropertyAttribute(string name)
    {
      this._name = name;
    }
  }
}      

RuntimeNamePropertyAttribute

可以看出來這個特性使用在Class上面的,也就是這個特性,使得XAML處理Name時,映射到了x:Name,才有了前面的結論。

我們再來檢視以下還有哪些類使用了這個特性。

在4個Assembly中找到了13個,比較眼熟的有:

Timeline、BeginStoryboard、FrameworContentElement、FramewordElement、VisualState、VisualStateGroup,

這些也基本都是WPF中很多常用類型的基類了。

結論:RuntimeNameProperty特性,使得XAML中使用Name和x:Name效果一樣,當編譯器遇到此特性後,

就将Name映射到x:Name,執行一樣的操作。

六、XAML中x:Name與Name并不完全等價。

不是所有類型都可以使用Name,但是任何類型都可以使用x:Name。

隻有擁有Name屬性,才可以在XAML中使用Name。不同于x:Name,因為這個是附加屬性。

并且該類型、或者其父類型标記了RuntimeNameProperty特性,才擁有與x:Name一樣的效果。

例如:<SolidColorBrush Color="Transparent" Name="ddd"/>便會報錯,因為SolidColorBrush沒有Name屬性。

隻能使用x:Name。<SolidColorBrush Color="Transparent" x:Name="ddd"/>

七、其他

1、分析為什麼要有x:Name

前面提到,XAML中經常需要通過名字來定位某個控件或對象,而SomeWpfType的Name屬性,隻是一個DP,我們可以設定兩個控件擁有相同的Name屬性。

那麼這樣就非常不利于定位控件,因為Name不是一個唯一的辨別了。

使用對象的引用有兩個好處:

1.在特定的範圍域内,能夠保證它的唯一性;

2.在視圖樹中查找某個對象時,通過引用對象的名稱比查找Name屬性更加簡單。

2. MSDN上對着幾個名詞的定義

FrameworkElement.Name - Gets or sets the identifying name of the element. The name provides a reference so that code-behind, such as event handler code, can refer to a markup element after it is constructed during processing by a XAML processor.

Remark:

The most common usage of this property is to specify a XAML element name as an attribute in markup.

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

Names must be unique within a namescope. For more information, see WPF XAML Namescopes.

x:Name Directive - Uniquely identifies XAML-defined elements in a XAML namescope. XAML namescopes and their uniqueness models can be applied to the instantiated objects, when frameworks provide APIs or implement behaviors that access the XAML-created object graph at run time.

The value of an x:Name directive usage must be unique within a XAML namescope. By default when used by .NET Framework XAML Services API, the primary XAML namescope is defined at the XAML root element of a single XAML production, and encompasses the elements that are contained in that XAML production. Additional discrete XAML namescopes that might occur within a single XAML production can be defined by frameworks to address specific scenarios. For example, in WPF, new XAML namescopes are defined and created by any template that is also defined on that XAML production. For more information about XAML namescopes (written for WPF but relevant for many XAML namescope concepts), see WPF XAML Namescopes.

希望對大家有幫助。

作者:

ColdJokeLife

出處:http://www.cnblogs.com/ColdJokeLife/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,如有問題,請聯系我,非常感謝。

繼續閱讀