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编写一个窗体  -->
<!--  只使用一个单独的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("Button Click");
        }
    </x:Code>
</Window>

 只使用xaml编写一个窗体

 只使用Code-Behind编写一个窗体
 

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属性更加简单。

引用至:https://www.cnblogs.com/ColdJokeLife/p/3368683.html

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值