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

  这章主要说插件的菜单,可以说菜单是最核心的部分,前面我们已经实现了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 ICheckable
16     {
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, IDeactivate
25     {
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, ICheckable
39     {
40 
41     }
42 
43     /// <summary>
44     /// Denotes a <see cref="System.Window.Controls.Separater"/> instance.
45     /// </summary>
46     public interface ISeparaterPart
47     {
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 : IPart
55     {
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         #endregion
101 
102         #region Method
103         
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 value
114             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.Value
127                 };
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. Sort
163             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. Sort
178             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                 else
187                 {
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                         else
206                         {
207                             stillUnsortedItems.Add(newItem);
208                         }
209                     }
210                     else
211                     {
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                             else
221                             {
222                                 sortedItems.Insert(index + 1, newItem);
223                             }
224                         }
225                         else
226                         {
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 IPart
240             sortedItems.Apply(o => o.Value.OnAttached());
241 
242             return sortedItems;
243         }
244         
245         #endregion
246 
247         #region IPartManager Members
248 
249         private IObservableCollection<T> items = new BindableCollection<T>();
250         public IObservableCollection<T> Items
251         {
252             get { return items; }
253         }
254 
255         #endregion
256 
257         #region IPartImportsSatisfiedNotification Members
258 
259         public void OnImportsSatisfied()
260         {
261             ConfigParts();
262         }
263 
264         #endregion
265 
266         #region Private OrderItem Class
267 
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>> Children
277             {
278                 get
279                 {
280                     return children;
281                 }
282             }
283         }
284 
285         #endregion
286     }
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.com
10 //==================================================================================
11 namespace DemoApplication.Menu
12 {
13     [MenuPart(BaseMenu="插件")]
14     public class DockableTestPart : MenuPart
15     {
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.Menu
29 {
30     [MenuPart(BaseMenu="插件")]
31     public class DocTestViewPart : MenuPart
32     {
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.Menu
46 {
47     [MenuPart(BaseMenu="插件")]
48     public class DocTest1ViewPart : MenuPart
49     {
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/

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

posted on 2013-06-27 20:42  Zengg  阅读(5748)  评论(14编辑  收藏  举报