c#中的模態對話方塊和非模態對話方塊

簡單但不平凡發表於2015-08-05
模態對話方塊
彈出視窗阻止呼叫視窗的所有訊息響應。
只有在彈出視窗結束後呼叫視窗才能繼續。
在模態視窗“關閉”後,可以讀取模態視窗中資訊,包括視窗的返回狀態,視窗子控制元件的值。
非模態對話方塊
可以在彈出視窗和呼叫視窗之間隨意切換。
呼叫視窗呼叫show方法後,下面的程式碼可以立即執行。

在非模態視窗關閉後,視窗的所有資源被釋放,視窗不存在,無法獲取視窗的任何資訊。


何謂模態窗體?簡單的可以理解為窗體對話方塊,使用者必須在完成該窗體上的操作或關閉窗體後才能返回開啟此窗體的窗體。本文不對模態窗體的定義、特徵、功能做具體討論,主要把重點放在如何在.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屬性,指示窗體的邊框和標題欄的外觀和行為。設定這個屬性將不允許拖動調整窗體的大小。this.FormBorderStyle = FormBorderStyle.FixedSingle;
  • 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”鍵都相當於“單擊”了該按鈕。窗體的“取消”按鈕:如果設定了此按鈕,則使用者每次按“Esc”鍵都相當於“單擊”了該按鈕。

 

this.AcceptButton = this.buttonOK;

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; 
     } 
}
可見,新的實現方式程式碼減少了一半。

 

  另外,通過使用WindowsAPI可以禁用或去掉關閉按鈕

  首先引用新增using System.Runtime.InteropServices;

  然後將下面的程式碼寫入工程裡面來禁用關閉按鈕

        private const int SC_CLOSE = 0xF060;
        private const int MF_ENABLED = 0x00000000;
        private const int MF_GRAYED = 0x00000001;
        private const int MF_DISABLED = 0x00000002;
        [DllImport("user32.dll", EntryPoint = "GetSystemMenu")]
        private static extern IntPtr GetSystemMenu(IntPtr hWnd, int bRevert);
        [DllImport("User32.dll")]
        public static extern bool EnableMenuItem(IntPtr hMenu, int uIDEnableItem, int uEnable);

  再將下面程式碼寫在頁面載入的page_load事件中就ok啦

        IntPtr hMenu = GetSystemMenu(this.Handle, 0);
        EnableMenuItem(hMenu, SC_CLOSE, MF_DISABLED | MF_GRAYED);

  去掉關閉按鈕

  [DllImport("USER32.DLL")]
        private static extern int RemoveMenu(int hMenu, int nPosition, int wFlags);

        /// <summary>
        /// 返回值,非零表示成功,零表示失敗。
        /// </summary>
        /// <param name="iHWND">視窗的控制程式碼</param>
        /// <returns>是否成功</returns>
        private int RemoveXButton(int iHWND)
        {
            int iSysMenu;
            const int MF_BYCOMMAND = 0x400; //0x400-關閉
            iSysMenu = GetSystemMenu(this.Handle.ToInt32(), 0);
            return RemoveMenu(iSysMenu, 6, MF_BYCOMMAND);
        }

  .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);方法以模態方式開啟窗體。



相關文章