春鱼·编程观点

技术在进步, 世界在变得美好...

导航

让WEB FORM更像WINDOWS FORM: 控制窗体事件

[按]
你可能会误解本文标题:让WEB FORM 更像WINDOWS FORM。 你也许会把本文当作是讨论应用程序界面. 其实本文讨论的是ASP.NET表现层设计。  也许会给你一点启示:如何设计逻辑隔离良好的、更容易设计和管理的应用程序。作者是一个偏向于应用的开发人员,因此可能在某些方面涉论不深,希望可以与有更多想法的朋友交流。作者的电子邮件xujian(a)nwpu.edu.cn

[引言]
在项目设计和实施的过程中,我经常再想,既然我们必须给用户以样式美观并且易用的用户界面,就应该好好设计表现层的UI逻辑。或者想办法把业务逻辑、系统逻辑、和UI逻辑隔离得更好。使界面相关元素得到最大程度的重用和最大的灵活性。

本文的核心论述对象是ASCX(ASP.NET User Controls)。这里我们把ASCX比作WIN32应用程序的”窗体(FORM)“。我们知道,设计WINDOWS应用程序的时候,不管使用什么开发工具,用户界面的主要构成元素是“窗体”,我们预先设计好承担不同任务的窗体,然后在适当的时候把他们弹出来,任务完成后把他们关掉。这样就基本完成了用户界面的流程控制。

而ASCX是类似可嵌入到网页中的UI单元。我们可以通过编写HTML来开发一个ASCX。这只是ASP.NET框架所提供的一种机制。我们可以把ASCX做为FORM,像操作WINDOWS FORMS一样操作网页中的ASCX,来完成我们的UI逻辑。

由于ASCX可以嵌入页面中,这就使得最大程度重用页面构图提供了可能性。我们可以把ASPX页面当作ASCX的容器。ASPX仅包含UI逻辑。更复杂的业务上的逻辑由ASCX承担。甚至ASCX仅承载数据。(在我的实际经验中,我总是设计两类ASCX。一类是数据类,仅以一定格式显示数据,不承担业务逻辑;另外一种是事务型,用来完成某种操作。)ASPX控制其中的各个ASCX交互显示隐藏,由此完成流程控制。

实现的障碍在哪里?提供ASCX的主要目的是隐藏其中的逻辑,并且提供一定程度上的重用性。但是ASCX内部的逻辑并不能和其容器直接交流。也就是说ASCX对外界是一无所知的。承载ASCX的容器(可能是一个ASPX页面或者另一个ASCX)可以很方便的控制他的显示,隐藏,但是容器却不知道什么时候关闭他。在流程完成后,ASCX无法直接调用容器的方法是自己关掉。

关键性的技巧就是让ASCX可以向外引发事件。只要ASCX需要变动自己的UI状态,就以一定的事件通知容器。而不用管容器如何处理。而容器可以很容易捕获控件的事件而最初相应处理。这样就给我们提供了一种指导性的模式。

作者的设计。一般而言, 我给表现层的ASPX和ASCX都至少抽象一个基类。基类里包含所有ASCX都需要的内容。有安全方面的,有系统配置方面的。对于本文所论述的内容,我使所有ASCX都可以引发三种事件,分别代表ASCX生命周期的各个阶段,他们是:

MissionStart:事务开始,代表控件最初状态。
MissionAbort:事务中止,用户的操作取消。需要返回原界面。
MissionEnd:事务结束, 用户的操作完成,需要进一步处理。

这三个事件都没有任何参数,他们仅仅包含了界面逻辑,和业务逻辑没有关系。 这样我们可以在用户没有进行有结果的操作,并且需要返回原界面时引发MissionAbort事件,在用户操作完成,需要进一步处理时引发MissionEnd事件。(MissionStart我还从来没有应用到, 但我想预留这么一个事件是必要的。)

[有关源代码]
以下源代码摘自UserControlBase.cs 该类继承自System.System.Web.UI.UserControl
  /// <summary>
  /// 关键系统事件: 事务开始. ASCX生命周期开始.
  /// </summary>
  public event System.EventHandler MissionStart;

  /// <summary>
  /// 关键系统事件: 事务中止. 未进行到事件最终状态. ASCX关闭但操作未完成.
  /// </summary>
  public event System.EventHandler MissionAbort;

  /// <summary>
  /// 关键系统事件: 事务完成. 既定操作完成. ASCX被关闭, 通知容器向下进行.
  /// </summary>
  public event System.EventHandler MissionEnd;

  /// <summary>
  /// 引发MissionStart事件
  /// </summary>
  protected void RaiseMissionStart()
  {
   if(this.MissionStart != null)
   {
    this.MissionStart(this, new System.EventArgs());
   }
  }

  /// <summary>
  /// 引发MissionAbort事件
  /// </summary>
  protected void RaiseMissionAbort()
  {
   if(this.MissionAbort != null)
   {
    this.MissionAbort(this, new System.EventArgs());
   }
  }

  /// <summary>
  /// 引发MissionEnd事件
  /// </summary>
  protected void RaiseMissionEnd()
  {
   if(this.MissionEnd != null)
   {
    this.MissionEnd(this, new System.EventArgs());
   }
  }

ASCX需要从此基类派生. 例如:
public class ComplexSearch : XxxXxx.YyyYyy.UserControlBase
{
...
}


上述ASCX是一个做组合查询的UI. 有一个“取消“命令按钮. 我们在取消按钮的Click事件上做如下命令
This.RaiseMissionAbort();

之后容器捕获该事件, 在private void InitializeComponent()方法中加上:
this.XxxXxxxx.MissionAbort += new EventHandler(AttributeInsert_MissionAbort);

(注意:以上代码是+=以后的部分IDE可以自动为你完成. 并且自动生成复合编码约定的方法名):

  private void XxxXxxxx_MissionAbort(object sender, EventArgs e)
  {
   ///...(处理过程)
  }

以上仅仅是基类相关事件的定义以及应用范例. 实际情况也复杂不到哪里去. 不过除了这些之外, 作者所开发的项目中, 几乎每个ASCX都有着异常丰富的特性. 有着较高的可重用性/灵活性和独立性.

[几点与主题无关的原则]
1. 尽量抽象一些公共特性到基类
2. 尽量分离逻辑. 公开足够的特性(Properties), 方法, 事件.
3. 设计上多用一天, 可以减少几天的编码及维护工作量. 并且可为后继的工作带来便利.

[结束]
本着抛砖引玉的想法,本文仅仅讨论了给ASCX加上三个事件。想法简单明晰,但在一定程度上反映了”架构“上的一些思想。

posted on 2004-04-05 21:26  春鱼  阅读(1791)  评论(8编辑  收藏  举报