Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Create One Application for both Desktop and Web

4.37/5 (30 votes)
30 Oct 2008CPOL5 min read 1   3.9K  
With Habanero's support for Visual WebGui, you can write one application to release on either the desktop or web.

Introduction

Undoubtedly, Web 2.0 and AJAX have simplified the development pipeline for taking an application concept from the drawing board to the live environment. Even so, there is an argument for the installed desktop application - start-up speed, rich response, independence from connectivity. And then, there is the third scenario: provide an application that can launch either way.

Background

Habanero was launched in June 2007 as free open source, primarily as an object relational mapping (ORM) framework for .NET that went beyond the usual ORM data focus to provide a runtime-generated presentation layer. Peer programmed using test-driven development, Habanero simply put into practice the full development pipeline. Habanero's developers, Chillisoft, have been producing custom software for industrial and business clients for many years, and Habanero encapsulated most of the common routines.

Chillisoft had always had a preference for developing desktop applications, owing to the cumbersome nature of working with ASP.NET. That perspective changed when Gizmox released Visual WebGui. Simulating both the API and the look-and-feel of WinForms in a web environment, Visual WebGui (VWG) provided the platform for developers to design a web application using a desktop methodology. Even better, with its similarities to WinForms behaviour, the Habanero presentation layer could be adapted to accommodate VWG. Habanero version 2 was released in July 2008 as a full Enterprise Application Framework with support for both WinForms and VWG generation.

Setting up the Tiers

The attached project provides a simple application for a computer parts supplier called Replace-IT. We'll step through the creation of this application, and you can use the sample code as a reference point.

A key feature of ORM is the extraction of the business object layer away from the database. In the sample code, we provide a Firebird database, but you could just as easily store the data in MySQL or SQL Server. We've provided a BusinessObject (BO) project under the solution, where all the domain objects are stored, such as ComputerPart, OrderItem, and ComputerPartType.

In our example, we have created a generic UI layer that builds the appropriate user interface for the release environment. Finally, two execution projects are provided, one for a desktop application and one for a VWG web application.

Running the Applications

When you do open the sample projects, you'll need to set the absolute directory to the Firebird database file. Under the Replace_it project, open app.config and set the database value to include the full path. Do likewise for web.config under the Replace_it.VWG project. To choose which environment to run under, simply right-click on either Replace_it or Replace_it.VWG and choose "Set as Startup Project". Run both applications, and you'll see that you get almost the same result with both WinForms and your internet browser.

sample-screenshot-winforms.jpg

sample-screenshot-vwg.jpg

Introducing IControlFactory

The obvious question is: how can a generic UI layer produce specifically typed controls? And also, how can custom behaviour be assigned to a control so that it behaves differently in different environments? For instance, the web environment places a premium on data acquisition, so VWG provides a paging mechanism that prevents the user from loading a thousand items on one grid, while a desktop application typically has access to a database server through a faster connection.

To solve these challenges, Habanero uses a control factory that produces a control of a specific type with specific behaviour. A generic UI control manager (such as a form) can now create specific controls by calling a "create" method on a control factory that has been passed to it, rather than using a specifically typed constructor.

Let's look at an example from the code. The following class produces a form to display a grid of computer parts:

C#
public class ComputerPartsGridManager : ControlManager
{
    private IReadOnlyGridControl _computerPartsGrid;

    public ComputerPartsGridManager(IControlFactory 
           controlFactory) : base(controlFactory)
    {
    }

    protected override void InitialiseControl()
    {
        SetupComputerPartsGrid();
        AddGridFilters();
        LoadComputerPartsGrid();
    }

    private void SetupComputerPartsGrid()
    {
        _computerPartsGrid = _controlFactory.CreateReadOnlyGridControl();
        BorderLayoutManager manager = 
          _controlFactory.CreateBorderLayoutManager(_control);
        manager.AddControl(_computerPartsGrid, BorderLayoutManager.Position.Centre);
    }

    private void AddGridFilters()
    {
        BusinessObjectCollection<ComputerPartType> computerPartTypes = 
          Broker.GetBusinessObjectCollection<ComputerPartType>(null, "Code");
        List<string> options = new List<string>();
        computerPartTypes.ForEach(
                  delegate(ComputerPartType type) { options.Add(type.Code); });
        _computerPartsGrid.FilterControl.AddStringFilterComboBox(
                  "Category:", "PartCode", options, false);
        _computerPartsGrid.FilterControl.AddStringFilterTextBox(
                  "Description:", "Description");
    }

