AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(菜单篇)

来源:互联网 发布:知乎回答被折叠 编辑:程序博客网 时间:2024/05/17 03:21

这章主要说插件的菜单,可以说菜单是最核心的部分,前面我们已经实现了Document添加,现在主要就是生成具有层级关系的菜单,以及把菜单跟我们自定义的Document关联起来,也就是MenuPart->View->Model的关联,菜单的实现部分我也是网上参照别人的来实现的,由于代码比较多,我就抽一些重要的来说,其他的只能靠各位自己去体会了,不明白的可以照葫芦画瓢,这菜单部分可以直接抽出来用的,我们不需要明白它内部是怎么实现的,能用就行了,其实有些地方我也没有深入去了解,我们主要的任务是把现有的技术融合成一个可用的插件平台,技术的细节以后有时间再讨论。。。

额外提示所有关于UI的操作都是以绑定的方式实现的,如果不熟悉WPF以及MVVM模式可能会难以理解    [BY Zengg]

运行结果:

可以看到我们已经能做到通过菜单来控制插件的显示以及关闭,插件平台已经初具雏形

UICore部分

涉及到核心的项目结构部分:

IPart 定义了菜单的基本属性,主要的方法是 void Execute(),这是单击菜单后的执行方法, 之后我们得把Show出Document的动作写入Execute方法里面。

 1 namespace UICoreFramework 2 { 3     /// <summary> 4     /// Denotes an instance which can be executed. 5     /// </summary> 6     public interface IExecutable 7     { 8         bool CanExecute { get; } 9         void Execute();10     }11 12     /// <summary>13     /// Denotes an instance which can be checked.14     /// </summary>15     public interface ICheckable16     {17         bool IsCheckable { get; }18         bool IsChecked { get; set; }19     }20 21     /// <summary>22     /// Denotes a small UI widget that can display and interact.23     /// </summary>24     public interface IPart : ILocalizableDisplay, IExecutable, IActivate, IDeactivate25     {26         string InputGestureText { get; set; }27 28         void OnAttached();29         30         string Icon { get; set; }31 32         bool IsVisible { get; set; }33     }34 35     /// <summary>36     /// Concrete <see cref="IPart"/> which denotes a <see cref="System.Window.Controls.MenuItem"/> instance.37     /// </summary>38     public interface IMenuPart : IPart, ISeparaterPart, ICheckable39     {40 41     }42 43     /// <summary>44     /// Denotes a <see cref="System.Window.Controls.Separater"/> instance.45     /// </summary>46     public interface ISeparaterPart47     {48         bool IsSeparator { get; }49     }50 51     /// <summary>52     /// Denotes a manager class that manage the <see cref="IPart"/>s.53     /// </summary>54     public interface IPartManager<T> where T : IPart55     {56         IObservableCollection<T> Items { get; }57     }58 59     /// <summary>60     /// Denotes a manager node that holds the <see cref="IObservableCollection"/> item.61     /// </summary>62     public interface IObservableParent<T>63     {64         IObservableCollection<T> Items { get; }65     }66 }

 

PartBase IPart的抽象类主要实现了IPart接口以及一些共同的抽象函数。

namespace UICoreFramework{    /// <summary>    /// Base <see cref="IPart"/> class for various implementations of <see cref="IPart"/>.    /// </summary>    public abstract class PartBase : PropertyChangedBase, IPart    {        protected PartBase()            :this(null)        {        }        protected PartBase(string name)        {            DisplayName = name;            this.Name = name ?? GetType().Name.Replace("Part", string.Empty);            this.execute = ((i) => { });            this.canExecute = (() => IsActive);        }        public PartBase(string name, System.Action<PartBase> execute, Func<bool> canExecute = null)            : this(name)        {            this.execute = execute ?? ((i) => { });            this.canExecute = canExecute ?? (() => IsActive);        }        private string name;        public string Name        {            get { return name; }            protected set { name = value; NotifyOfPropertyChange(() => Name); }        }        private string displayName;        public string DisplayName        {            get { return displayName; }            set { displayName = value; NotifyOfPropertyChange(() => DisplayName); }        }        private string icon;        public string Icon        {            get { return icon; }            set { icon = value; NotifyOfPropertyChange(() => Icon); }        }        private string inputGestureText;        public string InputGestureText        {            get { return inputGestureText; }            set { inputGestureText = value; NotifyOfPropertyChange(() => InputGestureText); }        }        private bool isVisible = true;        public bool IsVisible        {            get { return isVisible; }            set { isVisible = value; NotifyOfPropertyChange(() => IsVisible); }        }        public virtual void OnAttached()        { }        #region IExecutable        private readonly System.Action<PartBase> execute;        /// <summary>        /// The action associated to the ActionItem        /// </summary>        public virtual void Execute()        {            this.execute(this);        }        private readonly Func<bool> canExecute;        /// <summary>        /// Calls the underlying canExecute function.        /// </summary>        public virtual bool CanExecute        {            get { return canExecute(); }        }        #endregion        #region Activation & Deactivation        public event EventHandler<ActivationEventArgs> Activated;        public event EventHandler<DeactivationEventArgs> AttemptingDeactivation;        public event EventHandler<DeactivationEventArgs> Deactivated;        private bool isActive = true;        public bool IsActive        {            get { return isActive; }        }        public void Activate()        {            if (IsActive)                return;            isActive = true;            OnActivate();            if (Activated != null)                Activated(this, new ActivationEventArgs { WasInitialized = false });            NotifyOfPropertyChange(() => CanExecute);        }        protected virtual void OnActivate() { }        public virtual void Deactivate(bool close)        {            if (!IsActive)                return;            if (AttemptingDeactivation != null)                AttemptingDeactivation(this, new DeactivationEventArgs { WasClosed = close });            isActive = false;            OnDeactivate(close);            NotifyOfPropertyChange(() => CanExecute);            if (Deactivated != null)                Deactivated(this, new DeactivationEventArgs { WasClosed = close });        }        protected virtual void OnDeactivate(bool close) { }        #endregion        #region IHandle<LanguageChangedEventArgs> Members        //public void Handle(LanguageChangedMessage message)        //{        //    this.UpdateDisplayName();        //}        #endregion    }}
PartBase

 

MenuPart 继承于PartBase,这个类跟界面的Menu.Item形成对应关系,也就是Menu.Item具有的重要属性MenuPart也基本会有,一个MenuPart等于一个Menu.Item,之后我们会把MenuPart的具体实例绑定到Menu.Item上,这样就实现了MenuPart和界面Menu.Item的关联。

namespace UICoreFramework{    /// <summary>    /// A menu part for various implementations of <see cref="IMenuPart"/>.    /// </summary>    public class MenuPart : PartBase, IMenuPart, IObservableParent<IMenuPart>    {        public MenuPart()            : base()        {        }        public MenuPart(string name)            : base(name)        {        }        public MenuPart(string name, System.Action<PartBase> execute, Func<bool> canExecute = null)            : base(name, execute, canExecute)        {        }        private IObservableCollection<IMenuPart> items = new BindableCollection<IMenuPart>();        IObservableCollection<IMenuPart> IObservableParent<IMenuPart>.Items        {            get { return items; }        }        #region ISeparaterPart Members        private bool _isSeparator = false;        public bool IsSeparator        {            get            {                return _isSeparator;            }            protected set            {                _isSeparator = value;                NotifyOfPropertyChange(() => IsSeparator);            }        }        #endregion        #region IMenuPart Members        private bool _isCheckable = false;        public bool IsCheckable        {            get            {                return _isCheckable;            }            set            {                _isCheckable = value;                NotifyOfPropertyChange(() => IsCheckable);            }        }        private bool _isChecked = false;        public bool IsChecked        {            get            {                return _isChecked;            }            set            {                _isChecked = value;                NotifyOfPropertyChange(() => IsChecked);            }        }        #endregion    }}
MenuPart

 

PartManager MenuPart的管理是由该类处理的,比如菜单的分层,排序等,该类对应着界面的Menu控件,之后会把PartManager绑定到Menu控件。

  1 namespace UICoreFramework  2 {  3     /// <summary>  4     /// Concrete <see cref="IPartManager"/> with manages <see cref="IPart"/> items, it uses MEF to construct the <see cref="IPart"/>s.  5     /// </summary>  6     public class PartManager<T> : IPartManager<T>, IPartImportsSatisfiedNotification where T : IPart  7     {  8         #region Constructor  9          10         public PartManager() 11         { 12         } 13          14         #endregion 15  16         #region Property 17          18         [ImportMany] 19         protected T[] InternalItems { get; set; } 20          21         #endregion 22  23         #region Method 24          25         protected virtual void ConfigParts() 26         { 27             if (InternalItems == null || InternalItems.Length == 0) 28             { 29                 return; 30             } 31  32             items.Clear(); 33             items.AddRange(InternalItems); 34         } 35          36         #endregion 37  38         #region IPartManager Members 39  40         private IObservableCollection<T> items = new BindableCollection<T>(); 41         public IObservableCollection<T> Items 42         { 43             get { return items; } 44         } 45  46         #endregion 47  48         #region IPartImportsSatisfiedNotification Members 49  50         public void OnImportsSatisfied() 51         { 52             ConfigParts(); 53         } 54  55         #endregion 56     } 57  58     /// <summary> 59     /// Extends the <see cref="IPartManager"/> that support <see cref="IPartMetaData"/>. 60     /// </summary> 61     /// <typeparam name="T">IPart</typeparam> 62     /// <typeparam name="TMetadata">The type of the metadata.</typeparam> 63     public class PartManager<T, TMetadata> : IPartManager<T>, IPartImportsSatisfiedNotification 64         where T : IPart 65         where TMetadata : IPartMetaData 66     { 67         #region Field 68          69         protected static readonly Func<TMetadata, string> BasePart; 70         protected static readonly Func<TMetadata, string> PreviousPart; 71         protected static readonly Func<TMetadata, string> NextPart; 72          73         #endregion 74  75         #region Constructor 76          77         static PartManager() 78         { 79             var props = typeof(TMetadata).GetProperties(); 80             BasePart = DynamicAccessEngine.GetPropertyDelegate<TMetadata, string>(props.FirstOrDefault(it => it.Name.Contains("Base")).Name); 81             PreviousPart = DynamicAccessEngine.GetPropertyDelegate<TMetadata, string>(props.FirstOrDefault(it => it.Name.Contains("Previous")).Name); 82             NextPart = DynamicAccessEngine.GetPropertyDelegate<TMetadata, string>(props.FirstOrDefault(it => it.Name.Contains("Next")).Name); 83         } 84  85         public PartManager() 86         { 87         } 88          89         #endregion 90  91         #region Property 92  93         [ImportMany] 94         protected Lazy<T, TMetadata>[] InternalItems 95         { 96             get; 97             set; 98         } 99         100         #endregion101 102         #region Method103         104         protected virtual void ConfigParts()105         {106             if (InternalItems == null || InternalItems.Length == 0)107             {108                 return;109             }110 111             items.Clear();112 113             //Sort items according to metadata's Base , Previous, Next value114             SortItems();115         }116 117         protected virtual void SortItems()118         {119             var items = InternalItems.Select((it) =>120             {121                 return new OrderItem<T>()122                 {123                     Base = BasePart(it.Metadata),124                     Before = PreviousPart(it.Metadata),125                     After = NextPart(it.Metadata),126                     Value = it.Value127                 };128             }).ToList();129 130             var roots = SortAndAttachItems(items.Where(it => string.IsNullOrEmpty(it.Base)).ToList());131 132             foreach (var item in items)133             {134                 var baseItem = items.FirstOrDefault(it => string.Equals(it.Value.Name, item.Base));135                 if (baseItem != null)136                 {137                     baseItem.Children.Add(item);138                 }139             }140 141             foreach (var item in roots)142             {143                 SortItem(item);144             }145 146             Items.AddRange(roots.Select(it => it.Value));147         }148 149         private void SortItem(OrderItem<T> item)150         {151             if (item.Children.Count == 0)152             {153                 return;154             }155 156             //1. Child recursion.157             foreach (var it in item.Children)158             {159                 SortItem(it);160             }161 162             //2. Sort163             var sortedItems = SortAndAttachItems(item.Children);164 165             foreach (var it in sortedItems)166             {167                 IObservableParent<T> parent = item.Value as IObservableParent<T>;168                 if (parent != null)169                 {170                     parent.Items.Add(it.Value);171                 }172             }173         }174 175         private List<OrderItem<T>> SortAndAttachItems(List<OrderItem<T>> items)176         {177             //1. Sort178             var sortedItems = new List<OrderItem<T>>();179             var unsortedItems = new List<OrderItem<T>>();180             foreach (var newItem in items)181             {182                 if (string.IsNullOrEmpty(newItem.Before) && string.IsNullOrEmpty(newItem.After))183                 {184                     sortedItems.Add(newItem);185                 }186                 else187                 {188                     unsortedItems.Add(newItem);189                 }190             }191 192             while (unsortedItems.Count > 0)193             {194                 List<OrderItem<T>> stillUnsortedItems = new List<OrderItem<T>>();195                 int startingCount = unsortedItems.Count;196                 foreach (var newItem in unsortedItems)197                 {198                     if (!string.IsNullOrEmpty(newItem.After))199                     {200                         var beforeItem = sortedItems.FirstOrDefault(it => it.Value.Name == newItem.After);201                         if (beforeItem != null)202                         {203                             sortedItems.Insert(sortedItems.IndexOf(beforeItem), newItem);204                         }205                         else206                         {207                             stillUnsortedItems.Add(newItem);208                         }209                     }210                     else211                     {212                         var afterItem = sortedItems.FirstOrDefault(it => it.Value.Name == newItem.Before);213                         if (afterItem != null)214                         {215                             int index = sortedItems.IndexOf(afterItem);216                             if (index == sortedItems.Count - 1)217                             {218                                 sortedItems.Add(newItem);219                             }220                             else221                             {222                                 sortedItems.Insert(index + 1, newItem);223                             }224                         }225                         else226                         {227                             stillUnsortedItems.Add(newItem);228                         }229                     }230                 }231                 if (startingCount == stillUnsortedItems.Count)232                 {233                     sortedItems.Add(stillUnsortedItems[0]);234                     stillUnsortedItems.RemoveAt(0);235                 }236                 unsortedItems = stillUnsortedItems;237             }238 239             //2. Call Attached method of IPart240             sortedItems.Apply(o => o.Value.OnAttached());241 242             return sortedItems;243         }244         245         #endregion246 247         #region IPartManager Members248 249         private IObservableCollection<T> items = new BindableCollection<T>();250         public IObservableCollection<T> Items251         {252             get { return items; }253         }254 255         #endregion256 257         #region IPartImportsSatisfiedNotification Members258 259         public void OnImportsSatisfied()260         {261             ConfigParts();262         }263 264         #endregion265 266         #region Private OrderItem Class267 268         private class OrderItem<U>269         {270             public string Base { get; set; }271             public string Before { get; set; }272             public string After { get; set; }273             public U Value { get; set; }274 275             private List<OrderItem<U>> children = new List<OrderItem<U>>();276             public List<OrderItem<U>> Children277             {278                 get279                 {280                     return children;281                 }282             }283         }284 285         #endregion286     }
PartManager

Util文件夹里的类是提供更方便的绑定方式,这里就不做过多解释

DemoApplication 主程序部分结构:

可以看到Menu放的就是我们的各个菜单,主要看一下AddinPart,这个就是我们从界面上看到有"插件"两个字的菜单

AddinPart

using System;using System.Collections.Generic;using System.Linq;using System.Text;using UICoreFramework;//=================================================================================////        Copyright (C) 20013-2014    //        All rights reserved//        //        description : 本系列于博客园首发,如果您想转载本博客,请注明出处,感谢支持  //        created by Zengg//        http://www.cnblogs.com/01codeworld///        Email:281365330@qq.com//==================================================================================namespace DemoApplication.Menu{    //public string MenuName { get; set; } //菜单名称    //public string BaseMenu { get; set; }//父菜单名称    //public string PreviousMenu { get; set; }//该入哪个菜单名的后面    //public string NextMenu { get; set; }//下一个菜单是什么    //PartManager会以注入的元数据做为依据进行菜单层级的排序    [MenuPart(PreviousMenu = "工具")]    public class AddinPart : MenuPart    {        public AddinPart()            : base("插件")        {        }    }}


在AddinPart下面我们可以看到已经有了3个插件,工具栏(DockableContent类型),测试界面,测试插件2,

这3个插件分别对应着

DockableTestPart DocTestViewPart DocTest1ViewPart

 1 //================================================================================= 2 // 3 //        Copyright (C) 20013-2014     4 //        All rights reserved 5 //         6 //        description : 本系列于博客园首发,如果您想转载本博客,请注明出处,感谢支持   7 //        created by Zengg 8 //        http://www.cnblogs.com/01codeworld/ 9 //        Email:281365330@qq.com10 //==================================================================================11 namespace DemoApplication.Menu12 {13     [MenuPart(BaseMenu="插件")]14     public class DockableTestPart : MenuPart15     {16          public DockableTestPart()17             : base("工具栏")18         {19 20         }21         public override void Execute()22         {23             IoC.Get<IDockScreenManager>()["工具栏"].Show();24         }25     }26 }27 28 namespace DemoApplication.Menu29 {30     [MenuPart(BaseMenu="插件")]31     public class DocTestViewPart : MenuPart32     {33         public DocTestViewPart()34             : base("测试界面")35         {36 37         }38         public override void Execute()39         {40             IoC.Get<IDockScreenManager>()["测试界面"].Show();41         }42     }43 }44 45 namespace DemoApplication.Menu46 {47     [MenuPart(BaseMenu="插件")]48     public class DocTest1ViewPart : MenuPart49     {50         public DocTest1ViewPart()51             : base("测试插件2")52         {53 54         }55         public override void Execute()56         {57             IoC.Get<IDockScreenManager>()["测试插件2"].Show();58         }59     }60 }


Execute执行的就是IDockScreen接口的Show方法,执行这个方法后对应的UI部件就会显示出来。

IDockScreen接口增加部分

    /// <summary>    /// 抽象出基本的Content类型,并实现属性通知接口,因为以后绑定到AvalonDock是有双向绑定的,这样我们要操作AvalonDock时    /// 就可以直接操作实现该接口的属性,比如DisplayName的属性用于绑定到AvalonDock的LayoutItem的Title    /// </summary>    public interface IDockScreen    {        DockType Type { get; set; }        DockSide Side { get; set; }        ICommand CloseCommand { get; }//双向绑定AvalonDock的LayoutItem的CloseCommand命令        string DisplayName { get; set; }        bool IsActive { get; set; }//双向绑定AvalonDock的LayoutItem的IsActive属性        bool IsSelected { get; set; }//双向绑定AvalonDock的LayoutItem的IsSelected属性        void Show();//把控件显示出来        void Close();//把控件关闭    }


接下来主要说到,怎么把IDockScreen类型和AvalonDock里的Document ,Anchorable进行双向绑定,我们来看一看DockView.xaml [BY Zengg]

 

<UserControl x:Class="DemoApplication.Views.DockView"             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"              xmlns:Micro="http://www.caliburnproject.org"             xmlns:UICore="clr-namespace:UICoreFramework;assembly=UICoreFramework"             mc:Ignorable="d"              xmlns:avalonDock="http://avalondock.codeplex.com"             d:DesignHeight="300" d:DesignWidth="800">    <UserControl.Resources>        <avalonDock:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>    </UserControl.Resources>    <Grid>        <Grid x:Name="layoutRoot">            <avalonDock:DockingManager DocumentsSource="{Binding Documents}" AnchorablesSource="{Binding DockableContents}"  Grid.Row="1" x:Name="dockManager"  AllowMixedOrientation="True"  >                <avalonDock:DockingManager.Theme>                    <avalonDock:AeroTheme/>                </avalonDock:DockingManager.Theme>                <avalonDock:DockingManager.LayoutItemTemplate>                    <DataTemplate>                        <ContentControl Micro:View.Model="{Binding}" IsTabStop="False" />                    </DataTemplate>                </avalonDock:DockingManager.LayoutItemTemplate>                <avalonDock:DockingManager.LayoutItemContainerStyleSelector>                    <UICore:PanesStyleSelector>                        <UICore:PanesStyleSelector.ToolStyle>                            <Style TargetType="{x:Type avalonDock:LayoutAnchorableItem}">                                <Setter Property="Title" Value="{Binding Model.DisplayName}"/>                                <Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}"></Setter>                                <Setter Property="IsActive" Value="{Binding Model.IsActive,Mode=TwoWay}"></Setter>                                <Setter Property="Visibility" Value="{Binding Model.Visibility,Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter={x:Static Visibility.Hidden}}"></Setter>                            </Style>                            </UICore:PanesStyleSelector.ToolStyle>                        <UICore:PanesStyleSelector.DocumentStyle>                            <Style TargetType="{x:Type avalonDock:LayoutItem}">                                <Setter Property="Title" Value="{Binding Model.DisplayName}"/>                                <Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}"></Setter>                                <Setter Property="IsActive" Value="{Binding Model.IsActive,Mode=TwoWay}"></Setter>                            </Style>                            </UICore:PanesStyleSelector.DocumentStyle>                                </UICore:PanesStyleSelector>                </avalonDock:DockingManager.LayoutItemContainerStyleSelector>                <avalonDock:LayoutRoot>                    <avalonDock:LayoutPanel  Orientation="Horizontal"  >                        <avalonDock:LayoutAnchorablePaneGroup DockWidth="200"    Orientation="Vertical"  >                            <avalonDock:LayoutAnchorablePane  >                            </avalonDock:LayoutAnchorablePane>                        </avalonDock:LayoutAnchorablePaneGroup>                        <avalonDock:LayoutPanel  Orientation="Vertical"  >                            <avalonDock:LayoutDocumentPaneGroup Orientation="Horizontal">                                <avalonDock:LayoutDocumentPane   >                                </avalonDock:LayoutDocumentPane>                            </avalonDock:LayoutDocumentPaneGroup>                            <avalonDock:LayoutAnchorablePaneGroup DockHeight="100"   Orientation="Horizontal"  >                                <avalonDock:LayoutAnchorablePane  >                                </avalonDock:LayoutAnchorablePane>                            </avalonDock:LayoutAnchorablePaneGroup>                        </avalonDock:LayoutPanel>                        <avalonDock:LayoutAnchorablePaneGroup DockWidth="200"    Orientation="Horizontal"  >                            <avalonDock:LayoutAnchorablePane >                            </avalonDock:LayoutAnchorablePane>                        </avalonDock:LayoutAnchorablePaneGroup>                    </avalonDock:LayoutPanel>                </avalonDock:LayoutRoot>            </avalonDock:DockingManager>        </Grid>    </Grid></UserControl>

在 <avalonDock:DockingManager.LayoutItemContainerStyleSelector> </avalonDock:DockingManager.LayoutItemContainerStyleSelector>区域里的部分,就是对Document ,Anchorable两种类型进行双向绑定。

Menu

我们的View文件夹多出了MenuView.xaml,MenuViewModel两个文件,这就是我们的菜单,来看看PartManager是怎么绑定到MenuView.xaml的

MenuView.xaml

<UserControl x:Class="DemoApplication.Views.MenuView"             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"              xmlns:fs="clr-namespace:UICoreFramework;assembly=UICoreFramework"             xmlns:Micro="http://www.caliburnproject.org"             mc:Ignorable="d"            d:DesignHeight="25" d:DesignWidth="200">    <UserControl.Resources>        <ResourceDictionary >            <ResourceDictionary.MergedDictionaries>            </ResourceDictionary.MergedDictionaries>            <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>            <fs:GenericBindingConverter x:Key ="GenericBindingConverter"/>                <LinearGradientBrush x:Key="AvalonDock_ThemeAero_BaseColor1"                        StartPoint="0,0"                        EndPoint="1,0">        <LinearGradientBrush.GradientStops>            <GradientStop Color="#EBEEFA" Offset="0" />            <GradientStop Color="#F4F7FC" Offset="1" />        </LinearGradientBrush.GradientStops>    </LinearGradientBrush>        </ResourceDictionary>    </UserControl.Resources>    <Grid  >        <Menu Name="MainMenu"  Background="{x:Null}"  ItemsSource="{Binding Items}" >            <Menu.ItemContainerStyle>                <Style TargetType="MenuItem" >                    <Setter Property="Header" Value="{Binding DisplayName}" />                    <Setter Property="IsCheckable" Value="{Binding IsCheckable}" />                    <Setter Property="InputGestureText" Value="{Binding Path=InputGestureText}"/>                    <Setter Property="IsChecked" Value="{Binding IsChecked, Mode=OneWay}" />                    <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource Self}, Path=DataContext, Converter={StaticResource GenericBindingConverter}, ConverterParameter=IObservableParent&lt;IMenuPart&gt;.Items}" />                    <Setter Property="IsEnabled" Value="{Binding CanExecute}" />                    <Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" />                    <Setter Property="Micro:Message.Attach" Value="[Event Click] = [Action Execute()]" />                    <Style.Triggers>                        <DataTrigger Binding="{Binding Path=IsSeparator}"                                Value="True">                            <Setter Property="Template">                                <Setter.Value>                                    <ControlTemplate TargetType="{x:Type MenuItem}">                                        <Separator                          Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/>                                    </ControlTemplate>                                </Setter.Value>                            </Setter>                        </DataTrigger>                    </Style.Triggers>                </Style>            </Menu.ItemContainerStyle>        </Menu>    </Grid></UserControl>

 

MenuViewModel

//=================================================================================////        Copyright (C) 20013-2014    //        All rights reserved//        //        description : 本系列于博客园首发,如果您想转载本博客,请注明出处,感谢支持  //        created by Zengg//        http://www.cnblogs.com/01codeworld///        Email:281365330@qq.com//==================================================================================namespace DemoApplication.Views{    [Export(typeof(IPartManager<IMenuPart>))]    public class MenuViewModel : PartManager<IMenuPart, IMenuPartMetaData>    {            }}

自定义菜单定义好了,我们要把它绑定到ShellView里面

<MetrolControls:MetroWindow x:Class="DemoApplication.ShellView"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:MetrolControls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"        Title="ShellView" Height="500" Width="800">    <Window.Resources>        <ResourceDictionary>            <ResourceDictionary.MergedDictionaries>                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colours.xaml"/>                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml"/>                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml"/>                            </ResourceDictionary.MergedDictionaries>            <LinearGradientBrush x:Key="AvalonDock_ThemeAero_BaseColor1"                        StartPoint="0,0"                        EndPoint="1,0">                <LinearGradientBrush.GradientStops>                    <GradientStop Color="#EBEEFA" Offset="0" />                    <GradientStop Color="#F4F7FC" Offset="1" />                </LinearGradientBrush.GradientStops>            </LinearGradientBrush>        </ResourceDictionary>    </Window.Resources>    <Grid Background="{StaticResource AvalonDock_ThemeAero_BaseColor1}" >        <Grid.RowDefinitions>            <RowDefinition Height="30"/>            <RowDefinition Height="*"/>            <RowDefinition Height="30"/>        </Grid.RowDefinitions>        <ContentControl  x:Name="MenuContent" Margin="0,2,0,0" Grid.Row="0"/>        <ContentControl  x:Name="DockContent" Margin="0,2,0,0" Grid.Row="1"/>    </Grid></MetrolControls:MetroWindow>

MenuContent就是MenuView  [BY Zengg]

ShellViewModel

//=================================================================================////        Copyright (C) 20013-2014    //        All rights reserved//        //        description : 本系列于博客园首发,如果您想转载本博客,请注明出处,感谢支持  //        created by Zengg//        http://www.cnblogs.com/01codeworld///        Email:281365330@qq.com//==================================================================================namespace DemoApplication{    [Export(typeof(IShell))]    class ShellViewModel : IShell    {        readonly IWindowManager windowManager;        [ImportingConstructor]        public ShellViewModel(IWindowManager windowManager)        {            this.windowManager = windowManager;        }        /// <summary>        /// DockView        /// </summary>        [Import]        public IDockScreenManager DockContent { get; set; }        [Import]        public IPartManager<IMenuPart> MenuContent { get; set; }    }}


大致的流程就是这样,我也说不了多细致毕竟也有一定的代码量,一个个来说不知道得写多久,大家自己研究源码吧!涉及到的基本知识:WPF的数据绑定,MVVM,MEF,如果明白这些基本知识,理解代码应该不难,我也是边更新代码边写博客,这只是给大家提供一个思路,如果想要更完善得大家自己去扩展完善,如果发现有错误的地方请及时告知。。非常感谢

章节预告:

4:寻找插件并加载插件

5:多语言化

6:换肤功能

第三部分源码:http://pan.baidu.com/share/link?shareid=859524889&uk=554439928

如果您看了本篇博客,觉得对您有所收获,请点击右下角的 [推荐]

如果您想转载本博客,请注明出处

如果您对本文有意见或者建议,欢迎留言

感谢您的阅读,请关注我的后续博客

作者:Zengg 

出处:http://www.cnblogs.com/01codeworld/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


<script type="text/javascript"><!--google_ad_client = "ca-pub-1944176156128447";/* cnblogs 首页横幅 */google_ad_slot = "5419468456";google_ad_width = 728;google_ad_height = 90;//--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>