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

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

运行结果:

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

UICore部分

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

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

 namespace UICoreFramework
{
/// <summary>
/// Denotes an instance which can be executed.
/// </summary>
public interface IExecutable
{
bool CanExecute { get; }
void Execute();
} /// <summary>
/// Denotes an instance which can be checked.
/// </summary>
public interface ICheckable
{
bool IsCheckable { get; }
bool IsChecked { get; set; }
} /// <summary>
/// Denotes a small UI widget that can display and interact.
/// </summary>
public interface IPart : ILocalizableDisplay, IExecutable, IActivate, IDeactivate
{
string InputGestureText { get; set; } void OnAttached(); string Icon { get; set; } bool IsVisible { get; set; }
} /// <summary>
/// Concrete <see cref="IPart"/> which denotes a <see cref="System.Window.Controls.MenuItem"/> instance.
/// </summary>
public interface IMenuPart : IPart, ISeparaterPart, ICheckable
{ } /// <summary>
/// Denotes a <see cref="System.Window.Controls.Separater"/> instance.
/// </summary>
public interface ISeparaterPart
{
bool IsSeparator { get; }
} /// <summary>
/// Denotes a manager class that manage the <see cref="IPart"/>s.
/// </summary>
public interface IPartManager<T> where T : IPart
{
IObservableCollection<T> Items { get; }
} /// <summary>
/// Denotes a manager node that holds the <see cref="IObservableCollection"/> item.
/// </summary>
public interface IObservableParent<T>
{
IObservableCollection<T> Items { get; }
}
}

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控件。

 namespace UICoreFramework
{
/// <summary>
/// Concrete <see cref="IPartManager"/> with manages <see cref="IPart"/> items, it uses MEF to construct the <see cref="IPart"/>s.
/// </summary>
public class PartManager<T> : IPartManager<T>, IPartImportsSatisfiedNotification where T : IPart
{
#region Constructor public PartManager()
{
} #endregion #region Property [ImportMany]
protected T[] InternalItems { get; set; } #endregion #region Method protected virtual void ConfigParts()
{
if (InternalItems == null || InternalItems.Length == )
{
return;
} items.Clear();
items.AddRange(InternalItems);
} #endregion #region IPartManager Members private IObservableCollection<T> items = new BindableCollection<T>();
public IObservableCollection<T> Items
{
get { return items; }
} #endregion #region IPartImportsSatisfiedNotification Members public void OnImportsSatisfied()
{
ConfigParts();
} #endregion
} /// <summary>
/// Extends the <see cref="IPartManager"/> that support <see cref="IPartMetaData"/>.
/// </summary>
/// <typeparam name="T">IPart</typeparam>
/// <typeparam name="TMetadata">The type of the metadata.</typeparam>
public class PartManager<T, TMetadata> : IPartManager<T>, IPartImportsSatisfiedNotification
where T : IPart
where TMetadata : IPartMetaData
{
#region Field protected static readonly Func<TMetadata, string> BasePart;
protected static readonly Func<TMetadata, string> PreviousPart;
protected static readonly Func<TMetadata, string> NextPart; #endregion #region Constructor static PartManager()
{
var props = typeof(TMetadata).GetProperties();
BasePart = DynamicAccessEngine.GetPropertyDelegate<TMetadata, string>(props.FirstOrDefault(it => it.Name.Contains("Base")).Name);
PreviousPart = DynamicAccessEngine.GetPropertyDelegate<TMetadata, string>(props.FirstOrDefault(it => it.Name.Contains("Previous")).Name);
NextPart = DynamicAccessEngine.GetPropertyDelegate<TMetadata, string>(props.FirstOrDefault(it => it.Name.Contains("Next")).Name);
} public PartManager()
{
} #endregion #region Property [ImportMany]
protected Lazy<T, TMetadata>[] InternalItems
{
get;
set;
} #endregion #region Method protected virtual void ConfigParts()
{
if (InternalItems == null || InternalItems.Length == )
{
return;
} items.Clear(); //Sort items according to metadata's Base , Previous, Next value
SortItems();
} protected virtual void SortItems()
{
var items = InternalItems.Select((it) =>
{
return new OrderItem<T>()
{
Base = BasePart(it.Metadata),
Before = PreviousPart(it.Metadata),
After = NextPart(it.Metadata),
Value = it.Value
};
}).ToList(); var roots = SortAndAttachItems(items.Where(it => string.IsNullOrEmpty(it.Base)).ToList()); foreach (var item in items)
{
var baseItem = items.FirstOrDefault(it => string.Equals(it.Value.Name, item.Base));
if (baseItem != null)
{
baseItem.Children.Add(item);
}
} foreach (var item in roots)
{
SortItem(item);
} Items.AddRange(roots.Select(it => it.Value));
} private void SortItem(OrderItem<T> item)
{
if (item.Children.Count == )
{
return;
} //1. Child recursion.
foreach (var it in item.Children)
{
SortItem(it);
} //2. Sort
var sortedItems = SortAndAttachItems(item.Children); foreach (var it in sortedItems)
{
IObservableParent<T> parent = item.Value as IObservableParent<T>;
if (parent != null)
{
parent.Items.Add(it.Value);
}
}
} private List<OrderItem<T>> SortAndAttachItems(List<OrderItem<T>> items)
{
//1. Sort
var sortedItems = new List<OrderItem<T>>();
var unsortedItems = new List<OrderItem<T>>();
foreach (var newItem in items)
{
if (string.IsNullOrEmpty(newItem.Before) && string.IsNullOrEmpty(newItem.After))
{
sortedItems.Add(newItem);
}
else
{
unsortedItems.Add(newItem);
}
} while (unsortedItems.Count > )
{
List<OrderItem<T>> stillUnsortedItems = new List<OrderItem<T>>();
int startingCount = unsortedItems.Count;
foreach (var newItem in unsortedItems)
{
if (!string.IsNullOrEmpty(newItem.After))
{
var beforeItem = sortedItems.FirstOrDefault(it => it.Value.Name == newItem.After);
if (beforeItem != null)
{
sortedItems.Insert(sortedItems.IndexOf(beforeItem), newItem);
}
else
{
stillUnsortedItems.Add(newItem);
}
}
else
{
var afterItem = sortedItems.FirstOrDefault(it => it.Value.Name == newItem.Before);
if (afterItem != null)
{
int index = sortedItems.IndexOf(afterItem);
if (index == sortedItems.Count - )
{
sortedItems.Add(newItem);
}
else
{
sortedItems.Insert(index + , newItem);
}
}
else
{
stillUnsortedItems.Add(newItem);
}
}
}
if (startingCount == stillUnsortedItems.Count)
{
sortedItems.Add(stillUnsortedItems[]);
stillUnsortedItems.RemoveAt();
}
unsortedItems = stillUnsortedItems;
} //2. Call Attached method of IPart
sortedItems.Apply(o => o.Value.OnAttached()); return sortedItems;
} #endregion #region IPartManager Members private IObservableCollection<T> items = new BindableCollection<T>();
public IObservableCollection<T> Items
{
get { return items; }
} #endregion #region IPartImportsSatisfiedNotification Members public void OnImportsSatisfied()
{
ConfigParts();
} #endregion #region Private OrderItem Class private class OrderItem<U>
{
public string Base { get; set; }
public string Before { get; set; }
public string After { get; set; }
public U Value { get; set; } private List<OrderItem<U>> children = new List<OrderItem<U>>();
public List<OrderItem<U>> Children
{
get
{
return children;
}
}
} #endregion
}

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

