1.3 Windows窗体模型设计

下面开始进入Windows窗体开发的精彩世界。

像很多编程语言的讲解一样,这里也从最简单的“Hello World”程序开始,下面是Windows Form版的“Hello World”程序。

1.3.1“Hello World”程序

开始使用Windows窗体非常简单。下面示例运行后将显示一个顶级窗口(称为窗体),并将标题栏文本设置为“Hello World”。

namespace Microsoft.Samples.WinForms.Cs.SimpleHelloWorld {
   using System;
   using System.Windows.Forms;
   public class SimpleHelloWorld : Form {
      [STAThread]
      public static int Main(string[] args) {
         Application.Run(new SimpleHelloWorld());
         return 0;
      }
      public SimpleHelloWorld() {
         this.Text = "Hello World";
      }
   }
}

没错,就是这么简单。OK,读者已经学会了最简单的Windows窗体程序,下面探讨Windows窗体应用程序模型。

1.3.2 Windows窗体应用程序模型

Windows窗体的应用程序编程模型主要由窗体、控件及其事件组成。本主题涉及Windows窗体应用程序模型的以下方面。

1.窗体

在Windows窗体中,Form类是在应用程序中显示的任何窗口的表示形式。可以使用Form类的BorderStyle属性创建标准窗口、工具窗口、无边框窗口和浮动窗口。还可使用Form类创建有模式窗口,如对话框。通过设置Form类的MDIContainer属性,可以创建一种特殊类型的窗体MDI窗体。MDI窗体可以在其工作区内包含名为MDI子窗体的其他窗体。Form类为键盘处理(Tab键顺序)和滚动窗体的内容提供内置的支持。

当为应用程序设计用户界面时,通常创建一个从Form派生的类。然后可以添加控件、设置属性、创建事件处理程序,以及向窗体添加编程逻辑。

2.控件

添加到窗体中的每个组件(如Button、TextBox或RadioButton)称为控件。Windows窗体包括通常与Windows关联的所有控件及类似Windows窗体DataGrid的自定义控件。

通常可以通过设置属性与控件进行交互,以更改其外观和行为。例如,下面的Form的派生类向窗体添加一个Button控件,并设置该控件的Size和Location。

public class HelloWorldForm : System.Windows.Forms.Form {
   private Button button1 = new Button() ;
   private TextBox textBox1 = new TextBox();
   [STAThread]
   public static int Main(string[] args) {
      Application.Run(new HelloWorldForm());
      return 0;
   }
   public HelloWorldForm() {
      this.Text = "Hello Windows Forms World";
      this.AutoScaleBaseSize = new Size(5,13);
      this.ClientSize = new Size(392,117);
      this.MinimumSize = new Size(392,(117 + SystemInformation.CaptionHeight));
      this.AcceptButton=button1;
      button1.Location = new Point(256,64);
      button1.Size = new Size(120,40);
      button1.TabIndex = 2;
      button1.Text = "Click Me!";
      button1.Click += new System.EventHandler(button1_Click);
      textBox1.Text = "Hello Windows Forms World";
      textBox1.TabIndex = 1;
      textBox1.Size = new Size(360,20);
      textBox1.Location = new Point(16,24);
      this.Controls.Add(button1);
      this.Controls.Add(textBox1);
   }
}

窗体对于何时可设置控件的属性提供有限的限制。控件没有阻止更新其状态的模式。创建控件的新实例后,可以立即更改其状态。例如,下面的代码提供两个示例,演示创建Button控件的有效方法。

Button button1 = new Button();
button1.Location = new Point(256,64);
button1.Size = new Size(120,40);
button1.TabIndex = 1;
button1.Text = "Click Me!";
this.Controls.Add(button1);
Button button1 = new Button();
this.Controls.Add(button1);
button1.Location = new Point(256,64);
button1.Size = new Size(120,40);
button1.TabIndex = 1;
button1.Text = "Click Me!";

Windows窗体确保用户创建的代码是有效的。例如,如果设置一个Windows控件的Windows样式位的属性(该属性仅可在创建控件时设置),则Windows窗体控件放弃基础Windows控件,并创建一个新控件。只有在不使用Windows窗体而直接访问控件的基础HWND时,此功能才有可能不合需要。用户无法保持对HWND的引用,因为代码中所设置的属性可能使其无效。

3.事件

Windows窗体编程模型基于事件。当控件更改状态,如当用户单击按钮时,它引发一个事件。为了处理事件,应用程序为该事件注册一个事件处理方法。在Visual Basic中,有以下两种途径可以注册事件处理方法。

  • 如果使用WithEvents关键字声明控件变量,可以在方法的声明中使用Handles关键字,将该方法注册为事件处理方法。
  • 可使用AddHandler在运行时注册事件处理方法。

下面的代码阐释注册事件处理方法的两种途径。

