WPF笔记(一)之初识XMAL

本人对Java更熟悉,所以写的时候会不自觉与Java对比

XAML原理

XAML是XML的扩展,在WPF中XAML用于绘制UI,可以理解为用XAML去定义C#的UI类,在XAML中声明了一个标签,就意味着创建了一个对象。

比如WPF项目刚创建好时,XAML为

<Window x:Class="MyWpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        
    </Grid>
</Window>

上面例子中有<Window><Grid>两个标签,就意味着创建了System.Windows.WindowSystem.Windows.Controls.Grid的对象,然后<Window>标签上定义了一些属性。

一般来讲,XAML中的属性叫Attribute,对象中的属性叫Property,对象中声明的私有变量叫字段field。Java中没有属性,而是使用Get/Set方法进行了代替。

然后是标签的属性,一行一行来说。
首先是Title、Height、Width这一行,对应Window对象的标题和大小,不再多说。
然后是xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation",其中xmlns指的是名称空间,代表引用哪些名称空间。这里值看似是一个网址,其实是硬编码到XAML编译器中的,XAML解析时看到这个网址的名称空间就会自动把一些UI相关程序集和名称空间引入进来。
接着是xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"这里xmlns:x加了一个x的区分,用来表示不同名称空间,加了区分之后,使用这个名称空间的内容时就需要把区分作为前缀添加的标签上。这一行引用的名称空间里面主要是一些XAML解析需要的程序集。
然后是x:Class这一行,代表在MyWpfApp命名空间中创建一个MainWindow的类,和MainWindow.xml.cs中的MainWindow是同一个类,继承自Window。

可以在MainWindow.xml.cs中看到MainWindow使用了partial来声明,这个关键字用来在不同文件中声明同一个类,Java中没有这个功能

了解上面的信息之后,对一个最简单的XAML程序的结构就清楚了。

XAML语法基础

XAML中有两种方法给对象的属性赋值:

  • 使用字符串进行简单赋值
  • 使用元素属性(Property Element)进行复杂赋值

使用简单字符串进行赋值的例子:

<Rectangle x:Name="rectangle" Width="200" Height="150" Fill="Blue"/>

这是一个矩形的控件,其中有一个Fill属性用来填充内部的颜色,Rectangle类的Fill属性是Brush类型,Brush的子类很多,比如SolidColorBrush:单色笔刷。所以上面的属性Fill="Blue"就相当于下面的C#代码

...
SolidColorBrush sBrush = new SolidColorBrush();
sBrush.Color = Color.Blue;
this.rectangle.Fill = sBrush;
...

可以看到Blue这个字符串最终被翻译成了一个SolidColorBrush对象,并赋值给了rectangle。

那么为什么是SolidColorBrush而不是其他Brush子类呢?并且如果要设置的对象比较复杂的时候,字符串不就很复杂、很难写了吗?

使用TypeConverter类将XAML中的Attribute转换为对象的Property

对于第一个问题,字符串如何解析的逻辑由TypeConvert决定。

举个例子,现在有一个可以在XAML中声明的类Human:

public class Human
{
    public string Name { get; set; }
    public Human Child { get; set; }
}

想要在XAML中创建出这个类的对象,并且给Child属性赋值,Child的名字叫“ABC”:

<Window.Resources>
    <local:Human x:Key="human" Child="ABC"/>
</Window.Resources>

直接这么写是编译不过去的,因为XAML解析器并不知道怎么把字符串转化为Human类型,此时就需要TypeConvert类决定如何转换:

public class StringToHumanTypeConverter: TypeConverter
{
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string name)
        {
            Human h = new Human();
            h.Name = name;
            return h;
        }

        return base.ConvertFrom(context, culture, value);
    }
}

Visual Studio 中重写方法时,直接public override就会有提示(IDEA用多了总想着Ctrl+i、Ctrl+o)

然后在Human类上添加上特性:

[TypeConverter(typeof(StringToHumanTypeConverter))]
public class Human
{
    public string Name { get; set; }
    public Human Child { get; set; }
}

这样,XAML解析器就知道怎么把“ABC”这个字符串转化为Human对象,然后赋值给Child属性了。

C#中类上面被中括号包起来的叫特性,类似Java中的注解

使用属性元素方式进行属性赋值

XAML中,非空标签都有自己的内容(Content),内容中的子级标签都是副标签内容的一个元素(Element),那么属性元素的意思就是把一个元素当做属性。

也就是上面矩形的例子用属性元素改写一下,将会是这样:

<Rectangle x:Name="rectangle" Width="200" Height="150">
    <Rectangle.Fill>
        <SolidColorBrush Color="Blue"/>
    </Rectangle.Fill>
</Rectangle>

<Rectangle.Fill>标签代表要给Rectangle的Fill属性赋值,赋给的值就是标签括住的内容。
与之前的代码效果完全一致,这种写法主要用来设置属性复杂的对象。


比如给矩形设置一个渐变的颜色

<Rectangle x:Name="rectangle" Width="200" Height="150">
    <Rectangle.Fill>
        <LinearGradientBrush>
            <LinearGradientBrush.StartPoint>
                <Point X="0" Y="0"/>
            </LinearGradientBrush.StartPoint>
            <LinearGradientBrush.EndPoint>
                <Point X="1" Y="1"/>
            </LinearGradientBrush.EndPoint>
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Offset="0.2" Color="LightBlue"/>
                    <GradientStop Offset="0.7" Color="Pink"/>
                    <GradientStop Offset="1.0" Color="Blue"/>
                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </Rectangle.Fill>
</Rectangle>

可以看到这样自由度非常高。

标记扩展

