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

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

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

#region 程序集 WindowsBase.dll, v3.0.0.0
// C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll
#endregion 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标记扩展语法格式:

  <元素对象 对象属性=”{扩展标记 扩展标记属性 = 扩展属性值}” />
      这个是不是很熟悉,如果还是不够直观的话,我们可以通过代码来进行说明:      
<TextBox Text=”{Binding Path=ProductName}”/>

  再来一个复杂一些的例子吧,

<Popup IsOpen="{Binding Path=IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" x:Name="SubMenuPopup" Focusable="false" AllowsTransparency="true" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}"/>

  类似的这种我们在WPF中见到的是在是太多了,那么既然基类是一个抽象方法那么我们是不是可以通过重载这种方式来写自己的MarkupExtension呢?这个当然是可以的,我们可以通过下面的几个例子来进行相应的说明。

  示例1:通过MarkupExtension绑定MenuItem的Icon属性。

  我们知道,MenuItem的Icon属性可以通过下面的方式进行设置:

<MenuItem Header="New">
<MenuItem.Icon>
<Image Source="data/cat.png"/>
</MenuItem.Icon>
</MenuItem>

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

 <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

 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的属性、方法、事件、字段等等,这个具体的实现过程可以参考后面的代码。

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做了一个简单的介绍,最重要的是能够通过这种方式来实现自己的合理绑定的目的,同时通过这种合理的扩展方式也能够让我们的代码更加灵活多变,最后附上整个测试用的DEMO,希望有需要的点击进行下载,这篇文章只是一个抛砖引玉的作用,希望读完之后能引发自己更多的共鸣,最终代码越写越好。

WPF中自定义MarkupExtension的更多相关文章

  1. 在WPF中自定义你的绘制(五)

    原文:在WPF中自定义你的绘制(五) 在WPF中自定义你的绘制(五)                                                                   ...

  2. 在WPF中自定义你的绘制(三)

    原文:在WPF中自定义你的绘制(三) 在WPF中自定义你的绘制(三)                                                                  ...

  3. 在WPF中自定义你的绘制(四)

    原文:在WPF中自定义你的绘制(四)                                   在WPF中自定义你的绘制(四)                                 ...

  4. 在WPF中自定义你的绘制(一)

    原文:在WPF中自定义你的绘制(一)   在WPF中自定义你的绘制(一)                                                                 ...

  5. 在WPF中自定义你的绘制(二)

    原文:在WPF中自定义你的绘制(二)   在WPF中自定义你的绘制(二)                                                                 ...

  6. 在VS2005中设置WPF中自定义按钮的事件

    原文:在VS2005中设置WPF中自定义按钮的事件 上篇讲了如何在Blend中绘制圆角矩形(http://blog.csdn.net/johnsuna/archive/2007/08/13/17407 ...

  7. WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探

    原文:WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探         最近因为项目需要,开始学习如何使用WPF开发桌面程序.使用WPF一段时间之后,感 ...

  8. 示例:WPF中自定义MessageService应用DialogHost、Snackbar、NotifyIcon显示各种场景提示消息

    原文:示例:WPF中自定义MessageService应用DialogHost.Snackbar.NotifyIcon显示各种场景提示消息 一.目的:不同交互场景需要提示不同的消息,不同的消息需要用不 ...

  9. 示例:WPF中自定义StoryBoarService在代码中封装StoryBoard、Animation用于简化动画编写

    原文:示例:WPF中自定义StoryBoarService在代码中封装StoryBoard.Animation用于简化动画编写 一.目的:通过对StoryBoard和Animation的封装来简化动画 ...

随机推荐

  1. 第8章 java中的并发工具类

    8.1 等待线程完成的CountDownLatch 作用:让一个线程等待其余线程完成之后在继续执行,如主线程等待开启服务的子线程执行完毕后主线程继续执行,类似于join.

  2. go中rune和byte的用处

    参考:https://www.jianshu.com/p/4fbf529926ca rune是用来区分字符值和整数值的 byte 等同于int8,即一个字节长度,常用来处理ascii字符 rune 等 ...

  3. DeeplabV3+ 在自己环境下跑出现的错误

    1. no module named 'deeplab' 解决办法:把 models/research 和 models/research/slim 加到环境变量path中不管用,需要在 cmd 中运 ...

  4. JVM-Java内存区域

    JVM虚拟机运行时数据区结构分为: 其中方法区和堆是所有线程共享的内存区域,而Java栈.本地方法栈.程序计数器是线程私有的. 我们详细介绍运行时数据区的各个区域及其作用. 程序计数器: 一块较小的内 ...

  5. 每秒高达1.6亿次操作的并发键值存储库 FASTER 诞生

    FASTER 在过去十年中,云中的数据密集型应用程序和服务有了巨大的增长.数据在各种边设施(例如,设备,浏览器和服务器)上创建,并由云应用程序处理用来获得数据价值或做出决策.应用程序和服务可以处理收集 ...

  6. 如何用CSS3画出一个立体魔方?

    前言 最近在写<动画点点系列>文章,上一期分享了< 手把手教你如何绘制一辆会跑车 >,本期给大家带来是结合CSS3画出来的一个立体3d魔方,结合了js让你随心所欲想怎么转,就怎 ...

  7. Flask发送邮件

    参考:官方文档:https://pythonhosted.org/Flask-Mail/ 1.安装插件  Flask-Mail (pip install Flask-Mail) 2.配置 Flask- ...

  8. 1000/problem/A

    传送门: [http://codeforces.com/contest/1000/problem/A] 题意: 一个比赛颁奖,要准备T-Shirt给获奖者,但有的去年获奖过,衣服尺寸可以不改,有的需要 ...

  9. iOS开发——无网占位图的实现

    https://www.jianshu.com/p/d537393fe247 https://github.com/wyzxc/CQPlaceholderViewhttps://github.com/ ...

  10. 简约时尚商城wordpress主题-storefront

    wordpress主题:简约时尚商城主题-storefront 简简单的商城模板,挺适合一些懒人所用.后天功能也挺不错,希望大家喜欢. WooCommerce 集成 商城是基为用 WooCommerc ...