To better understand how to access and customize XAF
building blocks, let's briefly describe how default UI screens are generated in
XAF.
On the top of the UI screen structure, there are
Windows and Frames linked to
Templates (
Frame.Template), which are usually platform-dependent entities (e.g., forms, web pages or user controls) defining the Frame's content structure and appearance
in the UI.
There are several types of Frames (both platform-agnostic and
platform-dependent), depending on whether their represented UI element has a
parent or not: e.g. Frames of the main application form or of a separate dialog form are called Windows, because they are independent and have no parents, while a nested ListView's Frame is not Window.
Here is the respective classes hierarchy for clarity:
Frame
• NestedFrame
• Window
• WinWindow
•
WebWindow
•
PopupWindow
Frame is a container for
Controllers (
Frame.Controllers or
Frame.GetController<T>) that help implement a custom behavior and
interaction within a Frame. Note that all application logic and behavior is
basically provided by Controllers (
both system and user-defined). For easier
access, Controllers provide useful properties, events and virtual methods to
access the Frame itself and its contents at various points in its life cycle (
Controller.Frame,
WindowController.Window, ViewController.View, ViewController.ObjectSpace,
Controller.Activated, etc.).
Both Frame and Window also serve as a site for a
View
(
Frame.View, ViewController.View). Any View placed within a Frame consists of special sub-elements
or items, which represent the actual content of a UI screen. Technically, each of
these View elements wraps a certain visual control to display or edit specific
data in the UI . View items can be even be complex enough to host other Views.
The placement of content items on the screen, as well as
many of their basic settings, are determined by the information from the
Application Model corresponding to that View node (e.g., the
Views | Address_DetailView node you can see in the Model Editor tool). To learn more on how Views present or lay out their
content, check out the following important concepts:
At once, the Application Model is used to preserve the
state or current settings of the View and its controls until the next time.
This article is mainly devoted to Views, so please check
out the
UI Element Overview article if you are interested to learn more on the
structure of XAF screens.
View and Item types
The following diagram displays hereditary relations of various
View types:
View
•
CompositeView
•
DashboardView
•
ObjectView
• DetailView
• ListView
There are also several built-in
ViewItem types:
ViewItem
• ControlDetailItem
•
DashboardViewItem
•
PropertyEditor
•
ListPropertyEditor
•
DetailPropertyEditor
•
WinPropertyEditor
•
DXPropertyEditor
• StringPropertyEditor
• ...
•
WebPropertyEditor
•
ASPxPropertyEditor
• ASPxStringPropertyEditor
• ...
•
ActionContainerViewItem
• WinActionContainerViewItem
•
WebActionContainerViewItem
• StaticText
•
StaticTextDetailItem (DevExpress.ExpressApp.W**.Editors)
• StaticImage
•
StaticImageDetailItem (DevExpress.ExpressApp.W**.Editors)
• ...
Remember that for each of these built-in View and Item types there are corresponding elements in the Application Model (IModelDetailView, IModelDashboardView, IModelListView, IModelViewItem, IModelPropertyEditor, etc. that determine their content, layout structure and other options.
The use of certain View items varies and depends on the
View type:
•
PropertyEditor and its descendants for various data types, including
user-defined and built-in platform-agnostic (ListPropertyEditor, DetailPropertyEditor) and
platform-dependent editors;
• Other ViewItem descendants, including ActionContainerViewItem
, ControlDetailItem, StaticText, StaticImage, etc. (user-defined and built-in)
ListView is a special type of View, which consists of two internal items - ListEditorViewItem and
NestedFrameItem. These two service items are undocumented and are not supposed
to be used by customers in their code. Instead, ListView provides the additional
mechanism for accessing the ListView's content and controls -
List Editors.
Traversing View items and their controls
The View, ViewItem and ListEditor abstractions provided by
our framework are very often used to access underlying form controls for
various customizations. In accomplishing this task, it is common to implement a
custom
ViewController descendant targeted to a required View type and handle
its events or override virtual methods, depending on the moment when you need
to perform this customization.
• To access a ViewItem or ListEditor object from a
ViewController, it is common to override the OnActicated virtual method (or
handle the Activated event) of a custom ViewController class descendant,
because View items and List Editors are available right after View creation. Of
course, you can use other events and methods which occur later according to the
View life cycle.
• To access the underlying control of a ViewItem, it is
common to handle the ViewItem.ControlCreated event, while accessing the
ListEditor's control usually requires handling the View.ControlsCreated or
ListEditor.ControlsCreated events (or overriding the OnViewControlsCreated
method) within a custom ViewController class descendant. Of course, you can use
other events and methods which occur later according to the View life cycle.
When using this approach, a starting point is almost always
the
ViewController.View property that gives you access to the underlying
elements or API to access them. It is often required to cast the View object to
a concrete View type, e.g., DetailView or ListView, to gain access to more View
type specific options.
For your reference, here is the list of most popular APIs
used to implement common tasks with View items:
• To identify a ViewItem, use the ViewItem.Id property.
• To identify a PropertyEditor, use the PropertyEditor.Id
or PropertyEditor.PropertyName properties.
• To access ALL ViewItem objects, you can use the
CompositeView.Items property (see Example #1 below).
• To access ViewItem objects of a specific type, you can
use the CompositeView.GetItems<T> method (see Example #2 below).
• To access a ViewItem object by its Id, you can use the
CompositeView.FindItem method (see Example #3 below).
• To access a ListEditor object of a certain ListView, use
the
ListView.Editor property. It is often required to cast it to a specific
ListEditor type to access the underlying control and additional options. Refer
to the
Access Grid Control Properties help article for an example.
• To access the underlying control of View items or
editors, use the ViewItem.Control or ListEditor.Control properties. Most built-in
View items and editors also provide specialized properties for easier access,
e.g., GridListEditor.Grid, ASPxPropertyEditor.Editor,
ASPxLookupPropertyEditor.DropDown, etc.
Practical implementation considerations
0. Due to WinForms or ASP.NET platform specifics,
underlying controls of View items and List Editors may not be immediately ready
for customization right after the control is created. It is often required to
subscribe to specialized control events indicating their "ready"
state:
WinForms: handle the
HandleCreated, VisibleChanged or
ParentChanged events of the
System.Windows.Forms.Control object (in certain
cases it is even required to apply customizations via the
Control.BeginInvoke
method).
ASP.NET: handle the
Init, Load or PreRender server side
events of the
System.Web.UI.Control object or handle the
PagePreRender event of
the static
WebWindow.CurrentRequestWindow object. Of course, it is also fine to
perform certain customizations on the client side via JavaSript.
1. When iterating through the CompositeView.Items
collection (via foreach, for or LINQ) to locate a required ViewItem object, it
is often helpful to test its type or the ViewItem.Id property, corresponding to
the Id property of a respective item node in the Application Model (see Example
#1 below).
2. To avoid casting the ViewController.View object to a
required View type every time, inherit your custom controller class not from
the base ViewController class, but from the generic
ViewController<ViewType> or ObjectViewController<ViewType,
ObjectType> types, whose View property type will be the same as the
specified ViewType parameter (see Example #4 below).
3. Take into account the
CompositeView.DelayedItemsInitialization option when writing code that accesses
the
ViewItem.Control property. Since the default value for this option is true,
then in most scenarios it is recommended to handle the
ViewItem.ControlCreated
event of the required ViewItem object or test whether the properties accessed
are null (see Example #2 below). Refer to the
Access Editor Settings article
for more example code on this task.
4. There are some specifics when accessing items that host
or contain other Views inside DetailView or DashboardView. Such Views are
typically embedded into a container View with the help of special type of
platform-agnostic ViewItem classes:
• ListPropertyEditor is used to represent collection
properties of business objects in DetailView (see Example #4 below);
• DetailPropertyEditor is used to represent aggregated
reference properties of business objects in DetailView (see Example #5 below);
• DashboardViewItem is used to represent nested Views of
any type within a DashboardView (see Example #7 below).
5. Embedded or nested Views are hosted within a
NestedFrame, which is technically a descendant of the Frame type with the
ViewItem property that can help you identify what is hosted inside it (see
Example #8 below).
6. To access a parent View from the ViewItem object, use
the ViewItem.View property (see Example #9 below).
7. The View.Control property is very rarely used or should
not be used at all. Instead, it is better to use specialized properties of a
certain View object, e.g., ListView.Editor.Control,
CompositeView.LayoutManager.Container.
8. Certain List Editors (e.g., GridListEditor) may
internally instantiate PropertyEditor objects (e.g., for grid column editors)
to create underlying controls for viewing or editing business object properties
in a ListView. However, these PropertyEditor objects are not "alive"
and are primarily only used as a template to create a control. Thus you should
not try to access these service PropertyEditor objects in your code via a
ViewController.
9. If you need to apply the same global customization to
the underlying PropertyEditor control in both ListView and DetailView, then you
may consider implementing a descendant of the corresponding PropertyEditor type
(and overriding its virtual methods) instead of implementing a ViewController
and writing different code for both ListView and DetailView.
10. Normally you should not modify the Application Model
information corresponding to a certain View or its items in your code when
underlying controls are already created, unless you preserve the current
control state into your own model options. When doing so, bear in mind that
your model changes will not be taken into account until the next control
creation. So, a general rule here is that you either need to customize the
control directly or customize the corresponding Application Model options
before the control is created and rendered.
Check out the following code examples to better understand
these points:
using System;
using System.Linq;
using DevExpress.ExpressApp;
using System.Collections.Generic;
using DevExpress.ExpressApp.Layout;
using DevExpress.ExpressApp.Editors;
namespace MainDemo.Module.Controllers {
public class MyViewController : ViewController {
public MyViewController() {
TargetViewType = ViewType.DetailView;
}
protected override void OnActivated() {
base.OnActivated();
//Example #1
foreach(ViewItem item in ((DetailView)View).Items) {
if((item is ControlDetailItem) & item.Id == "Item1") { /*...*/ }
}
//Example #2
foreach(ActionContainerViewItem item in ((DetailView)View).GetItems<ActionContainerViewItem>()) {
if(item.Control != null) { /*...*/ }
else {
item.ControlCreated += (s, e) => { /*...*/ };
}
}
//Example #3
PropertyEditor editor = ((DetailView)View).FindItem("Property") as PropertyEditor;
//Example #4
ListPropertyEditor editorForCollectionProperty = ((DetailView)View).FindItem("CollectionProperty") as ListPropertyEditor;
editorForCollectionProperty.ControlCreated += (s, e) => {
ListView nestedListView = ((ListPropertyEditor)s).ListView;
NestedFrame nestedFrame = ((ListPropertyEditor)s).Frame as NestedFrame;
};
//Example #5
DetailPropertyEditor editorForAggregatedReferenceProperty = ((DetailView)View).FindItem("AggregatedReferenceProperty") as DetailPropertyEditor;
editorForAggregatedReferenceProperty.ControlCreated += (s, e) => {
DetailView nestedDetailView = ((DetailPropertyEditor)s).DetailView;
NestedFrame nestedFrame = ((DetailPropertyEditor)s).Frame as NestedFrame;
};
}
}
//Example #6
public class MyGenericDashboardViewViewController : ViewController<DashboardView> {
protected override void OnActivated() {
base.OnActivated();
foreach(DashboardViewItem item in View.GetItems<DashboardViewItem>()) {
//Example #7
item.ControlCreated += (s, e) => {
DashboardViewItem dashboardViewItem = (DashboardViewItem)s;
View nestedView = dashboardViewItem.InnerView;
NestedFrame nestedFrame = dashboardViewItem.Frame as NestedFrame;
//Example #8
DashboardView containerView1 = (DashboardView)nestedFrame.View;
//Or
DashboardView containerView2 = (DashboardView)dashboardViewItem.View;
//Example #9
ViewItem nestedFrameViewItem = nestedFrame.ViewItem;
};
}
}
}
}
See Also