 //=================================================================================
//
// Copyright (C) 20013-2014
// All rights reserved
//
// description : 本系列于博客园首发,如果您想转载本博客,请注明出处,感谢支持
// created by Zengg
// http://www.cnblogs.com/01codeworld/
// Email:281365330@qq.com
//==================================================================================
namespace DemoApplication.Menu
{
[MenuPart(BaseMenu="插件")]
public class DockableTestPart : MenuPart
{
public DockableTestPart()
: base("工具栏")
{ }
public override void Execute()
{
IoC.Get<IDockScreenManager>()["工具栏"].Show();
}
}
} namespace DemoApplication.Menu
{
[MenuPart(BaseMenu="插件")]
public class DocTestViewPart : MenuPart
{
public DocTestViewPart()
: base("测试界面")
{ }
public override void Execute()
{
IoC.Get<IDockScreenManager>()["测试界面"].Show();
}
}
} namespace DemoApplication.Menu
{
[MenuPart(BaseMenu="插件")]
public class DocTest1ViewPart : MenuPart
{
public DocTest1ViewPart()
: base("测试插件2")
{ }
public override void Execute()
{
IoC.Get<IDockScreenManager>()["测试插件2"].Show();
}
}
}

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="" d:DesignWidth="">
<UserControl.Resources>
<avalonDock:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</UserControl.Resources>
<Grid>
<Grid x:Name="layoutRoot">
<avalonDock:DockingManager DocumentsSource="{Binding Documents}" AnchorablesSource="{Binding DockableContents}" Grid.Row="" 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="" 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="" Orientation="Horizontal" >
<avalonDock:LayoutAnchorablePane > </avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutAnchorablePaneGroup>
</avalonDock:LayoutPanel> <avalonDock:LayoutAnchorablePaneGroup DockWidth="" 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="" d:DesignWidth="">
<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="" />
<GradientStop Color="#F4F7FC" Offset="" />
</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="" Width="">
<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="" />
<GradientStop Color="#F4F7FC" Offset="" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</ResourceDictionary>
</Window.Resources>
<Grid Background="{StaticResource AvalonDock_ThemeAero_BaseColor1}" >
<Grid.RowDefinitions>
<RowDefinition Height=""/>
<RowDefinition Height="*"/>
<RowDefinition Height=""/>
</Grid.RowDefinitions>
<ContentControl x:Name="MenuContent" Margin="0,2,0,0" Grid.Row=""/>
<ContentControl x:Name="DockContent" Margin="0,2,0,0" Grid.Row=""/>
</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/

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

AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(菜单篇)的更多相关文章