……
//Create the button
Button button1 = new Button() ;
button1.Location = new Point(256,64);
button1.Size = new Size(120,40);
button1.Text = "Click Me!";
this.Controls.Add(button1);
//Register the event handler
button1.Click += new System.EventHandler(button1_Click);
……
//The event handling method
private void button1_Click(object sender,EventArgs evArgs) {
   MessageBox.Show("Hello Windows Forms World!");
}

只对特定控件的特定事件调用某个事件处理方法。这使得可以避免窗体中出现处理所有控件的所有事件的单个方法。此功能还使代码更易理解和维护。而且,因为Windows窗体事件结构基于委托,所以事件处理方法是类型安全的并可以声明为私有的。此功能使编译器得以在编译时检测方法签名不匹配情况。它还使Form类的公共接口不与公共事件处理方法相混淆。读者可在.NET框架SDK文档中找到关于委托的更多信息。

4.事件类

每个事件有两个支持类,如下所示:

  • 用于注册事件处理方法的EventHandler委托类。EventHandler的签名指示事件处理方法的签名。
  • 包含有关引发的事件的数据的EventArgs类。

EventHandler的签名为:第一个参数包含对引发事件的对象(发送方)的引用,第二个参数包含关于该事件(EventArgs的一个实例)的数据。例如,Button上的Click事件使用以下事件处理程序。

public delegate void EventHandler(object sender,EventArgs e);

因此,Click事件的任何事件处理方法都必须具有以下签名。

<access> void <name>(object sender,EventArgs evArgs)

对于强类型语言,如果事件处理方法的签名与委托签名不匹配,将发生编译时错误。

很多事件使用一般的EventHandler和EventArgs类。但是,一些事件要求包含针对所引发事件的类型的附加信息。例如,鼠标移动事件包括有关鼠标指针或鼠标按钮位置的信息。这些事件定义其自己的类,这些类必须从EventHandler和EventArgs类继承。例如,MouseDown事件使用MouseEventHandler和MouseEventArgs类。

5.事件命名约定

可以在特定种类的状态更改之前和之后引发事件。在状态更改前引发的事件通常带有后缀“ing”。在状态更改后引发的事件通常带有后缀“ed”。例如,SessionEnding事件是状态更改前引发的,SessionEnded事件是状态更改后引发的。如果某状态更改仅导致一个事件被引发,则该事件通常没有后缀。例如,Click事件。

6.可取消的事件

根据应用程序中的情况,可能需要取消某个事件。某些事件可以取消。这些事件使用CancelEventHandler和CancelEventArgs类。CancelEventArgs类包含名为Cancel的属性。如果此属性设置为true,那么当该事件处理方法返回时,将取消该事件。通常,只有在状态更改前引发的事件才是可以取消的。取消事件将取消状态更改。

7.用一个事件处理方法处理多个事件

如果要用一个事件处理程序处理多个事件,可通过将同一方法注册到多个事件来实现。每个事件都必须具有相同的签名。当对多个事件使用一个事件处理方法时,可以从sender参数确定哪个控件引发了事件。下面的示例阐释处理来自两个按钮控件的事件的单个事件处理方法。

……
Button button1 = new Button() ;
Button button2 = new Button() ;
……
button1.Click += new System.EventHandler(button_Click);
button2.Click += new System.EventHandler(button_Click);
……
//The event handling method
private void button_Click(object sender,EventArgs evArgs) {
   if (sender==button1) {
      MessageBox.Show("Button1 Pushed!");
   } else if (sender==button2) {
      MessageBox.Show("Button2 Pushed!");
   }
}

1.3.3 Windows窗体中的动态布局

在调整窗体大小时,可动态调整该窗体上控件的大小。

Windows窗体提供三种控制窗体布局的方法,如下所示:

  • 锚定
  • 停靠
  • 自定义

如果将控件锚定到其容器的边缘,当调整该容器的大小时,该控件与指定边缘间的距离保持不变。控件可锚定到任意组合的容器边缘。如果将控件锚定到其容器的相对边缘,则在调整该容器大小时调整该控件的大小。

例如,如果将TextBox控件锚定到窗体的左右边缘,则当调整窗体的大小时,TextBox的宽度发生变化。

下面的代码将TextBox控件锚定到其容器的上、左和右边缘。

textBox1.Anchor = AnchorStyles.Top BitOr AnchorStyles.Left BitOr AnchorStyles. Right

如果控件停靠到其容器的边缘,则当调整该容器的大小时,该控件仍与该边缘保持接触。例如,在Windows资源管理器中,TreeView控件停靠在窗口的左边缘,ListView控件停靠在窗口的右边缘。如果多个控件停靠到一个边缘,则第一个控件停靠到容器的边缘,而其他控件在不覆盖其他控件的情况下停靠得尽可能离该边缘近。

下面的代码将Panel控件停靠在容器的左边缘。

Panel1.Dock = DockStyle.Left;

可以使用在Control类上公开的Layout事件编写自己的布局管理器。当任何导致控件显示子控件的事件发生时,都引发此事件。这些事件包括Resize、显示/隐藏子控件,以及添加/移除子控件。应使用此事件执行任何自定义布局逻辑。