WPF动态添加菜单按钮
近期研究了SharpDevelop这个项目,发现菜单解耦的方式非常好用,虽然还没有学到精髓,但决定用自己现有的知识实现一样的功能。SharpDevelop使用的是加载dll的方式,以及加载xml文件,这种方式非常好,就是编写xml文件也是件困难的事。我是使用的是MEF的方式,动态加载插件。
先写抽象类,在后续的代码的菜单中继承这个类型
public abstract class AbsCommand:ICommand
{
public virtual bool IsEnabled
{
get { return isEnabled; }
set { isEnabled = value; }
}
bool isEnabled = true;
public object Owner { get; set; }
public abstract void Run();
void ICommand.Execute(object parameter)
{
Run();
}
//必须注册事件,才能实现菜单的使能在后续的插件中编辑
event EventHandler ICommand.CanExecuteChanged
{
add { CommandManager.RequerySuggested+=value; }
remove { CommandManager.RequerySuggested -= value; }
}
bool ICommand.CanExecute(object parameter)
{
return this.IsEnabled;
}
}
使用特性的方式,将命令导出,所以首先定义导出元数据的接口,主要包含菜单的名称,以及菜单栏所在的父节点
public interface ICommandPath
{
string MenuPath { get; }
string LabelContent { get; }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false), MetadataAttribute]
public class ExtensionPathAttribute : Attribute, ICommandPath
{
public ExtensionPathAttribute() : base() { }
public string MenuPath { get; set; }
public string LabelContent { get; set; }
}
使用一个容器动态加载全部的指令
/// <summary>
/// MEF容器,加载全部的指令
/// </summary>
public sealed class MEFContainer : IDisposable
{
#region 构造函数
public MEFContainer()
{
InifMef();
}
#endregion 构造函数
#region 公有函数
/// <summary>
/// 通过名称获取指令
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public IEnumerable<Tuple<AbsCommand, ICommandPath>> GetAllCommand()
{
foreach (Lazy<AbsCommand, ICommandPath> current in Tools)
{
yield return Tuple.Create<AbsCommand, ICommandPath>(current.Value, current.Metadata);
}
}
public void Dispose()
{
if (null != catalog)
{
foreach (var cat in catalog.Catalogs)
{
cat.Dispose();
}
catalog.Dispose();
catalog = null;
}
if (null != container)
{
container.ReleaseExports(Tools);
container.Dispose();
container = null;
}
Tools = null;
}
#endregion 公有函数
#region 私有函数
private void InifMef()
{
catalog = new AggregateCatalog();
//新添加的菜单放在根目录ExpCom文件夹下,且必须UntilComm开头(可根据需要修改)
catalog.Catalogs.Add(new DirectoryCatalog(basepath + "\\ExpCom", "UntilComm.*"));
container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
#endregion 私有函数
#region 私有变量
private string basepath = AppDomain.CurrentDomain.BaseDirectory;
[ImportMany(typeof(AbsCommand))]
private IEnumerable<Lazy<AbsCommand, ICommandPath>> Tools;
private AggregateCatalog catalog;
private CompositionContainer container;
#endregion 私有变量
}
界面加入菜单,博主使用的是WPF做的,首先在界面添加一个x:name=“MainMenu” 的 Menu,然后在控件的构造函数下插入如下的代码,即可完成加入菜单选项
public EmptyGrid()
{
InitializeComponent();
DataContext = this;
//this.CommandBindings.Add(new CommandBinding(ApplicationCommands.New, CreateNewDataFile));
//this.CommandBindings.Add(new CommandBinding(NewTableCom, New_Table_Comm, NewTable_CanExcute));
//this.CommandBindings.Add(new CommandBinding(DelTable, DelTabExcute));
//this.CommandBindings.Add(new CommandBinding(ReNameTable, RenameTabExcute));
mEFContainer = new MEFContainer();
var allcomm = mEFContainer.GetAllCommand();
foreach (var item in allcomm)
{
string[] ret = item.Item2.MenuPath.Split(';');
if (ret == null || ret.Length == 0)
continue;
ItemCollection collection = MainMenu.Items;
foreach (var label in ret)
{
MenuItem matchitem = collection.OfType<MenuItem>().FirstOrDefault(child => child.Header.ToString() == label);
if (null == matchitem)
{
matchitem = new MenuItem { Header = label };
collection.Add(matchitem);
}
collection = matchitem.Items;
}
MenuItem additem = new MenuItem();
AbsCommand command = (AbsCommand)Activator.CreateInstance(item.Item1.GetType());
command.Owner = this;
additem.Header = item.Item2.LabelContent;
additem.Command = command;
collection.Add(additem);
}
}
新建菜单
[ExtensionPath(MenuPath = "工具", LabelContent = "数据库查询")]
[Export(typeof(AbsCommand))]
public class TestCom : AbsCommand
{
/// <summary>
/// 菜单栏的使能条件
/// </summary>
public override bool IsEnabled
{
get
{
//EmptyGrid form = Owner as EmptyGrid;
//if (form == null)
// return false;
//if (!File.Exists(DBConstParame.DbPath) || string.IsNullOrEmpty(form.SelectTable))
//{
// return false;
//}
return true;
}
}
public override void Run()
{
//可通过Owner获取主界面对象
//EmptyGrid form = Owner as EmptyGrid;
Trace.WriteLine("My First Menuiten");
}
}
这个是第一次写博客,后续有什么好的想法,继续和大家分享,这个是在公司编写的demo,由于文件加密无法上传源码。