  1. AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(一)

    随着IOS7由之前UI的拟物化设计变为如今的扁平化设计,也许扁平化的时代要来了,当然我们是不是该吐槽一下,苹果什么时候也开始跟风了,自GOOGLE和微软界面扁平化过后,苹果也加入了这一队伍. Aval ...

  2. AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(二)

    上次已经建立了可运行的基本框架,这篇就说说怎么把我们自定义的View自动加载并添加到AvalonDock里面,AvalonDock里有3种类型的UI部件,Document, DockableConte ...

  3. Caliburn.Micro 杰的入门教程2 ,了解Data Binding 和 Events(翻译)

    Caliburn.Micro 杰的入门教程1(翻译)Caliburn.Micro 杰的入门教程2 ,了解Data Binding 和 Events(翻译)Caliburn.Micro 杰的入门教程3, ...

  4. 从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器

    从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器 之前时间一直在使用Caliburn.Micro这种应用了MVVM模式的WPF框架做开发,是时候总结一下了. Calibu ...

  5. WPF +MVVM(Caliburn.Micro)项目框架

    最近做了一个软件,这个软件不是网站,但是与HTML,AJAX等技术密切相关,也不是只有单纯的数据库增删改查,还涉及到线程协调,比较复杂的文本处理…… 这样的软件,用OA,ERP的框架显然是不合适的,因 ...

  6. Caliburn.Micro学习笔记(一)----引导类和命名匹配规则

    Caliburn.Micro学习笔记目录 用了几天时间看了一下开源框架Caliburn.Micro 这是他源码的地址http://caliburnmicro.codeplex.com/ 文档也写的很详 ...

  7. Caliburn.Micro学习笔记(二)----Actions

    Caliburn.Micro学习笔记目录 上一篇已经简单说了一下引导类和简单的控件绑定 我的上一个例子里的button自动匹配到ViewModel事件你一定感觉很好玩吧 今天说一下它的Actions, ...

  8. Xamarin 的 MVVM 之 Caliburn.Micro

    约定 Caliburn.Micro 以下简称 CMXamarin.Form 以下简称 XF 摘要CM 当前已释出 3.0 beta 版https://github.com/Caliburn-Micro ...

  9. 开源框架Caliburn.Micro

    Caliburn.Micro学习笔记----引导类和命名匹配规则   用了几天时间看了一下开源框架Caliburn.Micro 这是他源码的地址http://caliburnmicro.codeple ...

随机推荐

  1. Ajax中get提交和post提交的区别

    $.post("HandlerLiuYan.ashx", { leixing: leixing, danwei: danwei, liuyan: liuyan, name: nam ...

  2. Part 15 Scalar user defined functions in sql server

    Scalar user defined functions in sql server Inline table valued functions in sql server Multi statem ...

  3. HDU1064 第一道JAVA

    简单的不能再简单的题目, 不过倒是可以来练练新学的JAVA.. import java.util.Scanner; public class Hello{ public static void mai ...

  4. 方法:一个简单的读取配置文件.properties的工具类 JAVA

    import java.util.ResourceBundle; public class ConfigHelper { private static ConfigHelper instance; p ...

  5. java集合(交集,并集,差集)

    说明:这里没有求差集的代码,有了交集和并集,差集=并集-交集       package com; import java.util.ArrayList; import java.util.HashS ...

  6. Error: An App ID with identifier "*****" is not avaliable. Please enter a different string.

    Error: An App ID with identifier "*****" is not avaliable. Please enter a different string ...

  7. Linux C 程序 进程间通信(20)

    进程间通信 1.进程间通信的几种手段:    (1).管道        数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道),如果要建立全双工通信,需要建立两个管道        只能用于 ...

  8. silverlight视频、音频

    几天发现MediaElement播放不了wav格式的音频文件,在网上找到一篇解决的文章: http://www.cnblogs.com/rupeng/archive/2011/02/20/195936 ...

  9. java 反射的踩的一个坑

    今天工作的时候用到了一个反射.其业务简单描述为:系统启动时将需要定时调用的方法签名保存到数据库中,开启线程定时从数据库中读取对应的方法签名,通过反射生成实例后调用方法.完成一定的定时任务. 写到的方法 ...

  10. 一款jQuery立体感动态下拉导航菜单特效

    一款jQuery立体感动态下拉导航菜单特效,鼠标经过,在菜单栏上方下拉出一个背景图片,效果十分不错的一款jquery特效. 对IE6都是兼容的,希望大家好好研究研究. 适用浏览器:IE6.IE7.IE ...