大多数的属性赋值都是为属性生成一个新的对象,但是有时候我们需要把一个对象赋值给两个对象的,或者一个对象的属性依赖于其他对象的属性,这时候就要使用啊扩展标记了。

比如这里有一个文本框,以及一个滑动条,想要的效果是吧滑动条的值显示在文本框里:

<TextBox Text="{Binding ElementName=slider1, Path=Value, Mode=OneWay}"/>
<Slider x:Name="slider1" Margin="5"/>

上面的代码中,Text="{Binding ElementName=slider1, Path=Value, Mode=OneWay}"就是标记扩展,这是XAML特有的语法:

  • 当XAML编程器遇到这句代码时会把花括号的内容解析成相应的对象
  • 对象的数据类型是紧邻左花括号的字符串(这里就是Binding)
  • 对象的属性由一连串逗号连接的子字符串进行初始化(属性值不再加引号)

这个写法与C#3.0的对象初始化语法非常相似:

Binding binding = new Binding() { ElementName=slider1, Mode=BindingMode.OneWay };

几个小点:

标记扩展是可以嵌套的:Text={Binding Source={StaticResource myDataSource}, Path=PersonName}
标记扩展有一些简写语法,例如{Binding Value, ...}{Binding Paht=Value, ...}是等价的,{StaticResource myString, ...}{StaticResource ResourceKey=myString, ...}是等价的。第一种称为固定位置参数,第二种是具名参数。固定位置参数实际上就是类的构造器的位置。

导入程序集

在XAML中引入类库或其他程序集的语法是:

xmlns:映射名="clr-namespace:类库中名称空间名;assembly=类库文件名"

比如MyLibary.dll中有两个名称空间,在XAML中引用是:

xmlns:common="clr-namespace:Common;assembly=MyLibary"
xmlns:controls="clr-namespace:Controls;assembly=MyLibary"

其中:

  • xmlns是XAML中用来声明名称空间的属性,是XML namespace的缩写。
  • xmlns:后面的映射名是自定义的,所有引用的名称空间都要加上映射名(无映射名的默认名称空间已经被XAML占用)
  • 引号中的字符串决定引用那个类库的那个名称空间(Visual Studio可以自动提示)。

x名称空间

  • x:Class:指定XAML编译成的类与那个类结合(该类必须声明为partial)
  • x:Name:为XAML中的标签对象创建引用变量(也就是起个名字,接着就可以在xaml.cs的类中使用了)
  • x:FieldModifier:指定x:Name的作用域(public或者private之类的)
  • x:Key:在XAML中,可以把要复用的资源放在资源字典中(Resource Dictionary),然后通过key检索出来进行复用(在XAML对应的文件中也可以使用 String str = this.FindResource("aStr") as String;)。
  • x:Type:在C#中,Type类类似Java中的Class类,而x:Type就是专门给这种类型的属性赋值的(有了Type类之后就可以使用反射了)。
  • x:Null显式的给属性赋Null值(可以去除默认值或者同一赋值时排除个别控件)。
  • x:Array提供一个List(一般用作静态的DataSource)
  • x:Static引用静态成员(字段和属性都可以)

举几个例子:


x:Key举例

<Window x:Class="MyWpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyWpfApp"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        xmlns:tc="clr-namespace:MyWpfApp.TestClass"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <!-- 设定静态资源的Key -->
        <sys:String x:Key="aStr">Hello WPF</sys:String>
    </Window.Resources>
    <Grid>
        <!-- 在这里引用(如果及设置Text属性,又在标签内添加文字,运行结果是字符串拼在一块,但是在设计窗口中体现不出来) -->
        <TextBlock Text="{StaticResource ResourceKey=aStr}"></TextBlock>
    </Grid>
</Window>



x:Type举例

首先创建一个被反射的类:

class ClassForType : Window
{
    public void SayHello()
    {
        MessageBox.Show("Hello");
    }
}

然后创建一个自定义的Button类:

public class MyButton : Button
{
    // Type属性,在XAML中被设置
    public Type SetMe { get; set; }

    // 重写Button的click时间
    protected override void OnClick()
    {
        base.OnClick();
        // 反射创建本地类型的对象(dll中的对象需要使用Assembly类进行反射创建)
        ClassForType instance = Activator.CreateInstance(this.SetMe) as ClassForType;
        instance.SayHello();
    }
}

最后在XAML中给属性进行赋值:

<!-- tc是我自定义的名称空间 -->
<tc:MyButton SetMe="{x:Type TypeName=tc:ClassForType}"/>



x:Array

一个把x:Array作为数据源的ListBox举例

<ListBox>
    <ListBox.ItemsSource>
        <x:Array Type="sys:String">
            <sys:String>Tom</sys:String>
            <sys:String>Tim</sys:String>
            <sys:String>Mac</sys:String>
        </x:Array>
    </ListBox.ItemsSource>
</ListBox>

搜了一下关于x:Array的讲解并不是很多,看这个介绍好像用起来也不是特别方便,估计一般用来在XAML中写一些静态的值



x:Static

首先添加一个静态成员:

namespace MyWpfApp
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        // 静态字段
        public static string Title = "高山月小";
        // 静态只读属性
        public static string AText { get { return "水落石出"; } }

        ...

    }
}

在XAML中使用:

<Window x:Class="MyWpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyWpfApp"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        xmlns:tc="clr-namespace:MyWpfApp.TestClass"
        Title="{x:Static local:MainWindow.Title}" Height="450" Width="800">
    <Grid>
        <TextBox Text="{x:Static local:MainWindow.AText}"/>
    </Grid>
</Window>

VS2017中,XAML不能识别第一个字段,但实际运行是有效果的。对C#开发规范来说,字段公开时必须以属性形式,因此不用担心这个问题。

标签: WPF

添加新评论