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


一、前言

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

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

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

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

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

 

二、XAML與Code-Behind

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

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

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

我們在XAML上寫標簽,其實與后台寫代碼是等效的。只要你想,完全可以只使用XAML或者只是用Code-Behind來寫程序。

示例:

<!--  只使用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編寫一個窗體
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被設置了多次。

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

<!-- 兩個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

效果圖:

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

結論:

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來測試一下。

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屬性

效果圖:

下面,區別終於有了,我們再來看IL的內容:

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

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

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的內容:

結論:

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的源碼看看吧。

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的特性。

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

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.

Remark:

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.

 

希望對大家有幫助。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM