下图为OpenExpressApp的系统架构图,其中在UI层支持WPF和ASP.NET MVC,目前首先实现了对WPF的支持。在《信息系统开发平台OpenExpressApp - 理解核心元素ObjectView》中的ObjectView的生成控件功能都是委托给AutoUI静态类库来完成的,本篇将讲解AutoUI功能。
应用模型贯穿于整个架构层
哪些地方调用了AutoUI静态类
{
public override object CreateControl()
{
Control = AutoUI.CreateTreeListControl(BOType, View);
...
}
}
- ListObjectView中Grid列表视图的ListEditor的CreateControl,调用了AutoUI.CreateListControl生成Grid控件
{
public virtual object CreateControl()
{
Control = AutoUI.CreateListControl(BOType, View);
...
}
}
- DetailObjectView的CreateControl调用AutoUI.CreateDetailView生成对象编辑详细面板
{
public override object CreateControl()
{
var control = AutoUI.CreateDetailView(BOType, this, true);
return control;
}
}
- NavigateQueryObjectView的CreateControl调用AutoUI.CreateDetailView生成对象导航详细面板
{
public override object CreateControl()
{
var control = AutoUI.CreateDetailView(BOType, this, true);
return control;
}
}
- 模块和View通过CreateMainToolBar、CreateChildToolBar自动生成工具条
AutoUI静态类方法介绍
CreateDetailView
CreateDetailView生成详细信息视图,生成结果是一个DockPanel,分为上下两部分,上面部分是AutoGrid,用来显示详细属性编辑器控件,下面部分是子对象列表信息区域。
详细信息区域
通过对象标识【ShowInDetail】来判断是否需要显示该属性,如果需要显示,则通过属性编辑器来生成具体的控件,然后布局在AutoGrid中
AutoGrid detailGrid = new AutoGrid();
detailGrid.SetValue(DockPanel.DockProperty, Dock.Top);
//一般加四列,ConditionQuery/NavigateQuery加两列
detailGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
detailGrid.ColumnDefinitions.Add(new ColumnDefinition());
if (RegionType.Data == detailView.RegionType)
{
detailGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
detailGrid.ColumnDefinitions.Add(new ColumnDefinition());
}
//加入所有标记了ShowInDetail的属性
var typeInfo = ApplicationModel.GetBusinessObjectInfo(boType);
foreach (var p in typeInfo.BOPropertyInfos)
{
if (p.ShowInDetail)
{
//detailGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
PropertyEditor editor = PropertyEditorsFactory.GetDefaultEditor(p.EditorName);
editor.CreateControl(p, detailView);
detailGrid.Children.Add(editor.LabelControl as UIElement);
detailGrid.Children.Add(editor.Control as UIElement);
}
}
子对象区域
如果有子对象,则生成一个子对象区域,现在是生成页签样式,每个子对象分别一个Tab页面,子对象又可以层级生成子对象。
//生成子UIElement
UIElement child = CreateChildObjectView(boType, detailView, detailGrid, recur);
if (null != child)
{
detailPanel.Children.Add(child);
}
else
{
detailPanel.Children.Add(new Label());
}
在生成子对象时,需要考虑生成子对象工具条、导航面板、布局等
/// <summary>
/// 生成子控件
/// </summary>
/// <param name="parentType">根据这个对象的孩子属性生成</param>
/// <param name="parentView">生成这个视图的子控件</param>
/// <param name="parentControl">已经生成好的父控件</param>
/// <param name="recur">是否递归生成下层的子控件</param>
/// <returns></returns>
private static UIElement CreateChildObjectView(
Type parentType,
ObjectView parentView,
FrameworkElement parentControl,
bool recur
)
{
//生成的控件都放在这个TabControl里面
TabControl tab = null;
BusinessObjectInfo parentBOInfo = ApplicationModel.GetBusinessObjectInfo(parentType);
IList<BusinessObjectsPropertyInfo> bosPropertyInfo = parentBOInfo.BOsPropertyInfos;
if ((bosPropertyInfo.Count == 1) && (null != parentBOInfo.TreeChildPropertyInfo))
{
//递归在父对象的View对应的Toolbar上生成按钮
BusinessObjectInfo treeChildBoInfo = parentBOInfo;
while (null != treeChildBoInfo.TreeChildPropertyInfo)
{
treeChildBoInfo = treeChildBoInfo.TreeChildBoInfo;
AutoUI.CreateChildToolBar(parentView.ToolBar, treeChildBoInfo.BOType, parentView);
}
}
else if (((bosPropertyInfo.Count > 1) && (null != parentBOInfo.TreeChildPropertyInfo))
|| ((bosPropertyInfo.Count > 0) && (null == parentBOInfo.TreeChildPropertyInfo)))
{
if (Direction.Horizontal == parentBOInfo.BusinessObjectAttribute.Direction)
{
parentControl.SetValue(DockPanel.DockProperty, Dock.Left);
parentControl.SetValue(ResizingPanel.ResizeWidthProperty, new GridLength(200));
}
//多个细表时设定细表高度
else if (parentControl.GetType() != typeof(AutoGrid))
{
parentControl.SetValue(DockPanel.DockProperty, Dock.Top);
parentControl.SetValue(ResizingPanel.ResizeHeightProperty, new GridLength(100));
}
tab = new TabControl();
foreach (var o in bosPropertyInfo)
{
DockPanel rootDck = new DockPanel();//包含当前属性对应控件以及子属性对应控件
ToolBar childTb = null;
//创建子Toolbar
if (parentView.RegionType != RegionType.NavigateQuery)
{
childTb = new ToolBar();
childTb.SetValue(DockPanel.DockProperty, Dock.Top);
rootDck.Children.Add(childTb);
}
ResizingPanel rootResizingPanel = new ResizingPanel();
rootDck.Children.Add(rootResizingPanel);
if (Direction.Horizontal == ApplicationModel.GetBusinessObjectInfo(o.BOType).BusinessObjectAttribute.Direction)
{
rootResizingPanel.Orientation = Orientation.Horizontal;
}
else
{
rootResizingPanel.Orientation = Orientation.Vertical;
}
//创建列表
ListObjectView lv = new ListObjectView(o);
lv.PropertyName = o.Name;
lv.RegionType = parentView.RegionType;
parentView.AddChildView(lv);
DockPanel dck = new DockPanel();
//添加导航面板
if (null != lv.NavigateQueryView)
{
//增加一个ListViewController,以便导航查询数据
UIElement navigateControl = lv.NavigateQueryView.Control as UIElement;
//初始化导航面板对象
lv.NavigateQueryView.Data = Activator.CreateInstance(lv.NavigateQueryView.BOType);
(lv.NavigateQueryView.CurrentObject as BusinessBase).BeginEdit();
ResizingPanel pnlNavigate = new ResizingPanel();
navigateControl.SetValue(ResizingPanel.ResizeWidthProperty, new GridLength(200));
pnlNavigate.Children.Add(navigateControl);
ListViewController lvController = new ListViewController(lv, null);
//添加列表
pnlNavigate.Children.Add(lvController.Control as UIElement);
dck.Children.Add(pnlNavigate);
}
else
//添加列表
dck.Children.Add(lv.Control as UIElement);
rootResizingPanel.Children.Add(dck);
StackPanel tabHeader = new StackPanel() { Orientation = Orientation.Horizontal };
tabHeader.Children.Add(new TextBlock() { Text = o.Label });
Button btnMax = new Button();
btnMax.CommandParameter = lv;
ButtonCommand.SetCommand(btnMax, CommandRepository.Commands[CommandNames.MaxShowView]);
tabHeader.Children.Add(btnMax);
TabItem ti = new TabItem()
{
Header = tabHeader,
Content = rootDck,
};
tab.Items.Add(ti);
if (null != childTb)
{
AutoUI.CreateChildToolBar(childTb, o.BOType, lv);
}
//递归查找子子对象
if (recur)
{
UIElement childchild = CreateChildObjectView(o.BOType, lv, dck, recur);
if (null != childchild)
{
rootResizingPanel.Children.Add(childchild);
}
}
}
}
return tab;
}
CreateListControl
CreateListControl生成DataGrid列表控件,通过对象类属性标识为【ShowInList】或者【ShowInLookup】来动态生成列,具体列编辑和显示控件由属性对应的GridColumn来完成。
public static object CreateListControl(Type boType, ListObjectView view)
{
DataGrid dg = new SelectionDataGrid()
{
CanUserAddRows = false,
AutoGenerateColumns = false,
VerticalGridLinesBrush = new SolidColorBrush(Colors.Gray),
HorizontalGridLinesBrush = new SolidColorBrush(Colors.Gray),
};
BusinessObjectInfo boInfo = ApplicationModel.GetBusinessObjectInfo(boType);
if (0 != boInfo.BusinessObjectAttribute.Height)
{
dg.Height = (int)boInfo.BusinessObjectAttribute.Height;
}
//生成每一列
foreach (var item in boInfo.BOPropertyInfos)
{
bool generate;
if (view.RegionType == RegionType.LookupList)
{
generate = item.ShowInLookup;
}
else
{
generate = item.ShowInList;
}
if (generate)
{
DataGridColumn c = item.CreateDefaultGridColumn(view);
dg.Columns.Add(c);
}
}
return dg;
}
CreateTreeListControl
CreateTreeListControl生成树形控件。由于树形控件支持多对象显示,现在生成时是采用一种简便的算法,如果各对象的属性Name一样,则认为显示在同一列中;父对象的属性排在前面。
/// <summary>
/// 自动生成树形列表UI
/// </summary>
/// <param name="boType"></param>
/// <returns></returns>
public static object CreateTreeListControl(Type boType, ListObjectView view)
{
MultiObjectTreeView otv = new MultiObjectTreeView();
otv.SelectNodesOnRightClick = true;
//都按懒加载关系处理
//if (ApplicationModel.GetBusinessObjectInfo(boType).LazyTreeNodeRelation)
// otv.LazyTreeNodeRelation = true;
//装载多个对象的属性,按照
BusinessObjectInfo boInfo = ApplicationModel.GetBusinessObjectInfo(boType);
IList<BusinessObjectPropertyInfo> propInfos = new List<BusinessObjectPropertyInfo>();
foreach (var item in boInfo.BOPropertyInfos)
{
propInfos.Add(item);
}
//把模型下的需要一同显示在树中的子模型的列,加入List中
while (null != boInfo.TreeChildPropertyInfo)
{
boInfo = boInfo.TreeChildBoInfo;
foreach (var item in boInfo.BOPropertyInfos)
{
bool exist = false;
foreach (var propInfo in propInfos)
{
if (propInfo.Name == item.Name)
{
exist = true;
break;
}
}
if (!exist)
{
propInfos.Add(item);
}
}
}
//使用list里面的属性生成每一列
foreach (var item in propInfos)
{
bool generate;
if (view.RegionType == RegionType.LookupList)
{
generate = item.ShowInLookup;
}
else
{
generate = item.ShowInList;
}
if (generate)
{
GridViewColumn c = item.CreateDefaultTreeColumn(view);
(otv.Tree as TreeListView).Columns.Add(c);
}
}
return otv;
}
CreateMainToolBar、CreateChildToolBar
CreateMainToolBar生成根对象工具条,系统根据当前对象类型的相关设置在Command注册库中查找并自动添加
/// <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;
}
}
CreateChildToolBar生成子对象工具条,这个方法一般在前面介绍的CreateDetailView方法中生成子对象区域时使用。
具体编辑器控件生成
AutoUI静态类方法主要是生成Grid、TreeGrid、Panel等大的控件,具体针对每个属性的编辑和显示控件是由属性编辑器、GridColumn和TreeColumn来生成的,而GridColumn和TreeColumn的具体编辑控件又是由属性编辑器来生成的,所以属性编辑器是AutoUI内部的重要部分。
属性编辑器
在《内置支持的属性编辑方式》讲过支持的属性编辑器类型,每种类型都对应一个编辑器类。由于每个属性编辑器都比较类似,下面简单说明一下string属性编辑器。
每个属性编辑器都有一个方法CreateControlCore来生成编辑控件,StringPropertyEditor 生成了一个TextBox并绑定到对象属性上。还有一个方法SetControlReadOnly来控制只读时控件的状态改变。
public class StringPropertyEditor : WPFPropertyEditor
{
private TextBox tb;
public override void SetControlReadOnly()
{
tb.IsReadOnly = ReadOnly;
}
protected override object CreateControlCore()
{
tb = new TextBox()
{
Name = PropertyInfo.Name,
};
//绑定TextBox到对象属性
Binding textBinding = new Binding(PropertyInfo.Name);
//如果是只读属性,则设置binding mode
if (!PropertyInfo.PropertyInfo.CanWrite) textBinding.Mode = BindingMode.OneWay;
tb.SetBinding(TextBox.TextProperty, textBinding);
return tb;
}
}
为了重用这些属性编辑器,GridColumn和TreeColumn的列的具体编辑控件都是由属性编辑器来生成,例如StringTreeColumn内部使用了StringPropertyEditor
public class StringTreeColumn : TreeColumn
{
private StringPropertyEditor _editor;
public StringTreeColumn(BusinessObjectPropertyInfo info, ListObjectView view)
: base(info, view)
{
this._editor = new StringPropertyEditor();
}
public override PropertyEditor Editor
{
get
{
return this._editor;
}
}
}
自定义对象编辑界面
前面讲的生成控件都是按照对象标识以及对象属性标识,以及相关的命令设置来生成。实际开发中,可能存在一些业务对象需要其他界面样式来显示(如使用报表控件ReportView来显示列表信息),而AutoUI是处理共性要求的界面表现,这时我们框架就需要提供一种扩展机制,允许用户自定义编辑界面来对应到业务对象。AutoUI生成各类UI时先判断业务对象该类型下的UI有没有自定义,如果有则直接生成对应的Type指定的UserControl,否则按照上面的AutoUI自定生成。
/// <summary>
/// 业务对象模型
/// </summary>
public class BusinessObjectInfo
{
/// <summary>
/// AutoUI不满足应用需要时,通过挂接此属性来自定义明细UI
/// </summary>
public Type ModuleUIType { get; private set; }
public Type ListViewUIType { get; private set; }
public Type DetailViewUIType { get; private set; }
}
/// <summary>
/// 业务对象模型
/// </summary>
public class BusinessObjectInfo
{
/// <summary>
/// AutoUI不满足应用需要时,通过挂接此属性来自定义明细UI
/// </summary>
public Type ModuleUIType { get; private set; }
public Type ListViewUIType { get; private set; }
public Type DetailViewUIType { get; private set; }
}
信息系统开发平台OpenExpressApp - 理解核心元素ObjectView
更多内容: 开源信息系统开发平台之OpenExpressApp框架.pdf
欢迎转载,转载请注明:转载自周金根 [ http://zhoujg.cnblogs.com/ ]