一、要将FeatureCenter的动态库FeatureCenter.Module.dll引入到你的应用程序中
二、 在需要直接加载DetailView类前加入:
[AutoCreatableObject(ViewEditMode = ViewEditMode.View)]
表明此类是要直接加载DetailView的
这个比较灵活,比帮助中的简单,方便,如:
[NonPersistent]
[DefaultClassOptions][AutoCreatableObject(ViewEditMode = ViewEditMode.View)]
public class DomainObject1
{
public string Title { get; set; }
public string Context { get; set; }
private static DomainObject1 instance;
protected DomainObject1() : base() {}
public static DomainObject1 Instance
{
get
{
if(instance == null) {
instance = new DomainObject1();
}
return instance;
}
}
}
}
三、修改你的Module.cs
public sealed partial class FeatureCenterTestModule : ModuleBase
{
public FeatureCenterTestModule()
{
DevExpress.ExpressApp.ConditionalEditorState.Core.EditorStateRuleManager.CustomParseProperties += EditorStateRuleManager_CustomParseProperties;
Disposed += FeatureCenterTestModule_Disposed;
InitializeComponent();
DateRangeRepository.RegisterRange(new DateRange("Rolling 1994", new RangePoint(new DateTime(1994, 1, 1), 0, TimeIntervalType.Day), new RangePoint(new DateTime(1994, 1, 1), DateTime.Today.DayOfYear, TimeIntervalType.Day)));
DateRangeRepository.RegisterRange(new DateRange("Rolling 1995", new RangePoint(new DateTime(1995, 1, 1), 0, TimeIntervalType.Day), new RangePoint(new DateTime(1995, 1, 1), DateTime.Today.DayOfYear, TimeIntervalType.Day)));
DateRangeRepository.RegisterRange(new DateRange("Rolling 1996", new RangePoint(new DateTime(1996, 1, 1), 0, TimeIntervalType.Day), new RangePoint(new DateTime(1996, 1, 1), DateTime.Today.DayOfYear, TimeIntervalType.Day)));
Hints.RegisterViewSpecificHints();
}
private void Application_CustomizeFormattingCulture(object sender, CustomizeFormattingCultureEventArgs e) {
e.FormattingCulture.NumberFormat.CurrencySymbol = System.Globalization.CultureInfo.GetCultureInfo("en-US").NumberFormat.CurrencySymbol;
}
private void application_CustomProcessShortcut(object sender, CustomProcessShortcutEventArgs e) {
if (e.Shortcut != null && !string.IsNullOrEmpty(e.Shortcut.ViewId)) {
IModelView modelView = Application.FindModelView(e.Shortcut.ViewId);
if(modelView is IModelObjectView) {
ITypeInfo typeInfo = ((IModelObjectView)modelView).ModelClass.TypeInfo;
AutoCreatableObjectAttribute attribute = typeInfo.FindAttribute<AutoCreatableObjectAttribute>(true);
if(attribute != null && attribute.AutoCreatable) {
IObjectSpace objSpace = Application.CreateObjectSpace();
object obj;
if(typeof(XPBaseObject).IsAssignableFrom(typeInfo.Type)) {
obj = objSpace.FindObject(typeInfo.Type, null);
if(obj == null) {
obj = objSpace.CreateObject(typeInfo.Type);
}
}
else {
if (typeInfo.Type == typeof(DomainObject1))
{
obj = DomainObject1.Instance;
} else {
obj = Activator.CreateInstance(typeInfo.Type);
}
}
//obj = typeof(BaseObject).IsAssignableFrom(typeInfo.Type) ? objSpace.CreateObject(typeInfo.Type) : Activator.CreateInstance(typeInfo.Type);
DetailView detailView = Application.CreateDetailView(objSpace, obj, true);
if(attribute.ViewEditMode == ViewEditMode.Edit) {
detailView.ViewEditMode = DevExpress.ExpressApp.Editors.ViewEditMode.Edit;
}
e.View = detailView;
e.Handled = true;
}
}
}
}
private void AuditTrailServiceInstance_QueryCurrentUserName(object sender, QueryCurrentUserNameEventArgs e) {
//Do not remove this line. Usually, the current user name is supplied by an XAF application's Security System.
//However, the FeatureCenter demo doesn't use the Security System module.
//That's why we have to use the WindowsIdentity, to get the current user name.
e.CurrentUserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
}
private void AuditTrailServiceInstance_CustomizeAuditTrailSettings(object sender, CustomizeAuditTrailSettingsEventArgs e) {
e.AuditTrailSettings.Clear();
e.AuditTrailSettings.AddType(typeof(FeatureCenter.Module.Audit.SimplePropertiesAuditedObject), true);
e.AuditTrailSettings.AddType(typeof(FeatureCenter.Module.Audit.CollectionPropertiesAuditedObject), true);
e.AuditTrailSettings.AddType(typeof(FeatureCenter.Module.Audit.AuditSettingsObject), new string[] { "Name", "AuditedProperty" });
e.AuditTrailSettings.AddType(typeof(FeatureCenter.Module.Audit.ObjectAuditingModesObject), true);
}
private void FeatureCenterTestModule_Disposed(object sender, EventArgs e)
{
DevExpress.ExpressApp.ConditionalEditorState.Core.EditorStateRuleManager.CustomParseProperties -= EditorStateRuleManager_CustomParseProperties;
}
private void EditorStateRuleManager_CustomParseProperties(object sender, DevExpress.ExpressApp.ConditionalEditorState.Core.CustomParsePropertiesEventArgs e) {
if (e.TypeInfo.Type == typeof(FeatureCenter.Module.ConditionalEditorState.HideShowEditorsObject)) {
e.Handled = true;
}
}
private void CustomizeAppearanceDemoObject(ITypeInfo ti) {
if(ti != null) {
IMemberInfo mi1 = ti.FindMember("HideSimpleLayoutItem");
if(mi1 == null) {
mi1 = ti.CreateMember("HideSimpleLayoutItem", typeof(bool));
mi1.AddAttribute(new ImmediatePostDataAttribute());
mi1.AddAttribute(new VisibleInListViewAttribute(false));
mi1.AddAttribute(new VisibleInLookupListViewAttribute(false));
}
IMemberInfo mi2 = ti.FindMember("HideSimpleLayoutGroup");
if(mi2 == null) {
mi2 = ti.CreateMember("HideSimpleLayoutGroup", typeof(bool));
mi2.AddAttribute(new ImmediatePostDataAttribute());
mi2.AddAttribute(new VisibleInListViewAttribute(false));
mi2.AddAttribute(new VisibleInLookupListViewAttribute(false));
}
IMemberInfo mi3 = ti.FindMember("HideTabPageGroup");
if(mi3 == null) {
mi3 = ti.CreateMember("HideTabPageGroup", typeof(bool));
mi3.AddAttribute(new ImmediatePostDataAttribute());
mi3.AddAttribute(new VisibleInListViewAttribute(false));
mi3.AddAttribute(new VisibleInLookupListViewAttribute(false));
}
}
}
protected override BusinessClassesList GetBusinessClassesCore() {
BusinessClassesList classesList = base.GetBusinessClassesCore();
classesList.Add(typeof(OidGenerator));
return classesList;
}
protected override void CustomizeModelApplicationCreatorProperties(ModelApplicationCreatorProperties creatorProperties) {
base.CustomizeModelApplicationCreatorProperties(creatorProperties);
creatorProperties.RegisterObject(
typeof(DevExpress.ExpressApp.Validation.IModelRuleBase),
typeof(FeatureCenter.Module.Validation.RuleStringLengthComparison),
typeof(FeatureCenter.Module.Validation.IRuleStringLengthComparisonProperties));
creatorProperties.RegisterObject(
typeof(DevExpress.ExpressApp.Validation.IModelRuleBase),
typeof(FeatureCenter.Module.Validation.CodeRuleObjectIsValidRule),
typeof(DevExpress.Persistent.Validation.IRuleBaseProperties));
}
protected override ModuleTypeList GetRequiredModuleTypesCore() {
ModuleTypeList moduleTypeList = base.GetRequiredModuleTypesCore();
moduleTypeList.Add(typeof(DevExpress.ExpressApp.ConditionalAppearance.ConditionalAppearanceModule));
moduleTypeList.Add(typeof(DevExpress.ExpressApp.SystemModule.SystemModule));
return moduleTypeList;
}
public override void AddGeneratorUpdaters(ModelNodesGeneratorUpdaters updaters) {
base.AddGeneratorUpdaters(updaters);
updaters.Add(new ModelImageSourcesUpdater());
updaters.Add(new ConditionalEditorStateNodesGeneratorUpdater());
updaters.Add(new CustomDetailViewItemsGenarator());
updaters.Add(new CustomDetailViewLayoutGenarator());
}
public override void Setup(XafApplication application) {
base.Setup(application);
if (!XafTypesInfo.IsInitialized) {
XafTypesInfo.Instance.AddEntityToGenerate("DcNamedBaseObject", typeof(IDcNamedBaseObject));
XafTypesInfo.Instance.AddEntityToGenerate("DcDisableEnableEditorsObject", typeof(IDcDisableEnableEditorsObject));
XafTypesInfo.Instance.AddEntityToGenerate("DcDisableEnableEditorsAggregatedObject", typeof(IDcDisableEnableEditorsAggregatedObject));
XafTypesInfo.Instance.AddEntityToGenerate("DcDisableEnableEditorsLookupObject", typeof(IDcDisableEnableEditorsLookupObject));
XafTypesInfo.Instance.AddEntityToGenerate("DcDisableEnableEditorsCollectionElementObject", typeof(IDcDisableEnableEditorsCollectionElementObject));
}
Application.CustomizeFormattingCulture += new EventHandler<CustomizeFormattingCultureEventArgs>(Application_CustomizeFormattingCulture);
application.CustomProcessShortcut += new EventHandler<CustomProcessShortcutEventArgs>(application_CustomProcessShortcut);
AuditTrailService.Instance.TimestampStrategy = new LocalAuditTimestampStrategy();
AuditTrailService.Instance.QueryCurrentUserName += new QueryCurrentUserNameEventHandler(AuditTrailServiceInstance_QueryCurrentUserName);
AuditTrailService.Instance.CustomizeAuditTrailSettings += new CustomizeAuditSettingsEventHandler(AuditTrailServiceInstance_CustomizeAuditTrailSettings);
//((ViewVariantsModule)application.Modules.FindModule(typeof(ViewVariantsModule))).GenerateVariantsNode = false;
}
public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
base.CustomizeTypesInfo(typesInfo);
CustomizeAppearanceDemoObject(typesInfo.FindTypeInfo(typeof(FeatureCenter.Module.ConditionalEditorState.HideShowEditorsObject)));
CustomizeAppearanceDemoObject(typesInfo.FindTypeInfo(typeof(FeatureCenter.Module.ConditionalAppearance.ConditionalAppearanceHideShowEditorsObject)));
}
public override void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
base.ExtendModelInterfaces(extenders);
extenders.Add<IModelMember, IModelClassMemberExtender>();
extenders.Add<IModelPropertyEditor, IModelPropertyEditorClassMemberExtender>();
extenders.Add<IModelLayoutGroup, IModelLayoutGroupExtender>();
extenders.Add<IModelView, IModelViewDemoDescription>();
}
}
public class ModelImageSourcesUpdater : ModelNodesGeneratorUpdater<ImageSourceNodesGenerator> {
public override void UpdateNode(ModelNode node) {
//IModelAssemblyResourceImageSource customImages = (IModelAssemblyResourceImageSource)node[CustomImages.CustomImagesAssemblyInfo.AssemblyName];
//if(customImages == null) {
// customImages = node.AddNode<IModelAssemblyResourceImageSource>(CustomImages.CustomImagesAssemblyInfo.AssemblyName);
// customImages.Index = node.NodeCount - 1;
//}
//customImages.Folder = "Images";
}
}
public class ConditionalEditorStateNodesGeneratorUpdater : ModelNodesGeneratorUpdater<ConditionalEditorStateNodesGenerator> {
public override void UpdateNode(ModelNode node) {
ITypeInfo ti = XafTypesInfo.Instance.FindTypeInfo(typeof(FeatureCenter.Module.ConditionalEditorState.HideShowEditorsObject));
if(ti != null) {
IModelEditorStateRule hideSimpleLayoutItem = node.AddNode<IModelEditorStateRule>("HideShowEditorsObject.HideSimpleLayoutItem ID");
hideSimpleLayoutItem.ModelClass = node.Application.BOModel[ti.FullName];
hideSimpleLayoutItem.Attribute = new EditorStateRuleAttribute("HideShowEditorsObject.HideSimpleLayoutItem ID", "SimpleLayoutItem", EditorState.Hidden, "HideSimpleLayoutItem = 'True'", ViewType.DetailView);
IModelEditorStateRule hideSimpleLayoutGroup = node.AddNode<IModelEditorStateRule>("HideShowEditorsObject.HideSimpleLayoutGroup ID");
hideSimpleLayoutGroup.ModelClass = node.Application.BOModel[ti.FullName];
hideSimpleLayoutGroup.Attribute = new EditorStateRuleAttribute("HideShowEditorsObject.HideSimpleLayoutGroup ID", "SimpleLayoutGroup", EditorState.Hidden, "HideSimpleLayoutGroup = 'True'", ViewType.DetailView);
IModelEditorStateRule hideTabPageGroup = node.AddNode<IModelEditorStateRule>("HideShowEditorsObject.HideTabPageGroup ID");
hideTabPageGroup.ModelClass = node.Application.BOModel[ti.FullName];
hideTabPageGroup.Attribute = new EditorStateRuleAttribute("HideShowEditorsObject.HideTabPageGroup ID", "TabPageGroup", EditorState.Hidden, "HideTabPageGroup = 'True'", ViewType.DetailView);
}
}
}
这样你就可以直接使用DetailView
You can customize the information loaded to the Application Model via the Model Editor. It allows you to edit the information at design time, forming Application Model layers in each project. The layers are superimposed onto one another in a certain order. Values from every successive layer override the corresponding values from the previous layer (see the Application Model Basics topic). However, there is one more approach you can use to customize the information stored in the Application Model. In most cases, this approach is less suitable because the changes to be made in the Application Model are performed in code. But, this approach is useful when the feature that you implement in a module demands the modification of the information generated in the Application Model, by default. When implementing a new feature, you may need to add custom nodes to the Application Model. You may also need to customize an existing node by adding custom properties to it. In this way, you can allow other developers or end-users to change the behavior of your code by modifying your custom nodes and properties in the Model Editor. This topic explains how to extend and customize the Application Model in Code.
你可以用模型编辑器自定义应用程序模型信息。你可以在设计时候编辑它,在每个项目形成应用模型层。每层按照一定的顺序叠加。从每一个连续的值将覆盖层从上一层的相应值(见应用模型基础主题)。然而你可以用另外一种方式存储应用程序模型自定义信息。在大多数情况下,这种方法不太合适,因为将要在应用模型中所做的更改执行的代码。但是,这种方法是非常有用的功能,您在一个模块的需求落实在应用模型产生的信息的修改,默认情况下。当实现一个新功能,需要在应用程序模型中添加自定义节点。你也还需要为已有的自定义节点添属性。通过这种方式,允许其它开发者或者终端用户更改你的代码用模型编辑器中自定义节点和属性的操作。这个主题说明如何用代码扩展和自定义应用程序模型。
Several typical scenarios are provided in this topic. You can select one of them or combine them, to handle more complex customizations:
这个主题包含几个典型案例。你可以选择其中一个或者几个用来处理更复杂的自定义。
If it is required to access properties exposed by an existing node, use the approach described in the Access the Application Model in Code topic.
如果需要访问已有节点的公共属性,请参考用代码访问应用程序模型
Add a Custom Property to the Existing Node
为应用节点添加一个自定义属性
To add new properties to the existing node, you should first define an interface derived from the IModelNode interface, and exposing the required properties:
为已有节点添加新的属性,首先定义一个继承IModelNode接口的接口,并公开所需属性。
C#
VB
using DevExpress.ExpressApp.Model;
// ...
public interface IModelMyModelExtension : IModelNode {
string MyCustomProperty { get; set; }
}
Then, extend the required node in your Module or Controller. Override the ModuleBase.ExtendModelInterfaces method or implement the IModelExtender interface, respectively.
然后用模块或者控件扩展需求的节点。分别覆写ModuleBase.ExtendModelInterfaces方法或实现IModelExtender接口。
C#
VB
public sealed partial class CustomizeModelExampleModule : ModuleBase {
// ...
public override void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
base.ExtendModelInterfaces(extenders);
extenders.Add<IModelApplication, IModelMyModelExtension>();
}
// ...
}
Rebuild your solution and open the Model Editor, to see how the code above affects the Application Model.
重新编译项目并打开模型编辑器,观察以上代码在应用程序模型中的实现效果。
The IModelApplication node now exposes the MyCustomProperty property. To add the property to another node, substitute the interface corresponding to the required node, instead of the IModelApplication (see Application Model Structure).
IModelApplication节点现在公开MyCustomProperty属性。要添加另一个节点的熟悉,代替接口对应所需节点,而不是IModelApplication(见应用程序模型结构)
You can decorate nodes' properties with the following attributes:
Attribute(特性)
Description(描述)
Browsable
Indicates that a property is visible in the Model Editor. All properties added via the approach described above are visible, by default. To hide a property, decorate it with the Browsable attribute and pass false as the attribute's parameter.
在模型编辑器中指出属性是否显示。默认情况下,所以属性通过这种方式标示是否显示。隐藏用特性Browsable设置参数为false
Category(分类)
Specifies the target property category. The properties of each node are grouped by their categories in the Model Editor's property grid. The default category is "Misc". To assign a category to the property, decorate the property with the Category attribute and pass a category name as the attribute's parameter. If the specified category does not exist, it will be added.
指定目标属性分类。每个属性节点分组在模型编辑器属性表格分类中。默认分类是“Misc”。标示属性分类,用一个分类名称参数修饰属性。如果指定的分类不存在,将被添加。
DataSourceProperty
Specifies a name of the property which exposes a list of the target property's possible values. You will be able to choose a value via the drop-down list in the Model Editor's property grid. When the current property's node exposes a list of child nodes (implements the IModelList<ChildNodeType> interface, as illustrated in the Extend The Application Model with New Nodes section), you can pass "this" as the DataSourceProperty attribute parameter. As the result, the child nodes list will be the current property possible values list.
指定一个属性可选的值列表。在模型编辑器中通过下拉列表选择一个值,当当前属性节点公开了子节点列表(实现了IModelList<ChildNodeType>接口,如用新节点扩展应用程序模型图所示部分),你可以用“this”作为DataSourceProperty特性的参数。返回结果,子节点列表将用作当前属性可选值列表。
DefaultValue
Specifies the target property's default value.
指定目标属性的默认值。
Editor(编辑)
Binds a custom editor to the property (see EditorAttribute).
为属性绑定一个自定义编辑(见EditorAttribute)。
Description(描述)
Specifies the target property's description displayed in the Model Editor.
指定在模型编辑器中显示目标属性的描述。
Localizable(本地化)
Specifies that the target property can be localized. To define a localizable property, decorate it with the Localizable attribute and pass true as the attribute's parameter.
是否本地化目标属性。如果需要本地化目标属性,则设置Localizable特性参数为“true”.
ReadOnly(只读)
To prohibit property value modification via the Model Editor, decorate the property with the ReadOnly attribute and pass true as the attribute's parameter. Alternatively, you can omit the property's setter. But, the ReadOnly attribute affects only the Model Editor, and does not restrict the property modification in code.
禁止用模型编辑器修改属性值,用ReadOnly特性修饰,并设置特性参数为“true”。或者你省略属性的写操作。但是,ReadOnly只影响模型编辑器,不作用于代码修改目标属性。
Required(必须)
Indicates that a target property should have a value. The Model Editor will not allow you to save changes until you supply this property value.
表明一个目标属性有一个值。模型编辑器不允许属性值为空。
Examples of some of these attributes in use will be provided later in this topic.
下面用一些特性的列子.
Note (备注)
Do not create the property named "Application". Otherwise, this property will hide the node's inherited IModelNode.Application property. Hiding the Application property causes the "Cannot compile the generated code" exception.
不能用名称为”Application”的属性。否则,这个属性将隐藏继承IModelNode.Application属性。隐藏应用程序属性会导致“不能编译生成的代码”的异常。
Extend The Application Model with New Nodes(用新节点扩展应用程序模型)
The extension of the Application Model with new nodes is performed similarly to adding new properties. First, you should define interfaces representing your custom nodes and their properties:
用新节点扩展应用程序模型跟添加新属性相似。首先,定义接口描述你的自定义节点和他们的属性。
C#
VB
using System.ComponentModel;
// ...
[KeyProperty("Name")]
public interface IModelMyChildNode : IModelNode {
string Name { get; set; }
[Localizable(true)]
string MyStringProperty { get; set; }
int MyIntegerProperty { get; set; }
}
public interface IModelMyNodeWithChildNode : IModelNode {
IModelMyChildNode MyChildNode { get; }
}
public interface IModelMyNodeWithChildNodes : IModelNode, IModelList<IModelMyChildNode> {
}
As you can see, each custom node is derived from the IModelNode interface. The IModelMyNodeWithChildNode node exposes a single IModelMyChildNode child node.
正如你所看到的,每个自定义节点都继承于IModelNode接口。IModelMyNodeWithChildNode公开了一个IModelMyChildNode子节点。
Note (备注)
When determining whether a property will be a child node or value, the existence of the setter in its declaration is taken into account. If the property has a setter, then it will not automatically be filled with values, and vice versa. So, the MyChildNode property has no setter (marked as ReadOnly in VB).
在判断一个属性是否是一个子节点或值,要考虑定义写属性。如果这个属性有一个可写,那么他的值不会自动填充,反之亦然。因此,MyChildNode属性没有可写(在VB中标志用ReadOnly)
The IModelMyNodeWithChildNodes node exposes the list of IModelMyChildNode nodes, as it supports the IModelList<IModelMyChildNode> interface.
IModelMyNodeWithChildNodes公开了IModelMyChildNode节点列表,因为他支持IModelList<IModelMyChildNode> 接口。
You can decorate interfaces, representing nodes, with the following attributes:
你可以用下列特性修饰接口,节点。
Attribute(特性)
Description(描述)
DisplayName
(显示名称)
Specifies the node's display name, which is used as the node's caption in the Model Editor.
指定一个节点的显示名称,这是用作模型编码器中用节点的标题。
DisplayProperty
(显示属性)
Specifies the node's property name, whose value is used as the node's caption in the Model Editor.
指定节点的属性名称,其值是用做模型编辑器节点的标题
ImageName
(图片名称)
Specifies the node's image, which is used in the Model Editor. See the ImageNameAttribute topic for additional information.
指定节点图片,用在模型编辑器中。其他信息见ImageNameAttribute。
KeyProperty
(关键键属性)
Specifies the node's key property. The key property is used to identify nodes. Only a string property can be a key property. If the KeyProperty attribute is omitted, the Id key property is generated automatically. The use of this attribute is illustrated in the code above.
指定节点的关键属性。关键属性是用来识别节点。只用字符串属性才能用作关键属性。如果忽略,ID关键属性自动生成。上面代码演示了这个作用。
To add these nodes to a certain parent node in the Application Model, extend the required node's interface, as described in the Add a Custom Property to the Existing Node section of this topic.
在应用程序模型中为某一个父节点添加这些节点,扩展需要的节点的接口,见为存在的节点添加自定义属性。
C#
VB
public interface IModelMyModelExtension : IModelNode {
// ...
IModelMyNodeWithChildNode MyNodeWithOneChildNode { get; }
IModelMyNodeWithChildNodes MyNodeWithSeveralChildNodes { get; }
}
// ...
public override void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
base.ExtendModelInterfaces(extenders);
extenders.Add<IModelApplication, IModelMyModelExtension>();
}
Rebuild your solution and open the Model Editor to see how the code above affects the Application Model.
重新不要你的项目,并打开模型编辑器,看到如下结果。
You can add child nodes to the MyNodeWithSeveralChildNodes node via the context menu.
你可以通过上下文菜单为MyNodeWithSeveralChildNodes添加子节点。
Customize the Application Model via the Nodes Generator Class
通过自定义应用程序模型节点生成类
In some scenarios, it is required to implement logic to be executed when the custom node is added to the Application Model. The so-called Generator classes serve this purpose. The Generator is the ModelNodesGeneratorBase descendant. The ModelNodesGeneratorBase class exposes the GenerateNodesCore virtual method. To implement the required logic, override this method. To access the node for which the generator is invoked, use the method's node parameter. The following snippet illustrates Generator implementation.
在某些情况下,当给应用程序模型添加自定义节点,它需要逻辑实现。所谓生成器类能达到这个目的。生成器是ModelNodesGeneratorBase的后代。ModelNodesGeneratorBase公开了虚方法GenerateNodesCore。覆写该虚方法,实现需求逻辑。用生成器访问节点,用方法的节点参数。下面演示生成器的实现。
C#
VB
using DevExpress.ExpressApp.Model.Core;
// ...
public class MyChildNodesGenerator : ModelNodesGeneratorBase {
protected override void GenerateNodesCore(ModelNode node) {
for (int i = 0; i < 10; i++) {
string childNodeName = "MyChildNode " + i.ToString();
node.AddNode<IModelMyChildNode>(childNodeName);
node.GetNode(childNodeName).Index = i;
}
}
}
Note (备注)
The node parameter exposes the Application property, providing you with access to the whole Application Model.
节点参数公开了应用程序属性,供你访问整个应用程序模型。
This Generator creates ten IModelMyChildNode nodes and assigns indexes to them. To apply the Generator to the required node, use the ModelNodesGenerator attribute:
这个生成器建立十个IModelMyChildNode节点并标出索引。要应用生成所需节点,请用ModelNodesGenerator 特性
C#
VB
[ModelNodesGenerator(typeof(MyChildNodesGenerator))]
public interface IModelMyNodeWithChildNodes : IModelNode, IModelList<IModelMyChildNode> {
}
Each Node can have only one generator. However, additional customizations can be performed via the Generator Updaters
每个节点只有一个生成器。然而,通过Generator Updaters可以实现另外的自定义。
Rebuild your solution and open the Model Editor, to see how the code above affects the Application Model.
Note(备注)
The Generator affects the Application Model zero layer. So, the GenerateNodesCore method will be automatically executed on demand, when the data stored in the MyNodeWithSeveralChildNodes is required for the first time. For details on the Model layers, refer to the Application Model Basics topic. To ensure that the generator is executed only once, you can do the following:
生成器作用于应用程序模型零层。因此,GenerateNodesCore方法将被按需执行,第一次必须存储MyNodeWithSeveralChildNodes数据。有关模型层的详细情况,请参考应用程序模型基础主题。为了确保生成器只被生成一次,你可以按照以下方法做:
Implement a Property Having a List of Predefined Values
实现一个拥有预定义值列表的属性
Let's assume that the IModelMyNodeWithChildNodes node should expose the SelectedChildNode property, representing the "selected" child node. The user-friendly approach is to provide a drop-down, allowing you to choose one of the valid values in the Model Editor. So, the property should have a list of predefined values. This property can be implemented in the following manner:
假设IModelMyNodeWithChildNodes节点应公开SelectedChildNode属性,代表选择的子节点。为用户提供最好的方法是下列,在模型编辑器中允许选择也该可用值。因此,这个属性应有一个预定义的值列表。这个属性的实现方式如下:
C#
VB
using DevExpress.Persistent.Base;
// ...
public interface IModelMyNodeWithChildNodes : IModelNode, IModelList<IModelMyChildNode> {
[DataSourceProperty("this")]
IModelMyChildNode SelectedChildNode { get; set; }
}
Generally the DataSourceProperty attribute can be used to specify a name of the current node's property which exposes a list of the target property's possible values. Additionally, you can pass "this" as the DataSourceProperty attribute parameter (as illustrated in the code above). In this case, a list of the target property's possible values will be filled with the current node's child nodes.
一般来说DataSourceProperty特性可被用于给节点属性提供可用值列表。另外你可用“this”作为DataSourceProperty特性的参数(如图上代码所示)。这样,当前节点的子节点填充到目标属性的可用值列表。
Rebuild your solution, and open the Model Editor to see how the code above affects the Application Model.
重新编译项目并打开模型编辑器,看到如下结果。
You can add and remove child nodes, and the drop-down list will reflect changes instantly.
Note(备注)
To define a more complex logic for generating the list or predefined values, implement the supplementary property holding the required list, as described in the text section of this topic. Then, make this property hidden, by applying the Browsable(false) attribute to it. Finally, decorate your target property with the DataSourceProperty attribute and pass the supplementary hidden property name as the parameter. Note that the list items must be of the same type as the target property.
要定义一个更复杂的生成列表或预定义值逻辑,实现属性持有所需的补充列表,参考本主题文本所述。然后,使用Browsable(false)特性隐藏这个属性。最后,用DataSourceProperty修饰目标属性并作为参数补充隐藏属性名称。注意作为目标属性列表属性必须是相同类型。
Use the Domain Logic to Implement the Calculated Property
用域逻辑实现可计算属性
To customize the node's properties behavior, the Domain Logic (which is a part or the Domain Components technology) can be used. The common use case of Domain Logic is implementing calculated properties. The following code illustrates how to implement the NumberOfChildNodes property, which gets the number of current node's child nodes.
用域逻辑(域组件)实现自定义节点的属性行为。通常域逻辑实现可计算属性。下面代码演示如何实现NumberOfChildNodes属性,计算当前节点的子节点个数。
C#
VB
using DevExpress.ExpressApp.DC;
// ...
public interface IModelMyNodeWithChildNodes : IModelNode, IModelList<IModelMyChildNode> {
// ...
int NumberOfChildNodes { get; }
}
// ...
[DomainLogic(typeof(IModelMyNodeWithChildNodes))]
public class MyNodeWithChildNodesLogic {
public static int Get_NumberOfChildNodes(
IModelMyNodeWithChildNodes modelMyNodeWithChildNodes) {
return modelMyNodeWithChildNodes.NodeCount;
}
}
The DomainComponentAttribute in the code above indicates that the MyNodeWithMultipleChildNodesLogic class represents the Domain Logic for the IModelMyNodeWithChildNodes node. This class exposes the Get_NumberOfChildNodes method, executed when getting the NumberOfChildNodes property value. This method returns the number of IModelMyChildNode child nodes exposed by the IModelMyNodeWithChildNodes node. For details on Domain Logic, refer to the Domain Components Basics topic.
在上面的代码DomainComponentAttribute表明MyNodeWithMultipleChildNodesLogic类代表的IModelMyNodeWithChildNodes节点域逻辑。这个类公开Get_NumberOfChildNodes方法,执行时获取NumberOfChildNodes属性值。此方法返回IModelMyChildNode子节点的情况,IModelMyNodeWithChildNodes节点暴露节点的数目。有关域逻辑的细节,请参考域组件基本主题。
Rebuild your solution, and open the Model Editor to see how the code above affects the Application Model.
重新编译项目并打开模型编辑器,看到如下结果。
You can add and remove child nodes and the NumberOfChildNodes property value will reflect changes instantly.
你可用添加或者移除子节点,下列列表立即发生改变。
Each node can have a single Generator assigned. However, you can "attach" one or more Updaters to the Generator. The Generator Updater is the ModelNodesGeneratorUpdater<T> class' descendant. The ModelNodesGeneratorUpdater<T> class exposes the UpdateNode virtual method. To implement the required logic, override this method. To access the node for which the Updater is invoked, use the method's node parameter. The following snippet illustrates the implementation of two Generator Updaters.
每个节点有一个生成器分配。然而,你可以附加一个或多个Updaters给生成器。这个生成器Updater是ModelNodesGeneratorUpdater<T>类的后代。ModelNodesGeneratorUpdater<T> 类公开虚方法UpdateNode。覆写这个方法实现需求逻辑。要访问该更新被调用时,使用该方法的节点参数节点。下面的代码片段说明了两个生成器更新者的执行情况。
C#
VB
public class MyChildNodesUpdater1 : ModelNodesGeneratorUpdater<MyChildNodesGenerator> {
public override void UpdateNode(ModelNode node) {
foreach (IModelMyChildNode childNode in ((IModelMyNodeWithChildNodes)node)) {
childNode.MyIntegerProperty = childNode.Index + 1;
}
}
}
public class MyChildNodesUpdater2 : ModelNodesGeneratorUpdater<MyChildNodesGenerator> {
public override void UpdateNode(ModelNode node) {
((IModelMyNodeWithChildNodes)node).SelectedChildNode = "MyChildNode 9";
}
}
Note (备注)
The node parameter exposes the Application property, providing you with access to the whole Application Model.
这个节点参数公开应用程序属性,供你访问整个应用程序模型。
The first Updater sets the IModelMyChildNode.MyIntegerProperty values. The second sets the IModelMyNodeWithChildNodes.SelectedChildNode property value. All the Updaters should be registered in the overridden ModuleBase.AddGeneratorUpdaters method:
第一次更新设置IModelMyChildNode.MyIntegerProperty值。第二次设置IModelMyNodeWithChildNodes.SelectedChildNode属性值。所有的更新都需覆写ModuleBase.AddGeneratorUpdaters方法注册。
C#
VB
public sealed partial class CustomizeModelExampleModule : ModuleBase {
// ...
public override void AddGeneratorUpdaters(ModelNodesGeneratorUpdaters updaters) {
base.AddGeneratorUpdaters(updaters);
updaters.Add(new MyChildNodesUpdater1());
updaters.Add(new MyChildNodesUpdater2());
}
// ...
}
Rebuild your solution and open the Model Editor, to see how the code above affects the Application Model.
重新编译项目并打开模型编辑器,看到如下结果。
Note (备注)
If you previously modified these property values in the Model Editor, you should reset differences for the MyNodeWithSeveralChildNodes node to see the changes. To do this,right-click this node and select Reset Differences. The reason for this is that the Updaters operate at the Application Model zero layer, and changes can be overridden in higher layers.
如果你之前修改哪些属性用模型编辑器,你应当重置MyNodeWithSeveralChildNodes节点看到不同变化。其原因是更新者在应用程序模型零层操作,并能在更高层覆写。
To see another example of a custom Generator Updater, refer to the EnumDescriptor.GenerateDefaultCaptions method description.
看其他自定义生成器更新者的例子,请参考EnumDescriptor.GenerateDefaultCaptions方法描述。
eXpressApp Framework > Concepts > Application Model > 应用程序结构基础
业务应用程序是复杂的工程。从头开始建立一个应用程序,你不得不实现数据存储、数据可视化、业务逻辑、等等。使用XAF只需要实现业务模型。默认自动生成应用程序界面。为此,XAF应用程序界面模型结构信息用了一个特殊的数据存储。在它是基于应用程序中代码(定义的类)和引用模块生成的。本篇详述如何收集应用程序模型信息和如何用它自定义默认应用程序界面。
应用程序模型结构
当你运行一个Windows Forms或者ASP.NET应用程序时,应用程序模型将建立界面元素。例如,当框架建立一个列表显示一些联系人对象,每列的显示,本地化,标题和其它设置都由应用程序模型生成。应用程序模型提供了详细的业务类,控制器,按钮,属性编辑和其它有影响的应用程序功能。
应用程序模型信息呈树形显示。树节点通常有一组属性,实际上指定的应用程序模型数据。规则,根据该节点及其属性在模型中的组织,是由应用程序模型结构定义的。默认情况下,应用程序模型是有如下组织方法:根节点(应用程序)和几个子节点(按钮设计,业务模型,创建项目,图片资源,导航项目,详细视图项目,视图,等等)。

