Current filter:
                                You should refresh the page.
                                Support Center
                                0
                                  • In this article, we will discuss a better way of building Windows client applications using the Microsoft.Net Framework. The result of this tutorial will be a library and sample application that will allow us to build module and UI independent Windows applications more easily.

                                    This article is divided into two parts:

                                    1. Develop a simple Application Framework. This section is not dependant upon Developer Express libraries, and so .Net developers who are not Developer Express customers can use it.

                                    2. Improve the Application Framework by using Developer Express libraries. To compile and run the applications provided, install the following Developer Express Libraries:

                                    a) XtraNavBar
                                    b) XtraBars Suite
                                    c) XtraGrid Suite
                                    d) XtraPrinting Library

                                    Issues considered in this paper include:

                                        * the best way of implementing application layouts - using User Controls
                                        * Module Inheritance using User Controls
                                        * Creating an actions layer
                                        //* How long it takes to migrate from one Menu&ToolBar library to another. Using this Application Framework, you can do it pretty fast as you only need to modify the code in one module.
                                        * Setting up the XtraNavBar control at run-time
                                        * Using XtraBars and XtraGrid in the Application Framework
                                        * Adding a printing capability using the XtraPrinting Library

                                You must  log in  or  register  to leave comments

                                1 Solution

                                0

                                We will develop our application framework piecemeal, starting from the simplest task and at each step we will add functionality, thus keeping code changes as simple as possible. There are 6 steps in total. You can download and compile the project at every step.

                                Note that the approach to building applications described in this article is just a suggestion. We don't claim that this approach is the best in all possible scenarios. This is just a basic framework that can be easily extended or changed to get the maximum benefit for each particular application you create.

                                Application Framework. Why do I need it?

                                Microsoft introduced into the .Net Framework many useful classes to help us build Windows Form apps more effectively. So, the obvious question is: why should we add another layer to our code? There was a time when I did not even think about such things, but the benefits of doing so are many...

                                Almost 10 years ago, I joined a company that developed Contact and Sales management systems. At that time, they used VB+MS SQL for their development. However, a month after I joined, Borland released Delphi 1, the first RAD tool with a true object oriented language. It was decided to use Delphi for the next large project. We were really excited about it. We decided that we would have one application for all our modules, so we could reuse our code better. We were young. Most of us, including me, had just graduated from college. We worked like mad dogs coding dozens and dozens of specs for different modules. Everything went well, until we started to test our application in a real environment. Suddenly, we found that fixing bugs was not as easy a task as we had thought. Fixing a bug in one module produced several bugs in other modules. Coding new modules was taking more and more time. The logic of our menu/toolbar system that was used for dozens of different modules became a complete nightmare. Nobody was able to understand it since it contained a lot of "case/switch" operations, loads of "if" statements, etc. We were only able to finish the project by everybody putting in enormous amounts of personal time. When the project was finished (it took about a year) everybody was extremely tired and exhausted. Most of the developers left the company for a vacation and never came back!

                                I would be lying if I said that we only had problems because we did not use an Application Framework. There were a lot of mistakes made during the project. I guess we made nearly all the possible mistakes that could be made while working on a software project. Of course, we never improved our code. Automatic tests - what were they? At that time, we had not heard about them and we hardly did any testing at all. Everybody only cared about their own modules and the shared code was a real mess. I could go on, but I'm sure you know what I'm saying.

                                However, I know for sure that not having an Application Framework is one of the main reasons we had problems during development. When the project was almost finished, I found time to look at the most of the modules. I was pretty surprised. Much of the required functionality of every module was quite common, although it was implemented in different ways.

                                The next time I participated in a similar project, I pushed everybody to spend several days on creating a very simple framework. It allowed us:

                                 1. to add/remove modules by adding/removing one line of code
                                 2. to share common functionality between modules
                                 3. unify menu/toolbars usage

                                The time spent on coding this layer was paid back many times during further development. From that time, I have used a modification of that framework in most of my Windows applications.

                                While working at Developer Express, I have looked at the code of projects written by our customers. There have been some good approaches but some implementations were not good. There were some projects that reminded me of my first experiments in writing large applications. Sometimes people were fighting with introducing inheritance in modules, Menu and ToolBar systems, etc. I feel that this article will definitely help them enormously. Those who have already written their own Application Framework and use it successfully may well be able to borrow some ideas and code. We, at Developer Express, would be happy to know that this article will make your life a little easier (helping developers is the main reason we are working here at Developer Express).

                                Create the simplest module-independent application framework

                                In this first step, we will create an Application Framework library that will allow the creation of independent Modules. The main form, on which modules will be shown, won't know anything about their content. The Modules themselves will not know where they are shown and located. It will allow you to use a module within different parts of your application(s), and develop and/or test modules separately from the main application code. You and your team will get the impression and feeling that your application is well written. This is a more psychological thing, but it is very effective.

                                Application Layout

                                The first decision is the kind of application layout we are going to use: SDI or MDI. Several years ago, there were many disputes about which is better: SDI or MDI. Now that Microsoft has introduced the SDI look in MS Office applications, most people start to choose SDI. So, we will choose SDI, with my apologies to MDI fans. However, with small modifications, you can adopt the library to MDI without any problems.

                                Here is a typical SDI application layout, first introduced in MS Outlook.

                                The menu/toolbar system is marked in blue, the navigation panel in yellow, the status bar/panel in green and the working area in gray.

                                Let's create an application using this layout. It will contain two modules: Module 1 and Module 2.
                                In this application, we will use a standard menu and a Panel control docked to the left, containing a list box (to create a navigation area). To create a work area (in gray), we will add a Panel that has its dock property set to fill the entire area. Finally, we will place a splitter control between the navigator and the work area. To simplify the task, we will not include a task bar into our application.

                                The current task is to create an application framework that allows the creation of independent modules for a developer to be able to add/remove a single line of code for adding/removing each module.

                                All the modules in an application based on our framework will be inherited (directly or indirectly) from the BaseModule class. The BaseModule class is inherited from the .Net UserControl class.

                                In the current step, we will not put any functionality into the BaseModule class, so we leave it empty.

                                [C#]
                                namespace ApplicationFramework { /// The base module class of the Application Framework public class BaseModule : System.Windows.Forms.UserControl { } }

                                Since creating all module instances on application start-up is a bad approach, we need to create module registration classes. There are two classes in the Modules.cs file: ModuleInfo and ModuleInfoCollection.

                                The ModuleInfo class contains a module name and module class type properties. We will use the module name for identification purposes and the module class type for creating an instance of the module class when we need to show the module.

                                The ModuleInfoCollection class is inherited from CollectionBase and contains a list of modules registered in our application. To register a new module, use the Add method. The ShowModule method will display the module on a windows control. Properties Count and Items will provide access to all registered modules. You may use the foreach C# construction as well.

                                [C#]
                                Modules.cs using System; using System.Windows.Forms; using System.Collections; using System.ComponentModel; using System.Reflection; namespace ApplicationFramework { // Contains information about a module public class ModuleInfo { string name; Type moduleType; BaseModule module; public ModuleInfo(string name, Type moduleType) { if(!moduleType.IsSubclassOf(typeof(BaseModule))) throw new ArgumentException("moduleClass has to be inherited from ModuleBase"); this.name = name; this.moduleType = moduleType; this.module = null; } public string Name { get { return this.name; } } //Show the module on a control public void Show(Control control) { CreateModule(); module.Visible = false; module.Parent = control; module.Dock = DockStyle.Fill; module.Visible = true; } //Make the module invisible public void Hide() { if(module != null) module.Visible = false; } // Create a module instance protected void CreateModule() { if(this.module == null) { ConstructorInfo constructorInfoObj = moduleType.GetConstructor(Type.EmptyTypes); if (constructorInfoObj == null) throw new ApplicationException(moduleType.FullName + " doesn't have a public constructor with empty parameters"); this.module = constructorInfoObj.Invoke(null) as BaseModule; } } // Module instance public BaseModule Module { get { return this.module; } } } // The list of modules registered in the system [ListBindable(false)] public class ModuleInfoCollection : CollectionBase { static ModuleInfoCollection instance; ModuleInfo currentModule; // Create a static class instance static ModuleInfoCollection() { instance = new ModuleInfoCollection(); } ModuleInfoCollection() : base() { this.currentModule = null; } public ModuleInfo this[int index] { get { return List[index] as ModuleInfo; } } public ModuleInfo this[string name] { get { foreach(ModuleInfo info in this) if(info.Name.Equals(name)) return info; return null; } } // Register the module in the system public static void Add(string name, Type moduleType) { ModuleInfo item = new ModuleInfo(name, moduleType); instance.Add(item); } public static ModuleInfoCollection Instance { get { return instance; } } //Show the module on a control public static void ShowModule(ModuleInfo item, Control parent) { if(item == instance.currentModule) return; if(instance.currentModule != null) instance.currentModule.Hide(); item.Show(parent); instance.currentModule = item; } // return the module currently displayed public static ModuleInfo CurrentModuleInfo { get { return instance.currentModule; } } void Add(ModuleInfo value) { if(List.IndexOf(value) < 0) List.Add(value); } } }

                                Now we need to set-up menu and navigation controls on our main form, so users may visually run modules.

                                [C#]
                                frmMain.cs using System; //... namespace ApplicationFramework { public class frmMain : System.Windows.Forms.Form { //... public frmMain() { InitializeComponent(); // Set up menu and navigation controls RegisterModules(); //Show the first module by default if(ModuleInfoCollection.Instance.Count > 0) ModuleInfoCollection.ShowModule(ModuleInfoCollection.Instance[0], pnlWorkingArea); } // Set up menu and navigation controls private void RegisterModules() { foreach(ModuleInfo mInfo in ModuleInfoCollection.Instance) { lbNavigation.Items.Add(mInfo.Name); MenuItem menuItem = new MenuItem(); this.mView.MenuItems.Add(menuItem); menuItem.Text = mInfo.Name; menuItem.Click += new System.EventHandler(this.mView_Click); } } //... // Change the module on changing the selected index in the navigation listbox private void lbNavigation_SelectedIndexChanged(object sender, System.EventArgs e) { if(lbNavigation.SelectedIndex > -1) ModuleInfoCollection.ShowModule( ModuleInfoCollection.Instance[lbNavigation.SelectedIndex], pnlWorkingArea); } // Change the module on a navigation menu item click private void mView_Click(object sender, System.EventArgs e) { ModuleInfoCollection.ShowModule(ModuleInfoCollection.Instance[((MenuItem)sender).Text], pnlWorkingArea); } private void menuItem2_Click(object sender, System.EventArgs e) { Close(); } } }

                                The last step is to create new modules and register them within the Application Framework.

                                [C#]
                                Module1.cs namespace ApplicationFramework { public class Module1 : ApplicationFramework.BaseModule { // ... } }

                                To register a new module, modify the ModulesRegistration code file and add a new line in the Register method

                                [C#]
                                ModulesRegistration.cs namespace ApplicationFramework { public class ModulesRegistration { //Register your modules here static public void Register() { ModuleInfoCollection.Add("Module1", typeof(Module1)); ModuleInfoCollection.Add("Module2", typeof(Module2)); } } }

                                Do not forget to call the module registration method

                                [C#]
                                frmMain.cs // ... [STAThread] static void Main() { // call module registration before running the main form ModulesRegistration.Register(); Application.Run(new frmMain()); }

                                Summary

                                As you can see, with just a little code, we have achieved our aim of creating a framework with independent modules.

                                Of course, this Application Framework doesn't have a lot of features, and in the real world you will need to enlarge it. For example, in most applications I've written, there was a need to provide module security. Based on security privileges, the module may be accessible or not by the end-user. This is quite easy to introduce by writing code in the module registration method. The base module doesn't implement any feature, and this is not normal either. In most cases, you will need to introduce functionality directly into the base module. The only thing you have to remember is that any feature introduced into the base module will appear in the rest of the modules automatically. As such, you should provide a module inheritance scheme, for example: BaseModule -> BaseDataModule -> BaseGridModule etc., where every module adds functionality.

                                Introduction of Actions

                                In the previous steps we built a small Application Framework that allows us to create independent modules. Now it is time to think about adding functionality to modules, and the first problem that we need to resolve is how to create a layer between UI objects in the main form and business code in modules.

                                In other words, we want to have a Menu and Toolbar system located on the main form. Menu items and toolbar buttons need their visible, enable and other properties to reflect the business logic contained in the module currently displayed. Menu and toolbar items should not know details of the module displayed, and modules should not be aware of the existence of menus and toolbars at all. We even want to be able to change UI controls, e.g. move from a standard menu system to the Developer Express XtraBars Suite or vice versa without changing the code in the modules. Also, we want to be able to test module functionality in a test engine that does not even create a main form.

                                Basically, we need one more layer between the UI on the main form and the business code in the modules. I call this layer Actions.

                                Action classes are located in the actions.cs file. The abstract Action class is not aware about a real UI class it is working with. It allows our modules to be independent from UI classes. Action has the following abstract properties: Visible, Enabled, IsDown. They have to be overridden in successor classes. The Actions class allows you to manage your actions. This class contains a global (static) action hash-table. To register a new action in your system, call the RegisterAction method. An instance of the Actions class is created in the BaseModule. For the system to know that your module supports a particular action, you have to call the AddSupportedAction method. To access a particular action class, use the indexed Actions class property.

                                [C#]
                                using System; using System.Collections; namespace ApplicationFramework { // The abstract Action class public abstract class Action { object control; object key; public Action(object control) { this.control = control; } // If true, the action is implemented in the module and the UI object is visible // If false, the action is not implemented and the UI object is invisible public abstract bool Visible { get; set; } // Enabled state of the UI object public abstract bool Enabled { get; set; } // True if the UI control is in the down/pushed state public virtual bool IsDown { get { return false; } set {} } // The link to the UI control protected object Control { get { return control; } } // The action identifying code internal protected object Key { get { return key; } set { key = value; } } } public delegate void ActionModuleHandler(object key, object sender, EventArgs e); // The action manager class // It is created in every module // There is a static hash-table of registered global actions. public class Actions { // The global action list static Hashtable registeredActions; // The list of actions supported in the module Hashtable supportedActions; static Actions() { registeredActions = new Hashtable(); } // Register the global action. The key is the action identifying code public static void RegisterAction(object key, Action action) { // If there is already an action with the same identifying code, throw an exception if(Actions.registeredActions[key] != null) throw new ApplicationException(string.Format("There is already registered action with the key '{0}'.", key)); // Add the action into the static hash table Actions.registeredActions.Add(key, action); action.Key = key; } public static void PerformAction(object key, object sender, EventArgs e) { // For the module currently displayed if(ModuleInfoCollection.CurrentModuleInfo != null) { BaseModule module = ModuleInfoCollection.CurrentModuleInfo.Module; // Call the perform action for the module currently displayed module.Actions.PerformModuleAction(key, sender, e); module.UpdateActions(); } } public static void PerformAction(Action action, object sender, EventArgs e) { Actions.PerformAction(action.Key, sender, e); } // This event will be handled in the BaseModule class public event ActionModuleHandler OnPerformModuleAction; public Actions() { // Create a hashtable of actions supported in the current module this.supportedActions = new Hashtable(); } // Notify that the action will be supported // Provide an actionHandler parameter if you want to perform the operation // On the action in a separate method // If the actionHandler is null, the Actions.PerformAction method will be called // Please look at the PerformModuleAction method public void AddSupportedAction(object key, ActionModuleHandler actionHandler) { if(! Actions.registeredActions.ContainsKey(key)) new System.Exception(string.Format("The action key '{0}' is incorrect", key)); this.supportedActions.Add(key, actionHandler); } public void AddSupportedAction(object key) { AddSupportedAction(key, null); } // Remove the action from the supported action list public void RemoveSupportedActions(object key) { this.supportedActions.Remove(key); } public Action this[object key] { get { if(! this.supportedActions.ContainsKey(key)) return null; else return Actions.registeredActions[key] as Action; } } // Make UI controls with supported actions visible // Make UI controls with non-supported actions invisible public void UpdateVisibility() { foreach(object key in Actions.registeredActions.Keys) ((Action)Actions.registeredActions[key]).Visible = this.supportedActions.ContainsKey(key); } // It is called on clicking UI controls bound to actions public void PerformModuleAction(object key, object sender, EventArgs e) { object handler = this.supportedActions[key]; if(handler != null) ((ActionModuleHandler)handler)(key, sender, e); else { if(this.OnPerformModuleAction != null) this.OnPerformModuleAction(key, sender, e); } } } }

                                Here is the implementation of the action class for a ToolBarButton. As you can see, the code is pretty simple

                                [C#]
                                // Action class for a .Net ToolBar button public class ToolBarButtonAction : Action { public ToolBarButtonAction(ToolBarButton button): base(button) { // Use the Tag property to bind the Button object to the action button.Tag = this; } public ToolBarButton Button { get { return Control as ToolBarButton; } } public override bool Visible { get { return Button.Visible; } set { Button.Visible = value; } } public override bool Enabled { get { return Button.Enabled; } set { Button.Enabled = value; } } public override bool IsDown { get { return Button.Pushed; } set { Button.Pushed = value; } } }

                                Here is an example of registering actions in the global actions hash table. This method should be called after the main form's initialization

                                [C#]
                                private void RegisterActions() { Actions.RegisterAction(ActionKeys.Operation1, new ToolBarButtonAction(this.toolBarButton1)); Actions.RegisterAction(ActionKeys.Operation2, new ToolBarButtonAction(this.toolBarButton2)); Actions.RegisterAction(ActionKeys.Operation3, new ToolBarButtonAction(this.toolBarButton3)); }

                                Finally, we need to add some functionality into our BaseModule class. To register supported actions, override the RegisterActions method using the Actions property. Override UpdateActions to change the Enabled actions and IsDown properties.

                                [C#]
                                // The base module class of the Application Framework public class BaseModule : System.Windows.Forms.UserControl { private System.ComponentModel.Container components = null; private Actions actions; public BaseModule() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // Create an action instance this.actions = new Actions(); // Create a handler on Actions.OnPerformModuleAction += new ActionModuleHandler(DoActionModuleHandler); // Register supported actions RegisterActions(); } public Actions Actions { get { return actions; } } // This method has to be overridden in successor classes // to update action states, like Enabled and the IsDown property public virtual void UpdateActions() {} // This method has to be overridden in successor classes // Here you should register supported actions protected virtual void RegisterActions() {} protected virtual void DoActionModuleHandler(object key, object sender, EventArgs e) {} // ... }

                                Here is an example of implementing virtual methods of the BaseModule class in a successor

                                [C#]
                                // Update an action state public override void UpdateActions() { base.UpdateActions(); // Make action1 enabled if checkbox1 is unchecked Actions[ActionKeys.Operation1].Enabled = ! this.checkBox1.Checked; } protected override void RegisterActions() { base.RegisterActions(); // We will only support one new action in this module: Operation1 Actions.AddSupportedAction(ActionKeys.Operation1, new ActionModuleHandler(DoOperation1)); } void DoOperation1(object key, object sender, EventArgs e) { // Show the times Action1 has been performed in textBox1 this.textBox1.Text = (int.Parse(this.textBox1.Text) + 1).ToString(); }
                                Summary

                                Now we have a prototype of a small, but fully functional Application Framework. We can create independent modules and bind these modules to UI objects on the main form.


                                Improving the Application Framework by using Developer Express components

                                Add the Developer Express XtraNavbar control

                                We created the first version of the Application Framework in the previous step and it works fine from our point of view. However, if we were to show it to the boss or our end-user, they would laugh at us. A Listbox is not the best choice for a navigation control in modern applications.
                                Developer Express provides the XtraNavBar control with over ten different paint styles to enhance the display. It will allow you to give your application a modern look.
                                The NavBar library has easy to use designers that will help you set up the control at design-time. Unfortunately, we have to do everything by code since the main module must not know about the modules we are going to introduce into the system (and we want to manage modules by adding/removing a single line of code).

                                First, we have to introduce additional features into our Application Framework. NavBar controls divide items into categories. So, we have to introduce categories into our module registration classes. Furthermore, we want to have images for items and groups in the Navbar control, as it will radically improve the look of the application. As such, we need to introduce image properties in our registration classes, too.

                                To deal with groups/categories we will create two classes: CategoryInfo and CategoriesInfo in our module registration C# code file.

                                [C#]
                                public class CategoryInfo { string name; int imageIndex; public CategoryInfo(string name, int imageIndex) { this.name = name; this.imageIndex = imageIndex; } public string Name { get { return name; } } public int ImageIndex { get { return imageIndex; } } public int Index { get { for(int i = 0; i < CategoriesInfo.Instance.Count; i ++) if(CategoriesInfo.Instance[i] == this) return i; return -1; } } } [ListBindable(false)] public class CategoriesInfo : CollectionBase { static CategoriesInfo instance; // Create a static instance of the class static CategoriesInfo() { instance = new CategoriesInfo(); } public CategoryInfo this[int index] { get { return List[index] as CategoryInfo; } } public CategoryInfo this[string name] { get { for(int i = 0; i < Count; i ++) if(name.ToUpper() == this[i].Name.ToUpper()) return this[i]; return null; } } // Register the category in the system public static void Add(string name, int imageIndex) { CategoryInfo item = new CategoryInfo(name, imageIndex); instance.List.Add(item); } public static void Add(string name) { CategoriesInfo.Add(name, -1); } public static CategoriesInfo Instance { get { return instance; } } }

                                Than we have to add Category and ImageIndex support into the ModuleInfo class

                                [C#]
                                // Contains information about the module public class ModuleInfo { // ... CategoryInfo category; int imageIndex; public ModuleInfo(string name, Type moduleType, CategoryInfo category, int imageIndex) { // Throw an exception if the module is not inherited from the BaseModule class if(!moduleType.IsSubclassOf(typeof(BaseModule))) throw new ArgumentException("moduleClass has to be inherited from BaseModule"); // If there is no category yet, create a default if(CategoriesInfo.Instance.Count == 0) CategoriesInfo.Add("Default"); if(category == null) category = CategoriesInfo.Instance[0]; this.name = name; this.category = category; this.imageIndex = imageIndex; this.moduleType = moduleType; this.module = null; } public int CategoryIndex { get { return categoryIndex; } } public int ImageIndex { get { return imageIndex; } } // ... }

                                Finally, modify the module registration method in the ModuleInfoCollection class

                                [C#]
                                // The list of modules registered in the system [ListBindable(false)] public class ModuleInfoCollection : CollectionBase { //... // Register the module in the system public static void Add(string name, Type moduleType, CategoryInfo category, int imageIndex) { ModuleInfo item = new ModuleInfo(name, moduleType, category, imageIndex); instance.Add(item); } //.. }

                                The next step is to create NavBar control groups, items and links according to the modules registered in our Application Framework. We have to change the RegisterModules method in the main form C# file

                                [C#]
                                // Set up menu and navigation controls private void RegisterModules() { foreach(CategoryInfo cInfo in CategoriesInfo.Instance) { // Add a NavBar group NavBarGroup group = navBar.Groups.Add(); // Set up the NavBar group caption group.Caption = cInfo.Name; // Set up the NavBar group large image index group.LargeImageIndex = cInfo.ImageIndex; // Use large images for the NavBar Group group.UseSmallImage = false; // Expand the group by default group.Expanded = true; } foreach(ModuleInfo mInfo in ModuleInfoCollection.Instance) { // Create a NavBar item NavBarItem item = navBar.Items.Add(); // Set up the NavBar item caption item.Caption = mInfo.Name; // Set up the Navbar item small image index item.SmallImageIndex = mInfo.ImageIndex; // Use the item’s tag property for module identification item.Tag = mInfo; // Add the NavBar item to the appropriate Navbar group navBar.Groups[mInfo.Category.Index].ItemLinks.Add(item); // ... } }

                                Summary

                                Now our application looks much better because of using a Developer Express NavBar control as the navigation control.

                                Add the Developer Express XtraBars Suite

                                Now it is time to replace the old-fashioned standard menu and toolbar system with another one. Because of our Actions layer, this is not a big problem. Whichever library we decide to choose, we only need to modify the main form. Here we will show how to migrate to the Developer Express XtraBars Suite.

                                Drop a BarManager component on the main form and call the "Import From MainMenu" item in the Property Window. An ExpressBars toolbar menu will be created based upon the standard main menu.

                                First, let's modify code for adding modules into a "Go" sub item. XtraBars provides different bar item types, and the BarListItem type is best for navigation between modules.

                                [C#]
                                private void RegisterModules() { // ... foreach(ModuleInfo mInfo in ModuleInfoCollection.Instance) { // ... // Add a new item to the bar list item this.barListItemModules.Strings.Add(mInfo.Name); } }

                                Here is the ItemClick event handler code for the BarListItemModules item.

                                [C#]
                                private void barListItemModules_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { if(barListItemModules.ItemIndex > -1) ModuleInfoCollection.ShowModule( ModuleInfoCollection.Instance[barListItemModules.Strings[barListItemModules.ItemIndex]], this.pnlWorkingArea); }

                                Now, we have to write the BarItemAction class. All XtraBars item classes are inherited from the BarItem class. This means that we may use a BarItemAction for all XtraBars items.

                                [C#]
                                // Action class for DevExpress barItem public class BarItemAction : Action { public BarItemAction(BarItem barItem): base(barItem) { // Assign the handler on the ItemClick event barItem.ItemClick += new ItemClickEventHandler(DoItemClick); } public BarItem Item { get { return Control as BarItem; } } public override bool Visible { get { return Item.Visibility != BarItemVisibility.Never; } set { if(value) Item.Visibility = BarItemVisibility.Always; else Item.Visibility = BarItemVisibility.Never; } } public override bool Enabled { get { return Item.Enabled; } set { Item.Enabled = value; } } // IsDown property makes sense for BarButtonItem only public override bool IsDown { get { if(Item is BarButtonItem) return (Item as BarButtonItem).Down; return false; } set { if(Item is BarButtonItem) (Item as BarButtonItem).Down = value; } } void DoItemClick(object sender, ItemClickEventArgs e) { Actions.PerformAction(this, sender, e); } }

                                And the last step is to modify the RegisterActions method

                                [C#]
                                private void RegisterActions() { Actions.RegisterAction(ActionKeys.Operation1, new BarItemAction(this.BarButtonItem1)); Actions.RegisterAction(ActionKeys.Operation2, new BarItemAction(this.BarButtonItem2)); Actions.RegisterAction(ActionKeys.Operation3, new BarItemAction(this.BarButtonItem3)); }
                                Summary

                                As you can see, we did all modifications to the main module only; and the task was quite easy. How do you migrate from one Menu&ToolBar library to another in your application? I guess it could be a real pain.

                                Create a Developer Express Grid module

                                We have greatly improved our application appearance by replacing the standard controls with the Developer Express XtraNavBar and XtraBars libraries. Now, it is time to think about improving our framework in terms of module content. In your application, you are unlikely to inherit the "end-user" module from the BaseModule directly. In most applications, there are usually several modules that show objects/records on a list. Generally, we use a Grid control for this purpose and this is typically a central control in an application. You will need to write code around the grid, e.g. showing/hiding the column customization window, etc. Of course, it makes no sense to code this functionality for every module containing a grid control. We will create a BaseGridModule module. The Developer Express XtraGrid will be located on this module. Besides grid actions, we will introduce Export actions. Although we will add support for Export actions in the base module, by default, these actions will be disabled. So, actual Grid Modules have to override methods to re-enable them.

                                First, we need to add new actions to the ActionsKeys class located in the ActionsKey file. Now it contains:

                                [C#]
                                public class ActionKeys { public const string Grid_Grouping = "grouping"; // ... public const string Grid_Customization = "customization"; public const string Export_HTML = "ExportToHTML"; public const string Export_XML = "ExportToXML"; public const string Export_XLS = "ExportToXLS"; public const string Export_Text = "ExportToText"; // ... public const string Operation3 = "Operation3"; }

                                We now need to create XtraBars items, correctly place them in the menu/toolbar system and bind them to our actions.

                                [C#]
                                private void RegisterActions() { Actions.RegisterAction(ActionKeys.Operation1, new BarItemAction(this.BarButtonItem1)); // ... // Grid Actions Actions.RegisterAction(ActionKeys.Grid_Grouping, new BarItemAction(this.btnGridGrouping)); // ... Actions.RegisterAction(ActionKeys.Grid_Customization, new BarItemAction(this.btnGridColumnCustomization)); // Export Actions Actions.RegisterAction(ActionKeys.Export_HTML, new BarItemAction(this.btnExportToHTML)); Actions.RegisterAction(ActionKeys.Export_XML, new BarItemAction(this.btnExportToXML)); Actions.RegisterAction(ActionKeys.Export_XLS, new BarItemAction(this.btnExportToXLS)); Actions.RegisterAction(ActionKeys.Export_Text, new BarItemAction(this.btnExportToText)); }

                                Further we have the more interesting task. We need to introduce Export actions support in the BaseModule. It will allow us to override two methods in inherited modules, so as to implement the exporting feature: IsExportTypeSupported and DoExport. Below you will find the code that does this task.

                                [C#]
                                public enum ExportType {Html, Xml, Xls, Txt}; public class BaseModule : System.Windows.Forms.UserControl { // ... // This method has to be overridden in successor classes // to update action states, like Enabled and the IsDown property public virtual void UpdateActions() { Actions[ActionKeys.Export_HTML].Enabled = IsExportTypeSupported(ExportType.Html); Actions[ActionKeys.Export_XML].Enabled = IsExportTypeSupported(ExportType.Xml); Actions[ActionKeys.Export_XLS].Enabled = IsExportTypeSupported(ExportType.Xls); Actions[ActionKeys.Export_Text].Enabled = IsExportTypeSupported(ExportType.Txt); } // This method has to be overridden in successor classes // Here you should register supported actions protected virtual void RegisterActions() { Actions.AddSupportedAction(ActionKeys.Export_HTML, new ActionModuleHandler(DoExport)); Actions.AddSupportedAction(ActionKeys.Export_XML, new ActionModuleHandler(DoExport)); Actions.AddSupportedAction(ActionKeys.Export_XLS, new ActionModuleHandler(DoExport)); Actions.AddSupportedAction(ActionKeys.Export_Text, new ActionModuleHandler(DoExport)); } // ... // This method has to be overridden in successor classes //Returns true if the module supports the export type protected virtual bool IsExportTypeSupported(ExportType exportType) {return false;} // This method has to be overridden in successor classes protected virtual void DoExport(ExportType exportType, string AFileName) { //Do nothing by default } // Returns ExportType by the action key protected ExportType GetExportTypeByAction(object key) { if(ActionKeys.Export_XML.Equals(key)) return ExportType.Xml; if(ActionKeys.Export_XLS.Equals(key)) return ExportType.Xls; if(ActionKeys.Export_Text.Equals(key)) return ExportType.Txt; return ExportType.Html; } // Do exporting void DoExport(object key, object sender, EventArgs e) { //Get the Export type ExportType exportType = GetExportTypeByAction(key); //Create the save dialog and set it up SaveFileDialog saveDialog = new SaveFileDialog(); saveDialog.DefaultExt = GetExportDefaultExtenstions(exportType); saveDialog.Filter = GetExportFilters(exportType); saveDialog.FileName = "export." + saveDialog.DefaultExt; //Do export if the end-user presses OK if(DialogResult.OK == saveDialog.ShowDialog()) DoExport(exportType, saveDialog.FileName); } string GetExportDefaultExtenstions(ExportType exportType) { switch(exportType) { case ExportType.Html: return "html"; case ExportType.Xml: return "xml"; case ExportType.Xls: return "xls"; case ExportType.Txt: return "txt"; } return ""; } string GetExportFilters(ExportType exportType) { switch(exportType) { case ExportType.Html: return "HTML File (*.html)|*.html"; case ExportType.Xml: return "XML File (*.xml)|*.xml"; case ExportType.Xls: return "Microsoft Excel 4.0 Worksheet (*.xls)|*.xls"; case ExportType.Txt: return "Text file (*.txt)|*.txt"; } return ""; } // ... }

                                The new BaseGridModule needs to override the IsExportTypeSupported and DoExport methods to support exporting

                                [C#]
                                public class BaseGridModule : ApplicationFramework.BaseModule { public BaseGridModule() { // This call is required by the Windows Form Designer. InitializeComponent(); } protected override bool IsExportTypeSupported(ExportType exportType) {return true;} protected override void DoExport(ExportType exportType, string AFileName) { DevExpress.XtraExport.IExportProvider provider = null; // Create an export provider based on the export type switch(exportType) { case ExportType.Html: provider = new DevExpress.XtraExport.ExportHtmlProvider(AFileName); break; case ExportType.Xml: provider = new DevExpress.XtraExport.ExportXmlProvider(AFileName); break; case ExportType.Xls: provider = new DevExpress.XtraExport.ExportXlsProvider(AFileName); break; case ExportType.Txt: provider = new DevExpress.XtraExport.ExportTxtProvider(AFileName); break; } if(provider != null) { // Create an export link DevExpress.XtraGrid.Export.BaseExportLink link = this.gridControl.DefaultView.CreateExportLink(provider); // Do export if(link != null) link.ExportTo(true); } } // ... }

                                The last task is to add support for grid actions directly to the BaseGridModule class. XtraGrid is built based on the view technology. It gives excellent expansibility, and you don't have to fight to find a needed method/property among hundreds of grid control methods/properties, since the CardView presentation has totally different properties from the GridView and the BandedView.

                                However, binding our grid actions to all grid view types will require some additional coding. For example, the ShowBand action doesn't make any sense for the CardView, and so should be disabled for it. Coding with a bunch of "if" statements for every action is a very poor technique. So, it is obvious that we need to build one more abstraction. The simplest thing that came to my mind was to create a GridViewActionsHelperBase class that could work with Grid Actions without the need to know about the view type we are currently dealing with.

                                [C#]
                                public class GridViewActionsHelperBase { BaseView baseView; public GridViewActionsHelperBase(BaseView baseView) { this.baseView = baseView; } public BaseView View { get { return this.baseView; } } // grouping public virtual bool Support_Grouping { get { return false; } } public virtual bool IsDown_Grouping { get { return false; } } public virtual void Perform_Grouping() {} // ... // customization public virtual bool Support_Customization { get { return false; } } public virtual bool IsDown_Customization { get { return false; } } public virtual void Perform_Customization() {} }

                                Create one class for every view that is inherited from it: CardViewActionsHelper, GridViewActionsHelper and BandedGridViewActionsHelper.

                                [C#]
                                public class GridViewActionsHelper : GridViewActionsHelperBase { public GridViewActionsHelper(GridView view) : base(view) {} public GridView GridView { get { return View as GridView; } } // grouping public override bool Support_Grouping { get { return true; } } public override bool IsDown_Grouping { get { return GridView.OptionsView.ShowGroupPanel; } } public override void Perform_Grouping() { GridView.OptionsView.ShowGroupPanel = ! GridView.OptionsView.ShowGroupPanel; } // customization public override bool Support_Customization { get { return true; } } public override bool IsDown_Customization { get { return (GridView.CustomizationForm != null) && GridView.CustomizationForm.Visible; } } public override void Perform_Customization() { if((GridView.CustomizationForm == null) || (! GridView.CustomizationForm.Visible)) GridView.ColumnsCustomization(); else GridView.CustomizationForm.Hide(); } }

                                Before performing any operation with a grid action, we will create the correct Grid Action Helper class and call its methods.

                                [C#]
                                public override void UpdateActions() { base.UpdateActions(); Actions[ActionKeys.Grid_Grouping].Enabled = GridActionsHelper.Support_Grouping; Actions[ActionKeys.Grid_Grouping].IsDown = GridActionsHelper.IsDown_Grouping; // ... Actions[ActionKeys.Grid_Customization].Enabled = GridActionsHelper.Support_Customization; Actions[ActionKeys.Grid_Customization].IsDown = GridActionsHelper.IsDown_Customization; } // Perform a grid grouping action void DoGridGrouping(object key, object sender, EventArgs e) { GridActionsHelper.Perform_Grouping(); } // Return the correct Grid Action helper protected GridViewActionsHelperBase GridActionsHelper { get { if(gridActionsHelper == null) gridActionsHelper = CreateGridActionsHelper(); return gridActionsHelper; } } // Create the correct Grid Action helper protected virtual GridViewActionsHelperBase CreateGridActionsHelper() { BaseView view = this.gridControl.KeyboardFocusView; if(view != null) { if(view is BandedGridView) return new BandedGridViewActionsHelper(view as BandedGridView); if(view is GridView) return new GridViewActionsHelper(view as GridView); if(view is CardView) return new CardViewActionsHelper(view as CardView); } return new GridViewActionsHelperBase(this.gridControl.KeyboardFocusView); } // Release the Grid Action Helper after changing the view void OnKeyboardFocusViewChanged(object sender, ViewFocusEventArgs e) { this.gridActionsHelper = null; UpdateActions(); }

                                Summary

                                In this step, we have created the base list module for our Application Framework.

                                Add Printing capability into the Application Framework

                                The last feature that we will add into our Application Framework is a printing capability. We will introduce Print Actions and implement them in the base module in the same way as we did for Export Actions.

                                After adding Print Actions support into the BaseModule, we will have three more protected virtual methods: HasPrinting, DoPrint and DoPreview.

                                These methods are overridden in the BaseGridModule to be able to print the XtraGrid:

                                [C#]
                                // Should return true if the module supports printing protected override bool HasPrinting { get { return true; } } protected override void DoPrint() { DevExpress.XtraPrinting.PrintHelper.Print(gridControl); } protected override void DoPreview() { DevExpress.XtraPrinting.PrintHelper.ShowPreview(gridControl); }

                                As you can see, it is a pretty easy task if you're using the XtraPrinting library.

                                Summary

                                With this step, we have introduced Print support into the Application Framework, and implemented it for the base Grid module.

                                In the attachment, you will find sample projects demonstrating this technology in the step-by-step fashion.

                                Show all comments
                                • Solutions Vet Inc. 09.12.2012

                                  Any chances that you could add a VB version of your example?

                                • Hi Magens,

                                  I should note that this article is very old and it is not adapted to the most recent version of our products. We'll probably update it in the future. In the meantime, I have attached a VB version of the last step from this article.

                                • Martin McSharry 11.05.2012

                                  Is there a more updated or similar paper, I'm starting a new project and would like to follow this pattern.

                                • Thank you for your response. We will consider updating this article in the future. Generally, the implementation of some classes was changed, but the idea of this article is relevant now. You can review our demos (e.g., our XtraTreeList demo) that follow this strategy.

                                • Gerard D'Rozario 04.04.2013

                                  How would you adapt this to an MDI model - ie where more than one module or multiple copies of one module could be seen in a tabbed interface?

                                • Stas (DevExpress Support) 04.05.2013

                                  Since your question is not related to this article, I have created a separate ticket How to adapt the AK6758 article to an MDI model I suggest you continue our discussion there.

                                You must  log in  or  register  to leave comments

                                If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

                                FOLLOW US

                                DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, Silverlight, ASP.NET, WinForms, HTML5 or Windows 8, DevExpress tools help you build and deliver your best in the shortest time possible.

                                Copyright © 1998-2014 Developer Express Inc.
                                All trademarks or registered trademarks are property of their respective owners