65.9K
CodeProject 正在变化。 阅读更多。
Home

为桌面和 Web 创建一个应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.37/5 (29投票s)

2008年10月30日

CPOL

5分钟阅读

viewsIcon

100801

downloadIcon

3911

在 Habanero 的支持下,您可以使用 Visual WebGui 编写一个应用程序,然后部署到桌面或 Web。

引言

毫无疑问,Web 2.0 和 AJAX 已经简化了将应用程序概念从草图变为实际产品的开发流程。即便如此,桌面应用程序仍然有其优势——启动速度快、响应丰富、独立于网络连接。而第三种场景是:提供一个可以两种方式运行的应用程序。

背景

Habanero 于 2007 年 6 月作为免费开源项目推出,主要是一个面向 .NET 的对象关系映射 (ORM) 框架,它超越了普通的 ORM 数据焦点,提供了一个运行时生成的表示层。Habanero 采用测试驱动开发进行协作编程,简单地实现了完整的开发流程。Habanero 的开发者 Chillisoft 多年来一直为工业和商业客户生产定制软件,Habanero 封装了大多数常用例程。

Chillisoft 一直偏爱开发桌面应用程序,因为 ASP.NET 的工作方式很麻烦。当 Gizmox 发布 Visual WebGui 时,这种观点发生了改变。Visual WebGui (VWG) 在 Web 环境中模拟了 WinForms 的 API 和外观,为开发人员提供了一个使用桌面方法设计 Web 应用程序的平台。更妙的是,由于其与 WinForms 行为的相似性,Habanero 的表示层可以适应 VWG。Habanero 2.0 版本于 2008 年 7 月发布,成为一个完整的企业应用程序框架,支持 WinForms 和 VWG 的生成。

设置分层

附带的项目为一家名为 Replace-IT 的电脑零件供应商提供了一个简单的应用程序。我们将逐步介绍该应用程序的创建过程,您可以将示例代码作为参考。

ORM 的一个关键特性是将业务对象层从数据库中分离出来。在示例代码中,我们提供了一个 Firebird 数据库,但您同样可以轻松地将数据存储在 MySQL 或 SQL Server 中。我们在解决方案下提供了一个 BusinessObject (BO) 项目,其中存储了所有领域对象,例如 ComputerPartOrderItemComputerPartType

在我们的示例中,我们创建了一个通用的 UI 层,它会为部署环境构建适当的用户界面。最后,提供了两个执行项目,一个用于桌面应用程序,一个用于 VWG Web 应用程序。

运行应用程序

打开示例项目时,您需要设置 Firebird 数据库文件的绝对目录。在 Replace_it 项目下,打开 app.config 并设置数据库值以包含完整路径。在 Replace_it.VWG 项目下的 web.config 中也执行相同的操作。要选择运行哪个环境,只需右键单击 Replace_it 或 Replace_it.VWG,然后选择“设置为启动项目”。运行这两个应用程序,您会发现使用 WinForms 和互联网浏览器都能获得几乎相同的结果。

sample-screenshot-winforms.jpg

sample-screenshot-vwg.jpg

介绍 IControlFactory

显而易见的问题是:通用 UI 层如何生成特定类型的控件?另外,如何为控件分配自定义行为,使其在不同环境中表现不同?例如,Web 环境对数据获取要求很高,因此 VWG 提供了一个分页机制,可以防止用户在一个网格中加载一千个项目,而桌面应用程序通常可以通过更快的连接访问数据库服务器。

为了解决这些挑战,Habanero 使用一个控件工厂,该工厂可以生成具有特定行为的特定类型的控件。通用的 UI 控件管理器(例如窗体)现在可以通过调用传递给它的控件工厂的“创建”方法来创建特定的控件,而不是使用特定类型的构造函数。

让我们从代码中看一个例子。以下类用于生成一个显示计算机零件网格的窗体。

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);
    }
}

通过构造函数传递适当的 ControlFactory,并用于创建 ReadOnlyGridControl,这是一个 Habanero 控件,可在网格中显示对象,并提供过滤数据和在弹出窗口中编辑单个项目的工具。请注意,该控件是使用接口 IReadOnlyGridControl 实例化的。

创建通用主菜单

有一个名为 UIManager 的中间类,它为应用程序提供主菜单,为每个菜单项实例化相应的控件,并将控件工厂传递过去。在可执行项目中,UIManagerWin 继承自 UIManager,并实例化 ControlFactoryWin 以供 UI 层使用。最后一步是向项目的启动窗体/类添加代码。在 WinForms 可执行文件中,您会在 Program.cs 中添加两行代码,用于实例化一个类型的窗体并将其传递给 UIManager

代码中的流程

让我们从头开始,通过代码示例来跟踪这个过程。首先,在 Program.cs

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

这会调用 UIManagerWin 中重写的方法

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;
    }
}

子类会调用父类,父类会设置主菜单并为每个菜单项分配控件。

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);
}

最后一步是实例化正确的控件,如前面所示的示例网格代码所示。

开发成本是多少?

这种设计用户界面的方法有三个主要好处。首先,您可以发布到多个环境。其次,这种设计非常适合表示层的测试驱动开发,这是开发人员经常回避的挑战。第三,ControlFactory 引入了相当大的灵活性。虽然我们已经讨论了 Web 和桌面之间的差异,但即使是单个桌面应用程序也可以使用不同的 ControlFactory 在不同时间为同一个控件生成不同的行为。

至于成本,Visual Studio 设计器不支持这种类型的处理(当然,有些人会说:摆脱设计器万岁!)。其次,开发模式略有改变。您不是直接实例化控件,而是通过 ControlFactory 进行调用,并将结果分配给接口而不是特定类型。

总的来说,上面提供的代码提供了一个相当简单的应用程序基础。在此基础上,您的大部分工作将用于扩展通用 UI 层,添加您需要的所有控件来提供一个可用的应用程序。

Habanero 是免费开源的,您可能想查看一些用于实现此结果的有趣代码结构。您可以在官方网站下载完整包并访问教程和支持。

© . All rights reserved.