有关这些节点的详细介绍,参考应用程序模型架构帮助主题
有关生成业务模型和视图的详细信息,参考应用程序模型中的业务模型帮助主题
你可以在代码中访问应用程序模型。如何这样做,参考在代码中访问应用程序模型。
你可以用代码改变或扩展应用程序模型。如何这样做请参考:用代码自定义和扩展应用程序模型。
应用程序模型生成
应用程序模型不是立即完全生成。每个部分都是在需要时才生成。例如,如果某视图没被调用在运行应用程序时,它的节点并没有产生。这种“懒惰计算”的方法可以显着提高性能。
在内部,应用程序模型有一个层状结构
· 最后,主层。本身它不包含任何信息。它作为所有其它层的代理。通常,当你访问应用程序模型,你将处理的主层。
当需要从某个节点获取信息,该请求是针对主层。主层检查是否需求的信息已经在零层生成。如果不是,模型的需求部分则生成。然后主层要检查其它所有层,在零层叠加生产的信息,返回需求信息。如果层包含变量数据,如果有层包含不同数据,他们将汇集在最顶层。在中间层上的信息不能被修改。
当需要修改数据时,模型生成器将在顶层建立相应的模型块,保存改变的信息。
搜集用程序模型的节点信息,扫描所有工程项目中模型。信息从每个模块的代码搜集和扫描引用模块。
各个模块的扫描顺序下图所示:
图示例了一个简单的“MySolution”XAF Windows Forms应用程序需求的模块。MySolution模块需要BusinessClassLibraryCustomizationModule and SystemModule。MySolutionWindowsFormsModule模块需要MySolutionModule和SystemWindowsFormsModule。当生成应用程序模型时,首先扫描BusinessClassLibraryCustomizationModule和SystemModule模块。由于这些模块处于同级别引用,因此他们的扫描顺序不确定。SystemModule可能最先或最后,然后是MySolutionModule和SystemWindwosFormsModule。接着那些模块又处在相同的引用级别,扫描顺序不确定。最后扫描MySolutionWindwosFormsModule。
你可以在应用程序设计模块部分了解哪些模块需要在应用程序模型生成部分.它列出了所有添加到应用程序项目中的模块,等等。除了应用程序和模块设计,也有些替代方法添加模块。例如,你能在应用程序项目配置文件列出需求模块。详细情况请参考应用程序项目组件主题。所使用的应用程序模型生成的模块列表包括已添加,利用一切可能的办法。
自定义应用程序模型
· 生产应用程序模型的零层过程可以通过模型生成器自定义。当需要建立模型的一部分,模型生成器一次自动生成。模型生成器代表ModelNodesGeneratorBase后代,有GenerateNodesCore方法。所需的生产逻辑是实现此方法。每个模型生成器有几个模型生成器更新者附加。模型生成更新者代表ModelNodesGeneratorUpdater<T>的后代,和可用于执行其他自定义模型生成的一部分。看如何实现模型生成器和更新者,参考用代码扩展和自定义应用程序模型主题。
· To customize the Application Model at a certain Module's level, you can use the Model Editor invoked at design time. These customizations will be stored it the Module's XAFML file.
· 在某一个模块级自定义应用程序模型,你能在设计时用模型编辑器。模块中的XAFML文件存储这些自定义配置。
你可以在任何地方非常容易的撤销更改,撤销到初始值(最后,你能存储自动生成模型)。移除更改,简单的删除相应XafML文件。当然,你也可以在模型编辑器中只删除指定的改变(通过复位差异上下文菜单),或者通过编辑XafML文件。用MS Visual Studio打开一个XafML文件,选中右键单击再选择View Code。不同之处是XML格式。你可以很容的找需求更多或移除的节点或属性。不过我们建议你尽可能用模型编辑器,而不是直接编辑Xafml文件。
备注
使用应用程序模型改变设置,可以在代码中更改的设置(见内置属性)是不推荐的。例如,如果你重命名一个类,你所做的所有更改,将在应用程序模型中丢失。也建议你到该模块项目改变模型。如果你为一个应用程序项目工程自定义一个模型,你所做的改变不能应用到其他应用程序基于相同的逻辑。
· 最后,终端用户,可以自定义应用程序模型,可以直接更改,也可以在运行时通过模型编辑器更改,或者间接修改布局,更改皮肤,等等。终端用户自定义信息在Windows Forms应用程序中存储在Model.User.Xafml文件中,或者在ASP.NET 应用程序中存储在cookies中。