上图是eXpressApp Framework框架中的重要架构元素,其中View居于中间位置,是框架的核心概念之一,而OpenExpressApp也使用并扩充了这一核心概念。理解OEA必须理解这些核心概念,本篇将介绍一下在OEA中ObjectView的一些知识,明白这个概念后就能更加快速的理解AutoUI技术了。
下图是View相关的主要类图:
信息系统必不可少的需要在UI上显示数据,OEA使用了多种抽象的视图概念。视图可以访问指定数据并通过自动界面生成来装载显示数据,用户通过command机制可以扩充自己针对ObjectView的命令来操纵与ObjectView相关的内容。
从类库可以看出,框架支持两类视图:列表视图【ListObjectView】,详细视图【DetailObjectView】,DetailObjectView又派生了导航查询视图【NavigateQueryObjectView】和【CondtionQueryObjectView】,大家可以从下图看出各个ObjectView对应到的实际界面:
ObjectView
ObjectView是所有视图的基类,主要实现了两个接口,一个是ISelectionContext,另一个是IViewDataContext
/// <summary>
/// 选择的上下文,获取当前操作对象,以及多选时选择的多个对象
/// </summary>
public interface ISelectionContext
{
/// <summary>
/// 视图当前操作的对象
/// </summary>
object CurrentObject { get; }
/// <summary>
/// 选中的对象集合
/// </summary>
IList SelectedObjects { get; }
event EventHandler CurrentObjectChanged;
event SelectedItemChangedEventHandler SelectedItemChanged;
}
/// <summary>
/// 对象绑定上下文,View的数据来源
/// </summary>
public interface IViewDataContext
{
/// <summary>
/// 绑定的对象
/// </summary>
object Data { get; set; }
}
ObjectView内部主要实现了传递类型,和生成控件等虚方法。通过传递类库类型就可以生成对应的抽象View类,然后调研Control,ObjectView的继承类内部就会自动调用AutoUI生成显示数据的UI控件。
public abstract class ObjectView : ISelectionContext, IViewDataContext
{
/// <param name="boType">这个视图对应这个业务模型类型</param>
public ObjectView(Type boType)
{
this.BOType = boType;
RegionType = RegionType.Data;//默认为数据区域
}
public RegionType RegionType { get; set; } //数据区域
/// 当前View对应这个业务模型类型
public Type BOType { get; private set; }
/// 可以包含多个细表ListView
public readonly IList<ObjectView> ChildViews = new List<ObjectView>();
/// 作为子视图和查询视图时有Parent
public ObjectView Parent { get; set; }
/// <summary>
/// 如果还没有创建Control,则调用CreateControl进行创建。
/// </summary>
public virtual object Control
{
get
{
if (null == control)
{
control = CreateControl();
}
return control;
}
}
/// 创建当前ObjectView关联的Control
public abstract object CreateControl();
/// 添加子视图
public void AddChildView(ObjectView view)
{ }
/// 查找指定类型的子视图
public ObjectView GetChildView(Type type, bool recur)
{ }
/// 查找指定类型的父视图
public ObjectView GetParentView(Type type, bool recur)
{ }
/// 触发SelectedItemChanged事件
public virtual void OnSelectedItemChanged(object sender, SelectedItemChangedEventArgs e)
{ }
/// 查找指定类型的子视图
public ObjectView GetChildView(Type type, bool recur)
{
foreach (var item in ChildViews)
{
if (item.BOType == type)
{
return item;
}
if (recur)
{
ObjectView view = item.GetChildView(type, recur);
if (null != view)
{
return view;
}
}
}
return null;
}
/// 查找指定类型的父视图
public ObjectView GetParentView(Type type, bool recur)
{
if (Parent.BOType == type)
{
return Parent;
}
//多对象树形可能存在多个type,对于子对象来说,Parent可能指向同一个View
if (BOType == type)
{
return this;
}
if (recur)
{
ObjectView view = Parent.GetParentView(type, recur);
if (null != view)
{
return view;
}
}
return null;
}
}
DetialObjectView
DetailObjectView 表示单个实体的详细信息,单据样式模块一般左边是列表,右边上面是详细信息区域,下面是细表区域(可以查看上面具体UI图)。右边上面的视图就是一个DetailObjectView,它作为单个根对象的详细信息显示,并可以访问单据样式的列表对应的ListObjectView。根对象会带有很多子对象,每个子对象都对应一个子ListObjectView(如果是多对象树结构则只生成树的根对象对应的View)
public class DetailObjectView : ObjectView
{
/// 单据样式模块,DetailObjectView都对应到一个列表View
public ListObjectView ListView { get; set; }
public override object CreateControl()
{
var control = AutoUI.CreateDetailView(BOType, this, true);
return control;
}
public override IList SelectedObjects
{
get
{
ArrayList list = new ArrayList();
list.Add(this.CurrentObject);
return list;
}
}
protected override void OnSetData()
{
if (!(Data is IList))
{
CurrentObject = Data;
}
else if ((Data as IList).Count > 0)
{
CurrentObject = (Data as IList)[0];
}
(Data as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(DetailView_PropertyChanged);
(Data as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(DetailView_PropertyChanged);
}
private void DetailView_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
foreach (var view in ChildViews)
{
if (view.PropertyName == e.PropertyName)
{
view.Data = CurrentObject.GetPropertyValue(view.PropertyName);
}
}
}
NavigateQueryObjectView
NavigateQueryObjectView 表示导航查询条件查询类详细信息,一般作为导航条件查询面板显示,可以出现在查询模块和单据模块的查询面板中,也可以出现在单据模板的导航面板中。如果单据模块有导航视图,那么单据新增时,会自动把导航视图的导航项传递到单据新增对象的属性中。后面会单独再介绍如何用面向对象的方式来生成导航查询应用的示例。
public class NavigateQueryObjectView : DetailObjectView
{
public string NavigateObjectPropertyName { get; private set; }
public string NavigateIdPropertyName { get; private set; }
public NavigateQueryObjectView(Type boType)
: base(boType)
{
NavigateObjectPropertyName = "";
NavigateIdPropertyName = "";
//列表导航
foreach (BusinessObjectsPropertyInfo info in ApplicationModel.GetBusinessObjectInfo(boType).BOsPropertyInfos)
{
if (info.IsNavigateQueryItem)
{
NavigateObjectPropertyName = info.BOType.Name;
switch (info.QueryItemValueType)
{
case QueryItemValueType.Id:
NavigateIdPropertyName = NavigateObjectPropertyName + "Id";
break;
}
}
}
//Lookup属性导航
foreach (BusinessObjectPropertyInfo info in ApplicationModel.GetBusinessObjectInfo(boType).BOPropertyInfos)
{
if (info.IsNavigateQueryItem)
{
switch (info.QueryItemValueType)
{
case QueryItemValueType.Id:
NavigateIdPropertyName = info.Name;
NavigateObjectPropertyName = info.LookupAttribute.LookupPropertyName;
break;
}
}
}
}
public override object CreateControl()
{
var control = AutoUI.CreateDetailView(BOType, this, false);
return control;
}
}
CondtionQueryObjectView
CondtionQueryObjectView 表示条件查询对象详细信息,一般作为导航条件查询面板显示,通过【查询】按钮触发查询,通过传递View.CurrentData作为类库的GetList方法的参数来获取查询结果。类库继承与DetailObjectView本身没有实现什么特定的方法。
ListObjectView
ListObjectView 表示 多个对象实体的列表信息,OEA支持普通列表样式和树形样式,树形样式又支持单对象类型和多对象类型。 ListObjectView关联前面提到的两个查询ObjectView,也关联一个查询列表单对象的一个DetailObejctView。ListObejctView通过传入的对象类型是否实现了ITreeNode来判断是使用树形编辑器 TreeListEditor还是 ListEditor。
public class ListObjectView : ObjectView
{
private ListEditor listEditor;
/// 通过父对象的子属性来建立的View
public ListObjectView(BusinessObjectsPropertyInfo boPropInfo)
: this(boPropInfo.BOType)
{
this.BOsPropInfo = boPropInfo;
}
public ListObjectView(Type boType)
: base(boType)
{
IsTree = false;
if (typeof(ITreeNode).IsAssignableFrom(boType))
{
listEditor = new TreeListEditor(this, boType);
IsTree = true;
}
else
{
listEditor = new ListEditor(this, boType);
}
//如果类型有属性OrderNo,则表明需要处理记录顺序号
InOrder = null != boType.GetProperty(DBConvention.FieldName_OrderNo);
//查询面板View
Type condtioQueryType = ApplicationModel.GetBusinessObjectInfo(boType).CondtionQueryType;
if (null != condtioQueryType)
{
CondtioQueryView = new CondtionQueryObjectView(condtioQueryType);
CondtioQueryView.Parent = this;
CondtioQueryView.RegionType = RegionType.ConditionQuery;
}
Type navigateQueryType = ApplicationModel.GetBusinessObjectInfo(boType).NavigateQueryType;
if (null != navigateQueryType)
{
NavigateQueryView = new NavigateQueryObjectView(navigateQueryType);
NavigateQueryView.Parent = this;
NavigateQueryView.RegionType = RegionType.NavigateQuery;
}
}
/// 这个ListView可能会有一个 查询面板View
public CondtionQueryObjectView CondtioQueryView { get; private set; }
/// 这个ListView可能会有导航View
public NavigateQueryObjectView NavigateQueryView { get; set; }
/// 这个ListView可能还会关联一个DetailView用于显示某一行。
public DetailObjectView DetailView { get; set; }
}
ObjectView的遍历
通过上面的讲解,DetailObjectView可以访问ChildView,ListObjectView可以访问对应的单个对象的DetailObjectView和查询类View,这样就可以保证通过当前View可以遍历查找对象关系图中的任一对象类型所对应的ObjectView。这种查找方法在自定义功能代码中会用到。在遍历查找时尽量通过 GetChildView和 GetParentView查找子对象和父对象,而不直接使用.Parent和.Child[0]来操作,以免由于类库关系更改导致代码更改。对于下图所示UI,各个View之间存在如下关系:
注:这几类ObjectView应该和界面无关的,后期会把UI无关的部分挪入到OpenExpressApp.Module项目中
信息系统开发平台OpenExpressApp - 内置支持的列表编辑方式