zhouqijie

XAML

如果要编写UWP、使用WPF的Windows应用程序、使用WF的工作流、Sliverlight等,不仅需要了解C#,还需要XAML

System.Windows名称空间中除了System.Windows.Forms(包括较早的WindowsForms技术)之外的所有内容都属于WPF。



概述

XAML(eXtensible Application Markup Language, 可扩展应用程序标记语言)是一种声明性的XML语法。

XAML元素通常映射到.NET类或WindowsRuntime类上。示例:

[STAThread]
static void Main()
{
    //按钮  
    var btn = new Button(){ Content = "Click Me!"};
    btn.Click += (s, e) =>
    {
        b.Content = "Clicked!";
    }
    //窗口  
    var w = new Window(){ Title = "Demo", Content = b };
    //应用程序
    var app = new Application();
    app.Run(w);
}

使用XAML代码可以创建类似的UI:

<Window x:Class = "XAMLINtroWPF.MainWindow" xmlns="..." xmlns:x="" Title="XAML Demo" Height="350", Width="525">
    <!-etc.->
    <Button Content="Click Me!" Click="OnButtonClicked" />
    <!-etc.->
</Window>

<!--Application也可以用XAML定义-->

<Application x:Class="XAMLIntroWPF.App", xmlns="..." xmlns:x="..." StartUpUri="MainWindow.xaml">
    <Application.Resources>
    </Application.Resources>
</Application>

略。

public class Person{
    public string FirstName{get;set;}
    public string LastName{get;set;}
}
<Window x:Class="...." xmlns="..." xmlns:x="..." xmlns:datalib="clr-namespace:DataLib;assembly=DataLib" Title="...">
    <!--XXXXX-->
        <datalib:Person FirstName="AAA" LastName="BBB">
    <!--/XXXXX-->
</Window>

定义了一个XML命名空间别名datalib,它映射到程序集DataLib的.NET名称空间DataLib上。
有了这个别名,就可以在元素前面加上别名,使用该名称空间的所有类了。

对于UWP应用程序,XAML声明是不同的。

只要属性的类型可以表示为字符串,或者可以把字符串转换为属性类型。就可以把属性设置为XML特性。

CRE:属性可以映射为XAML元素的子元素。

WPF定义元素,可以使用x:Array扩展,其Type特性用于指定数组元素的类型。



依赖属性

XAML使用依赖属性来完成数据绑定动画属性变更通知样式化等。

下列示例定义了”Value”、”Maximum”、”Minimum”三个属性:

public class MyDependencyObject: DependencyObject
{
    //Value
    public int Value
    {
        get{ return (int)GetValue{ValueProperty};}
        get{ SetValue{ValueProperty, value};}
    }
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(int), typeof(MyDependencyObject));
    
    //Maxinum
    public int Maximum
    {
        get{ return (int)GetValue{MaximumProperty};}
        get{ SetValue{MaximumProperty, value};}
    }
    public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(nameof(Maximum), typeof(int), typeof(MyDependencyObject));
    
    //Minimum
    public int Minimum
    {
        get{ return (int)GetValue{MinimumProperty};}
        get{ SetValue{MinimumProperty, value};}
    }
    public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(nameof(Minimum), typeof(int), typeof(MyDependencyObject));
}

为了获得值变更的信息,依赖属性还支持值变更回调。在属性值发生变化时调用的DependencyProperty.Register()方法中,可以添加一个DependencyPropertyChanged事件处理程序。

public class MyDependencyObject: DependencyObject
{
    public int Value
    {
        get{ return (int)GetValue{ValueProperty};}
        get{ SetValue{ValueProperty, value};}
    }
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(int), typeof(MyDependencyObject), new PropertyMetadata(0, OnValueChanged, CoerceVlaue));

    private static void OnValueChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        //...
    }
    private static object CoerceValue(DependencyObject d, object baseBalue)
    {
        int newValue = (int)baseValue;
        //...
        return newValue;
    }
}

利用WPF,依赖属性支持强制检查,可以检查属性的值是否有效,例如是否在某个取值范围内。
把方法CoerceValue()传递给PropertyMetadata对象,再传递为DependencyProperty.Register()的一个参数。现在,SetValue方法的实现代码中,对属性值的更改每次都调用CoerceValue方法。




路由事件

在基于XAML的应用程序中,路由事件扩展了事件模型。元素包含元素,组成了一个层次模型。在路由事件中,事件通过元素的层次结构路由。

如果路由事件在控件中触发,例如按钮,那么可以用按钮处理事件,但它向上路由到所有父控件那里也可以触发。这称为冒泡

把事件的Handled属性设置为true,可以阻止事件冒泡到父控件。

略。

在WPF中,支持路由的事件比UWP应用程序支持的更多。除了沿着控件层次向上冒泡,WPF还支持隧道,隧道事件和冒泡方向相反-从外部进入内部控件。事件要么是冒泡事件,要么是隧道事件,要么是直接事件。

事件经常成对定义,PreviewMouseMove是一个隧道事件,从外到内。MouseMove事件跟在PreviewMouseMove之后,它是一个冒泡事件,从内到外。

要让MyDependencyObject支持在值改变时触发事件,必须改为继承自UIElement,因为这个类定义了AddHandler和RemoveHandler方法。



附加属性

依赖属性是可用于特定类型的属性。而通过附加属性AttachedProperty,可以为其他类型定义属性。

一些容器控件为其子控件定义了附加属性:

<DockPanel>
    <Button Content="Top", DockPanel.Dock="Top" />  
    <Button Content="Left", DockPanel.Dock="Left" />  
</DockPanel>

附加属性的定义与依赖属性非常相似。定义附加属性的类必须派生自DependencyObject基类,并定义一个普通的属性。接着不调用DependencyObject的Register()方法,而是调用RegisterAttached()方法。该方法注册一个附加属性,现在它可以用于每个元素。




扩展标记

通过扩展标记,可以扩展XAML的元素或特性语法。如果XML特性包含花括号{},就表示这是标记扩展的一个符号。

<!---->
<Button Content="Test" Background="{StaticResource gradientBrush1}">
<!---->
<Button Content="Test">
    <Button.Background>
        <StaticResourceExtension ResourceKey="gradientBrush1">
    </Button.Background>
</Button>

WPF可以创建自定义的标记扩展,而UWP只能使用预定义的标记扩展。

要创建标记扩展,可以定义继承自MarkupExtension基类的一个类。大多数标记扩展都有Extension后缀。

有了自定义标记扩展后,就只需要重写ProvideValue(IServiceProvider)方法,它返回扩展的值。返回的类型用MarkupExtensionReturnType特性注解类。

通过该方法的参数(IServiceProvider接口)可以查询不同的服务。例如IProvideValueTargetIXamlTypeResolver。IXamlTypeResolver可用于把XAML元素名解析为CLR对象。

[MarkupExtensionReturnType(typeof(string))]
public class CalculatorExtension : MarkupExtension
{
    //...
    public override object ProvideValue(IserviceProvider serviceProvider)
    {
        IProvideValueTarget provideValue = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;  
        //...
    }
}

XAML定义的标记扩展也很常用。例如:

  1. x.Array定义为ArrayExtension
  2. x.Type定义为TypeExtension。它根据字符串输入返回类型。
  3. x:Null定义为NullExtension。可以用在XAML中把值设置为空。
  4. x:Static定义为StaticExtension。可调用类的静态成员。

(END)