適用於:Windows 操作系統
.NET Framework 1.x,2.0運行時環境
.NET Windows開發Visual Studio 2005
概述
何謂模式窗體?簡單的可以理解為窗體對話框,用戶必須在完成該窗體上的操作或關閉窗體後才能返回打開此窗體的窗體。本文不對模式窗體的定義、特征、功能做具體討論,主要把重點放在如何在.net窗體應用程序中有效的使用模式窗體,解決使用模式窗體中碰到的常見問題。
模式窗體的屬性設置
在.net中一個System.Windows.Forms.Form類就表示一個窗體,通過visual studio 2005設計器能夠直接添加窗體,切換到設計模式,在屬性窗口中會顯示屬於該窗體的屬性和事件。參照標准的模式窗體,以visual studio 2005程序的菜單工具->選項打開的那個選項對話框為例,對於設計器初始化的窗體還是需要進行一番設置才能達到專業化。令人高興的是這些設置都可以在設計器模式中通過屬性設置實現,筆者將通過代碼來實現相應功能,下面對其進行詳細描述。
Form.StartPosition屬性,確定窗體第一次出現時的位置。這裡設置為在父窗體的中間顯示。
this.StartPosition = FormStartPosition.CenterParent;
Form.HelpButton屬性,確定窗體的標題欄上是否有“幫助”按鈕。設置顯示,看上去更人性化,但實際不一定會對幫助功能進行實現。
this.HelpButton = true;
Form.MaximizeBox屬性,確定窗體標題欄的右上角是否有最大化框。設置不讓她顯示。
this.MaximizeBox = false;
Form.MinimizeBox屬性,確定窗體標題欄的右上角是否有最小化框。設置不讓他顯示。
this.MinimizeBox = false;
Form.ShowIcon屬性,指示是否在窗體的標題欄中顯示圖標。設置不顯示。
this.ShowIcon = false;
Form.ShowInTaskbar屬性,確定窗體是否出現在Windows任務欄中。這個當然要節省任務欄的寶貴空間。
this.ShowInTaskbar = false;
Form.FormBorderStyle屬性,指示窗體的邊框和標題欄的外觀和行為。設置這個屬性將不允許拖動調整窗體的大小,同時Icon屬性將失效,並不顯示窗體標題欄圖片。this.FormBorderStyle = FormBorderStyle.FixedDialog;
Form.ControlBox屬性,確定窗體是否有“控件/系統”菜單框。通過該設置可以隱藏標題欄的控制按鈕。在有些時候還是有必要設置為False,標題欄就不會再有控制按鈕。this.ControlBox = false;
通過對以上屬性的設置,基本實現模式窗體的靜態功能。對於是否允許調整窗體的大小可根據實際情況而定。
模式窗體中的按鈕
模式窗體中(比如visual studio 2005中的“選項”對話框)一般會有兩個基本按鈕,一個[確定]按鈕用來提交,另一個[取消]按鈕用來撤銷提交,有時候會增加一個[應用]按鈕,不過像“幫助”菜單中的“關於”模式窗體可能就只有一個[確定]按鈕。Windows窗體為用戶操作友好性提供了比較好的支持。我們可以在Form設計界面的屬性設置中找到AcceptButton和CancelButton兩個屬性,默認值為空即顯示(無)。在屬性中可以通過選擇窗體上的按鈕來設置值。屬性修改生成的代碼如下。
先定義兩個Button,
private System.Windows.Forms.Button buttonOK;
private System.Windows.Forms.Button buttonCancel;
窗體的“接受”按鈕。如果設置了此按鈕,則用戶每次按“Enter”鍵都相當於“單擊”了該按鈕。
this.AcceptButton = this.buttonOK;
窗體的“取消”按鈕。如果設置了此按鈕,則用戶每次按“Esc”鍵都相當於“單擊”了該按鈕。
this.CancelButton = this.buttonCancel;
可見可以通過快捷鍵來方便的訪問特定按鈕,但這個有一些例外,比如窗體焦點剛好在buttonCancel上,當按{Enter}時實際按下的鍵會是buttonCancel而不是buttonOK,如果焦點停在第三個按鈕上,那{Enter}按下相當於點擊了該按鈕。另一個細節是通過鼠標點擊按鈕和快捷鍵操作按鈕的表現行為不一樣,快捷鍵操作Button不會顯示按鈕被按下的顯示效果,看上去什麼都沒有發生。
模式窗體的打開與關閉
談到模式窗體的打開,一般通過Form.ShowDialog ()方法或她的一個重載Form.ShowDialog (IWin32Window)來實現,其中後一個方法將窗體顯示為具有指定所有者的模式對話框。如下代碼所示,
OptionForm form = new OptionForm();
//form.ShowDialog();
form.ShowDialog(this);
對於指定所有者方式打開的模式窗體可以在模式窗體內部獲取主窗體的引用,
//在模式窗體內部訪問所屬窗體
MainForm form = this.Owner as MainForm;
注意,如果以Form.ShowDialog ()方式打開,那Form.Owner屬性會是空引用。
談到模式窗體的關閉,先來看一下模式窗體關閉後的返回值。無論是調用Form.ShowDialog ()方法還是Form.ShowDialog (IWin32Window)方法,都會在模式窗體關閉時返回System.Windows.Forms.DialogResult枚舉值。參考MSDN,該枚舉包含的值如下,
◆DialogResult.Abort,對話框的返回值是 Abort(通常從標簽為“中止”的按鈕發送)。
◆DialogResult.Cancel,對話框的返回值是 Cancel(通常從標簽為“取消”的按鈕發送)。
◆DialogResult.Ignore,對話框的返回值是 Ignore(通常從標簽為“忽略”的按鈕發送)。
◆DialogResult.No,對話框的返回值是 No(通常從標簽為“否”的按鈕發送)。
◆DialogResult.None,從對話框返回了 Nothing。這表明有模式對話框繼續運行。
◆DialogResult.OK,對話框的返回值是 OK(通常從標簽為“確定”的按鈕發送)。
◆DialogResult.Retry,對話框的返回值是 Retry(通常從標簽為“重試”的按鈕發送)。
◆DialogResult.Yes,對話框的返回值是 Yes(通常從標簽為“是”的按鈕發送)。
由於某些原因在實際用戶操作中比如選項數據無法保存,輸入的設置數據有問題,點擊[確定]按鈕需要阻止窗體的關閉以對輸入的設置進行調整。對於一些開發者在技術社區貼的阻止模式窗體關閉的代碼,我認為不是很好的實現。以下用代碼來描述該實現,注意其中用到了三個事件。
//注冊窗體關閉事件
this.FormClosing += new
System.Windows.Forms.FormClosingEventHandler(this.OptionForm_FormClosing);
//注冊確定按鈕事件
this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click);
//注冊取消按鈕事件
this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click);
三個事件對應的事件處理程序如下,
//確定按鈕處理程序
private void buttonOK_Click(object sender, EventArgs e)
{
//假設textBoxPath用來記錄目錄路徑,如果不存在要求用戶重新設置。
if (this.textBoxPath.Text.Trim().Length == 0)
{
MessageBox.Show("輸入路徑信息不對!");
this.textBoxPath.Focus();
}
else
{
this.DialogResult = DialogResult.OK;
}
}
//取消按鈕處理程序
private void buttonCancel_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
}
//窗體關閉處理程序,在關閉窗體時發生。
private void OptionForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (this.DialogResult != DialogResult.Cancel && this.DialogResult !=
DialogResult.OK)
e.Cancel = true;
}
上面的代碼都正常,就是事件寫多了,對上面代碼進行修改,去掉[取消]按鈕事件和窗體關閉事件以及相關的事件處理程序。首先需要在窗體構造函數中通過設置按鈕的DialogResult屬性來實現返回特定的DialogResult。
this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.OK;
this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
注冊確定按鈕事件,
//注冊確定按鈕事件
this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click);
//確定按鈕處理程序
private void buttonOK_Click(object sender, EventArgs e)
{
if (this.textBoxPath.Text.Trim().Length == 0)
{
MessageBox.Show("輸入的路徑信息不對!");
this.textBoxPath.Focus();
//設置文本框焦點
this.DialogResult = DialogResult.None;
}
}
可見,新的實現方式代碼減少了一半。
窗體的參數傳遞
對於窗體間的數據傳遞,是剛開始從事.Net窗體應用程序開發人員碰到的一個常見問題,在此講幾個常見的實現方式。此節內容適用於模式窗體或非模式窗體,部分方式延伸到一般類的操作。
(1)構造函數參數傳遞
通過構造函數傳遞參數應該是比較基本的參數傳遞方式,重載構造函數,通過帶參數的構造函數來實例化窗體。
在窗體類內部定義參數變量,
private object myParams;
實現構造函數,
public OptionForm(object parameters)
{
InitializeComponent();
this.myParams = parameters;//設置參數引用
}
實例化窗體,
OptionForm form = new OptionForm( myParams );
在實際使用過程中,需要注意傳入的是引用類型還是值類型,處理方式會有所不同。
(2)使用窗體的屬性
說起屬性關聯,上面已經提到過Form.Owner屬性,下面筆者根據MSDN文檔來比較完整的講一下,大部分的文字來自MSDN文檔,為保證其完整性,對其中一些屬性描述進行了擴展。
Form.Owner 屬性。獲取或設置擁有此窗體的窗體。
語法,public Form Owner { get; set; }
若要使某窗體歸另一個窗體所有,可為其 Owner 屬性分配一個對將成為所有者的窗體的引用。當一個窗體歸另一窗體所有時,它便隨著所有者窗體最小化和關閉。例如,如果 Form2 歸窗體 Form1 所有,則關閉或最小化 Form1 時,Form2 也會關閉或最小化。並且附屬窗體從不顯示在其所有者窗體後面。可以將附屬窗體用於查找和替換窗口之類的窗口,當選定所有者窗體時,這些窗口不應消失。
Form.OwnedForms 屬性。獲取 Form 對象的數組,這些對象表示此窗體擁有的所有窗體。
語法,public Form[] OwnedForms { get; }
此屬性返回包含此窗體擁有的所有窗體的數組。要使某窗體歸另一個窗體所有,可調用 AddOwnedForm 方法。分配給所有者窗體的窗體將保持被擁有狀態,直到調用了 RemoveOwnedForm 方法。如果窗體是多文檔界面 (MDI) 父窗體,則除了當前打開的所有 MDI 子窗體外,此屬性將返回所有顯示的窗體。
Form.MdiChildren 屬性。獲取窗體的數組,這些窗體表示以此窗體作為父級的多文檔界面 (MDI) 子窗體。
語法,public Form[] MdiChildren { get; }
此屬性使您得以獲取對當前在某 MDI 父窗體中打開的所有 MDI 子窗體的引用。若要創建 MDI 子窗體,請將要成為 MDI 父窗體的 Form 分配給該子窗體的 MdiParent 屬性。可以使用此屬性依次通過所有 MDI 子窗體,以執行一些操作,如當 MDI 父窗體關閉時將數據保存到數據庫中,或者根據應用程序中執行的操作更新子窗體上的字段。
Form.MdiParent 屬性。獲取或設置此窗體的當前多文檔界面 (MDI) 父窗體。
語法,public Form MdiParent { get; set; }
若要創建 MDI 子窗體,請將要成為 MDI 父窗體的 Form 分配給該子窗體的 MdiParent 屬性。可以從某 MDI 子窗體使用此屬性來獲取所有子窗體都需要的全局信息或者調用對所有子窗體執行操作的方法。
Form.ActiveForm 靜態屬性。獲取此應用程序的當前活動窗體。
語法,public static Form ActiveForm { get; }
表示當前活動窗體,或者如果沒有活動窗體,則為空引用。可以使用此方法獲得對當前活動窗體的引用,以在該窗體或其控件上執行操作。
Form.ActiveMdiChild 屬性。獲取當前活動的多文檔界面 (MDI) 子窗口。
語法,public Form ActiveMdiChild { get; }
返回表示當前活動的 MDI 子窗口的 Form,或者如果當前沒有子窗口,則返回 空引用。可使用此方法確定 MDI 應用程序中是否有任何打開的 MDI 子窗體。也可使用此方法從 MDI 子窗口的 MDI 父窗體或者從應用程序中顯示的其他窗體對該 MDI 子窗口執行操作。
ContainerControl.ParentForm 屬性。獲取將容器控件分配給的窗體。
語法,public Form ParentForm { get; }
將容器控件分配給的 Form。
以上屬性MSDN提供相應的代碼事例,可直接拿來調試使用。
(3)使用公共屬性
使用公共屬性也是一種比較常用的方式,通過窗體設計器添加的控件默認訪問修飾符為private級別,可以設置成public或Internal(在程序集內部可見)來對外公開。比如對窗體中的Button進行公開,那就可以訪問Button的相關屬性,同時也可以注冊事件或撤銷事件注冊。如,
OptionForm form = new OptionForm();
form.buttonOK.Click += new EventHandler(buttonOK_Click);
form.ShowDialog();
對於只允許讀取訪問或修改訪問的控件或變量可以通過屬性來控制。對(1)方式進行修改,去除重載構造函數,增加屬性也可以實現同樣的效果。
public object MyParams
{
get { return this.myParams; }
set { this.myParams = value; }
}
(4)使用公共方法
使用公共方法類似於屬性,對上面的同等實現如下,
//獲取參數
public object GetParams()
{
return this.myParams;
}
//設置參數
public void SetParams(object myParams )
{
this.myParams = myParams;
}
(5)使用靜態類該方式可以簡單的理解為靜態變量全局共享
通過下面代碼能夠比較清楚的理解,先來定義靜態類,
public static class ParameterSettings
{
//公共靜態變量
public static string Username = "Zhengzuo";
//私有靜態變量
private static string userRole = "Administrators";
//私有靜態變量
private static string password = "http://blog.csdn.net/zhzuo";
//內部屬性
internal static string UserRole
{
get { return userRole; }
}
//公共屬性
public static string Password
{
get { return password; }
private set { password = value; }
}
}
在需要訪問的地方通過以下方式進行,
string username = ParameterSettings.Username;
(6)窗體實現Singleton模式
string password = ParameterSettings.Password;
string userRole = ParameterSettings.UserRole;
ParameterSettings.Username = "鄭佐";//修改成新用戶名
Singleton模式是我們開發過程中最常用的模式之一。在技術社區經常看到有人談及對主窗體實現Singleton,但個人認為這不是一種妥當的做法,因為沒有這個必要。這裡通過另一個自定義類來進行演示。假設UserLoginInfo類用來保存登錄系統後的用戶憑據。
public class UserLoginInfo {
//實現Singleton模式,線程安全。
private readonly static UserLoginInfo currentUserInfo =
new UserLoginInfo();
//提供全局訪問點
public static UserLoginInfo CurrentUserInfo
{
get { return currentUserInfo; }
}
//阻止顯式實例化,但不能阻止反射方式調用。
private UserLoginInfo()
{ }
//公共變量
public string Username;
//私有變量
private static string userRole;
//私有變量
private static string password;
//內部屬性
internal string UserRole
{
get { return userRole; }
set { userRole = value; }
}
//公共屬性
public string Password
{
get { return password; }
internal set { password = value; }
}
}
在其他代碼中進行訪問,UserLoginInfo.CurrentUserInfo.Username ="鄭佐";
UserLoginInfo.CurrentUserInfo.UserRole = "dotnetlover";
UserLoginInfo.CurrentUserInfo.Password = "http://blog.csdn.net/zhzuo";
對於Singleton模式的實現方式有很多,編寫時需要考慮是否需要保證實例訪問的線程安全問題,以免引發不可預料的情況,為了提高性能可以考慮惰性實例化。關於Singleton模式的更多信息可以參考另一篇文章。
(7)發布事件進行訂閱
通過事件來傳遞參數應該說是一種推的實現方式,在產生事件時進行被動的獲取相關數據。這裡將通過一個自定義事件來演示數據的傳輸。
在自定義事件時,標准的做法都會先定義一個事件參數類,要麼直接使用基類EventArgs,或者從EventArgs繼承實現自己的參數類,假設自定義基類取名為OptionSettingEventArgs,
//選項設置事件參數類
public class OptionSettingEventArgs : EventArgs {
private string changedPath;
//構造函數
public OptionSettingEventArgs(string changedPath)
{
this.changedPath = changedPath;
}
//讀取參數
public string ChangedPath
{
get { return this.changedPath; }
}
}
以上參數類只包含一個修改後的路徑參數。接下去我們要對原先的OptionForm窗體增加事件定義,這裡使用.net 2.0中提供的泛型類來實現。
//定義事件
public event EventHandler<OptionSettingEventArgs> OptionSettingChanged; 編寫事件引發程序如下,
//引發OptionSettingChanged事件
protected virtual void OnOptionSettingChanged(OptionSettingEventArgs e)
{
if (OptionSettingChanged != null)
{
OptionSettingChanged(this, e);
}
}
對文件目錄選擇按鈕事件處理程序進行修改來實現事件激發,並沒有考慮直接從文本框直接數據輸入方式。
//通過目錄對話框設置新的路徑
private void buttonBrowser_Click(object sender, EventArgs e)
{
FolderBrowserDialog dialog = new FolderBrowserDialog();
DialogResult result = dialog.ShowDialog(this);
if (result == DialogResult.OK)
{
if(this.textBoxPath.Text != dialog.SelectedPath)
{
this.textBoxPath.Text = dialog.SelectedPath;
OptionSettingEventArgs args =
new OptionSettingEventArgs(dialog.SelectedPath);
OnOptionSettingChanged(args);
}
}
}
好了,一切准備工作完成,調用代碼如下,OptionForm form = new OptionForm();
//注冊事件
form.OptionSettingChanged += new EventHandler (form_OptionSettingChanged);
form.ShowDialog();
通過以下事件處理程序來驗證其正確性,
private void form_OptionSettingChanged(object sender, OptionSettingEventArgs e)
{
string newPath = e.ChangedPath;
MessageBox.Show(this, String.Format("新路徑為“{0}”。", newPath), "提示");
}
在實際開發過程中,合理的處理方式可能是以上幾種方式的組合。對於窗體間的參數傳遞,我在另一篇文章中也有比較多的基礎實例講解。
.Net Framework提供的模式窗體
.net Framework為我們提供了一些比較常用的對話框,在開發過程中省了不少事,以下對其進行介紹。
MessageBox。顯示可包含文本、按鈕和符號(通知並指示用戶)的消息框。通過MessageBox.Show 靜態方法來打開模式對話框。
public static DialogResult Show ( string text );
該方法包含多個重載版本。復雜的一個方法如下,
public static DialogResult Show ( IWin32Window owner, string text,
string caption, MessageBoxButtons buttons, MessageBoxIcon icon,
MessageBoxDefaultButton defaultButton, MessageBoxOptions options,
string helpFilePath, HelpNavigator navigator, Object param ) ;
根據不同的參數可以定制對話框的行為。
另外一些對話框提供了特定功能。
◆OpenFileDialog
打開文件對話框,從FileDialog類繼承,提示用戶打開文件,無法繼承此類。對於文件的打開操作屬於比較常見的。
◆SaveFileDialog
保存文件對話框,從FileDialog類繼承,提示用戶選擇文件的保存位置。無法繼承此類。
◆FolderBrowserDialog
目錄浏覽對話框,從CommonDialog類繼承,提示用戶選擇文件夾。無法繼承此類。
◆FontDialog
字體設置對話框,從CommonDialog類繼承,提示用戶從本地計算機上安裝的字體中選擇一種字體。可繼承該類。
◆ColorDialog
顏色設置對話框,從CommonDialog類繼承,表示一個通用對話框,該對話框顯示可用的顏色以及允許用戶定義自定義顏色的控件。可繼承該類。
◆PageSetupDialog
打印頁面設置對話框,從CommonDialog類繼承,允許用戶更改與頁面相關的打印設置,包括邊距和紙張方向。無法繼承此類。
◆PrintDialog
打印對話框,從CommonDialog類繼承,允許用戶選擇一台打印機並選擇文檔中要打印的部分。無法繼承此類。
◆PrintPreviewDialog
打印預覽對話框,從Form類繼承,表示包含 PrintPreviewControl 的對話框窗體。可繼承該類。由於該類從Form類繼承,所以除了通過
PrintPreviewDialog.ShowDialog ();
PrintPreviewDialog.ShowDialog (IWin32Window);
方法以模式方式打開窗體外,還可以通過PrintPreviewDialog.Show ();或其重載PrintPreviewDialog.Show (IWin32Window);方法按正常非模式方式打開。
上面列舉的文件對話框抽象基類FileDialog是從CommonDialog抽象類繼承,因此所有從該類繼承的對話框都可以通過CommonDialog.ShowDialog ();或其重載CommonDialog.ShowDialog (IWin32Window);方法以模式方式打開窗體。