使用C#编写Windows Forms应用程序
作为一个Windows开发者, 你应毫不犹豫成为Microsoft新的.NET的倡导者。在规范的客户端应用程序成为流行的同时,越来越多的组织正趋向于简单的,基于浏览器的可以通过网络交互(通常是Internet)的应用程序。这就意味着编程正在从独立的应用程序模式向更丰富的面向组件的模块化应用程序转变。
.NET平台正是为推动这一运动——特别是被称为Windows Forms的.NET用户界面模型。.NET使用了Windows Forms来编写一个应用程序的用户界面。你可以使用C#或Visual Basic.NET来创建这些特性。在这里我将向你展示C#为.NET平台创建基于Windows Forms用户方面的作用。这里的例程就是一个用C#写的基于Windows Forms的tic-tac-toe游戏。
每过几年,Windows的编程方式就会发生巨大的变化。去年夏天的Microsoft Professional Developers Conference (PDC)把Windows的开发者领回到了square one。而在几年前,平台才刚刚从DOS转变为Windows。现在,开发者已经解决了桌面上的问题。新的方向是开发Internet 框架来让愈来愈多的公司能搬到Web上。
在八十年代末,Windows开发者使用C和SDK写了大量的桌面应用程序,然后就是C++和框架,如Microsoft Foundation Class Library(MFC)。与此同时,VB开发领域也得到了坚实的立足点。在拥有强大优势诸如强大的开发环境和可管理开发者资源的runtime的面前,VB正逐步走向成熟,成为许多企业开发前端和中间层组件的得力工具。对于大多数其他应用程序,开发者都可以使用Windows Forms来创建用户界面。
让我们先总体看一下Windows Forms,然后再看一下使用C#来写基于Windows Forms的应用程序需要些什么。在外壳下,所有的Windows应用程序都是以同样的方式运行的。Windows会维护Windows类的集合(即windows的行为都是在WndProc()函数中定义的)。在Windows编程的早期,最大的任务就是写大约80行的样板代码为了能让它正确的运行,后来逐渐通过添加事件句炳来开发应用程序。MFC就使得开发者不用再去为Winmain()和WndProc()费神了。Windows Forms继承了这种忽略编程细节的趋势,所以你不必花大量的时间在书写那些枯燥的代码上。
随着基于SDK和MFC的发展,你可能仍然保留作为开发者对Windows API的钟爱。如果你需要严格的控制你的应用程序,使用C和SDK——或甚至是MFC来开发应用程序是必要的。如果你希望有灵活性,基于SDK或MFC的开发仍然是需要的。但是如果你觉得一个更简单,更直观的开发环境比严格控制或灵活性更重要的话,使用Windows Forms来开发基于窗体的用程序可能更适合你。
编写客户代码
>有了Windows Forms,你就可以编写.NET平台的客户代码。如果你曾使用过VB,你就可能对它基于窗体的应用程序模型很熟悉。而Windows Forms与此很相似。SDK或MFC的编程风格是直接与Windows API交互的,甚至当MFC中出现了框架,你仍仅仅是从底层的Windows API迈出了一小步。与此相反,Windows Forms隐藏了旧式Windows编程风格中的样板代码的细节,以带有菜单和标题栏的正规窗体的形式显示。Windows Forms能响应标准的事件,如鼠标的移动和菜单的选择,而且它们也可以控制在客户区的行为。然而,管理这些特性的语法比你用SDK或MFC编写程序的语法要抽象的多。
你可以以标准的窗口、多文档界面(MDI)、对话框或绘图程序表面的形式显示Windows Forms。随着VB的发展,它使用了用户界面(UI)发展中的窗体模型,即给一个Windows 窗体定义用户界面,通常就是意味着能在窗体的客户区安放控件。但是Windows Forms还可以更好的渲染你所希望的绘图表面。
除了能渲染绘图表面和管理标准控件集,Windows Forms通过属性来定义它们的外观。例如,要想编程在屏幕上移动一下Windows窗体,你只要设置Windows 窗体的X属性。Windows Forms使用方法来管理它们的行为,而且它们也可以通过响应事件来定义与用户的交互。
Windows Forms是在.NET Framework或Common Language Runtime(CLR)中运行的类的实例。编写一个Windows Forms应用程序通常就是实例化WinForm类的一个实例,配置它的属性并建立事件句柄。因为一个Windows 窗体就是一个标准的基于CLR的类,是完全支持继承的,你可以以标准的,面向对象的方式来建立基于Windows Forms的类之间的继承关系。
现在,让我们看一下开发环境。Microsoft的Visual Studio.NET不断在进步。不幸的是,IDE的PDC Tech Preview版本不是很稳定。所以我用C#开发的开发环境中包含一个文本编辑器和一个命令行编译器。Beta 1解决了很多方面,使VS.NET成为开发C#应用程序的可行环境。
但是如果你不喜欢使用VS.NET的beta版,那你也可以回到命令行的方式。C#的命令行编译器名为CSC.EXE。当运行它时,它通过命令行决定输出的位置、需要的资源文件、应用程序要使用的系统文件和最后可执行文件中要包含的C#文件。例如,以下命令行编译了一个名为someApp.cs的文件,引入了System.DLL的多种系统功能,在资源中包含了一个JPG文件,并且把最终的可执行文件放到名为in的目录下:
csc /out:in /R:System.DLL /res:XYZ.JPG someApp.cs
我们再一次回到了1989年你用命令行编译器通过一些批处理文件或makfiles来创建一个Windows应用程序的时候。在开发tic-tac-toe例程时,我也在一个简单的批处理文件中使用了命令行编译器。
现在,你可以通过C#和VB来使用Windows Forms。这两种语言在建立基于Windows Forms的应用程序方面是等同的。你可以使用它来创建大量当今需要的编程构架——特别是当开发基于Windows Forms的应用程序。
开始开发 Tic-Tac-Toe
那么,基于Windows Forms的应用程序是什么样的呢?看一下tic-tac-toe例程吧。一个C#基于Windows Forms的应用程序一开始通过一系列using声明先引入必要的定义(程序需要的类型定义)。
namespace CSharpTicTacToe {
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.WinForms;
// Windows Form code goes here?
};
第一个namespace关键字是可选的。但是对于设定功能的作用范围通常是很有用的——特别是在assembly过程中,一种编写DLL的新方式。在关键字之后,每一个using声明告诉C#编译器,程序所要用到的系统功能。因为tic-tac-toe游戏是一个Windows 窗体,源文件使用了System的WinForms namespace。而且,因为游戏使用了图形,源代码就要引入URT的绘图功能。
在你引用了namespace后,你就要通过从系统提供的Form类继承一个类来表示一个Windows 窗体。
public class CSharpTicTacToe : Form {
// Windows Form code goes here, including
// data members, a constructor, and
// some event handlers?
}
C#提倡枚举作为定义变量类型的一种方式,而不是指定一个整数范围,这样能维护类型的安全性并能提供尽可能多的信息。Tic-tac-toe游戏指定了三种枚举类型:player类型、用于在板上做标记的类型和对板上位置命名的类型。以下就是具体的描述。你可以在游戏的多个地方看到它们的用途。
public enum Player {
XPlayer,
OPlayer
}
public enum Mark
{
XMark,
OMark,
Blank
}
public enum Positions
TopLeft,
TopCenter,
TopRight,
MiddleLeft,
MiddleCenter,
MiddleRight,
BottomLeft,
BottomCenter,
BottomRight,
Unknown
}
一旦你定义了窗体,就需要一些数据成员,一个构造函数和一些事件句柄。我会依次向你
阐释。首先是基本的数据成员,一个tic-tac-toe板。Tic-tac-toe游戏的数据包含了一个表示游戏板的3*3的矩阵数组。这个游戏定义了一块板的格子(见清单1)。每一个板的格子都表示在屏幕上的一个位置并确定玩家是否做了标记。此外,格子还使用了一个X和一个O,来决定哪个玩家做了标记。我还会细致的说明的。
Tic-tac-toe板管理了3*3的格子(见清单2)。它也管理着BoardSpace对象3*3的数组,并用线条来划分tic-tac-toe的格子并让每一个格子来绘制它们自己。大部分的游戏逻辑都是由板来负责的,所以制作这个游戏的最主要的部分就是建立一个窗体,把板作为数据成员,并且当鼠标按下时请求板的绘制(见清单 3)。清单3包含了Windows Forms应用程序的初始化代码。注意这个过程就是初始化游戏板,创建一个Reset按钮和其事件句柄,然后截获MouseDown和Paint事件。
大部分的时间,响应事件就是重载(override)正确的函数。例如,游戏要响应MouseDown事件(通过把鼠标的位置交给板来处理)和Paint事件。当它生成了事件,系统就会自动的调用。你还可以为非系统的、用户定义的事件如按钮被按下而手工关联事件句柄。该游戏也可以创建一个Reset按钮来处理清除游戏板的事件。
Windows Forms编程最基本的就是基于用户界面,请求你来绘制屏幕的过程。Windows Forms定义了一个捕获WM_PAINT消息的良好方法。Form类包含了一个名为OnPaint()的函数来让你重载。通过重载这一方法,你可以捕获绘图事件并在屏幕上做你想做的。看一下例程的源代码,你会注意到Paint事件的参数包括一个Graphics对象,它类似于SDK编程时的一个设备上下文。Graphics对象包括了画线和图形、填充区域以及任何你想在屏幕上做的。
Tic-tac-toe游戏通过让游戏板自绘来响应Paint事件。如果你在例程中看一下TicTacToeBoard类和BoardSpace类,你就会发现每一个类都有一个Render()函数来使用Graphics对象的DrawLine()和DrawEllipse()方法在屏幕上绘图。Windows Forms和C#的强大地方就在于你不必考虑管理GDI类型的资源,因为.NET Framework为你做了。
Windows Forms也提供给你很多的可行性,包括在Windows 窗体上添加菜单和图标,显示对话框和捕获Paint和MouseDown事件以外的大量事件。
清单 1
public struct BoardSpace {
public BoardSpace(Mark mark,
int left,
int top,
int right,
int bottom) {
// Initialize internal state?
}
public void SetMark(Player player) {
// if the space is blank, mark it using
// the player enumeration
}
public void Render(Graphics g) {
Pen pen =
new Pen(Color.FromARGB(170, Color.Black), 3);
switch(m_mark) {
case Mark.XMark:
g.DrawLine(pen, m_left, m_top, m_right,
m_bottom);
g.DrawLine(pen, m_left, m_bottom, m_right,
m_top);
break;
case Mark.OMark:
int cx = m_right - m_left;
int cy = m_bottom - m_top;
g.DrawEllipse(pen, m_left, m_top, cx, cy);
break;
default:
break;
}
}
public Mark m_mark;
public int m_top, m_left, m_right, m_bottom;
};
--------------------------------------------------------------------------------
清单 2
public struct TicTacToeBoard {
BoardSpace[,] m_BoardSpaces;
public void Initialize() {
m_BoardSpaces = new BoardSpace[3,3];
// Initialize each space with a location on the screen and a
// blank mark.
// Here's the first space:
m_BoardSpaces[0, 0] = new BoardSpace(Mark.Blank, 1,
1, 50, 50);
// Do the rest like that?
}
public void ClearBoard() {
// loop through the spaces clearing them
}
public Player EvaluateGame() {
// Check adjacent marks and see who won.
}
public Positions HitTest(int x, int y, Player player) {
// Test the incoming Coords and mark the right space
// using the player enumeration
}
public void Render(Graphics g) {
Pen pen = new Pen(Color.FromARGB(170,
Color.Black), 5);
g.DrawLine(pen, 1, 50, 150, 50);
g.DrawLine(pen, 50, 1, 50, 150);
g.DrawLine(pen, 1, 100, 150, 100);
g.DrawLine(pen, 100, 1, 100, 150);
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
m_BoardSpaces[i, j].Render(g);
}
}
}
};
--------------------------------------------------------------------------------
清单 3
public class CSharpTicTacToe : Form {
public Player m_Player = Player.XPlayer;
TicTacToeBoard m_board = new TicTacToeBoard();
public CSharpTicTacToe() {
SetStyle(ControlStyles.Opaque, true);
Size = new Size(500, 500);
Text = "CSharp Tic Tac Toe";
m_board.Initialize();
//Finally add a button so that we can render to a bitmap
Button buttonRestart = new Button();
buttonRestart.Size=new Size(100,50);
buttonRestart.Location=new Point(300,100);
buttonRestart.Text="Restart";
buttonRestart.AddOnClick(new EventHandler(Restart));
this.Controls.Add(buttonRestart);
}
//Fired when the restart button is pressed
private void Restart(object sender, EventArgs e) {
m_Player = Player.XPlayer;
m_board.ClearBoard();
this.Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
Positions position = m_board.HitTest(e.X, e.Y, m_Player);
if(position == Positions.Unknown) {
return;
}
if(m_Player == Player.XPlayer) {
m_Player = Player.OPlayer;
} else {
m_Player = Player.XPlayer;
}
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
Graphics g = e.Graphics;
e.Graphics.SmoothingMode =
SmoothingMode.AntiAlias;
g.FillRectangle(new
SolidBrush(Color.FromARGB(250,
Color.White)), ClientRectangle);
m_board.Render(g);
}
public static void Main() {
Application.Run(new CSharpTicTacToe());
}
}
}