现在的位置: 首页 > 综合 > 正文

WPF 学习笔记 – 4. XAML

2013年08月15日 ⁄ 综合 ⁄ 共 6599字 ⁄ 字号 评论关闭

Microsoft 将 XAML 定义为 "简单"、"通用"、"声明式" 的 "编程语言"。这意味着我们会在更多的地方看到它(比如
Silverlight),而且它显然比其原始版本 XML (XAML 是一种基于 XML 且遵循 XML 结构规则的语言)
多了更多的逻辑处理手段。如果愿意的话,我们完全可以抛开 XAML 来编写 WPF 程序。只不过这有点类似用记事本开发 .NET
程序的意味,好玩不好用。XAML 的定义模式使得非编程人员可以用 "易懂" 的方式来刻画 UI,并且这种方式我们早已熟悉,比如
WebForm,亦或者是我一直念念不忘的 Delphi Form (偶尔想起而已,其实早将 Object Pascal 忘得精光了)。

<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
  </Grid>
</Window>

这是一个非常简单的 XAML,它定义了一个空白 WPF 窗体(Window)。XAML 对应于 .NET 代码,只不过这个过程由特定的 XAML 编译器和运行时解释器完成。当解释器处理上面这段代码时,相当于:

new Window1 { Title = "Window1" };

从这里我们可以体会两者的区别,用 XAML 的好处是可以在设计阶段就能看到最终的展现效果,很显然这是美工所需要的。你可以从 VS 命令行输入 "xamlpad.exe",这样你会看到直观的效果。

作为一种应用于 .NET 平台的 "语言",XAML 同样支持很多我们所熟悉也是必须的概念。

1. Namespace

XAML 默认将下列 .NET Namespace 映射到 "http://schemas.microsoft.com/winfx/2006/xaml/presentation":

System.Windows
System.Windows.Automation
System.Windows.Controls
System.Windows.Controls.Primitives
System.Windows.Data
System.Windows.Documents
System.Windows.Forms.Integration
System.Windows.Ink
System.Windows.Input
System.Windows.Media
System.Windows.Media.Animation
System.Windows.Media.Effects
System.Windows.Media.Imaging
System.Windows.Media.Media3D
System.Windows.Media.TextFormatting
System.Windows.Navigation
System.Windows.Shapes

除了这个包含绝大多数 WPF 所需类型的主要命名空间外,还有一个是 XAML 专用的命名空间 (System.Windows.Markup) —— "http://schemas.microsoft.com/winfx/2006/xaml"。使用非默认命名空间的语法有点类似于 C# Namespace Alias, 我们需要添加一个前缀,比如下面示例中的 "x"。

<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
    <TextBox x:Name="txtUsername" Background="{x:Null}"></TextBox>
  </Grid>
</Window>

我们还可以引入 CLR Namespace。

<collections:Hashtable
  xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
  xmlns:sys="clr-namespace:System;assembly=mscorlib">
  <sys:Int32 x:Key="key1">1</sys:Int32>
</collections:Hashtable>

2. Property

我们可以用下面两种方式来设置 XAML 元素的属性。

方式1

<Label Name="label10" Foreground="Red">Label10</Label>

 

方式2

<Label Name="label11">
  <Label.Content>
    Label11
  </Label.Content>
  <Label.Foreground>
    Blue
  </Label.Foreground>
</Label>

WPF 会按下列顺序将 XAML 中的属性字符串转换为实际属性值。

(1) 属性值以大括号开始,或者属性是从 MarkupExtension 派生的元素,则使用标记扩展处理。
(2) 属性用指定的 TypeConverter 声明的,或者使用了转换特性(TypeConverterAttribute),则提交到类型转换器。
(3) 尝试基元类型转换,包括枚举名称检查。

<Trigger Property="Visibility" Value="Collapsed,Hidden">
  <Setter ... />
</Trigger>

3. TypeConverter

WPF 提供了大量的类型转换器,以便将类似下面示例中的 Red 字符串转换城 SystemWindows.Media.Brushes.Red。

<Label Name="label10" Foreground="Red"></Label>

等价于

this.label10.Foreground = System.Windows.Media.Brushes.Red;

不过下面的代码更能反应运行期的实际转换行为

var typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(Brush));
this.label10.Foreground = (Brush)typeConverter.ConvertFromInvariantString("Red");

有关转换器列表,可参考:ms-help://MS.MSDNQTR.v90.chs/fxref_system/html/35bffd5f-b9aa-1ccd-99fe-b0833551e562.htm

