One more dive into the XAF Views Structure

XAF Team Blog
05 March 2011

Introduction

You might have already forgotten when we blogged about dashboards way back in August 2010, then recently we had a webinar about a refactored XAF Views structure. After that, we had a lot of feedback from customers asking about dashboards, and in this blog I wanted to summarize answers to the most often asked questions. For your convenience, I separated the information into several sections. I hope you find this information useful.


Refactoring Views and Controllers

First, let’s learn more about internals behind this feature. Technically, dashboards in XAF is a special type of View (similar to ListView and DetailView), but which can contain other Views and other View items like images, static texts, etc.  In order to provide this functionality, a new  DashboardView class  was  introduced. The main difference in this class from the known ListView and DetailView is that it is NOT descended from the ObjectView class – a base class for Views that can be bound to a business object. In other words, DashboardView is not bound to business objects anymore. It opens up significant capabilities, because we can put various View items into this View, for instance, items containing other Views. In order to provide this capability, a new DashboardViewItem  class was introduced. Dashboard item provides a way to set a criterion for objects within a View linked to it, as well as other settings, such  as whether show a tool bar of a linked View or not. The image below shows what the XAF Views structure looks like in 10.2:

 

The Controllers hierarchy was changed to provide a better way of working with new View types as well. A new ObjectViewController class was introduced:

using System;
using System.ComponentModel;

namespace DevExpress.ExpressApp {

    [ToolboxItem(false)]

    public class ObjectViewController : ViewController<ObjectView> {

        public ObjectViewController() : base() { }

    }

}

As you see this is a very simple class, which is descended from a generic ViewController class version, and which tied to the ObjectView class only. That means that this controller will work for both ListView and DetailView (see the above image for more details).

Using generics to target your controller to the required Views is very convenient and may be much faster than using a standard Add New Item… dialog in Visual Studio. As far as I know, most of advanced XAF users use this approach if they need to quickly link some customization or functionality with a certain View type only.

Let’s show how to create a controller that will work for a Dashboard view  using only the generic controller shown above:

using System;
using DevExpress.ExpressApp;

namespace MySolution {

    public class MyDashboardViewController : ViewController<DashboardView> { /*...*/ }

}

Alternatively, it is possible to set the TypeOfView property to the necessary View type  (see the above image for more details) in the constructor. That gives you more flexibility than when working with the TargetViewType enumeration, which is currently limited to ListView and DetailView only.


Implementing simple interaction between Views within a Dashboard

Let’s extend the controller above to show how to implement simple interaction between dashboard items or Views our dashboard contains:

using System;
using System.Collections;
using DevExpress.ExpressApp;
using DevExpress.Data.Filtering;
using DevExpress.ExpressApp.Editors;

namespace FeatureCenter.Module {

    public class EmployeesDashboardInteractionController : ViewController<DashboardView> {

        private const string TargetDashboardId = "DashboardView2";

        private const string ChartCriteriaId = "SelectedGridRecordsCriteria";

        private ListView chartListView;

        private ListView gridListView;

        protected override void OnActivated() {

            base.OnActivated();

            if (View.Id == TargetDashboardId) {

                foreach (DashboardViewItem item in View.GetItems<DashboardViewItem>()) {

                    item.ControlCreated += new EventHandler<EventArgs>(item_ControlCreated);

                }

            }

        }

        protected override void OnDeactivated() {

            if (View.Id == TargetDashboardId) {

                foreach (DashboardViewItem item in View.GetItems<DashboardViewItem>()) {

                    item.ControlCreated -= new EventHandler<EventArgs>(item_ControlCreated);

                }

                if (gridListView != null)

                    gridListView.SelectionChanged -= new EventHandler(gridListView_SelectionChanged);

            }

            base.OnDeactivated();

        }

        private void item_ControlCreated(object sender, EventArgs e) {

            DashboardViewItem item = ((DashboardViewItem)sender);

            if (item.Frame.View.Id == "Employee_Top5_ListView") {

                gridListView = (ListView)item.Frame.View;

                gridListView.SelectionChanged += new EventHandler(gridListView_SelectionChanged);

            }

            if (item.Frame.View.Id == "Order_ListView_Chart_Employees") {

                chartListView = (ListView)item.Frame.View;

            }

        }

        private void gridListView_SelectionChanged(object sender, EventArgs e) {

            ArrayList keys = new ArrayList();

            foreach (object selectedObject in ((ListView)sender).SelectedObjects) {

                keys.Add(ObjectSpace.GetKeyValue(selectedObject));

            }

            FilterChartListView(new InOperator("Employee.EmployeeID", keys));

        }

