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