4. MarkupExtension


XAML 的一种扩展,以便支持复杂的属性值。这些标记扩展通常继承自 MarkupExtension,并使用大括号包含。WPF
提供了一些常用的标记扩展,诸如
NullExtension、StaticExtension、DynamicResourceExtension、
StaticResourceExtension、Binding 等。和 Attribute 规则类似,我们通常可以省略 Extension
这个后缀。需要注意的是某些标记扩展属于 System.Windows.Markup,因此我们需要添加命名空间前缀。

<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
    <TextBox Background="{x:Null}"></TextBox>
  </Grid>
</Window>

我们可以为标记扩展提供其所需的构造参数。

<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
    <Label Content="{x:Static SystemParameters.IconHeight}" />
  </Grid>
</Window>


个例子中,我们将 System.Windows.SystemParameters.IconHeight 值作为参数传递给 "public
StaticExtension(string member)"
构造方法,这种参数通常被称作定位参数。而另外一种参数是将特定的值传给标记扩展对象属性,语法上必须指定属性名称,故被称之为命名参数。下面的例子表示
将 textBox1.Text 参数绑定到 Label.Content 上,这样当编辑框内容发生变化时,标签内容自动保持同步。

<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
    <TextBox Name="textBox1" />
    <Label Content="{Binding ElementName=textBox1, Path=Text}" />
 </Grid>
</Window>

标记扩展允许嵌套,并可以引用自身。我们看另外一个例子。

<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
    <TextBox Name="textBox2" Width="128" Text="{Binding RelativeSource={RelativeSource Self}, Path=Width}" />
  </Grid>
</Window>

这个例子的意思是将 TextBox.Text 内容绑定为其自身(Self)的高度值(Width)。

标记扩展带来一个问题就是大括号的转义,毕竟很多时候我们需要在内容显示中使用它。解决方法是在前面添加一对额外的大括号。

<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
    <Label Content="{}{Hello, World!}" />
  </Grid>
</Window>

如果觉得难看,也可以写成下面这样。

<Window x:Class="Learn.WPF.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1">
  <Grid>
    <Label>{Hello, World!}</Label>
  </Grid>
</Window>

5. Content

XAML 这点和 HTML 非常类似,我们可以将任何内容添加到元素内容项中,这带来更加丰富的 UI 表达能力,再也不像 WinForm 那样 "能做什么,不能做什么"。

<Button>
  <Hyperlink>Click</Hyperlink>
</Button>

有一点需要注意,内容项并不一定就是 Content。像 ComboBox、ListBox、TabControl 使用 Items 作为内容项。

6. XamlReader & XamlWriter


常情况下,XAML 在项目编译时会被压缩成 BAML (Binary Application Markup Language)
保存到资源文件中。BAML 只是包含 XAML 的纯格式声明,并没有任何事件之类的执行代码,切记不要和 MSIL 相混淆。XAML
运行期解释器解读 BAML 并生成相应的元素对象。

System.Windows.Markup 命名空间中提供了 XamlReader、XamlWriter 两个类型,允许我们手工操控 XAML 文件。

var
window = (Window)XamlReader.Parse("<Window
xmlns=/"http://schemas.microsoft.com/winfx/2006/xaml/presentation/"></Window>");
window.ShowDialog();

当然,我们还可以从文件流中读取。

using (var stream = new FileStream(@"test.xaml", FileMode.Open))
{
  var window = (Window)XamlReader.Load(stream);

  var button = (Button)window.FindName("btnOK");
  button.Click += (s, ex) => MessageBox.Show("Hello, World!");

  window.ShowDialog();
}

test.xaml

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window2" Height="300" Width="300">
  <Grid>
    <Button x:Name="btnOK">OK</Button>
  </Grid>
</Window>

需要注意的是 XamlReader 载入的 XAML 代码不能包含任何类型(x:Class)以及事件代码(x:Code)。

我们可以用 XamlWriter 将一个编译的 BAML 还原成 XAML。

var xaml = XamlWriter.Save(new Window2());
MessageBox.Show(xaml);

输出:

<Window2
  Title="Window2" Width="300" Height="300"
  xmlns="clr-namespace:Learn.WPF;assembly=Learn.WPF"
  xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <av:Grid>
    <av:Button Name="btnOK">OK</av:Button>
  </av:Grid>
</Window2>

XAML 的动态载入在使用动态皮肤场景时非常有用,现在只要了解一下即可。

抱歉!评论已关闭.