c# 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,希望有需要的點擊進行下載,這篇文章隻是一個拋磚引玉的作用,希望讀完之後能引發自己更多的共鳴,最終代碼越寫越好。

以上就是c# WPF中如何自定義MarkupExtension的詳細內容,更多關於WPF中自定義MarkupExtension的資料請關註WalkonNet其它相關文章!

推薦閱讀: