描述:
在本文中,我们将讨论使用 Microsoft.Net Framework 构建 Windows 客户端应用程序的更好方法。本教程的结果将是一个库和示例应用程序,使我们能够更轻松地构建模块和独立于 UI 的 Windows 应用程序。
本文分为两部分:
- 开发一个简单的应用程序框架。此部分不依赖于 Developer Express 库,因此不是 Developer Express 客户的 .Net 开发人员可以使用它。
- 使用 Developer Express 库改进应用程序框架。要编译和运行提供的应用程序,请安装以下 Developer Express 库:
a) XtraNavBar
b) XtraBars Suite
c) XtraGrid Suite
d) XtraPrinting Library
本文考虑的问题包括:
*实现应用程序布局的最佳方法 – 使用用户控件
*使用用户控件的模块继承
*创建操作层
//*从一个菜单和工具栏库迁移到另一个菜单和工具栏库需要多长时间。使用这个应用程序框架,您可以非常快地完成这一任务,因为您只需要修改一个模块中的代码。
*在运行时设置 XtraNavBar 控件
*在应用程序框架中使用 XtraBars 和 XtraGrid
*使用 XtraPrinting 库添加打印功能
答案:
我们将逐步开发我们的应用程序框架,从最简单的任务开始,每一步我们都会添加功能,从而使代码更改尽可能简单。总共有6个步骤。您可以在每一步下载并编译项目。
请注意,本文中描述的构建应用程序的方法只是一个建议。我们并不声称这种方法在所有可能的情况下都是最好的。这只是一个基本框架,可以轻松扩展或更改,以便为您创建的每个特定应用程序获得最大利益。
应用程序框架。为什么我需要它?
Microsoft 在 .Net Framework 中引入了许多有用的类来帮助我们更有效地构建 Windows 窗体应用程序。所以,显而易见的问题是:为什么我们应该在代码中添加另一层?曾经有一段时间我什至没有想过这些事情,但这样做的好处有很多……
大约 10 年前,我加入了一家开发联系和销售管理系统的公司。当时他们使用VB+MS SQL进行开发。然而,在我加入一个月后,Borland 发布了 Delphi 1,第一个具有真正面向对象语言的 RAD 工具。决定在下一个大型项目中使用 Delphi。我们对此感到非常兴奋。我们决定为所有模块创建一个应用程序,这样我们就可以更好地重用我们的代码。我们还年轻。我们大多数人,包括我,都刚刚大学毕业。我们像疯狗一样为不同的模块编写了几十种规格。一切都很顺利,直到我们开始在真实环境中测试我们的应用程序。突然,我们发现修复 bug 并不像我们想象的那么容易。修复一个模块中的错误会在其他模块中产生多个错误。编写新模块需要花费越来越多的时间。用于数十个不同模块的菜单/工具栏系统的逻辑变成了一场彻底的噩梦。没有人能够理解它,因为它包含大量的“case/switch”操作,大量的“if”语句等等。我们只有每个人都投入大量的个人时间才能完成这个项目。当项目完成时(大约花了一年),每个人都非常疲惫。大多数开发人员离开公司去度假,就再也没有回来!
如果我说我们之所以遇到问题只是因为我们没有使用应用程序框架,那我就是在撒谎。项目过程中犯了很多错误。我想我们几乎犯了在开发软件项目时可能犯的所有错误。当然,我们从未改进过我们的代码。自动测试——它们是什么?当时,我们还没有听说过它们,也几乎没有做过任何测试。每个人只关心自己的模块,共享的代码真是一团糟。我可以继续说下去,但我相信你知道我在说什么。
但是,我确信没有应用程序框架是我们在开发过程中遇到问题的主要原因之一。当项目即将完成时,我抽出时间查看了大部分模块。我很惊讶。尽管以不同的方式实现,但每个模块所需的大部分功能都是相当常见的。
下次我参与类似的项目时,我敦促大家花几天时间创建一个非常简单的框架。它允许我们:
1. 通过添加/删除一行代码来添加/删除模块
2. 在模块之间共享通用功能
3. 统一菜单/工具栏的使用
在进一步的开发过程中,花在这一层编码上的时间得到了多次回报。从那时起,我在我的大多数 Windows 应用程序中使用了该框架的修改版。
在 Developer Express 工作期间,我查看了客户编写的项目代码。有一些好的方法,但有些实施效果不佳。有一些项目让我想起了我编写大型应用程序的第一次实验。有时人们正在努力在模块、菜单和工具栏系统等中引入继承。我觉得这篇文章肯定会对他们有很大帮助。那些已经编写了自己的应用程序框架并成功使用它的人很可能可以借用一些想法和代码。我们 Developer Express 很高兴知道这篇文章将使您的生活变得更轻松(帮助开发人员是我们在 Developer Express 工作的主要原因)。
创建最简单的模块无关的应用程序框架
在第一步中,我们将创建一个应用程序框架库,该库将允许创建独立的模块。将显示模块的主窗体不会了解其内容。模块本身不知道它们在哪里显示和位于哪里。它将允许您在应用程序的不同部分中使用模块,并与主应用程序代码分开开发和/或测试模块。您和您的团队会给您的印象和感觉是您的申请写得很好。这是一个比较心理性的事情,但是却非常有效。
应用布局
第一个决定是我们要使用的应用程序布局类型:SDI 还是 MDI。几年前,对于SDI和MDI哪个更好存在很多争议。现在,微软已经在 MS Office 应用程序中引入了 SDI 外观,大多数人开始选择 SDI。所以,我们会选择SDI,向MDI粉丝致歉。不过,只需稍作修改,您就可以将该库应用于 MDI,不会出现任何问题。
这是一个典型的 SDI 应用程序布局,首先在 MS Outlook 中引入。
菜单/工具栏系统标记为蓝色,导航面板标记为黄色,状态栏/面板标记为绿色,工作区域标记为灰色。
让我们使用此布局创建一个应用程序。它将包含两个模块:模块 1 和模块 2。
在此应用程序中,我们将使用一个标准菜单和一个停靠在左侧的面板控件,其中包含一个列表框(用于创建导航区域)。为了创建一个工作区域(灰色),我们将添加一个面板,其停靠属性设置为填充整个区域。最后,我们将在导航器和工作区域之间放置一个分割器控件。为了简化任务,我们不会在应用程序中包含任务栏。
当前的任务是创建一个应用程序框架,允许创建独立的模块,以便开发人员能够添加/删除一行代码来添加/删除每个模块。
基于我们框架的应用程序中的所有模块都将从BaseModule类继承(直接或间接)。基础模块类继承自.Net UserControl类。
在当前步骤中,我们不会将任何功能放入 BaseModule 类中,因此将其留空。
C#namespace ApplicationFramework
{
/// The base module class of the Application Framework
public class BaseModule : System.Windows.Forms.UserControl {
}
}
由于在应用程序启动时创建所有模块实例是一种不好的方法,因此我们需要创建模块注册类。Modules.cs文件中有两个类:ModuleInfo和ModuleInfoCollection。
ModuleInfo 类包含模块名称和模块类类型属性。当我们需要显示模块时,我们将使用模块名称进行识别,并使用模块类类型来创建模块类的实例。
ModuleInfoCollection 类继承自 CollectionBase,包含在我们的应用程序中注册的模块列表。要注册新模块,请使用 Add 方法。ShowModule 方法将在 Windows 控件上显示模块。属性 Count 和 Items 将提供对所有已注册模块的访问。您也可以使用 foreach C# 构造。
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);
}
}
}
现在我们需要在主窗体上设置菜单和导航控件,以便用户可以直观地运行模块。
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();
}
}
}
最后一步是创建新模块并在应用程序框架中注册它们。
C#Module1.cs
namespace ApplicationFramework
{
public class Module1 : ApplicationFramework.BaseModule
{
// ...
}
}
要注册新模块,请修改 ModulesRegistration 代码文件并在 Register 方法中添加新行
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));
}
}
}
不要忘记调用模块注册方法
C#frmMain.cs
// ...
[STAThread]
static void Main()
{
// call module registration before running the main form
ModulesRegistration.Register();
Application.Run(new frmMain());
}
总结
正如你所看到的,只需要很少的代码,我们就达到了创建一个具有独立模块的框架的目的。
当然,这个应用程序框架没有很多功能,在现实世界中您需要扩展它。例如,在我编写的大多数应用程序中,都需要提供模块安全性。根据安全权限,最终用户可能可以访问该模块,也可以不访问该模块。通过在模块注册方法中编写代码,可以很容易地引入这一点。基本模块没有实现任何功能,这也不正常。在大多数情况下,您需要将功能直接引入到基本模块中。您唯一需要记住的是,引入基本模块的任何功能都会自动出现在其余模块中。因此,您应该提供一个模块继承方案,例如:BaseModule -> BaseDataModule -> BaseGridModule 等,
动作介绍
在前面的步骤中,我们构建了一个小型应用程序框架,它允许我们创建独立的模块。现在是时候考虑向模块添加功能了,我们需要解决的第一个问题是如何在主窗体中的 UI 对象和模块中的业务代码之间创建一个层。
换句话说,我们希望在主窗体上有一个菜单和工具栏系统。菜单项和工具栏按钮需要其可见、启用和其他属性来反映当前显示的模块中包含的业务逻辑。菜单和工具栏项不应该知道所显示模块的详细信息,并且模块根本不应该知道菜单和工具栏的存在。我们甚至希望能够更改 UI 控件,例如从标准菜单系统移动到 Developer Express XtraBars Suite,反之亦然,而无需更改模块中的代码。此外,我们希望能够在甚至不创建主窗体的测试引擎中测试模块功能。
基本上,我们需要在主窗体上的 UI 和模块中的业务代码之间多一层。我将这一层称为“动作”。
操作类位于actions.cs文件中。抽象Action类不知道它正在使用的真实 UI 类。它允许我们的模块独立于 UI 类。Action具有以下抽象属性:Visible、Enabled、IsDown。它们必须在后续类中被覆盖。Actions 类允许您管理您的操作。此类包含一个全局(静态)操作哈希表。要在系统中注册新操作,请调用RegisterAction方法。在BaseModule中创建Actions类的实例。为了让系统知道您的模块支持特定操作,您必须调用AddSupportedAction方法。要访问特定的操作类,请使用索引的操作类属性。
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);
}
}
}
}
下面是 ToolBarButton 的操作类的实现。正如你所看到的,代码非常简单
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; } }
}
以下是在全局操作哈希表中注册操作的示例。该方法应在主窗体初始化后调用
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));
}
最后,我们需要向BaseModule类添加一些功能。要注册支持的操作,请使用Actions属性重写RegisterActions方法。重写UpdateActions以更改Enabled操作和IsDown属性。
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) {}
// ...
}
下面是在继承中实现BaseModule类的虚方法的示例
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();
}
总结
现在我们有了一个小型但功能齐全的应用程序框架的原型。我们可以创建独立的模块并将这些模块绑定到主窗体上的UI对象。
使用 Developer Express 组件改进应用程序框架
添加 Developer Express XtraNavbar 控件
我们在上一步中创建了应用程序框架的第一个版本,从我们的角度来看,它工作得很好。然而,如果我们把它展示给老板或我们的最终用户,他们会嘲笑我们。列表框并不是现代应用程序中导航控件的最佳选择。
Developer Express为XtraNavBar控件提供了十多种不同的绘制样式来增强显示效果。它将使您的应用程序具有现代的外观。
NavBar 库具有易于使用的设计器,可帮助您在设计时设置控件。不幸的是,我们必须通过代码完成所有工作,因为主模块不能知道我们将要引入系统的模块(并且我们希望通过添加/删除一行代码来管理模块)。
首先,我们必须在应用程序框架中引入附加功能。导航栏控件将项目分为几类。因此,我们必须在模块注册类中引入类别。此外,我们希望在导航栏控件中包含项目和组的图像,因为它将从根本上改善应用程序的外观。因此,我们也需要在注册类中引入图像属性。
为了处理组/类别,我们将创建两个类:CategoryInfo和CategoryInfo在我们的模块注册 C# 代码文件中。
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; } }
}
我们必须将 Category 和 ImageIndex 支持添加到ModuleInfo类中
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; } }
// ...
}
最后修改ModuleInfoCollection类中的模块注册方法
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);
}
//..
}
下一步是根据我们的应用程序框架中注册的模块创建导航栏控件组、项目和链接。我们必须更改主窗体 C# 文件中的RegisterModules方法
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);
// ...
}
}
总结
现在,由于使用 Developer Express NavBar 控件作为导航控件,我们的应用程序看起来好多了。
添加 Developer Express XtraBars Suite
现在是时候用另一种系统取代老式的标准菜单和工具栏系统了。由于我们的操作层,这不是一个大问题。无论我们决定选择哪个库,我们只需要修改主窗体即可。在这里,我们将展示如何迁移到 Developer Express XtraBars Suite。
将 BarManager 组件拖放到主窗体上,然后在属性窗口中调用“Import From MainMenu”项。ExpressBars 工具栏菜单将基于标准主菜单创建。
首先,让我们修改代码以将模块添加到“Go”子项中。XtraBars 提供了不同的栏项类型,其中 BarListItem 类型最适合在模块之间导航。
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);
}
}
以下是BarListItemModules项的ItemClick事件处理程序代码。
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);
}
现在,我们必须编写BarItemAction类。所有 XtraBars 项目类均继承自 BarItem 类。这意味着我们可以对所有 XtraBars 项目使用 BarItemAction。
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);
}
}
最后一步是修改RegisterActions方法
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));
}
总结
如您所见,我们仅对主模块进行了所有修改;而且任务很简单。如何在应用程序中从一个菜单和工具栏库迁移到另一个库?我想这可能是一个真正的痛苦。
创建 Developer Express Grid 模块
通过使用 Developer Express XtraNavBar 和 XtraBars 库替换标准控件,我们极大地改进了应用程序的外观。现在,是时候考虑在模块内容方面改进我们的框架了。在您的应用程序中,您不太可能从BaseModule继承“最终用户”模块直接地。在大多数应用程序中,通常有几个模块在列表上显示对象/记录。通常,我们使用网格控件来实现此目的,这通常是应用程序中的中央控件。您将需要围绕网格编写代码,例如显示/隐藏列自定义窗口等。当然,为每个包含网格控件的模块编写此功能是没有意义的。我们将创建一个BaseGridModule模块。Developer Express XtraGrid 将位于该模块上。除了网格操作之外,我们还将引入导出操作。尽管我们将在基本模块中添加对导出操作的支持,但默认情况下,这些操作将被禁用。因此,实际的网格模块必须重写方法才能重新启用它们。
首先,我们需要添加新的动作ActionsKeys类位于ActionsKey文件中。现在它包含:
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";
}
我们现在需要创建 XtraBars 项目,将它们正确放置在菜单/工具栏系统中并将它们绑定到我们的操作。
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));
}
此外,我们还有更有趣的任务。我们需要在 BaseModule 中引入导出操作支持。它将允许我们重写继承模块中的两个方法,从而实现导出功能:IsExportTypeSupported和DoExport。您将在下面找到执行此任务的代码。
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 "";
}
// ...
}
新的BaseGridModule需要重写IsExportTypeSupported和DoExport方法来支持导出
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);
}
}
// ...
}
最后一个任务是将网格操作的支持直接添加到 BaseGridModule 类。XtraGrid是基于视图技术构建的。它提供了出色的可扩展性,并且您不必在数百种网格控件方法/属性中寻找所需的方法/属性,因为 CardView 呈现与 GridView 和 BandedView 具有完全不同的属性。
但是,将网格操作绑定到所有网格视图类型将需要一些额外的编码。例如,ShowBand 操作对于 CardView 没有任何意义,因此应该禁用它。为每个动作使用一堆“if”语句进行编码是一种非常糟糕的技术。因此,很明显我们需要再构建一个抽象。我想到的最简单的事情是创建一个 GridViewActionsHelperBase 类,它可以与网格操作一起使用,而无需了解我们当前正在处理的视图类型。
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() {}
}
为从其继承的每个视图创建一个类:CardViewActionsHelper、GridViewActionsHelper 和 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();
}
}
在使用网格操作执行任何操作之前,我们将创建正确的网格操作帮助器类并调用其方法。
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();
}
总结
在这一步中,我们为应用程序框架创建了基本列表模块。
将打印功能添加到应用程序框架中
我们将添加到应用程序框架中的最后一个功能是打印功能。我们将引入打印操作,并以与导出操作相同的方式在基本模块中实现它们。
在 BaseModule 中添加打印操作支持后,我们将多出三个受保护的虚拟方法:HasPrinting、DoPrint 和 DoPreview。这些方法在BaseGridModule
中被重写,以便能够打印 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);
}
如您所见,如果您使用 XtraPrinting 库,这是一项非常简单的任务。
总结
通过这一步,我们已将打印支持引入到应用程序框架中,并为基本网格模块实现了它。
在附件中,您将找到以分步方式演示该技术的示例项目。
评论0