• XAML属性赋值转换之谜(WPF XAML语法解密)


    XAML与XML类似,就是XML延伸过来的。为了更好的表达一些功能,WPF对XML做了扩展,有些功能是WPF在后台悄悄的替你做了。有时候,虽然实现了某个功能,但是对实现原理还是很茫然。今天就讲讲XAML中赋值操作。

    1 通过类型转换赋值

    赋值是最简单最常见的操作,举例:

     <Button  Width="200" Height="100">
     </Button>

    这里把Width值赋值为200;用代码实现赋值,则为Button.With = 200; 这种赋值操作很直接,大家都能理解。但是仔细想想,感觉有点不对劲。XAML表达式Width="200",这里200是字符串,Width类型是double。字符串200怎么就转换成double了!你会说,200很明显可以转换为double类型,有什么大惊小怪的!

    有时,程序实现的逻辑操作很傻瓜,人很容易理解的事,程序并不一定能理解。需要你告诉XAML编译器,怎么把字符串型转换成double型。确实有 一个转换类悄悄的把字符串型转换成了double型。

    通过元文件,可以查到Width属性定义。

    //
            // 摘要:
            //     获取或设置元素的宽度。
            //
            // 返回结果:
            //     元素的宽度,单位是与设备无关的单位(每个单位 1/96 英寸)。默认值为 System.Double.NaN。此值必须大于等于 0.0。有关上限信息,请参见“备注”。
            [Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
            [TypeConverter(typeof(LengthConverter))]
            public double Width { get; set; }
    Width属性定义[TypeConverter(typeof(LengthConverter))]。这句话就表明width转换类型是LengthConverter。当XAML编译器看到Width赋值操作,就会调用LengthConverter。输入是字符串,返回就是double。
    你可能感觉到,对这个属性讲解有点啰嗦。我这里是想告诉你:几乎所有的赋值操作,都需要这种转换。
    引申: 更深一步讲,如果我们定义了一个属性,这个属性是一个复杂的类型。在XAML如何赋值? 比如自己定义了类型如下:
    public class MyPointItem
     {
            public double Latitude { get; set; }
            public double Longitude { get; set; }
     }

     有一个类包含此属性:

      public class MyClass
     {
      public MyPointItem Item { get; set; }
     }

    在XAML语法中如何对Item赋值,XAML语法只认识字符串型。这时需要参考上文Width处理方式。需要自己定义些转换类。定义一个类型继承TypeConverter,实现里面的函数。

    比如这样赋值MyClass.Item = "123,456";你需要告诉编译器,如何将"123,456"转化成类型MyPointItem。这里字符串用逗号分隔,你可以用别的符号分隔;比如“#”,只要你的转换函数能处理就行。完整的处理函数如下:

    //定义转换类型
        public class MyPointItemConverter : TypeConverter
        {
            public override bool CanConvertFrom( ITypeDescriptorContext context, Type sourceType)
            {
                if (sourceType is string)
                    return true;
                return base.CanConvertFrom(context, sourceType);
            }
    
            public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
            {
                if (destinationType is MyPointItem)
                    return true;
    
                return base.CanConvertTo(context, destinationType);
            }
    
            public override object ConvertFrom(ITypeDescriptorContext context,
                        CultureInfo culture, object value)
            {
                if (value is string)
                {
                    try
                    {
                        return MyPointItem.Parse(value as string);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(string.Format("Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), ex.Message), ex);
                    }
                }
    
                return base.ConvertFrom(context, culture, value);
            }
    
    
            public override object ConvertTo(ITypeDescriptorContext context,
                CultureInfo culture, object value, Type destinationType)
            {
                if (destinationType == null)
                    throw new ArgumentNullException("destinationType");
    
                MyPointItem gpoint = value as MyPointItem;
    
                if (gpoint != null)
                    if (this.CanConvertTo(context, destinationType))
                        return gpoint.ToString();
    
                return base.ConvertTo(context, culture, value, destinationType);
            }
        }
    
        //自定义类型
        [TypeConverter(typeof(MyPointItemConverter))]
        public class MyPointItem
        {
            public double Latitude { get; set; }
            public double Longitude { get; set; }
    
            internal static MyPointItem Parse(string data)
            {
                if (string.IsNullOrEmpty(data))
                    return new MyPointItem();
    
                string[] items = data.Split(','); //用逗号分隔,和XAML赋值中字符串分隔符保持一致
                if (items.Count() != 2)
                    throw new FormatException("should have both latitude and longitude");
    
                double lat, lon;
                try
                {
                    lat = Convert.ToDouble(items[0]);
                }
                catch (Exception ex)
                {
                    throw new FormatException("Latitude value cannot be converted", ex);
                }
    
                try
                {
                    lon = Convert.ToDouble(items[1]);
                }
                catch (Exception ex)
                {
                    throw new FormatException("Longitude value cannot be converted", ex);
                }
    
                return new MyPointItem() { Latitude=lat, Longitude=lon };
            }
        }

    转换类型不是万能的: 只有类型转换,也会遇到难以处理的情况。比如MyClass.Item = "null"。我的意思是将Item赋值为null。但是编译不会这么处理,仍然会调用转换类型MyPointItemConverter,结果就会抛出异常!WPF为此又引入了扩展标识符的概念。

    2 扩展标识符

    扩展标识符有特殊的语法,如果属性赋值为null,语法如下:
    MyClass.Item ="{x:Null}"; 这里的Null其实是一个类型,继承自MarkupExtension;
    //
        // 摘要:
        //     实现 XAML 标记以返回 null 对象,可使用该对象在 XAML 中将值显式设置为 null。
        [MarkupExtensionReturnType(typeof(object))]
        [TypeForwardedFrom("PresentationFramework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
        public class NullExtension : MarkupExtension
        {
            //
            // 摘要:
            //     初始化 System.Windows.Markup.NullExtension 类的新实例。
            public NullExtension();
    
            //
            // 摘要:
            //     提供要用作值的 null 作为此标记扩展的输出。
            //
            // 参数:
            //   serviceProvider:
            //     可为标记扩展实现提供服务的对象。
            //
            // 返回结果:
            //     空引用。
            public override object ProvideValue(IServiceProvider serviceProvider);
        }
    MyClass.Item ="{x:Null}"这句话的意思就是:编译器生成类型NullExtension,调用函数ProvideValue,将此返回值赋值给MyClass.Item;

    再举个例子:
    Height="{x:Static SystemParameters.IconHeight}”;
    编译器处理逻辑是:生成类型StaticExtension,将字符串“SystemParameters.IconHeight”传给构造函数,调用函数ProvideValue,返回double类型。

    其实StaticExtension会将字符串“SystemParameters.IconHeight”认为一个静态变量。XAML眼里只有字符串!

    绑定 -- 一种很常用的扩展标识符类型
    看如下语法:
     <Button  Width="200" Height="200"
                     Content="{Binding Height,RelativeSource={RelativeSource Self}}">
      </Button>

    对content的赋值,是不是感到一头雾水! binding其实也是扩展标识,最终继承自MarkupExtension;

      Binding : BindingBase --> BindingBase : MarkupExtension;

    所以binding的作用也是将字符串转换成我们需要的类型。不过binding的参数比较多,有时候需要转好几个弯,才能找到真的源头!

    对于上面的赋值,咱做个分析,来看看编译器处理的步骤:

      1)生成Binding类型,构造函数传入“Height”,

       2)Binding有一个属性为RelativeSource,参见元文件

       

     //
            // 摘要:
            //     通过指定绑定源相对于绑定目标的位置,获取或设置绑定源。
            //
            // 返回结果:
            //     一个 System.Windows.Data.RelativeSource 对象,该对象指定要使用的绑定源的相对位置。默认值为 null。
            [DefaultValue(null)]
            public RelativeSource RelativeSource { get; set; }

    仔细看看代码,属性类型和变量名称都是RelativeSource,这是c#语法允许的。当然,这样做会使人困惑!

      RelativeSource={RelativeSource Self},第一个RelativeSource其实是Binding的属性名称,第二个是类型名。Self是一个枚举值。

    这句话的意思就是,生成一个类型RelativeSource,构造函数是枚举值Self;将这个变量赋值给属性RelativeSource。

    3) 当Content需要值时,就会调用Binding的ProvideValue。这个函数就会把Button的属性Height返回!

    当然这里绕了很大一圈,只实现了一个简单的操作:将Button的高度显示出来!感觉好费劲!
    但是:绑定有一个特点,可以感知“源”变量的变化!举例如下
     <StackPanel>
                <Button x:Name="btnTest" Width="200" Height="30"
                     Content="{Binding Height,RelativeSource={RelativeSource Self}}">
                </Button>
                <Button Margin="10" Width="200" Height="30" Click="Button_Click">增加高度</Button>
            </StackPanel>
    
    
    Button_Click函数:
     private void Button_Click(object sender, RoutedEventArgs e)
            {
                btnTest.Height += 10;
            }

    当执行Button_Click时,btnTest的高度增加10,显示内容也随之变化。是不是很神奇!为什么会变化?这里需要了解WPF特殊的属性“依赖属性”。这里就不深入讲解了!

    当然绑定的优点不仅仅是这些,WPF会用到大量绑定,如果这些绑定都用代码来实现,太繁琐,也不易理解。

    总结:微软为了让XAML好用,费了很多心思。为了XAML能做更多的工作,编译器会替你做很多事情!简单的一个赋值操作,背后门道很多!初学者如果不了解这些门道,就感到一片茫然!本文初步揭示了赋值操作背后的一些内幕,希望你读后,有豁然开朗的感觉!

     


  • 相关阅读:
    sys模块
    os模块
    datetime模块
    time模块

    random模块
    python文件两种用途
    模块的搜索路径
    如何修改cnblogs的文本编辑器
    socket状态
  • 原文地址:https://www.cnblogs.com/yuanchenhui/p/xaml_property_setting.html
Copyright © 2020-2023  润新知