在web頁面上我們可以通過frameset,iframe嵌套框架很容易實現各種導航+內容的佈局界面,而在winform、WPF中實現其實也很容易,我這裡就分享一個:在winform下實現左右佈局多視窗界面。我這裡說的多視窗是指一個父視窗包含多個子視窗,在winform中實現這種效果很簡單,即將某個窗...
在web頁面上我們可以通過frameset,iframe嵌套框架很容易實現各種導航+內容的佈局界面,而在winform、WPF中實現其實也很容易,我這裡就分享一個:在winform下實現左右佈局多視窗界面。
我這裡說的多視窗是指一個父視窗包含多個子視窗,在winform中實現這種效果很簡單,即將某個視窗的IsMdiContainer設為true,然後將其它子視窗的MdiParent設為其父視窗對象即可,這樣就完成了一個多視窗界面,效果如下:
點擊NEW新打開一個視窗,其效果如下:
請看我上圖紅色標註的地方,Windows菜單項下麵顯示的是當前所有已打開的子視窗,點擊某個菜單,即可快速切換到其它視窗,若關閉某個子視窗,與之相對應的菜單項也會自動被移除,實現這個功能也很簡單,只需要將菜單的MdiWindowListItem屬性設為需要顯示活動視窗列表的菜單項即可,如:this.menuStrip1.MdiWindowListItem = this.windowsToolStripMenuItem;
上述示例完整的實現代碼如下:
public partial class FormMdi : Form { private int formCount = 0; public FormMdi() { InitializeComponent(); this.menuStrip1.MdiWindowListItem = this.windowsToolStripMenuItem; } private void newToolStripMenuItem_Click(object sender, EventArgs e) { ShowChildForm<FormChild>(); } private void ShowChildForm<TForm>() where TForm : Form, new() { TForm childForm = new TForm(); childForm.Name = "frm" + Guid.NewGuid().ToString("N"); childForm.Text = string.Format("Child Form -{0}", ++formCount); childForm.MdiParent = this; childForm.WindowState = FormWindowState.Maximized; childForm.Show(); } }
相信實現上面這部份功能一般用過winform的人都會操作,我這裡就當是複習順便給新手一個參考,同時也為下麵要實現的左右佈局功能做一個鋪墊吧。
要實現左右佈局,並且能夠支持可動態調整左右占比的功能,非SplitContainer控制項莫屬了,如果不瞭解該控制項用法請自行在網上查找相關資料,我這裡就不作說明,如果要顯示WINDOWS已打開的子視窗情況,同樣也需要用到MenuStrip控制項,
最終設計的主視窗(FormMain)效果如下:
我這裡因為只是演示,所以菜單控制項上我只添加了兩個菜單項,分別為:WINDOWS,用於顯示WINDOWS已打開的子視窗列表,NEW,用於打開一個子視窗;SplitContainer控制項全部採用預設的,沒有放置任何控制項在其中,如果用在正式系統中,一般左邊Panel1中會放置一個樹形菜單,右邊Panel2中保持空即可,因為這個是用來作為子視窗的容器。
控制項層次結構如下圖示:
界面設計好了,下麵就實現最重要的兩個功能。
第一個功能:在右邊Panel2中顯示子視窗,實現代碼如下:
public FormMain() { this.IsMdiContainer = true; } private void ShowChildForm<TForm>() where TForm : Form, new() { TForm childForm = new TForm(); childForm.Name = "frm" + Guid.NewGuid().ToString("N"); childForm.Text = string.Format("Child Form -{0}", ++formCount); childForm.MdiParent = this; childForm.Parent = splitContainer1.Panel2; childForm.WindowState = FormWindowState.Maximized; childForm.Show(); }
簡要說明:
1.在視窗構造函數中動態的將IsMdiContainer設為true,當然也可以設計視圖中設置;
2.編寫一個顯示寫子視窗的方法,方法中需註意的地方:childForm.MdiParent = this;childForm.Parent = splitContainer1.Panel2,意思是:將當前視窗作為子視窗的父視窗,同時將Panel2指定為子視窗的父對象,這樣就能實現子視窗在Panel2中打開了。
第二個功能:在WINDOWS菜單項下顯示已打開的子視窗列表,這裡實現就沒有像文章一開始介紹的那樣簡單,使用那個方法是無效的,需要我們來自行實現,稍微有點複雜,但如果明白其實現原理,也就簡單明白了。
實現思路:當childForm載入到Panel2時,會觸發Panel2.ControlAdded事件,當childForm被關閉時,會觸發Panel2.ControlRemoved事件,我們可以統一訂閱這兩個事件,當childForm載入時,那麼就在WINDOWS菜單項下增加一個菜單項,反之則移除該菜單項,實現代碼如下:
this.splitContainer1.Panel2.ControlAdded += Panel2_ControlChanged; this.splitContainer1.Panel2.ControlRemoved += Panel2_ControlChanged; void Panel2_ControlChanged(object sender, ControlEventArgs e) { var frm = e.Control as Form; string menuName = "menu_" + frm.Name; bool exists = this.splitContainer1.Panel2.Controls.Contains(frm); if (exists) { var menuItem = GetMenuItem(menuName); if (menuItem != null) { menuItem.Checked = true; frm.BringToFront(); frm.Focus(); } else { windowsToolStripMenuItem.DropDownItems.Add(new ToolStripMenuItem() { Text = frm.Text, Name = menuName, Tag = frm, Checked = true }); } } else { var menuItem = GetMenuItem(menuName); if (menuItem != null) { windowsToolStripMenuItem.DropDownItems.Remove(menuItem); menuItem.Dispose(); } } } private ToolStripMenuItem GetMenuItem(string menuName) { var menuItems = windowsToolStripMenuItem.DropDownItems.Cast<ToolStripMenuItem>(); menuItems.ToList().ForEach(m => m.Checked = false); return menuItems.Where(m => m.Name == menuName).SingleOrDefault(); }
同時為了實現點擊WINDOWS菜單項的子菜單能夠快速切換子視窗,需要訂閱WINDOWS菜單項的DropDownItemClicked事件,當然也可以為新增的子菜單項訂閱Click事件,實現代碼如下:
windowsToolStripMenuItem.DropDownItemClicked += windowsToolStripMenuItem_DropDownItemClicked; void windowsToolStripMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e) { var menuItem = GetMenuItem(e.ClickedItem.Name); menuItem.Checked = true; var childForm = menuItem.Tag as Form; childForm.BringToFront(); childForm.Focus(); } private void CheckWindowsMenuItem(string menuName) { var menuItem = GetMenuItem(menuName); if (menuItem != null) { menuItem.Checked = true; } }
這樣就基本實現了在WINDOWS菜單項下顯示已打開的子視窗列表,並點擊指定的菜單項能夠切換當前活動的子視窗,但仍有一個不足的地方,那就是,當直接點擊子視窗來切換當前活動視窗時(說白了就是當點擊某個子視窗標題欄,該視窗就顯示在其它所有的子視窗最前面),WINDOWS菜單項下的子菜單勾選項沒有同步更新,一開始想到的是用Activated事件來進行處理,結果經測試發現有效,該Activated事件在點擊子視窗標題欄時並不會被觸發,所以只能換種方法,經過多次測試,發現當視窗從後面切換到前面時(稱為Z順序改變),子視窗就會發生重繪,從而觸發Paint方法,我們可以訂閱該事件,併進行處理,實現代碼如下:
private string currentChildFormName = null; //記錄當前活動子視窗名稱 childForm.Paint += (s, e) => { var frm=s as Form; if (!frm.Name.Equals(currentChildFormName) && this.splitContainer1.Panel2.Controls[0].Equals(frm)) //當容器中第一個控制項就是當前的視窗,則表明該視窗處於所有視窗之上 { CheckWindowsMenuItem("menu_" + frm.Name); currentChildFormName = frm.Name; } };
最後貼出完整的實現代碼:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class FormMain : Form { private int formCount = 0; private string currentChildFormName = null; public FormMain() { InitializeComponent(); this.IsMdiContainer = true; this.splitContainer1.Panel2.ControlAdded += Panel2_ControlChanged; this.splitContainer1.Panel2.ControlRemoved += Panel2_ControlChanged; windowsToolStripMenuItem.DropDownItemClicked += windowsToolStripMenuItem_DropDownItemClicked; } void windowsToolStripMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e) { var menuItem = GetMenuItem(e.ClickedItem.Name); menuItem.Checked = true; var childForm = menuItem.Tag as Form; childForm.BringToFront(); childForm.Focus(); } private void FormMain_Load(object sender, EventArgs e) { ShowChildForm<FormChild>(); } private void ShowChildForm<TForm>() where TForm : Form, new() { TForm childForm = new TForm(); childForm.Name = "frm" + Guid.NewGuid().ToString("N"); childForm.Text = string.Format("Child Form -{0}", ++formCount); childForm.MdiParent = this; childForm.Parent = splitContainer1.Panel2; childForm.WindowState = FormWindowState.Maximized; childForm.Paint += (s, e) => { var frm=s as Form; if (!frm.Name.Equals(currentChildFormName) && this.splitContainer1.Panel2.Controls[0].Equals(frm)) //當容器中第一個控制項就是當前的視窗,則表明該視窗處於所有視窗之上 { CheckWindowsMenuItem("menu_" + frm.Name); currentChildFormName = frm.Name; } }; childForm.Show(); } private void CheckWindowsMenuItem(string menuName) { var menuItem = GetMenuItem(menuName); if (menuItem != null) { menuItem.Checked = true; } } void Panel2_ControlChanged(object sender, ControlEventArgs e) { var frm = e.Control as Form; string menuName = "menu_" + frm.Name; bool exists = this.splitContainer1.Panel2.Controls.Contains(frm); if (exists) { var menuItem = GetMenuItem(menuName); if (menuItem != null) { menuItem.Checked = true; frm.BringToFront(); frm.Focus(); } else { windowsToolStripMenuItem.DropDownItems.Add(new ToolStripMenuItem() { Text = frm.Text, Name = menuName, Tag = frm, Checked = true }); } } else { var menuItem = GetMenuItem(menuName); if (menuItem != null) { windowsToolStripMenuItem.DropDownItems.Remove(menuItem); menuItem.Dispose(); } } } private ToolStripMenuItem GetMenuItem(string menuName) { var menuItems = windowsToolStripMenuItem.DropDownItems.Cast<ToolStripMenuItem>(); menuItems.ToList().ForEach(m => m.Checked = false); return menuItems.Where(m => m.Name == menuName).SingleOrDefault(); } private void newToolStripMenuItem_Click(object sender, EventArgs e) { ShowChildForm<FormChild>(); } } }
以下是系統自動生成的代碼:
namespace WindowsFormsApplication1 { partial class FormMain { /// <summary> /// 必需的設計器變數。 /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// 清理所有正在使用的資源。 /// </summary> /// <param name="disposing">如果應釋放托管資源,為 true;否則為 false。</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows 窗體設計器生成的代碼 /// <summary> /// 設計器支持所需的方法 - 不要 /// 使用代碼編輯器修改此方法的內容。 /// </summary> private void InitializeComponent() { this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.windowsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.newToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.menuStrip1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.SuspendLayout(); this.SuspendLayout(); // // menuStrip1 // this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.windowsToolStripMenuItem, this.newToolStripMenuItem}); this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.MdiWindowListItem = this.windowsToolStripMenuItem; this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Size = new System.Drawing.Size(1069, 25); this.menuStrip1.TabIndex = 1; this.menuStrip1.Text = "menuStrip1"; // // windowsToolStripMenuItem // this.windowsToolStripMenuItem.Name = "windowsToolStripMenuItem"; this.windowsToolStripMenuItem.Size = new System.Drawing.Size(73, 21); this.windowsToolStripMenuItem.Text = "Windows"; this.windowsToolStripMenuItem.Click += new System.EventHandler(this.windowsToolStripMenuItem_Click); // // newToolStripMenuItem // this.newToolStripMenuItem.Name = "newToolStripMenuItem"; this.newToolStripMenuItem.Size = new System.Drawing.Size(46, 21); this.newToolStripMenuItem.Text = "New"; this.newToolStripMenuItem.Click += new System.EventHandler(this.newToolStripMenuItem_Click); // // splitContainer1 // this.splitContainer1.BackColor = System.Drawing.SystemColors.ActiveCaption; this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; this.splitContainer1.Location = new System.Drawing.Point(0, 25); this.splitContainer1.Name = "splitContainer1"; // // splitContainer1.Panel2 // this.splitContainer1.Panel2.BackColor = System.Drawing.SystemColors.ScrollBar; this.splitContainer1.Size = new System.Drawing.Size(1069, 526); this.splitContainer1.SplitterDistance = 356; this.splitContainer1.TabIndex = 2; // // FormMain // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1069, 551); this.Controls.Add(this.splitContainer1); this.Controls.Add(this.menuStrip1); this.MainMenuStrip = this.menuStrip1; this.Name = "FormMain"; this.Text = "FormMain"; this.Load += new System.EventHandler(this.FormMain_Load); this.menuStrip1.ResumeLayout(false); this.menuStrip1.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); this.splitContainer1.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.MenuStrip menuStrip1; private System.Windows.Forms.ToolStripMenuItem windowsToolStripMenuItem; private System.Windows.Forms.SplitContainer splitContainer1; private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem; } }
以下是效果演示截圖:
如果大家有什麼更好的實現方法可以在下方評論,不足之處也歡迎指出,謝謝!