Search This Blog

Wednesday, January 29, 2014

How to traverse and customize XAF View items and their underlying controls

!!!The contents of this article were moved to www.devexpress.com/issue=KA18895!!!

XAF UI screen structure
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 user-defined and built-in ActionContainerViewItem, ControlDetailItem, StaticText, StaticImage, etc.

    • 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.

11. When ListView and DetailView are shown together (MasterDetailMode = ListViewAndDetailView), you can use the ListView.EditView property to access the embedded detail form and its content.

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

No comments:

Post a Comment