        private void FilterChartListView(CriteriaOperator criteria) {

            chartListView.CollectionSource.Criteria[ChartCriteriaId] = criteria;

        }

    }

}

This simple controller demonstrates how to filter data in a chart against a record, selected in another ListView. You can test this controller in the FeatureCenter demo, distributed with XAF:

 

How  could we approach this task a different way? 

We could create two controllers for two ListViews (one with a chart and the other one with a grid) and somehow access other Views/items from within each particular ListView controller. Obviously, this will be difficult, because these are two different controllers, targeting a certain View and activated at different points of time. In addition, they do not know much about the world outside the target View context. It would be much easier to implement simple interaction between them if we had  a way to access both ListViews from one place at the same time. The generic controller shown above is  designed exactly to solve these problems. Therefore, as you can see, its main benefit is that you can easily access nested Views/items from one place. In order to do this, the View.Items property or View.GetItems<T> method should be used. It is also worth noting that starting from version 10.2 , it is better to handle the ViewItem.ControlCreated event if you want to access an underlying control of a certain ViewItem.  This is due to the implementation of the  delayed View Items initialization in this version. You might have already used this approach when trying to provide interaction between the DetailView items, for instance, to perform drag and drop operations between two nested ListViews, etc.

I have to note that the capability to use generic controllers themselves is not new – it has been available in XAF  from version 10.1, unless I am mistaken. I would also note an interesting specific of generic controllers, which is worth letting you know because you will encounter it shortly, when you try to add Actions to such a controller. The issue is that the UI designer in Visual Studio does not work when the design class inherits from a generic class. This behavior is by design for Microsoft designers, and we cannot easily bypass it on our side. You can find a lot of information on this in Google and Microsoft forums, so please do not fault us. The good news is that you can still workaround it by declaring a temporary base class, a non-generic class, inhering from the generic one. For instance, we can do it as follows:

public class DashboardViewControllerBase : ViewController<DashboardView> { /*...*/ }

and after that we can implement our EmployeesDashboardInteractionController as follows:

public class EmployeesDashboardInteractionController : DashboardViewControllerBase { /*...*/ }

and be  able to use the designer for it. A nice trick, is not itSmile


Dashboards and security

There is no way to grant a permission for opening a particular dashboard View, as our security system is object-based and not View-based. If an object type shown in a View within a dashboard has corresponding permissions, e.g. to deny reading, then "Protected Content" will be shown in this View instead of actual data. However, it is possible to hide certain navigation items, corresponding to dashboards, based on the currently logged user. You can find more details in our Support Center database, e.g. here. Alternatively, you can customize the application model in the overridden OnLoggedOn method of the XafApplication class, and change the navigation items structure, as required. If you experience any difficulty applying these solutions, feel free to contact our Support Team.

We also have plans to improve the current state of affairs in future versions, for example, by providing an easy way to apply applications model differences based on the current user/role.

 

Apply different settings for Views in Dashboards

We were asked several times about using different settings for Views placed within a dashboard only. Let me explain. Imagine you have a Customer_ListView, and use it to show a list of customers in your application. Now you want to show a list of the most active customers within a dashboard. The first idea that comes to  mind is  to reuse the Customer_ListView and add an item for it in a dashboard. But what do you do if you want to remove some columns in this ListView when it is shown for the dashboard, and display all columns when it is shown in other places in your application? We are afraid that there is no way to reuse the Customer_ListView  in the current version of the XAF suite for that scenario. As a solution, you will have to create several ListViews with different settings and use them in respective places. In our particular case, we will clone the Customer_ListView via the Model Editor and name it Customer_ListView_Dashboard, then customize it and reuse it in our dashboard. In my opinion, it is the most correct solution for that task, because you will still have to distinguish these Views and it is better to do this explicitly, on the application model level.

 

What is next?

Although using controllers to do something is a recommended way to customize anything in our framework (our funny users even invented a special acronym for that – YAC - Yet Another Controller)), would  it  be better to have an easier way to interact between Views within a dashboard, ideally without writing a lot of code and with greater runtime customization capabilities?  Of course, it would be better!

            I want to provide a link to a similar discussion  with our customer in the Support Center, where the  main benefits of this approach are described. As you see, it leads to a feature request  for a standalone Dashboard Control, which would be available for non-XAF users also. So, if you are interested in this functionality, feel free to track it.

            When introducing all the changes I described above, we tried to minimize possible impacts as much as possible. However, this refactoring will not stop in 10.2. In the future, we are going to refactor the super base View class to remove all the functionality relating to working with business objects (what ObjectView would provide). There will also be changes to the ListView, as it would probably be a CompositeView. Please stay tuned!

UPDATED

See also the FAQ: How to traverse and customize XAF View items and their underlying controls article to learn more on the subject.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.