直播中
.NET平臺(tái)正是為推動(dòng)這一運(yùn)動(dòng)——特別是被稱為Windows Forms的.NET用戶界面模型。.NET使用了Windows Forms來編寫一個(gè)應(yīng)用程序的用戶界面。你可以使用C#或Visual Basic.NET來創(chuàng)建這些特性。在這里我將向你展示C#為.NET平臺(tái)創(chuàng)建基于Windows Forms用戶方面的作用。這里的例程就是一個(gè)用C#寫的基于Windows Forms的tic-tac-toe游戲。
每過幾年,Windows的編程方式就會(huì)發(fā)生巨大的變化。去年夏天的Microsoft Professional Developers Conference (PDC)把Windows的開發(fā)者領(lǐng)回到了square one。而在幾年前,平臺(tái)才剛剛從DOS轉(zhuǎn)變?yōu)閃indows。現(xiàn)在,開發(fā)者已經(jīng)解決了桌面上的問題。新的方向是開發(fā)Internet 框架來讓愈來愈多的公司能搬到Web上。
在八十年代末,Windows開發(fā)者使用C和SDK寫了大量的桌面應(yīng)用程序,然后就是C++和框架,如Microsoft Foundation Class Library(MFC)。與此同時(shí),VB開發(fā)領(lǐng)域也得到了堅(jiān)實(shí)的立足點(diǎn)。在擁有強(qiáng)大優(yōu)勢諸如強(qiáng)大的開發(fā)環(huán)境和可管理開發(fā)者資源的runtime的面前,VB正逐步走向成熟,成為許多企業(yè)開發(fā)前端和中間層組件的得力工具。對于大多數(shù)其他應(yīng)用程序,開發(fā)者都可以使用Windows Forms來創(chuàng)建用戶界面。
讓我們先總體看一下Windows Forms,然后再看一下使用C#來寫基于Windows Forms的應(yīng)用程序需要些什么。在外殼下,所有的Windows應(yīng)用程序都是以同樣的方式運(yùn)行的。Windows會(huì)維護(hù)Windows類的集合(即windows的行為都是在WndProc()函數(shù)中定義的)。在Windows編程的早期,最大的任務(wù)就是寫大約80行的樣板代碼為了能讓它正確的運(yùn)行,后來逐漸通過添加事件句炳來開發(fā)應(yīng)用程序。MFC就使得開發(fā)者不用再去為Winmain()和WndProc()費(fèi)神了。Windows Forms繼承了這種忽略編程細(xì)節(jié)的趨勢,所以你不必花大量的時(shí)間在書寫那些枯燥的代碼上。
隨著基于SDK和MFC的發(fā)展,你可能仍然保留作為開發(fā)者對Windows API的鐘愛。如果你需要嚴(yán)格的控制你的應(yīng)用程序,使用C和SDK——或甚至是MFC來開發(fā)應(yīng)用程序是必要的。如果你希望有靈活性,基于SDK或MFC的開發(fā)仍然是需要的。但是如果你覺得一個(gè)更簡單,更直觀的開發(fā)環(huán)境比嚴(yán)格控制或靈活性更重要的話,使用Windows Forms來開發(fā)基于窗體的用程序可能更適合你。
編寫客戶代碼
>有了Windows Forms,你就可以編寫.NET平臺(tái)的客戶代碼。如果你曾使用過VB,你就可能對它基于窗體的應(yīng)用程序模型很熟悉。而Windows Forms與此很相似。SDK或MFC的編程風(fēng)格是直接與Windows API交互的,甚至當(dāng)MFC中出現(xiàn)了框架,你仍僅僅是從底層的Windows API邁出了一小步。與此相反,Windows Forms隱藏了舊式Windows編程風(fēng)格中的樣板代碼的細(xì)節(jié),以帶有菜單和標(biāo)題欄的正規(guī)窗體的形式顯示。Windows Forms能響應(yīng)標(biāo)準(zhǔn)的事件,如鼠標(biāo)的移動(dòng)和菜單的選擇,而且它們也可以控制在客戶區(qū)的行為。然而,管理這些特性的語法比你用SDK或MFC編寫程序的語法要抽象的多。
你可以以標(biāo)準(zhǔn)的窗口、多文檔界面(MDI)、對話框或繪圖程序表面的形式顯示W(wǎng)indows Forms。隨著VB的發(fā)展,它使用了用戶界面(UI)發(fā)展中的窗體模型,即給一個(gè)Windows 窗體定義用戶界面,通常就是意味著能在窗體的客戶區(qū)安放控件。但是Windows Forms還可以更好的渲染你所希望的繪圖表面。
除了能渲染繪圖表面和管理標(biāo)準(zhǔn)控件集,Windows Forms通過屬性來定義它們的外觀。例如,要想編程在屏幕上移動(dòng)一下Windows窗體,你只要設(shè)置Windows 窗體的X屬性。Windows Forms使用方法來管理它們的行為,而且它們也可以通過響應(yīng)事件來定義與用戶的交互。
Windows Forms是在.NET Framework或Common Language Runtime(CLR)中運(yùn)行的類的實(shí)例。編寫一個(gè)Windows Forms應(yīng)用程序通常就是實(shí)例化WinForm類的一個(gè)實(shí)例,配置它的屬性并建立事件句柄。因?yàn)橐粋€(gè)Windows 窗體就是一個(gè)標(biāo)準(zhǔn)的基于CLR的類,是完全支持繼承的,你可以以標(biāo)準(zhǔn)的,面向?qū)ο蟮姆绞絹斫⒒赪indows Forms的類之間的繼承關(guān)系。
現(xiàn)在,讓我們看一下開發(fā)環(huán)境。Microsoft的Visual Studio.NET不斷在進(jìn)步。不幸的是,IDE的PDC Tech Preview版本不是很穩(wěn)定。所以我用C#開發(fā)的開發(fā)環(huán)境中包含一個(gè)文本編輯器和一個(gè)命令行編譯器。Beta 1解決了很多方面,使VS.NET成為開發(fā)C#應(yīng)用程序的可行環(huán)境。
但是如果你不喜歡使用VS.NET的beta版,那你也可以回到命令行的方式。C#的命令行編譯器名為CSC.EXE。當(dāng)運(yùn)行它時(shí),它通過命令行決定輸出的位置、需要的資源文件、應(yīng)用程序要使用的系統(tǒng)文件和最后可執(zhí)行文件中要包含的C#文件。例如,以下命令行編譯了一個(gè)名為someApp.cs的文件,引入了System.DLL的多種系統(tǒng)功能,在資源中包含了一個(gè)JPG文件,并且把最終的可執(zhí)行文件放到名為in的目錄下:
csc /out:in /R:System.DLL /res:XYZ.JPG someApp.cs
我們再一次回到了1989年你用命令行編譯器通過一些批處理文件或makfiles來創(chuàng)建一個(gè)Windows應(yīng)用程序的時(shí)候。在開發(fā)tic-tac-toe例程時(shí),我也在一個(gè)簡單的批處理文件中使用了命令行編譯器。
現(xiàn)在,你可以通過C#和VB來使用Windows Forms。這兩種語言在建立基于Windows Forms的應(yīng)用程序方面是等同的。你可以使用它來創(chuàng)建大量當(dāng)今需要的編程構(gòu)架——特別是當(dāng)開發(fā)基于Windows Forms的應(yīng)用程序。
開始開發(fā) Tic-Tac-Toe
那么,基于Windows Forms的應(yīng)用程序是什么樣的呢?看一下tic-tac-toe例程吧。一個(gè)C#基于Windows Forms的應(yīng)用程序一開始通過一系列using聲明先引入必要的定義(程序需要的類型定義)。
namespace CSharpTicTacToe {
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.WinForms;
// Windows Form code goes here?
};
第一個(gè)namespace關(guān)鍵字是可選的。但是對于設(shè)定功能的作用范圍通常是很有用的——特別是在assembly過程中,一種編寫DLL的新方式。在關(guān)鍵字之后,每一個(gè)using聲明告訴C#編譯器,程序所要用到的系統(tǒng)功能。因?yàn)閠ic-tac-toe游戲是一個(gè)Windows 窗體,源文件使用了System的WinForms namespace。而且,因?yàn)橛螒蚴褂昧藞D形,源代碼就要引入U(xiǎn)RT的繪圖功能。
在你引用了namespace后,你就要通過從系統(tǒng)提供的Form類繼承一個(gè)類來表示一個(gè)Windows 窗體。
public class CSharpTicTacToe : Form {
// Windows Form code goes here, including
// data members, a constructor, and
// some event handlers?
}
C#提倡枚舉作為定義變量類型的一種方式,而不是指定一個(gè)整數(shù)范圍,這樣能維護(hù)類型的安全性并能提供盡可能多的信息。Tic-tac-toe游戲指定了三種枚舉類型:player類型、用于在板上做標(biāo)記的類型和對板上位置命名的類型。以下就是具體的描述。你可以在游戲的多個(gè)地方看到它們的用途。
public enum Player {
XPlayer,
OPlayer
}
public enum Mark
{
XMark,
OMark,
Blank
}
public enum Positions
TopLeft,
TopCenter,
TopRight,
MiddleLeft,
MiddleCenter,
MiddleRight,
BottomLeft,
BottomCenter,
BottomRight,
Unknown
}
一旦你定義了窗體,就需要一些數(shù)據(jù)成員,一個(gè)構(gòu)造函數(shù)和一些事件句柄。我會(huì)依次向你
闡釋。首先是基本的數(shù)據(jù)成員,一個(gè)tic-tac-toe板。Tic-tac-toe游戲的數(shù)據(jù)包含了一個(gè)表示游戲板的3*3的矩陣數(shù)組。這個(gè)游戲定義了一塊板的格子(見清單1)。每一個(gè)板的格子都表示在屏幕上的一個(gè)位置并確定玩家是否做了標(biāo)記。此外,格子還使用了一個(gè)X和一個(gè)O,來決定哪個(gè)玩家做了標(biāo)記。我還會(huì)細(xì)致的說明的。
Tic-tac-toe板管理了3*3的格子(見清單2)。它也管理著BoardSpace對象3*3的數(shù)組,并用線條來劃分tic-tac-toe的格子并讓每一個(gè)格子來繪制它們自己。大部分的游戲邏輯都是由板來負(fù)責(zé)的,所以制作這個(gè)游戲的最主要的部分就是建立一個(gè)窗體,把板作為數(shù)據(jù)成員,并且當(dāng)鼠標(biāo)按下時(shí)請求板的繪制(見清單 3)。清單3包含了Windows Forms應(yīng)用程序的初始化代碼。注意這個(gè)過程就是初始化游戲板,創(chuàng)建一個(gè)Reset按鈕和其事件句柄,然后截獲MouseDown和Paint事件。
大部分的時(shí)間,響應(yīng)事件就是重載(override)正確的函數(shù)。例如,游戲要響應(yīng)MouseDown事件(通過把鼠標(biāo)的位置交給板來處理)和Paint事件。當(dāng)它生成了事件,系統(tǒng)就會(huì)自動(dòng)的調(diào)用。你還可以為非系統(tǒng)的、用戶定義的事件如按鈕被按下而手工關(guān)聯(lián)事件句柄。該游戲也可以創(chuàng)建一個(gè)Reset按鈕來處理清除游戲板的事件。
Windows Forms編程最基本的就是基于用戶界面,請求你來繪制屏幕的過程。Windows Forms定義了一個(gè)捕獲WM_PAINT消息的良好方法。Form類包含了一個(gè)名為OnPaint()的函數(shù)來讓你重載。通過重載這一方法,你可以捕獲繪圖事件并在屏幕上做你想做的。看一下例程的源代碼,你會(huì)注意到Paint事件的參數(shù)包括一個(gè)Graphics對象,它類似于SDK編程時(shí)的一個(gè)設(shè)備上下文。Graphics對象包括了畫線和圖形、填充區(qū)域以及任何你想在屏幕上做的。
Tic-tac-toe游戲通過讓游戲板自繪來響應(yīng)Paint事件。如果你在例程中看一下TicTacToeBoard類和BoardSpace類,你就會(huì)發(fā)現(xiàn)每一個(gè)類都有一個(gè)Render()函數(shù)來使用Graphics對象的DrawLine()和DrawEllipse()方法在屏幕上繪圖。Windows Forms和C#的強(qiáng)大地方就在于你不必考慮管理GDI類型的資源,因?yàn)?NET Framework為你做了。
Windows Forms也提供給你很多的可行性,包括在Windows 窗體上添加菜單和圖標(biāo),顯示對話框和捕獲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());
}
}
}