分享在winform下实现左右布局多窗口界面
在web页面上我们可以通过frameset,iframe嵌套框架很容易实现各种导航+内容的布局界面,而在winform、WPF中实现其实也很容易,我这里就分享一个:在winform下实现左右布局多窗口界面。
我这里说的多窗口是指一个父窗口包含多个子窗口,在winform中实现这种效果很简单,即将某个窗口的IsMdiContainer设为true,然后将其它子窗口的MdiParent设为其父窗口对象即可,这样就完成了一个多窗口界面,效果如下:
点击NEW新打开一个窗口,其效果如下:
请看我上图红色标注的地方,Windows菜单项下面显示的是当前所有已打开的子窗口,点击某个菜单,即可快速切换到其它窗口,若关闭某个子窗口,与之相对应的菜单项也会自动被移除,实现这个功能也很简单,只需要将菜单的MdiWindowListItem属性设为需要显示活动窗口列表的菜单项即可,如:this.menuStrip1.MdiWindowListItem = this.windowsToolStripMenuItem;
上述示例完整的实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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中显示子窗口,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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菜单项下增加一个菜单项,反之则移除该菜单项,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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事件,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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方法,我们可以订阅该事件,并进行处理,实现代码如下:
1 2 3 4 5 6 7 8 9 10 | 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; } }; |
最后贴出完整的实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | 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>(); } } } |
以下是系统自动生成的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 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; } } |
以下是效果演示截图:
如果大家有什么更好的实现方法可以在下方评论,不足之处也欢迎指出,谢谢!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2014-01-04 .NET 笔试题--自已作答
2014-01-04 设计模式-观察者模式
2014-01-04 设计模式-迭代器模式
2014-01-04 设计模式-责任链模式