    private void LoadComputerPartsGrid()
    {
        BusinessObjectCollection<ComputerPart> computerParts = 
           new BusinessObjectCollection<ComputerPart>();
        computerParts.LoadAll();
        _computerPartsGrid.SetBusinessObjectCollection(computerParts);
    }
}

The appropriate ControlFactory is passed through the constructor, and is used to create a ReadOnlyGridControl, which is a Habanero control that lists objects in a grid and provides tools to filter the data and to edit individual items in a popup form. Note that the control is instantiated using the interface IReadOnlyGridControl.

Creating Generic Main Menus

There is an intermediate class called UIManager that provides a main menu for the application, instantiating the corresponding control for each menu item, passing through the control factory. In the executable project, UIManagerWin inherits from UIManager, and instantiates ControlFactoryWin for use by the UI layer. The final step involves adding code to the startup form/class for the project. In the WinForms executable, you would add two lines of code to Program.cs which instantiates a typed form and passes it through to the UIManager.

The Process Flow in Code

Let's follow this process from the top with code examples. First, in Program.cs:

C#
FormWin programForm = new FormWin();
new UIManagerWin().SetupMainForm(programForm);

Which calls an overridden method in UIManagerWin:

C#
public class UIManagerWin : UIManager
{
    public override void SetupMainForm(IFormHabanero programForm)
    {
        IMainMenuHabanero mainMenuHabanero = 
          SetupMainMenu(programForm, new ControlFactoryWin(), 
          new MenuBuilderWin());
        mainMenuHabanero.DockInForm(programForm);
        programForm.Text = GlobalRegistry.ApplicationName + 
          " " + GlobalRegistry.ApplicationVersion;
        programForm.WindowState = FormWindowState.Maximized;
    }
}

The child calls through to the parent, which sets up the main menu and assigns controls for each menu item:

C#
public abstract class UIManager
{
    public IMainMenuHabanero SetupMainMenu(IFormHabanero programForm, 
           IControlFactory controlFactory, IMenuBuilder menuBuilder)
    {
        HabaneroMenu menu = new HabaneroMenu("Main", 
                     programForm, GlobalUIRegistry.ControlFactory);

        HabaneroMenu dataMenu = menu.AddSubmenu("Data");
        HabaneroMenu.Item productsMenuItem = dataMenu.AddMenuItem("Computer Parts");
        productsMenuItem.ControlManagerCreator += 
          delegate(IControlFactory factory) 
          { return new ComputerPartsGridManager(factory); };
        HabaneroMenu.Item customerOrdersMenuItem = 
          dataMenu.AddMenuItem("Customer Orders");
        customerOrdersMenuItem.ControlManagerCreator += 
          delegate(IControlFactory factory) 
          { return new OrdersGridManager<CustomerOrder>(factory); };
        HabaneroMenu.Item supplierOrdersMenuItem = dataMenu.AddMenuItem("Supplier Orders");
        supplierOrdersMenuItem.ControlManagerCreator += 
          delegate(IControlFactory factory) 
          { return new OrdersGridManager<SupplierOrder>(factory); };

        HabaneroMenu toolsMenu = menu.AddSubmenu("Tools");
        HabaneroMenu.Item looklupEditorMenuItem = toolsMenu.AddMenuItem("Lookup Editor");
        looklupEditorMenuItem.ControlManagerCreator += 
          delegate(IControlFactory factory) { return new LookupEditorManager(factory); };

        return menuBuilder.BuildMainMenu(menu);
    }

    public abstract void SetupMainForm(IFormHabanero programForm);
}

The final step is to instantiate the right control, as illustrated in the example grid code presented earlier.

What is the Development Cost?

There are three big benefits to this approach for designing user interfaces. Firstly, you can release in multiple environments. Secondly, this design lends itself to test-driven development of the presentation layer, a challenge that developers frequently avoid. Thirdly, the ControlFactory introduces a considerable degree in flexibility. While we've discussed the differences between web and desktop, even a single desktop application can use different ControlFactorys to produce different behaviour on the same control at different times.

As for costs, the Visual Studio designer does not support this kind of typing (and some will, of course, say: good riddance to the designer!). Secondly, there's a slight change in development patterns. Instead of instantiating controls, you're calling through to the ControlFactory and assigning to an interface instead of a specific type.

On the whole, the amount of code presented above provides a fairly simple application foundation. From here, the majority of your work goes into extending the generic UI layer with all the controls you need to provide a working application.

Habanero is free open source, and you may want to view some of the interesting code structures used to achieve this outcome. You can download the full package and access tutorials and support at the official website.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)