下图为OpenExpressApp的系统架构图,其中在业务层中Command是作为一种系统内部提供以及可供外部扩展的一种机制。OpenExpressApp框架对功能的主要扩展之一就是Command机制,OEA提供的Command可以实现用户交互,更好的分离业务逻辑,带来更好的维护性和
可扩展性。
Command位于架构图业务层
![](https://images.cnblogs.com/cnblogs_com/zhoujg/168052/r_OpenExpressApp%20Framework.JPG)
Command的由来
![](https://pic002.cnblogs.com/img/zhoujg/200911/2009111611021741.png)
MVC是一种经典的架构模式,如上图所示:
模型(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型
”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作(OEA现在的类库上有UI相关信息只是基于属性的一种元模型实现,它和类模型不是一个概念,大家需要区分开来),但是模型中数据的变化一般会通过一种通知机制被公布(例如:WPF中绑定属性内部实现了通知机制)。
视图(View) 能够展现数据。在视图中一般没有业务上的逻辑。
控制器(Controller)
控制器可以访问视图和模型,用于控制应用程序的业务流程。控制器提高了应用程序的灵活性和可配置性,它可以用来连接不同的模型和视图去完成用户的需求。给
定一些可重用的模型和视图,控制器可以根据用户的需求选择适当的模型机型处理,然后选择适当的的视图将处理结果显示给用户。
OEA中的Command就是控制器,通过Command可以访问视图和模型,控制应用程序的业务。
主要类图
![](https://pic002.cnblogs.com/img/zhoujg/200911/2009111611341918.jpg)
CommandBase
CommandBase作为所有命令类的基类,主要定义了CanVisible、CanExecute和Execute几个方法, Execute执行方法在子类中实现。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public abstract class CommandBase
{
public CommandBase()
{
}
public virtual bool CanVisible(object param)
{
return true;
}
public virtual bool CanExecute(object param)
{
return true;
}
public abstract void Execute(object param);
}
ViewCommand、WPFViewCommand
ViewCommand、WPFViewCommand在CanVisible、CanExecute和Execute三个方法中传入的参数是ObjectView类型,通过ObjectView可以访问视图和模型。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public abstract class WPFViewCommand : ViewCommand
{
public override bool CanVisible(object param)
{
return CanVisible(param as ObjectView);
}
public override bool CanExecute(object param)
{
return CanExecute(param as ObjectView);
}
public override void Execute(object param)
{
Execute(param as ObjectView);
}
public virtual bool CanVisible(ObjectView view)
{
return true;
}
public virtual bool CanExecute(ObjectView view)
{
return true;
}
public abstract void Execute(ObjectView view);
}
WPFListViewCommand
WPFListViewCommand在CanVisible、CanExecute和Execute三个方法中传入的参数是ListObjectView类型。由于对ListObjectView进行扩展的比较多,所以单独实现了一个类。
WPFAutoCommand
WPFAutoCommand在生成窗体的View后自动调用的Command,可以在这个命令中挂接事件等。如以下代码示例:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
[Command("合同预算导航", ToolbarType = ToolbarType.Manual, TargetObjectType = typeof(Contract), Label = "合同预算导航", ToolTip = "设置值合同预算导航")]
public class SetPBSBudgetNavigateCriteriaCommand : WPFAutoCommand
{
public override void Execute(ObjectView view)
{
view.SelectedItemChanged += new SelectedItemChangedEventHandler(view_SelectedItemChanged);
}
void view_SelectedItemChanged(object sender, ObjectView view, SelectedItemChangedEventArgs e)
{
Contract contract = view.CurrentObject as Contract;
if ((null != contract) && (!contract.IsNew))
{
ContractPBSBudgetNavigateCriteria contractPBSBudgetNavigateCriteria = ((view as ListObjectView).DetailView.GetChildView(typeof(ContractPBSBudget), true) as ListObjectView).NavigateQueryView.CurrentObject as ContractPBSBudgetNavigateCriteria;
if (null != contract)
{
contractPBSBudgetNavigateCriteria.ContractId = (Guid)view.CurrentID;
contractPBSBudgetNavigateCriteria.PBSs = contract.PBSList;
}
else
{
contractPBSBudgetNavigateCriteria.PBSs = null;
}
}
}
}
PropertyEditorCommand
PropertyEditorCommand将属性编辑器PropertyEditor作为参数,框架内部的附件操作命令就是这类命令。
代码目录结构
Command在OEA中的目录结构如下图:
![](https://pic002.cnblogs.com/img/zhoujg/200911/2009111610191835.jpg)
Pattern
Pattern目录下为codeproject上的一个扩展WPF Command的一个实现,我用它来作为OEA的command应用模式。这里就不做进一步介绍了,感兴趣的可以去那个网站看看
CommandAdapter
OEA是通过CommandAdapter来把CommandBase适配到Pattern
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public class CommandAdapter : Itenso.Windows.Input.Command
{
private readonly CommandBase coreCommand;
public CommandAdapter(CommandBase coreCommand, Type ownerType, CommandDescription description) :
base(coreCommand.Name, ownerType, description)
{
if (coreCommand == null)
{
throw new ArgumentNullException("coreCommand");
}
this.coreCommand = coreCommand;
}
public CommandBase CoreCommand
{
get { return this.coreCommand; }
}
public override bool IngoreIsExecuting
{
get { return coreCommand.IngoreIsExecuting; }
}
protected override bool OnCanExecute(CommandContext commandContext)
{
return this.coreCommand.CanExecute(commandContext.CommandParameter);
}
protected override void OnExecute(CommandContext commandContext)
{
this.coreCommand.Execute(commandContext.CommandParameter);
}
}
ObjectEditCommand、TreeEditCommand、FileAttachmentCommand、LookupCommand、ViewExtCommand
这些Command都是OEA内置对对象、树形、附件、下拉列表、View的基本操作的支持,具体代码可以去看看源码,这里就不一一列出来了。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public class CommandNames
{
public const string Add = "Add";
public const string CopyAndNew = "CopyAndNew";
public const string AddChild = "AddChild";
public const string DeleteBillObject = "DeleteBillObject";
public const string DeleteListObject = "DeleteListObject";
public const string DeleteChildObject = "DeleteChildObject";
public const string Cancel = "Cancel";
public const string Refresh = "Refresh";
public const string Save_Bill = "Save_Bill";
public const string Save_List = "Save_List";
public const string InsertBefore = "InsertBefore";
public const string InsertFollow = "InsertFollow";
public const string MoveDown = "MoveDown";
public const string MoveUp = "MoveUp";
public const string LevelUp = "LevelUp";
public const string LevelDown = "LevelDown";
public const string ExpandAll = "ExpandAll";
public const string CollapseAll = "CollapseAll";
public const string ClearQueryCondition = "ClearQueryCondition";
public const string QueryObject = "QueryObject";
//Lookup
public const string ClearLookupPropertyValue = "ClearLookupPropertyValue";
//view
public const string MaxShowView = "MaxShowView";
//select
public const string SelectAll = "SelectAll";
//FileAttachment
public const string AddFile = "AddFile";
public const string OpenFile = "OpenFile";
public const string FileSaveAs = "FileSaveAs";
public const string ClearFile = "ClearFile";
}
如何使用
- 增加Command继承类,实现CanVisible、CanExecute和Execute方法
- 在类定义上增加属性标记,让OEA注册这个Command到资源库中
- 运行时由AutoUI自动生成对应的Command的按钮或者菜单项
Command类
如果我们需要对树形操作增加一个命令全部展开,那么首先需要生成一个类ExpandAllCommand,由于这个命令是对树形列表视图进行操作,所以选择从WPFListViewCommand继承过来。生成类后再在类上加上Command属性来注册命令。更多的示例可以查看OEA内部支持的一些Command。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
[Command(CommandNames.ExpandAll, Label = "全部展开", ToolTip = "全部展开")]
public class ExpandAllCommand : WPFListViewCommand
{
public override bool CanVisible(ListObjectView view)
{
return (null != view) && ((view is ListObjectView) && ((ListObjectView)view).IsTree);
}
public override void Execute(ListObjectView view)
{
(view.Control as MultiObjectTreeView).ExpandAll();
}
}
在类定义上增加了Command属性后,OEA会把这个Command到资源库中,以下将介绍以下属性以及OEA如何生成Command对应的按钮或者菜单项。
基于属性的模型支持
![](https://pic002.cnblogs.com/img/zhoujg/200911/2009111613411887.jpg)
OEA目前对UI模型和Command模型的支持都是基于属性来定义的,Command通过CommandAttribute来定义
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
[AttributeUsage(AttributeTargets.Class)]
public class CommandAttribute : Attribute
{
public CommandAttribute(string name)
{
this.Name = name;
this.CommandCategory = CommandCategory.RecordEdit;
this.ModuleType = ModuleType.Unspecified;
}
public string Name { get; set; }
public string Label { get; set; }
public string ToolTip { get; set; }
public string ImageName { get; set; }
public ViewType TargetViewType { get; set; }
/// <summary>
/// 这个命令显示在哪个类型的工具条上
/// </summary>
public ToolbarType ToolbarType { get; set; }
public ModuleType ModuleType { get; set; }
public Type TargetObjectType { get; set; }
public bool IngoreIsExecuting { get; set; }
public CommandCategory CommandCategory { get; set; }
}
另外为了方便的支持控制系统Command的是否可以使用,增加了NotAllowRemove、NotAllowNew、NotAllowEdit三
个属性。如果类定义上标识了这些属性,OEA在生成Command对应按钮时会进行判断是否使用这些Command。在
OpenExpressApp.MetaModel项目ApplicationModel.cs中的业务对象元信息BusinessObjectInfo
对象中有以下方法:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
/// <summary>
/// 为业务模型对象加入默认不可见命令
/// </summary>
/// <param name="bOInfo"></param>
public void AddNotVisibleCommand()
{
if (NotAllowNew)
{
ApplicationModel.AddNotVisibleCommand(CommandNames.Add, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.AddChild, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.CopyAndNew, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.InsertBefore, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.InsertFollow, this);
}
if (NotAllowRemove)
{
ApplicationModel.AddNotVisibleCommand(CommandNames.DeleteChildObject, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.DeleteListObject, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.DeleteBillObject, this);
}
if (NotAllowEdit)
{
ApplicationModel.AddNotVisibleCommand(CommandNames.MoveDown, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.MoveUp, this);
}
if (NotAllowNew && NotAllowRemove && NotAllowEdit)
{
ApplicationModel.AddNotVisibleCommand(CommandNames.Cancel, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.Save_Bill, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.Save_List, this);
ApplicationModel.AddNotVisibleCommand(CommandNames.Refresh, this);
}
}
注册Command
在OpenExpressApp.Module项目BaseModule初始化时遍历程序集类型注册Command
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
/// <summary>
/// 所有模块的基类
///
/// 加入业务模型 命令等
/// </summary>
public class BaseModule : IModule
{
/// 加入Module类在程序集内的 业务模型 命令等
public virtual void Initialize()
{
//如果是继承自这个类的类
if (this.GetType() != typeof(BaseModule))
{
var allTypes = this.GetType().Assembly.GetTypes();
foreach (var item in allTypes.Where(t => t.HasMarked<BusinessObjectAttribute>()))
{
//加入业务模型
BusinessObjectInfo bOInfo = ApplicationModel.AddBusinessObject(item);
//为业务模型加入默认命令
bOInfo.AddNotVisibleCommand();
}
foreach (var item in allTypes.Where(t => t.HasMarked<CommandAttribute>()))
{
//加入自定义命令
ApplicationModel.Commands.Add(Activator.CreateInstance(item) as CommandBase);
}
}
}
}
AutoUI使用Command生成按钮
Command定义完后需要通过按钮或者菜单项在UI中表现出来,OEA通过AutoUI功能进行自动生成按钮来实现,在《AutoUI自动生成界面》中介绍的CreateMainToolBar和CreateChildToolBar中会基于Command的一些属性来过滤可以使用的命令,CreateMainToolBar代码如下:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
/// <summary>
/// 生成主工具栏
/// </summary>
/// <param name="mainToolbar"></param>
/// <param name="boType"></param>
/// <param name="view"></param>
/// <param name="moduleType"></param>
public static void CreateMainToolBar(ToolBar mainToolbar, Type boType, ObjectView view, ModuleType moduleType)
{
view.ToolBar = mainToolbar;
//找到对应这个toolbar的所有command
var commands = ApplicationModel.Commands.Where(
c => ((c.TargetObjectType == boType) || (c.TargetObjectType == null)) &&
((c.ModuleType == ModuleType.Unspecified) || (c.ModuleType == moduleType)) &&
((c.ToolbarType == ToolbarType.Any) || (c.ToolbarType == ToolbarType.Main)));
//CommandCategory.Filter类型下的命令需要在一个下拉列表中显示
Panel filterPanel = null;
//ComboBox cb = null;
foreach (var c in commands)
{
bool notVisible = ApplicationModel.IsNotVisibleCommand(c.Name, ApplicationModel.GetBusinessObjectInfo(boType));
if (notVisible == false && (c.CanVisible(view)))
{
Button btn = new Button()
{
Name = "btn" + c.Name,
};
//第一个
if ((null == filterPanel) && (CommandCategory.Filter == c.CommandCategory))
{
filterPanel = new StackPanel() { Orientation = Orientation.Horizontal };
mainToolbar.Items.Add(filterPanel);
}
btn.CommandParameter = view;
ButtonCommand.SetCommand(btn, CommandRepository.Commands[c.Name]);
if (CommandCategory.Filter == c.CommandCategory)
{
filterPanel.Children.Add(btn);
}
else
{
mainToolbar.Items.Add(btn);
}
}
}
if (0 == mainToolbar.Items.Count)
{
mainToolbar.Visibility = Visibility.Collapsed;
}
}
更多内容:
开源信息系统开发平台之OpenExpressApp框架.pdf
欢迎转载,转载请注明:转载自周金根 [ http://zhoujg.cnblogs.com/ ]