摘要:本文討論了如何使用 Visual C++ .NET 的托管擴展針對 Windows 窗體編程,並提供了使用直接訪問 Windows 窗體類的手動編程技術的示例,以及使用 Windows 窗體設計器的示例。此外,本文還對 Windows 窗體和 Microsoft 基礎類 (MFC) 應用程序進行了比較。
簡介
長期以來,程序員們都使用 C 和 C++ 來開發 Windows GUI 應用程序。對於我們當中很多人來說,這一段歷史可以追溯到 Windows 2.0 時期,那時,我們使用基於 C 的 16 位 Windows API,即便只是顯示一個窗口,也需要編寫數十行代碼。幸運的是,隨著時間的推移,抽象的級別越來越高,越來越好。在 1992 年,Microsoft 發行了 Programmer's Workbench,其中包括 Microsoft 基礎類庫 1.0 版。Microsoft 基礎類庫 1.0 版包含大約 60 個類,用於包裝窗口化編程和繪制部分的 16 位 Windows API。到 2002 年為止,Microsoft 基礎類庫 7.0 版已經發展為超過 200 個類,並且,其用途已經擴展為能夠提供 Microsoft Win32 API 的完整 C++ 對象模型替代物。
雖然 MFC 非常強大,但它也有很多缺點,比如它只是 Win32 API 外的一層薄薄的面板,並且對於很多程序員來說,它太過復雜,很難有效地使用。通常,要開發 Windows 應用程序,仍需要直接訪問 Win32 API,特別是在對 Windows 應用程序所需的基本功能的要求不斷提升的情況下。因此,要開發任何功能真正強大的 Windows GUI 應用程序,需要耗費大量時間精力。為了應對應用程序開發難度不斷提高的狀況,Microsoft 於 2002 年初發行了一個針對 Windows 平台的新編程環境。該環境稱為 .NET 框架,它為開發人員提供了一個托管 應用程序運行庫,以及稱為 .NET 框架類庫的大量庫。.NET 框架可以管理內存和安全性,從而能夠產生更為可靠的應用程序。.NET 框架類庫提供了一個大型、資源豐富和統一的類庫,任何 .NET 語言(包括 Micrisoft 為 .NET 程序員提供的 C++ 的托管擴展和 C++ 的托管版),都可以以相同的方式同等地訪問該類庫。作為 .NET 框架的一部分,Windows 窗體是一組用於構建客戶端 Windows GUI 應用程序的類。
本文中,我們將深入了解如何使用 C++ 的托管擴展編寫 Windows 窗體代碼,先介紹如何從頭開始編寫,然後講解如何使用 Microsoft Visual Studio .NET 2003 來完成這一任務。與此同時,我們將著重介紹 Windows 窗體的一些常用功能,如自動布局和數據綁定。最後,我們將把注意力集中到 Windows 窗體與 MFC 的比較以及在進一步使用托管擴展時,如何混合使用這兩套工具。
什麼是 Windows 窗體?
Windows 窗體是一個窗口化工具包,而不像 MFC 一樣是完整的應用程序框架。事實上,相對於 Windows 窗體所提供的用於構建基於文檔的獨立應用程序的功能來說,MFC 提供的功能更多。例如,如果要生成一個文本編輯器,在 MFC 中,只需運行一個向導,選擇適當的選項並編寫若干行代碼即可完成。僅僅是通過運行該向導得到的應用程序就包含了一個狀態欄、一個工具欄(浮動),並實現了所有的 File、Edit 和 Help 菜單項,其中包括最近使用的文件列表和打印以及上下文相關的幫助,所有這些內容都包含在一個完全徽標兼容的單文檔界面 (SDI)、多 SDI 或多文檔界面 (MDI) 應用程序中。作為基於文檔的應用程序框架,沒有能夠與 MFC 相匹敵的競爭者。
但是,程序員們現在傾向於構建更多基於 HTML 的應用程序,或者能夠與業務對象或數據庫服務器通信的應用程序,而不是基於文檔的應用程序。.NET 框架和 Windows 窗體正是為這一目的而量身定做的。
這並不是說 Windows 窗體不能用來構建優秀的基於文檔的應用程序。實際上,由於 Windows 窗體只是 .NET 框架所提供的超過 2000 個類中的一小部分,您所需要的內容很有可能 Windows 窗體並沒有提供,而是位於該框架中的其他部分中。例如,Windows 窗體本身並不提供任何對象序列化支持,但是 .NET 框架類庫的其余部分提供了多種序列化對象圖的方法。
這是 MFC 和 Windows 窗體之間的主要區別。MFC 旨在替代基礎 Win32 API,但這並不能阻止 Win32 API 增長。事實上,就像 MFC 隨時間的不斷增長一樣,基礎 OS 的功能最少增加了十倍。但是,Windows 窗體只是 Win32 的窗口化部分的替代物。.NET 框架類的其余部分負責替代其余的 Win32。當然,框架永遠不可能替代整個 Win32 API,不過,由於在可預期的未來,大多數要添加到 Windows 中的新功能都要被添加到框架中,替代整個 Win32 API 將是一個未來的目標。
因此,雖然 Windows 窗體不能具有 MFC 的全部功能,但它的確提供了一組很強的功能,可以極大地便利客戶端應用程序開發人員,這其中包括一些 MFC 完全不具備的功能。下面,我們將介紹如何從頭開始構建應用程序,然後講解 Visual Studio .NET 2003 為 Windows 窗體 C++ 程序員提供的工作效率改進功能。
從頭開始創建 Windows 窗體
典型的 Windows 窗體應用程序有至少一個窗體。窗體就是一個窗口,也就是從 Windows 1.0 開始我們所看到的 Microsoft 用戶界面單元。通常,Windows 窗體應用程序中的一個窗體是主窗體,意思是它是應用程序的生存周期內可能顯示的所有其他窗體的父級或所有者。該窗體是顯示主菜單以及工具欄、任務欄等的位置。主窗體結束時,應用程序也退出。
應用程序的主窗體可以是一個簡單的消息框、一個對話框、一個 SDI 窗口、一個 MDI 窗口,或者更為復雜的控件,如 Visual Studio .NET 這樣的應用程序中具有多個子窗口、工具窗口和浮動工具欄的窗體。
如果您的應用程序極為簡單,可以使用充斥在任何窗口化系統中的簡樸的消息框來實現它:
#using <mscorlib.dll>
#using <System.dll>
#using <System.Windows.Forms.dll>
void __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
long lpCmdLine,
int nCmdShow)
{
System::Windows::Forms::MessageBox::Show("Hello, Windows Forms");
}
作為 C++ 程序員,您必定非常熟悉 WinMain 入口點,在托管應用程序中,仍需要該入口點。在我們的第一個 Windows 窗體應用程序中,唯一一行實際的代碼調用了 Windows::System::Forms::MessageBox 類的靜態 Show 方法,這其實只是調用包含在 Window::Systems::Forms 命名空間 內的 MessageBox 類的靜態方法的長寫形式。.NET 框架類庫中廣泛使用了命名空間來將類、結構、枚舉等類型分隔為不同的邏輯組。由於有數以千計的 Microsoft 雇員要從事添加 .NET 框架類庫的工作,而且有數以百計的第三方組織要擴展該類庫,同時有數以百萬計的程序員正在嘗試學習它,因此這種分隔是非常必要的。沒有命名空間,就需要采用各種復雜的約定來唯一地命名各種對象(像現有的 Win32 API 一樣)。
命名空間中的類型的定義位於 .NET 程序集 中。程序集是打包為 DLL 或 EXE 的托管類型的容器。#using 指令用於將程序集中的類型應用到您的應用程序中。例如,mscorlib 和 System 程序集提供了基本的 .NET 框架類型,如 int 和 string。System.Windows.Forms 程序集包含了 Windows 窗體類型。
使用 #using 將程序集應用到應用程序中後,就可以以前面講到的長寫形式引用類型,或者,可以使用標准的 C++ using namespace 語句來省略鍵入代碼的工作:
#using <mscorlib.dll>
#using <System.Windows.Forms.dll>
using namespace System::Windows::Forms;
void __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
long lpCmdLine,
int nCmdShow)
{
MessageBox::Show("Hello, Windows Forms");
}
因此,雖然這個簡單的示例很有效地演示了最基本的 .NET 框架和 C++ 的托管擴展的概念,但它並不能很好地演示典型的 Windows 窗體程序。對於真實的應用程序,將需要一個 Form 類(或從 Form 派生的類)的實例,如下所示:
void __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
long lpCmdLine,
int nCmdShow)
{
Form* form = new Form();
...
}
form 變量引用了托管類型的一個實例。托管對象是由 .NET 框架的公共語言運行庫 (CLR) 處理的,它們的生存周期由垃圾回收器控制的,該回收器在一定的時間取消分配內存。因此,C++ 程序員無需顯式地刪除托管類型,但是同樣不能指望在任何特定時刻(如關閉范圍時)破壞對象。
創建窗體後,就需要顯示它。如果您曾經看過 Windows 窗體的文檔,可能已經注意到了 Form 方法 Show,這意味著:
void __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
long lpCmdLine,
int nCmdShow)
{
Form* form = new Form();
form->Show(); // This is not what you want to do.
}
雖然上面的代碼可以顯示窗體,但是必須眼疾手快才可以看到該窗體,這是因為 Show 以無模式的方式顯示窗體。這意味著在 Show 將新的窗體顯示到屏幕上之後,會立即將控制權返回給 Main 函數,該函數在返回時將退出進程,同時關閉剛剛顯示不久的窗體。要以有模式的方式顯示窗體,文檔建議使用 ShowDialog 函數:
void __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
long lpCmdLine,
int nCmdShow)
{
Form* form = new Form();
form->ShowDialog(); // This is not quite what you want to do.
}
雖然這些代碼事實上可以顯示一個空窗體,並且在用戶關閉它之後才會將控制權返回給 Main 函數,但通常您並不會編寫這樣的代碼。相反,您將把一個窗體指定為主窗體,使得應用程序的其他部分可以把它當作主窗體來進行訪問。為此,可以將主窗體作為一個參數傳遞給 Windows 窗體的 Application 對象的 Run 方法:
#using <mscorlib.dll>
#using <System.dll>
#using <System.Windows.Forms.dll>
using namespace System::Windows::Forms;
void __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
long lpCmdLine,
int nCmdShow)
{
Application::Run(new Form);
}
Application 類的靜態 Run 方法將顯示主窗體並開始發送 Windows 消息,直到主窗體關閉為止。主窗體關閉後,Run 將返回,讓我們的 Main 函數退出,以便結束進程。要實際查看這一過程,可以使用下面的命令行編譯這個小小的 Windows 窗體應用程序:
C:MSDNMYFIRS~1>cl /clr MyFirstApp.cpp
現在,編譯器已經生成了 MyFirstApp.exe,可以執行它了。關閉窗體時,MyFirstApp.exe 將退出,結束您的第一個 Windows 窗體應用程序。
要想增添一些趣味,可以在新窗體上設置一個屬性。像 .NET 框架類庫中的大多數對象一樣,Form 對象有一些可以訪問的屬性、可以調用的方法和可以處理的事件。可以直接在 Form 類的實例上設置屬性,但通常我們不會這麼做。相反,每個自定義窗體都是一個派生自 Form 的類,並且會初始化自己的屬性,如下所示:
__gc class MyForm : public Form
{
public:
MyForm()
{
Text = "Hello, Windows Forms!";
}
};
void __stdcall WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
long lpCmdLine,
int nCmdShow)
{
Application::Run(new MyForm);
}
在 C++ 的托管擴展中,自定義托管類型是使用 __gc 修飾符聲明的。請注意,MyForm 類派生自 Form,然後在構造函數中初始化了它自己的屬性。這提供了一個很好的用法模型,如新的 Main 函數中所示,該函數創建了 MyForm 類的一個實例。
但這個窗體仍舊很乏味。除系統提供的特點外,它沒有任何交互功能。我們可以通過添加一個按鈕來增加一些交互功能,如下所示:
MyForm()
{
Text = "Hello, Windows Forms!";
Button* button = new Button();
button->Text = "Click Me!";
this->Controls->Add(button);
}
向窗體添加按鈕就是創建一個新的 Button 對象,設置我們需要的屬性,並將其添加到該窗體所管理的控件列表中。雖然上面的代碼會在窗體上產生一個按鈕,在單擊它後看上去像是被單擊了,但並不會發生別的事情,這是因為您還沒有處理該按鈕的 click 事件。您可以這樣來處理該事件:
__gc class MyForm : public Form
{
public:
MyForm()
{
Text = "Hello, Windows Forms!";
Button* button = new Button();
button->Text = "Click Me!";
button->Click += new EventHandler(this, button_click);
this->Controls->Add(button);
}
void button_click(Object* sender, EventArgs* e)
{
MessageBox::Show("Ouch!");
}
};
處理按鈕的 Click 事件涉及兩項工作。第一項是創建具有適當簽名的處理程序方法,我們稱其為 button_Click。絕大多數 .NET 框架事件的簽名都是一個函數,該函數什麼都不返回,但是獲取兩個參數:一個表示事件的發送方(本例中為按鈕)的對象,和一個 System::EventArgs 對象(或者一個從 EventArgs 類派生的對象)的實例。
預訂事件所需的第二項內容是在 MyForm 構造函數中使用 += 運算符的行。這種表示法意味著您要向與特定對象上的特定事件有關的所有其他方法的列表中添加一個方法。這要求 EventHandler 委托對象的一個實例。委托是一個類,該類將對事件的調用轉換為預訂該事件的方法的調用。
請注意,Button 類上的 Click 事件是對 EventHandler 委托的引用,因此,要將自己的方法添加到訂戶列表中,您還需要創建一個該種類的委托的實例。當然,搞清楚您所感興趣的所有事件的委托簽名,或者通過手動編寫代碼來將控件添加到窗體上,很快會成為很乏味的事情。幸運的是,這同樣可以避免,因為 Visual Studio .NET 2003 中集成了 Windows Forms for C++。
使用 Visual Studio .NET 設計器創建 Windows 窗體
在 Visual Studio .NET 2003 之前,只有 C# 和 Visual Basic .NET 具有可視窗體設計器支持。C++ 的托管擴展沒有。很幸運,現在 Visual Studio .NET 附帶了 C++ 的托管擴展的 Windows 窗體設計器。
大多數 Windows 窗體項目都在 New Project 對話框中開始,您可以通過單擊 File,指向 New,然後單擊 Project,或者通過按 CTRL+SHIFT+N 來訪問該對話框。
運行 Windows 應用程序向導時,可以隨意選擇項目名稱和位置,然後就可以在設計器中得到一個空白窗體。
在 MFC 中,僅對對話框支持拖放設計和用戶界面布局。普通視圖必須在代碼中進行布局。但是,Windows 窗體則以一種統一的方式對待所有的窗體,因此相同的拖放設計器適用於各種窗體。窗體的類型(有模式或無模式,對話框或視圖)取決於它的使用方式,而不是設計方式。
設計器中的 Windows 窗體和 MFC 的下一個重大區別是設計器在代碼中而不是單獨的資源文件中保存控件和布局信息。這與 MFC 很不相同,MFC 將 Win32 對話框資源中的對話框布局信息保存在 .rc 文件中。這兩種方案都各有優缺點,但是 MFC 程序員很快會注意到其中的不同之處。適應起來需要一個過程。
您應當注意的另一件事情是 C++ 的托管擴展項目向導將實現代碼生成到了 .h 文件而不是 .cpp 文件中。這可能是由於 C++ 設計器必須適應現有的設計器體系結構而不是 Visual Studio .NET 2002,而後者僅支持 C# 和 Visual Basic .NET 這樣的語言,這些語言不分割聲明和定義代碼。生成的頭文件(可以通過右鍵單擊設計圖面然後選擇 View Code 或通過按 F7 來訪問)如下所示:
namespace MySecondApp
{
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
/// <summary>
/// Summary for Form1
public __gc class Form1 : public System::Windows::Forms::Form
{
public:
Form1(void)
{
InitializeComponent();
}
private:
/// <summary>
/// Required designer variable.
/// </summary>
System::ComponentModel::Container * components;
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
void InitializeComponent(void)
{
this->components = new System::ComponentModel::Container();
this->Size = System::Drawing::Size(300,300);
this->Text = "Form1";
}
};
}
上面的大多數代碼應當都是為大家所熟知的,包括頂部的 using 語句和從 Form 基類派生的自定義窗體。唯一一處與您的實現不同的地方是窗體的構造函數中調用 InitializeComponent 以設置窗體的屬性,而不是在構造函數本身中進行設置。之所以這麼做,是為了使 Windows 窗體設計器有地方放置初始化窗體上的控件和窗體本身的代碼。
Windows 窗體應用程序項目模板產生了一個 .cpp 文件:
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
System::Threading::Thread::CurrentThread->ApartmentState =
System::Threading::ApartmentState::STA;
Application::Run(new Form1());
return 0;
}
以上代碼看上去同樣很熟悉。Main 函數提供了應用程序的入口點和對 Application::Run 的調用,傳入主窗體類的一個實例。將 ApartmentState 變量設置為單線程單元 (STA),是為了 COM 的適當惰性初始化能夠在拖放操作中以用戶友好的方式使用,以及宿主 COM 控件。
返回到 InitializeComponent 實現,可以看到窗體設計器在那裡放置了代碼。例如,將按鈕從工具箱拖動到窗體的設計圖面將把 InitializeComponent 實現更改為如下所示的代碼:
void InitializeComponent(void)
{
this->button1 = new System::Windows::Forms::Button();
this->SuspendLayout();
//
// button1
//
this->button1->Location = System::Drawing::Point(0, 0);
this->button1->Name = "button1";
this->button1->TabIndex = 0;
this->button1->Text = "button1";
//
// Form1
//
this->AutoScaleBaseSize = System::Drawing::Size(5, 13);
this->ClientSize = System::Drawing::Size(288, 253);
this->Controls->Add(this->button1);
this->Name = "Form1";
this->Text = "Form1";
this->ResumeLayout(false);
}
請注意,上面的代碼同樣與您以前生成的代碼很類似,但它們是由設計器創建的。不幸的是,要使這一進程能夠可靠地運行,設計器需要能夠完全控制 InitializeComponent 方法。事實上,您可以看到向導生成的 InitializeComponent 代碼包裝在一個區域中(默認情況下將隱藏代碼),並標記了說明性的注釋:
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
這可能看上去像是您首選的編程語言,但 InitializeComponent 實際上是設計器用於管理設計圖面的對象模型的序列化形式。雖然您可以對這些代碼進行某些細微的更改,如更改新按鈕的 Text 屬性,但是重大的更改可能會被忽略,甚至被刪除。我們建議您將自定義窗體初始化放入窗體的構造函數中對 InitializeComponent 的調用之後,以確保設計器不會破壞您的代碼。
當然,設計器所帶來的好處抵消了它的這種對代碼的侵犯。例如,您無須通過編寫多行代碼來設置窗體或它所包含的控件的屬性,只需要右鍵單擊所需的對象並選擇 Properties(或按下 F4)來調出選定對象的屬性浏覽器。
所有非默認的屬性(表示為以粗體顯示的值)都會為您寫入 InitializeComponent 方法,您無須再編寫這些代碼。與此類似,要選擇窗體或窗體的控件的事件以進行處理,可以單擊屬性浏覽器窗口頂部的 Events 閃電形圖標。
在屬性浏覽器窗口中,可以以多種方式實現事件的處理。一種方法是通過單擊和鍵入在激發事件時要調用的函數的名稱來找出要處理的選定對象的事件,如 button_Click。在輸入事件處理程序的名稱後按 Enter,將把您帶到具有該名稱和正確的簽名的事件處理程序的正文,您可以立即著手實現:
private: System::Void button_Click(Object* sender, EventArgs* e)
{
}
如果您像一般情況下一樣希望為各個對象處理的各個事件是唯一的,或者您不關心處理程序的名稱,只需在屬性浏覽器中雙擊該事件的名稱,就會根據控件和事件的名稱為您生成一個事件處理程序名稱。例如,如果在 Form 1 窗體的 Load 事件上雙擊,事件處理程序名稱將為 Form1_Load。
此外,如果您要處理對象的默認事件,只需雙擊對象本身就可以進行處理 - 這樣會像您雙擊屬性浏覽器事件列表中的事件名稱一樣生成一個事件處理程序名稱。對象的默認事件指的是直觀上特定類型最常處理的事件。例如,按鈕的默認事件為 Click,窗體的默認事件為 Load。不幸的是,設計器和屬性浏覽器都沒有提示特定類型的默認事件是什麼,但是實踐中通常不會由多少例外情形。
安排控件
設計器的好處在於您可以在窗體內安排控件的布局,確保整齊地排列所有的元素(如圖 1 所示),而不像大小被調整時的狀態(如圖 2 所示)。
圖 1 理想大小且整齊布局的窗體
圖 2 整齊布局的窗體大小被調整
用戶調整窗體大小不是為了能有更多灰色的空間,他們是為了能夠有更多空間用於將數據輸入控件中。要達到這一目的,需要重新調整控件的大小,以占據新的可用空間。這可以通過處理窗體的 Resize 事件並編寫代碼來手動完成。或者,也可以通過定位 來實現。
定位是 Windows 窗體為了對窗體和窗體所包含的控件進行自動布局控制而提供的一種方法。默認情況下,所有的控件都定位到左上方,這樣,在調整窗體的大小並移動時,所有的控件都將相對於窗體左上角保持它們的位置。但是,在本例中,最好在調整窗體大小時加寬或縮窄文本框控件。這可以通過設置各個文本框的 Anchor 屬性來實現。顯示控件的屬性浏覽器並選擇 Anchor 屬性,將顯示如圖 3 所示的編輯器。
圖 3 設置 Anchor 屬性
要將文本框更改為定位到右邊緣以及上邊緣和左邊緣,只需要單擊右側的定位框,然後將 Anchor 屬性更改為 Top、Left 和 Right。這將導致文本框的大小隨窗體的大小一起變化,如圖 4 所示。
圖 4 將文本框定位到頂部、左側和右側以及將按鈕定位到底部和右側
雖然默認的定位為左上方,但根本不需要將這些邊緣作為定位設置的一部分。例如,您可以看到在圖 4 中,OK 和 Cancel 按鈕被定位到了右下角,這與 Windows 對話框的習慣相同。
如果您不想生成對話框樣式的窗體,而是要生成窗口樣式的窗體,定位將不是最好的選擇。例如,如果您要構建資源管理器樣式的應用程序,該應用程序在頂部有一個菜單欄和一個工具欄,底部有一個狀態欄,一個樹視圖和一個列表視圖占據其余空間,並用控件之間的拆分器來確定控件所占據的空間,將不能使用定位。此時,您需要使用停靠。
默認情況下,控件的 Dock 屬性設置為 None。您可以在屬性浏覽器中更改 Dock 屬性,方法是選擇一個要停靠的邊緣,或者選擇占據剩余的空間。
例如,狀態欄、樹視圖和列表視圖的 Dock 屬性可能會顯示後兩者被一個拆分器控件拆分開來,所有這些都已經安排完畢,您無須編寫任何代碼。
定位、停靠和拆分並不是排列窗體上的控件的僅有的方法。Windows 窗體還支持分組控件和處理特殊情況下的自定義布局。此外,Windows 窗體支持在父級內排列窗口、這通常稱為多文檔界面 (MDI)。
數據綁定
數據綁定 是指這樣一種能力:將一個或多個控件的內容綁定到一個數據源,使得當其中一方被更新時,另一方也得到更新。數據綁定不光在 Windows 窗體中受到良好支持,它還完全集成到了 Visual Studio .NET 本身當中。
從服務器資源管理器將一個表拖放到設計圖面上將創建兩個組件,一個用於連接到數據庫的連接 和一個通過連接在雙方之間傳送數據的適配器。在設計器中右鍵單擊適配器並選擇 Generate Dataset,將創建一個新的數據集,它是一個從 DataSet 派生的類,生成該類專用於保存您從服務器資源管理器拖出來的表的數據。默認的 General Dataset 選項還會創建新數據集的一個實例,用於與控件相關聯。
得到數據的源後,就可以將數據綁定到一個或多個控件。Windows 窗體提供了多個數據庫綁定控件,包括 ListBox 和 ComboBox 等,其中 DataGrid 靈活性最高。
窗體上有了數據集後,要將數據網格綁定到它並將其作為數據源,只需在屬性浏覽器中設置數據網格的 DataSource 和 DataMember 屬性,並在加載窗體時填充該數據集:
void InitializeComponent(void)
{
...
this->dataGrid1->DataMember = "Customers";
this->dataGrid1->DataSource = this->dataSet11;
...
}
private: System::Void Form1_Load(System::Object* sender, System::EventArgs* e)
{
sqlDataAdapter1->Fill(dataSet11);
}
以上只是數據綁定的一般用途以及數據網格的特定用途的冰山一角。有關指向更多數據綁定資源的鏈接,請參閱後面的“參考”部分。
從 MFC 遷移
作為 MFC 程序員,您花在現有代碼基上的時間與精力一定是非常多的。將 MFC 代碼遷移到 .NET 框架需要進行仔細的規劃。以下是一些注意事項:
•如果可以承受得了從頭開始,將能夠得到可維護性最好的代碼基,但是耗時最長。
•如果您的大多數 MFC 代碼在 COM 控件中(或者可以遷移到 COM 控件),您就可以使用 Windows 窗體作為這些控件的宿主,並為框架編寫新的托管代碼。
•如果您需要升級 MFC 應用程序本身,可以使用 MFC 7.1 提供的功能,將 Windows 窗體控件宿主為 COM 控件,仍舊保留 MFC 代碼為非托管。有關指向以上方案的詳細信息的鏈接,請參閱“參考”部分。
•如果您想要在 Windows 窗體應用程序中使用托管代碼,但是要避免使用 COM Interop 的開銷,可以取消 MFC 項目的“Use Managed Extensions”的選項,以便能夠在相同的代碼中混合使用托管和非托管類型。有關指向以上方案的詳細信息的鏈接,也請參閱“參考”部分。
適用於您的選項要視具體情況而定,但是一般說來,我們建議您采用您自己可以編寫 .NET 框架的大部分新代碼的策略,雖然這意味著您所生成的一些 Windows 窗體功能原本是在 MFC 中開始了解並喜歡上的。
小結
本文介紹了一些最引人注目的新 Windows 窗體功能,但是 Windows 窗體具有更多功能,如全自動部署、對話框數據交換和驗證、MDI 應用程序、繪制和打印(包括打印預覽)、用戶控件、自定義組件和設計時支持、清單和類型化資源、非編譯資源本地化和應用程序設置等等。
對於 MFC 和 Windows 窗體的比較,您需要牢記的一點是它們是在非常不同的時代為了解決大不相同的問題而分別構建的。MFC 是一個基於文檔的應用程序框架。Windows 窗體是一個 n 層應用程序的窗口化庫。兩者之間存在區別很正常。其中一些區別,如定位和停靠,使得 Windows 窗體更具優勢。其他的區別,如對象序列化,存在於 .NET 框架類庫的其他部分中。另有一些功能根本不存在,這正是進行基於文檔的應用程序開發的有趣之處。
不過,有兩件事非常清楚:Microsoft 正在大踏步靠向托管代碼,而托管代碼的優勢是非常引人注目的。.NET 框架提供了內存和安全性兩者的自動處理,從而能夠得到更為可靠的應用程序,並可以提高開發人員的工作效率。.NET 框架類庫提供了一個大型、資源豐富和統一的類庫。有了這樣的組合,我們將能夠多快好省地構建 Windows 應用程序。