WPF中自定义MarkupExtension

Posted Hello 寻梦者!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF中自定义MarkupExtension相关的知识,希望对你有一定的参考价值。

  在介绍这一篇文章之前,我们首先来回顾一下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的主要内容,如果未能解决你的问题,请参考以下文章

WPF MarkupExtension

WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了

WPF MarkupExtension

WPF MarkupExtension

WPF 我的程序出现“MarkupExtension 对 Setter.Value 无效”错误但在运行时工作正常

MarkupExtension的使用