博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(菜单篇)
阅读量:4954 次
发布时间:2019-06-12

本文共 20513 字,大约阅读时间需要 68 分钟。

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

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

运行结果:

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

UICore部分

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

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

1 namespace UICoreFramework 2 { 3     ///  4     /// Denotes an instance which can be executed. 5     ///  6     public interface IExecutable 7     { 8         bool CanExecute { get; } 9         void Execute();10     }11 12     /// 13     /// Denotes an instance which can be checked.14     /// 15     public interface ICheckable16     {17         bool IsCheckable { get; }18         bool IsChecked { get; set; }19     }20 21     /// 22     /// Denotes a small UI widget that can display and interact.23     /// 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     /// 36     /// Concrete 
which denotes a
instance.37 ///
38 public interface IMenuPart : IPart, ISeparaterPart, ICheckable39 {40 41 }42 43 /// 44 /// Denotes a
instance.45 ///
46 public interface ISeparaterPart47 {48 bool IsSeparator { get; }49 }50 51 /// 52 /// Denotes a manager class that manage the
s.53 ///
54 public interface IPartManager
where T : IPart55 {56 IObservableCollection
Items { get; }57 }58 59 ///
60 /// Denotes a manager node that holds the
item.61 ///
62 public interface IObservableParent
63 {64 IObservableCollection
Items { get; }65 }66 }

 

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

namespace UICoreFramework{    ///     /// Base 
class for various implementations of
. ///
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
execute, Func
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
execute; ///
/// The action associated to the ActionItem /// public virtual void Execute() { this.execute(this); } private readonly Func
canExecute; ///
/// Calls the underlying canExecute function. /// public virtual bool CanExecute { get { return canExecute(); } } #endregion #region Activation & Deactivation public event EventHandler
Activated; public event EventHandler
AttemptingDeactivation; public event EventHandler
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
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{    ///     /// A menu part for various implementations of 
. ///
public class MenuPart : PartBase, IMenuPart, IObservableParent
{ public MenuPart() : base() { } public MenuPart(string name) : base(name) { } public MenuPart(string name, System.Action
execute, Func
canExecute = null) : base(name, execute, canExecute) { } private IObservableCollection
items = new BindableCollection
(); IObservableCollection
IObservableParent
.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     ///   4     /// Concrete 
with manages
items, it uses MEF to construct the
s. 5 ///
6 public class PartManager
: IPartManager
, 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
items = new BindableCollection
(); 41 public IObservableCollection
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 ///
59 /// Extends the
that support
. 60 ///
61 ///
IPart
62 ///
The type of the metadata.
63 public class PartManager
: IPartManager
, IPartImportsSatisfiedNotification 64 where T : IPart 65 where TMetadata : IPartMetaData 66 { 67 #region Field 68 69 protected static readonly Func
BasePart; 70 protected static readonly Func
PreviousPart; 71 protected static readonly Func
NextPart; 72 73 #endregion 74 75 #region Constructor 76 77 static PartManager() 78 { 79 var props = typeof(TMetadata).GetProperties(); 80 BasePart = DynamicAccessEngine.GetPropertyDelegate
(props.FirstOrDefault(it => it.Name.Contains("Base")).Name); 81 PreviousPart = DynamicAccessEngine.GetPropertyDelegate
(props.FirstOrDefault(it => it.Name.Contains("Previous")).Name); 82 NextPart = DynamicAccessEngine.GetPropertyDelegate
(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
[] 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
()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
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
parent = item.Value as IObservableParent
;168 if (parent != null)169 {170 parent.Items.Add(it.Value);171 }172 }173 }174 175 private List
> SortAndAttachItems(List
> items)176 {177 //1. Sort178 var sortedItems = new List
>();179 var unsortedItems = new List
>();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
> stillUnsortedItems = new List
>();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
items = new BindableCollection
();250 public IObservableCollection
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
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
> children = new List
>();276 public List
> 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
()["工具栏"].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
()["测试界面"].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
()["测试插件2"].Show();58 }59 }60 }

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

IDockScreen接口增加部分

///     /// 抽象出基本的Content类型,并实现属性通知接口,因为以后绑定到AvalonDock是有双向绑定的,这样我们要操作AvalonDock时    /// 就可以直接操作实现该接口的属性,比如DisplayName的属性用于绑定到AvalonDock的LayoutItem的Title    ///     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

 

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

Menu

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

MenuView.xaml

 

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
))] public class MenuViewModel : PartManager
{ }}

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

MenuContent就是MenuView 

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;        }        ///         /// DockView        ///         [Import]        public IDockScreenManager DockContent { get; set; }        [Import]        public IPartManager
MenuContent { get; set; } }}

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

章节预告:

4:寻找插件并加载插件

5:多语言化

6:换肤功能

第三部分源码:

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

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

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

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

作者:Zengg 

出处:

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

转载于:https://www.cnblogs.com/01codeworld/p/3159785.html

你可能感兴趣的文章
人需要治愈
查看>>
linux中configure文件默认执行结果所在位置
查看>>
Spring MVC例子
查看>>
jmeter 断言
查看>>
玩玩小爬虫——抓取时的几个小细节
查看>>
error C4996: 'fopen'
查看>>
Windows向Linux上传文件夹
查看>>
20180104-高级特性-Slice
查看>>
6个SQL Server 2005性能优化工具介绍
查看>>
nginx启动、关闭命令、重启nginx报错open() "/var/run/nginx/nginx.pid" failed
查看>>
day14 Python 内置函数、匿名函数和递归函数
查看>>
BZOJ 3097 Hash Killer I
查看>>
UINavigationController的视图层理关系
查看>>
html阴影效果怎么做,css 内阴影怎么做
查看>>
宏观经济
查看>>
译:面试投行的20个Java问题
查看>>
综合练习:词频统计
查看>>
BZOJ1026: [SCOI2009]windy数
查看>>
ASP.NET应用程序和ASP.NET网站所共有的文件: App_Browsers 等
查看>>
ASP.NET杂货店实战视频 VS2010+SQL2008 三层架构设计开发讲解
查看>>