WPF MarkupExtension 实践

WPF MarkupExtension 实践

我们首先来回顾一下上一篇中 MarkupExtension 的一些基础的概念,首先当然是 XAML 了,XAML 全称是 Extensible Application Markup Language (可扩展应用程序标记语言),是专门用于 WPF 技术中的 UI 设计语言,通过使用 XAML 语言,我们能够快速设计软件界面,同时能够通过绑定这种机制能够很好地实现界面和实现逻辑之间的解耦,这个就是 MVVM 模式的核心了。

Markup Extension,顾名思义,就是对 xaml 的扩展,在 XAML 中,规定如果属性以{}开始及结束,就是 Markup Extension,Markup Extension 指的是继承于 MarkupExtension 的类,首先我们通过一张图来看看 WPF 中有哪些已知的 Markup Extension。

看了这张图片之后是不是对这个 MarkupExtension 有一个常规的认识,你会发现这个在 WPF 中实在是太重要了,通过这个 MarkupExtension 我们能够实现绑定、资源等等一系列的操作,在介绍完这个之后,我们来看看,这个抽象的 MarkupExtension 基类到底是什么?里面包含些什么?怎么去使用它?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;

namespace System.Windows.Markup
{
// 摘要:
// 为所有 XAML 标记扩展提供基类。
public abstract class MarkupExtension
{
// 摘要:
// 初始化从 System.Windows.Markup.MarkupExtension 派生的类的新实例。
protected MarkupExtension();

// 摘要:
// 在派生类中实现时,返回一个对象,此对象被设置为此标记扩展的目标属性的值。
//
// 参数:
// serviceProvider:
// 可以为标记扩展提供服务的对象。
//
// 返回结果:
// 将在扩展应用到的属性上设置的对象值。
public abstract object ProvideValue(IServiceProvider serviceProvider);
}
}

其实看看里面的内容,仅仅提供了一个抽象的方法 ProvideValue,我们在继承这个抽象类后需要去重载这个抽象方法,然后来实现自己的逻辑。

在对整个 MarkupExtension 介绍之后,我们可以对它进行一个总结,那就是 XAML 标记扩展语法格式:

<元素对象 对象属性=”{扩展标记 扩展标记属性 = 扩展属性值}” />

这个是不是很熟悉,如果还是不够直观的话,我们可以通过代码来进行说明:

1
2
3
4
5
<MenuItem Header="New">
<MenuItem.Icon>
<Image Source="data/cat.png"/>
</MenuItem.Icon>
</MenuItem>

这个是 MSDN 介绍的常规方式,在这里我们可以通过三种不同的方式来达到这个目的,具体来看看是怎么实现的吧?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<Menu Grid.Column="0">
<MenuItem Header="文本">
<MenuItem Header="重做">
<MenuItem.Icon>
<Image Stretch="Uniform" Source="{extension:ImageBinding Redo}"></Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="撤销">
<MenuItem.Icon>
<Image Stretch="Uniform" Source="{extension:ImageBinding Undo}"></Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="保存所有">
<MenuItem.Icon>
<Image Stretch="Uniform" Source="{Binding SaveAll,Converter={StaticResource SourceConverter}}"></Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="测试">
<MenuItem.Icon>
<Image Stretch="Uniform" Source="Resources/Images/Redo.png"></Image>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="编辑"></MenuItem>
<MenuItem Header="视图"></MenuItem>
<MenuItem Header="插件"></MenuItem>
</Menu>

第一种方式就是我们今天重点介绍的通过继承 MarkupExtension 来实现同样的效果,我们来具体分析一下这个 ImageBinding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ImageBindingExtension : System.Windows.Markup.MarkupExtension
{
public ImageBindingExtension(string path)
: this()
{
Path = path;
}

public ImageBindingExtension()
{
}

[ConstructorArgument("path")]
public string Path
{
get;
set;
}


public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

if (target.TargetObject is Setter)
{
return new Binding(Path) { Converter = ImgaeSourceConverter.Default };
}
else
{
Binding binding = new Binding(Path) { Converter = ImgaeSourceConverter.Default };
return binding.ProvideValue(serviceProvider);
}

}
}

这里面我们定义的 Path 属性就是绑定到 ViewModel 中的一个特定的属性,这里我们通过重写 ProvideValue 方法,最终调用 BindingBase 的 ProvideValue 返回 ImageSource 对象,这里是通过一个转换器来实现源属性(字符串)到目标属性 ImageSource 的转换的,我们会发现,其实这种方法和直接绑定并设置转换器其实效果是一样的,只不过第一种方式更为直观,将所有的转换过程都放在了重写 ProvideValue 函数的过程中了,这个读者在后面可以对照 demo 去认真思考然后加以总结。

示例 2:通过 MarkupExtension 绑定到 ListBox 的 ItemsSource 属性

这个稍微复杂一些,我们在 Reflection 这个 MarkupExtension 中加入了一些自定义的属性,这些属性能够控制后面返回的数据源的最终内容,其实这个也是非常好理解的,我们在定义 RelativeSource 这个 MarkupExtension 的时候,也是通过定义 Mode、AncestorType、AncestorLevel 等属性组合起来最终实现在视觉树上找到最终的元素。在代码里面也不复杂主要是通过反射来获取 Button 的属性、方法、事件、字段等等,这个具体的实现过程可以参考后面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class ReflectionExtension : System.Windows.Markup.MarkupExtension
{
public Type CurrentType { get; set; }
public bool IncludeMethods { get; set; }
public bool IncludeFields { get; set; }
public bool IncludeEvents { get; set; }

public ReflectionExtension(Type currentType)
{
this.CurrentType = currentType;
}

public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.CurrentType == null)
{
throw new ArgumentException("Type argument is not specified");
}

ObservableCollection<string> collection = new ObservableCollection<string>();
foreach (PropertyInfo p in this.CurrentType.GetProperties())
{
collection.Add(string.Format("属性 : {0}", p.Name));
}

if (this.IncludeMethods)
{
foreach (MethodInfo m in this.CurrentType.GetMethods())
{
collection.Add(string.Format("方法 : {0} with {1} argument(s)", m.Name, m.GetParameters().Count()));
}
}
if (this.IncludeFields)
{
foreach (FieldInfo f in this.CurrentType.GetFields())
{
collection.Add(string.Format("字段 : {0}", f.Name));
}
}
if (this.IncludeEvents)
{
foreach (EventInfo e in this.CurrentType.GetEvents())
{
collection.Add(string.Format("事件 : {0}", e.Name));
}
}
return collection;
}

}

今天就如何自定义 MarkupExtension 做了一个简单的介绍,最重要的是能够通过这种方式来实现自己的合理绑定的目的,同时通过这种合理的扩展方式也能够让我们的代码更加灵活多变

评论