Blogs

Gary's Blog

March 2009 - Posts

  • Using XAF to Build Windows MDI Applications

         

    This blog post will demonstrate how to use XAF, along with DXperience controls, to create an MDI application, the image below shows what the finished application will look like:

    As you can see from the image, the child forms are represented by tabs within the main form. However, you will be able to customize the location and appearance of the child forms via the XtraTabbedMdiManager component. You can read more on the XtraTabbedMdiManager class in the documentation.

    There are 7 short steps to creating this MDI application and they are as follows:

    Step 1 - Create a new XAF Windows solution and give it a name (MDIDemo):

     

     

    Step 2 - Create the MDIMainForm And MDIChildForm Templates

    To build an MDI application, you should develop two Frame Templates:

    • MDIMainForm - a Frame Template for the Main Window (the Window that is first displayed when starting an application);
    • MDIChildForm - a Frame Template for child Windows (all the Windows except the Main Window).

    You won't have to create these Templates from scratch. The eXpressApp Framework installation provides the sources of the main Templates. You can use the MainForm and DetailViewForm Templates as prototypes for your new Templates:

    • In the Solution Explorer, create a new folder called "Templates" within the MDIDemo.Win application project.
    • Invoke the context menu for the new "Templates" folder and select the Add | Existing Item... menu item. In the invoked dialog, navigate to the [ProgramFilesFolder]\Developer Express Inc\eXpressApp Framework\Sources\FrameTemplates\ folder. Select the MainForm.cs and DetailViewForm.cs files from the "CS" folder if your project is in C#, or the MainForm.vb and DetailViewForm.vb files from the "VB" folder if your project is in Visual Basic. Press the Add button. Visual Studio will create local copies of these two files.
    • Rename the added MainForm.cs (MainForm.vb) file to MDIMainForm.cs (MDIMainForm.vb), and the DetailViewForm.cs (DetailViewForm.vb) file to MDIChildForm.cs (MDIChildForms.vb). Rename the MainForm and DetailViewForm classes and all the references to them inside these files in the following manner:
      MainForm to MDIMainForm and DetailViewForm to MDIChildForm.

    Then, you should modify the MDIMainForm Template as defined below. The MDIChildForm Template doesn't need to be modified.

    Step 3 - Modify the MDIMainForm Template

    You have to make some changes in the MDIMainForm Template. For this purpose, open the MDIMainForm file in the Visual Studio Form Designer. There, do the following:

    • Via the Properties window, set the MainForm's IsMdiContainer property to true.
    • Remove the viewSitePanel from the form. Since this form is just a container for child forms, it must not contain Views. In the MDIMainForm.cs, modify the Initialize method call in the following manner:
    Initialize(mainBarManager, containers, new IActionContainer[] { cObjectsCreation,
    cRecordEdit, cView, cPrint, cExport }, null, navigation);

    • From the DX: Navigation & Layout page of the Toolbox, drag and drop an XtraTabbedMdiManager component to the form.

    Now, create the menu items that will manage MDI functions:

    • Select the barManager component on the form. This will allow you to perform toolbar design-time customizations.
    • Click the small [Add] link on the MainMenu toolbar (the toolbar at the top, by default).
    • Select the Menu (BarSubItem) item in the invoked context menu. This will create a new toolbar item. Give it the Window name (for instance).
    • Click the new toolbar item twice (two clicks with a short pause in between, not a double click), to expand it. Via the [Add] link inside the invoked menu, add a Button (BarButtonItem) toolbar item. Set the new item's name to, for instance, Close All Windows .
    • Select the Close All Windows toolbar item, and add an ItemClick event handler via the Properties window. In this event handler, implement the code that closes all open child Windows, as shown below:

    private void barButtonItemCloseAll_ItemClick(object sender, ItemClickEventArgs e) {
    foreach(Form child in this.MdiChildren) {
      child.Close();
      if(child.Visible)
         break;
    }
    • Using the same technique as above, add an MDI children list (BarMdiChildrenListItem) item to the Window toolbar item. This is a special item type, which will be populated with open child Windows automatically. It will also allow navigating to the selected Window.

    A child form will be shown as a tab within the main form. Both the main and child forms will have their own toolbars. This will look unattractive because of the nested MainMenu toolbars inside each tab. So, you should merge the items of the main and child MainMenu toolbars. The BarManager does this automatically, but you should hide a child Window's MainMenu toolbar in code. For this purpose, add a handler to the barManager's Merge event, via the Properties window:

    using DevExpress.XtraBars;
    //...
    private void barManager_Merge(object sender, DevExpress.XtraBars.BarManagerMergeEventArgs e) {
        foreach (Bar childBar in e.ChildManager.Bars) {
          if (childBar == e.ChildManager.MainMenu || childBar == e.ChildManager.StatusBar)
             continue;
          childBar.Visible = false;
        }
    }

    Step 4 - Add the Filters Action Container to the Detail View Form Template

    There are Actions that are enabled when there is a View on the current frame. The MDIMainForm Template doesn't contain a View. So, the Actions that are mapped to the Action Containers contained in this Template are displayed disabled. For instance, the FilterController.SetFilterAction is displayed by the Filters Action Container contained in the MDIMainForm Template. You can make this Action and other similar Actions available, if you add the Filters Action Container and other required Action Containers to the MDIChildForm Template. This Template's Action Containers are displayed in the main form, because of the merge operation performed under the main and detail form's Action Containers (see above).

    The code below demonstrates the code snippets that you should add to the MDIChildForm.Designer.cs (MDIChildForm.Designer.vb) and MDIChildForm.cs (MDIChildForm.vb) files, to add the Filters Action Container:

    In the MDIChildForm.Designer.cs (MDIChildForm.Designer.vb) file:

    using DevExpress.ExpressApp.Utils;
    
    //...
    
    partial class MDIChildForm {
       private void InitializeComponent() {
       //...
       this.cFilters = new DevExpress.ExpressApp.Win.Templates.ActionContainers.ActionContainerBarItem();
       //...   
       // 
       // mainBarManager
       // 
       //...
       this.barManager.Items.AddRange(new DevExpress.XtraBars.BarItem[] {
          //...
          this.cFilters,
          //...
          });
       // 
       // barSubItemEdit
       // 
       this.barSubItemEdit.LinksPersistInfo.AddRange(new DevExpress.XtraBars.LinkPersistInfo[] {
          //...
          new DevExpress.XtraBars.LinkPersistInfo(this.cFilters, true)
          });
       // 
       // StandardToolBar
       // 
       //...
       this.StandardToolBar.LinksPersistInfo.AddRange(new DevExpress.XtraBars.LinkPersistInfo[] {
          //...
          new DevExpress.XtraBars.LinkPersistInfo(this.cFilters, true),
          //...
          });
       // 
       // cFilters
       // 
       this.cFilters.Caption = CaptionHelper.GetLocalizedText(FrameTemplatesDetailViewForm, "Filters","Filters");
       this.cFilters.ContainerId = "Filters";
       this.cFilters.Id = 26;
       this.cFilters.MergeType = DevExpress.XtraBars.BarMenuMerge.MergeItems;
       this.cFilters.Name = "cFilters";
       //...
       private DevExpress.ExpressApp.Win.Templates.ActionContainers.ActionContainerBarItem cFilters;
    }

    Step 5 - Replace the Default Templates with the MDIMainForm and MDIChildForm

    By default, an application uses the MainForm and DetailViewForm Templates to display the Main Window and Windows with Detail Views, respectively. You should change this default behavior, and make the application use your MDIMainForm and MDIChildForm Templates instead. For this purpose, open the Program.cs file, and add a handler to the XafApplication.CreateCustomTemplate. This event is raised each time a Template is required.

    static class Program {
        static void Main() {
            MDIDemoWindowsFormsApplication application = new MDIDemoWindowsFormsApplication();
            application.CreateCustomTemplate +=
               new EventHandler<CreateCustomTemplateEventArgs>(application_CreateCustomTemplate);
            // ...
        }
    }

    Implement the CreateCustomTemplate event handler, as shown below:

    using DevExpress.ExpressApp.Win.CustomTemplates;
    //...
    static void application_CreateCustomTemplate(object sender, CreateCustomTemplateEventArgs e) {
       if(e.Context == TemplateContext.ApplicationWindow) {
          e.Template = new MDIMainForm();
       } else
       if(e.Context == TemplateContext.View) {
          e.Template = new MDIChildForm();
       } else {
          e.Template = null;
       }
    }

    Step 6 - Implement the MDIStrategy

    A Show View Strategy is a special abstraction which determines how and where to show Views (see ShowViewStrategyBase). The eXpressApp Framework comes with two Windows Forms Show View Strategies:

    • ShowInSingleWindowStrategy
      Using this strategy, child windows are displayed within the Main Window.
    • ShowInMultipleWindowsStrategy
      Using this strategy, child windows are displayed individually.

    In your MDI application, you should implement a new strategy by inheriting from the ShowViewStrategy class:

    using DevExpress.ExpressApp.Win;
    using System.Collections.Generic;
    using DevExpress.ExpressApp;
    //...
    class MDIStrategy : DevExpress.ExpressApp.Win.WinShowViewStrategyBase {
        private List<WinWindow> delayedToShow = new List<WinWindow>();
        private List<WinWindow> childWindows = new List<WinWindow>();
        private void window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
            e.Cancel = true;
        }
        private void window_Closed(object sender, EventArgs e) {
            WinWindow window = sender as WinWindow;
            window.Closing -= new System.ComponentModel.CancelEventHandler(window_Closing);
            window.Closed -= new EventHandler(window_Closed);
            childWindows.Remove(window);
        }
        private void Form_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e) {
            Form form = sender as Form;
            WinWindow window = FindChildWindowByForm(form);
            e.Cancel = false;
            if (e.CloseReason == System.Windows.Forms.CloseReason.MdiFormClosing) {
                if (window.Form.MdiParent.MdiChildren[0] == window.Form) {
                    e.Cancel = !CanCloseMDIParentWindow();
                }
                if (!e.Cancel) {
                    window.Form.FormClosing -= new FormClosingEventHandler(Form_FormClosing);
                    window.View.SynchronizeInfo();
                    window.View.Close(false);
                }
            }
            else {
                e.Cancel = !window.CanClose();
            }
        }
        private WinWindow FindChildWindowByForm(Form form) {
            foreach (WinWindow window in childWindows) {
                if (window.Form == form) {
                    return window;
                }
            }
            return null;
        }
        private bool CanCloseMDIParentWindow() {
            foreach (WinWindow window in childWindows) {
                if (window.View != null && !window.View.CanClose()) {
                    return false;
                }
            }
            return true;
        }
        protected override void ShowViewFromCommonView(ShowViewParameters parameters,
              ShowViewSource showViewSource) {
            WinWindow existWindow = FindWindowByView(parameters.CreatedView);
            if (existWindow != null) {
                parameters.CreatedView.Dispose();
                parameters.CreatedView = existWindow.View;
                existWindow.Show();
            }
            else {
                ShowViewInNewWindow(showViewSource.SourceFrame,
                   parameters.CreatedView, TemplateContext.View, parameters.Controllers);
            }
        }
        protected override void ShowViewCore(ShowViewParameters parameters,
              ShowViewSource showViewSource) {
            if (parameters.TargetWindow == TargetWindow.Current &&
                  showViewSource.SourceFrame == MainWindow) {
                parameters.TargetWindow = TargetWindow.Default;
            }
            base.ShowViewCore(parameters, showViewSource);
        }
        protected override void ShowViewFromLookupView(ShowViewParameters parameters,
              ShowViewSource showViewSource) {
            ShowInModalWindow(parameters, showViewSource.SourceFrame);
        }
        protected override void BeforeShowWindow(WinWindow window) {
            if (window != MainWindow) {
                if (window.Form.MdiParent != MainWindow.Form) {
                    window.Form.MdiParent = MainWindow.Form;
                    window.Closing += new System.ComponentModel.CancelEventHandler(window_Closing);
                    window.Form.FormClosing += new System.Windows.Forms.FormClosingEventHandler(Form_FormClosing);
                    window.Closed += new EventHandler(window_Closed);
                    childWindows.Add(window);
                }
            }
        }
        public MDIStrategy(XafApplication application) : base(application) { }
        public override void ShowStartupWindow() {
            delayedToShow.Clear();
            try {
                base.ShowStartupWindow();
            }
            finally {
                System.Windows.Forms.Application.DoEvents();
                foreach (WinWindow child in delayedToShow) {
                    ShowWindow(child);
                }
            }
        }
        public override void ShowWindow(WinWindow window) {
            if (!window.IsMain && MainWindow == null) {
                delayedToShow.Add(window);
            }
            else {
                base.ShowWindow(window);
            }
        }
    }

    The MDIStrategy class overrides the following methods to perform the MDI specific functionality:

    • ShowViewFromCommonView

      Called when a View requests another View to be shown (for instance, when the New action is executed within a List View). According to the MDIStrategy, the requested View is shown within a new Window, i.e. a new MDI child form. In addition, if this View is already being shown, a new Window isn't created.

      In a case when a View within a lookup Property Editor or within another View is requested, the ShowViewFromLookupView method is called.

    • ShowViewFromLookupView

      Called when a View in a lookup Property Editor requests another View to be shown. According to the MDIStrategy, the requested View is shown in a modal window.

    • BeforeShowWindow
      Called before showing a Window. Sets the parent form to the current child form.
    • ShowStartUpWindow and ShowWindow
      The ShowStartUpWindow method is called when an application starts. It creates the Main Window, activates its Controllers and calls the ShowWindow method. Some of the activated Controllers can be required to show a child Window by calling the ShowWindow method. Since the Main Window isn't shown yet, problems can occur. To avoid this, the ShowWindow method is overridden. If the Main Window isn't shown, this method adds the Window passed as a parameter to the delayed Window list. The ShowStartUpWindow method shows the Windows from this list after showing the Main Window.

    Step 7 - Replace the Default Strategy with the MDIStrategy

    By default, the WinApplication's Manager uses the ShowInSingleWindowStrategy or ShowInMultipleWindowsStrategy strategy, to show Windows in a UI. You should change this default behavior, and make the Manager use your MDIStrategy instead. To accomplish this, open the Program.cs file and specify the ShowViewStrategy property of the WinApplication's Manager:

    static class Program {
        public static void Main() {
            MDIDemoWindowsFormsApplication application = new MDIDemoWindowsFormsApplication();
            application.ShowViewStrategy = new MDIStrategy(application);
            // ...
        }
    }

    And that’s all there is to creating an MDI application with XAF :-)

    Digg This
  • XAF – Applying HTML Formatting to Windows Forms UI Elements

         

    XAF uses controls from the XtraEditors suite to build windows forms UI elements. Now, some of these XtraEditor controls allow XAF to format the display text using HTML. So, which of these editors can be formatted using HTML?

    • Static Text Detail View Item's text.
    • Property Editor's captions in Detail Views.
    • Column captions of the Windows Forms GridListEditor.
      By default, XAF uses this List Editor to visualize List Views in Windows Forms applications.

    XAF allows these three elements to be customized via the associated attributes of the Application Model. A complete list of the HTML tags that you can use can be found here. Of course, you can switch the ability to format the display text in this way via the EnableHTMLFormatting attribute of the Application Model's Application | Options node.

    Okay, so let’s have an example or two. Firstly, let’s look at the Static Text Detail View Item's text. Set the Caption attribute of the Application Model's Application | Views | Contact_DetailView | Items | WebPageAddress node to the following value:

    <size=12><color=red><b>Web </b><color=0,255,0><i>Page </i><color=#0000FF><u>Address</u></color></size>

    The following image shows part of the resulting Contact Detail View:

    Now let’s have a look at the column's caption of the default list editor. Set the Caption attribute of the Application Model's Application | Views | Contact_ListView | Columns | LastName node to the following value:

    <size=10><color=#CFCAFF><size=+4>L<color=#B0A8FF><size=+4>A<color=#6F79FF><size=+4>S<color=#4D3EFF><size=+4>T <color=#4D3EFF><size=-4>N<color=#6F79FF><size=-4>A<color=#B0A8FF><size=-4>M<color=#CFCAFF><size=-4>E</color></size>

    The following image shows the resulting Contact List View:

    This is yet another example of how, by leveraging other Developer Express controls, XAF makes you life as a developer easier and you more productive.

    Digg This
  • XAF – Generating Business Classes for Existing Data Tables

         

    The philosophy of XAF (the 80% case, if you will) is that the framework will be used for the creation of green field projects. Although that is how we expect the framework to be used in the majority of cases, this does not mean that we have forgotten about those developers who are working on applications for which there is already an existing database. In this post, we’ll look at how XAF supports the developer in generating business classes for existing data tables.

    To generate these classes we are going to use the design time wizard; to do this, in the Solution Explorer, select the Add | New Item... context menu option within your common module, that’s the module that is used by both the Windows Forms and ASP.NET application projects:

    Next, select the “Persistent Classes” template from the “Add New Item” dialog, and click on the “Add” button:

    The Wizard will then open and you will be able to configure the connection string to your chosen database.

    Multiple database systems (MS SQL Server, DB2, MySql, Firebird, etc) are supported by the wizard. Use the Provider combo box to select the required database type. Note that the corresponding database provider assembly must be available on your machine, otherwise the wizard will fail. :-)

    Click “Next” and a dialog will open allowing you to select which table and columns are to be included. Note that tables with composite keys are not supported by XPO (the technology used under the hood to support XAF) and so such tables will not be included in the list:

    Click the “Finish” button and the classes will be generated for you:

    [Persistent("Customers")]
    public class Customer : XPLiteObject {
        string fAddress;
        [Size(60)]
        public string Address {
            get { return fAddress; }
            set { SetPropertyValue<string>("Address", ref fAddress, value); }
        }
        //...
        string fCustomerID;
        [Key]
        [Size(5)]
        public string CustomerID {
            get { return fCustomerID; }
            set { SetPropertyValue<string>("CustomerID", ref fCustomerID, value); }
        }
        //...
        public Customer(Session session) : base(session) { }
        public Customer() : base(Session.DefaultSession) { }
        public override void AfterConstruction() { base.AfterConstruction(); }
    }

    Having done this, you’ll need to remember to remove the default constructor, as the default Session is not used to create objects in XAF.

    With the classes generated, there is only one task left to complete. Currently, relationships between tables are not reflected in the auto generated classes. So, you'll have to declare the relationships manually:

    [Persistent("Customers")]
    public class Customer : XPLiteObject {
        [Association("Customer-Orders", typeof(Order)), Aggregated]
        public XPCollection Orders {
            get { return GetCollection("Orders"); }
        }
        string fCustomerID;
        [Key]
        [Size(5)]
        public string CustomerID { }
        //...
        public Customer(Session session) : base(session) { }
        public override void AfterConstruction() { base.AfterConstruction(); }
    }
    [Persistent("Orders")]
    public class Order : XPLiteObject {
        Customer fCustomer;
        [Association("Customer-Orders")]
        public Customer Customer { }
        int fOrderID;
        [Key(true)]
        public int OrderID { }
        public Order(Session session) : base(session) { }
        //...
    }

    The code above demonstrates the One-to-Many relationship between Customer and Order objects. The "One" part of this relationship is represented by the Customer property of the Customer type in the Order class. The "Many" part is represented by the Orders property of the XPCollection type in the Customer class. Both these parts use the Association attribute.

    To enable cascading deletion of the dependent objects (if a customer is deleted all the related orders for that customer are also deleted), you can specify the Aggregated attribute for the corresponding properties (see the Orders collection in the definition of the Customer class above).

    Since the Customer field in the Order table now stores a link to the Customer object, you should populate it by the values that correspond to the values stored in the CustomerID field. For this purpose, you can write the following code in the module's updater:

    public class Updater : ModuleUpdater {
        public override void UpdateDatabaseAfterUpdateSchema() {
            Order orderObject = Session.FindObject<Order>(CriteriaOperator.Parse("Customer is null"));
            while (orderObject != null) {
                if (!string.IsNullOrEmpty(orderObject.CustomerID)) {
                    orderObject.Customer = Session.FindObject<Customer>(new BinaryOperator("CustomerID", orderObject.CustomerID));
                    orderObject.Save();
                }
                orderObject = Session.FindObject<Order>(CriteriaOperator.Parse("Customer is null"));
            }
        }
    }
    As you an see from this post, even although XAF is aimed at the developer in a green field project, we have not forgotten those who are working on applications where the database tables already exist. By leveraging the wizard in XPO, XAF allows you to generate business classes for existing data tables.
    Digg This
  • DevExpress Hailing Frequencies

         

    “…you are rubbish at explaining stuff, @Ben_Hall draws me pictures!”

    Is something a friend said to me this week. I include it here for two reasons; firstly, because I think it’s funny, but mainly because it’s a succinct reminder of one of the problems of evangelism; that different people absorb information in different ways and require an evangelist to use different media if they are going to get their message across. In this case I was trying to define one of the metrics CodeRush charts (Cyclocmetric Complexity) to a semi technical friend via IM. So, to be fair, I was on a bit of a hiding to nothing as IM is not the ideal medium in which to do this. However, it does show that a good evangelist needs to be aware of how their audience best absorbs information and that they must adapt their delivery (but not the message) accordingly.

    Here at DevExpress we are aware of that, and we present our message in a number of ways. Firstly, we do this in person. Now this is my favourite way of getting our message “out there”, though I accept that it is not necessarily the most effective means, if what you are looking to do is to get your message out to a large as audience as possible. As you’ll know Oliver and I are just back from BASTA Spring (you can read about that here, here and here) and I’ll be speaking at events in Glasgow, the South West and Belfast in the coming months, whilst Oliver will be at DevWeek and BASTA Italy in the near future. If you are attending these events please remember to say hello; it’s a lonely old job being an evangelist sometimes :-)

    Of course we don’t only deliver the DevExpress message face to face, we also do it face to camera, as it were. Creating videos to help explain our products is another medium that DevExpress make use of to help get the word out.

    We’ve not forgotten the written word either, there are blogs covering all our products as well as forums, and of course, the written documentation that we provide. Further to this, if you prefer your written information in small, bite sized pieces, then a number of us are currently to be found hanging out on twitter: Julian, Mehul, Oliver and myself can all be found there, along with the DevExpress account itself.

    Getting back to the quote above, we maybe can’t draw you a picture, but we certainly go the extra mile to accommodate your preferred means of absorbing information, but if you feel there is something else we could be doing, then feel free to comment here.

    Digg This
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.