http://www.cnblogs.com/island/archive/2008/12/02/mditab.html

创建MDI应用程序

  1. 先创建”Windows窗体应用程序”解决方案TabableMDIApp.
  2. 选中TabableMDIApp项目,添加新建项“MDI父窗体”:TabableMDIApp。VS自动创建的MDI父窗体已经帮我们做好了一个标准MDI应用程序所需要的大多数操作,而我们需要做的就是点点鼠标、喝喝茶、听听音乐、吹吹牛。
  3. 将Program.cs中的Form1改为TabableMDIApp,这样不出意外的话编译就应该可以通过了。这样只花了几秒钟的时间就创建了一个基本的MDI应用程序——框架。效果如下:

添加标签页管理功能

不过我们需要的是一个像Firefox那样不仅具有多窗体而且还具有相应的子窗体标签页管理控件。所以还需要进行如下操作:

  1. 在工具箱中拖一个TabControl到TabableMDIApp窗体中可将其命名为“TabPageManager”,并移除VS默认添加的tabPage1和tabPage2标签页(清空TabControl的TabPages属性中的标签页集合),设置TabControl的Dock属性为Top。
  2. 然后创建子窗体模板:修改VS自动添加的Form1窗体:将其文件重命名为RichTextChildTabForm,同时将Form1类重构为RichTextChildTabForm。
  3. 在RichTextChildTabForm.cs中添加两个属性,分别用于存储父窗体的TabControl对象和对应的子标签页,如下所示:

private TabControl tabPageManager;

public TabControl TabPageManager {

set { tabPageManager = value; }

}

private TabPage tabPageChild;

public TabPage TabPageChild {

get { return tabPageChild; }

set { tabPageChild = value; }

}

4.         添加子窗体关闭处理代码:

// 当子窗体关闭时销毁相应的TabPage

private void RichTextChildTabForm_FormClosing( object sender, FormClosingEventArgs e ) {

this.tabPageChild.Dispose();

// 如果没有TabPage对象则隐藏TabControl

if(!tabPageManager.HasChildren ) {

tabPageManager.Visible = false;

}

}

5.  当子窗体激活的时候激活相应的标签页:

// 子窗体激活时激活相应的标签页

private void RichTextChildTabForm_Activated( object sender, EventArgs e ) {

tabPageManager.SelectedTab = tabPageChild;

if(! tabPageManager.Visible ) {

tabPageManager.Visible = true;

}

}

6.  当MDI父窗体中“新建窗口”菜单被点击新建子窗体时,为新建窗体的TabPageManager和TabPageChild属性设置相应值:

private void ShowNewForm( object sender, EventArgs e ) {

//Creating MDI child form and initialize its fields

RichTextChildTabForm childForm = new RichTextChildTabForm();

childForm.Text = "窗口 " + childFormNumber++;

childForm.MdiParent = this;

//child Form will now hold a reference value to the tab control

childForm.TabPageManager = this.tabPageManager;

//Add a Tabpage and enables it

TabPage tp = new TabPage();

tp.Parent = tabPageManager;

tp.Text = childForm.Text;

tp.Show();

//child Form will now hold a reference value to a tabpage

childForm.TabPageChild= tp;

//Activate the MDI child form

childForm.Show();

//Activate the newly created Tabpage

tabPageManager.SelectedTab = tp;

}

7.  当父窗体的TabControl上被选中的标签页索引发生变化时激活相应的标签页:

private void tabPageManager_SelectedIndexChanged( object sender, EventArgs e ) {

foreach( RichTextChildTabForm childForm in this.MdiChildren ) {

//Check for its corresponding MDI child form

if( childForm.TabPageChild.Equals(tabPageManager.SelectedTab ) ) {

//Activate the MDI child form

childForm.Select();

}

}

}

这样一个带标签页的MDI窗体应用程序就创建好了,运行效果如下:

在多个标签页中分别打开多个文件

在UltraEdit里面我们可以同时选中多个文本文件然后程序会自动在各个标签页中加载相应文件,在其他MDI应用程序中也会碰到类似现象下面我们可以按如下步骤实现上述功能:

  1. 先在RichTextChildTabForm中添加RichTextBox控件以用于加载显示我们将要选中的文本文件
  2. 设置父窗体的打开文件对话框:openFileDialog.Multiselect = true;以允许我们同时选中多个文件。同时打开多个选中文本代码如下:

private void OpenFile( object sender, EventArgs e ) {

OpenFileDialog openFileDialog = new OpenFileDialog();

//openFileDialog.InitialDirectory = Environment.GetFolderPath( Environment.SpecialFolder.Personal );

openFileDialog.Multiselect = true;

openFileDialog.Filter = "文本文件(*.txt)|*.txt|所有文件(*.*)|*.*";

if( openFileDialog.ShowDialog( this ) == DialogResult.OK ) {

string[] FileNames = openFileDialog.FileNames;

OpenSelectedFiles(FileNames);

}

}

private void OpenSelectedFiles( string[] FileNames ) {

RichTextChildTabForm childForm;

foreach( string fileToOpenItem in FileNames ) {

//Creating MDI child form and initialize its fields

childForm = new RichTextChildTabForm();

childFormNumber++;

childForm.Text = fileToOpenItem.Substring( fileToOpenItem.LastIndexOf( @"\" ) + 1 );

childForm.MdiParent = this;

//child Form will now hold a reference value to the tab control

childForm.TabPageManager = this.tabPageManager;

//Add a Tabpage and enables it

TabPage tp = new TabPage();

tp.Parent = tabPageManager;

tp.Text = childForm.Text;

tp.Show();

//child Form will now hold a reference value to a tabpage

childForm.TabPageChild = tp;

childForm._RichTextBox.LoadFile( fileToOpenItem, RichTextBoxStreamType.PlainText );

//Activate the MDI child form

childForm.Show();

//Activate the newly created Tabpage

tabPageManager.SelectedTab = tp;

}

}

不过又有新的问题了:如果我们重复打开同一个文件怎么办?比如我先前已经打开了C:\A.txt、C:\B.txt、C:\C.txt、C:\D.txt、C:\E.txt后来我出去溜达一圈忘了打开过什么了结果我又打开了C:\A.txt这又该如何呢?所以我们希望每一个标签页都知道自己打开了什么文件,可以在子标签页中添加一个属性:

private  string openedFileNameOfThisTab;

public string OpenedFileNameOfThisTab {

get { return openedFileNameOfThisTab; }

set { openedFileNameOfThisTab = value; }

}

,然后父窗体要能记住当前已经打开过的文件列表以及相应文件是在哪个标签页中打开的可以在父窗体中添加以下代码:

private static Hashtable openedFileHashtable = new Hashtable();

public static Hashtable OpenedFileHashtable {

get { return DispenserMDIParent.openedFileHashtable; }

}

同时我们将上面的文件打开方法定义为如下形式:

private void OpenSelectedFiles( string[] FileNames ) {

RichTextChildTabForm childForm;

foreach( string fileToOpenItem in FileNames ) {

// 如果该文件未被打开(不在打开文件哈希表中),则在新标签页中打开此文件

if( !openedFileHashtable.Contains( fileToOpenItem ) ) {

// 哈希表的键为打开文件的绝对路径含文件名,值为子窗口数即将要打开此文件的标签页索引:IndexNumber

openedFileHashtable.Add( fileToOpenItem, this.MdiChildren.Length );

//Creating MDI child form and initialize its fields

childForm = new RichTextChildTabForm();

childFormNumber++;

childForm.Text = fileToOpenItem.Substring( fileToOpenItem.LastIndexOf( @"\" ) + 1 );

childForm._RichTextBox.LoadFile(fileToOpenItem,RichTextBoxStreamType.PlainText);

childForm.OpenedFileNameOfThisTab = fileToOpenItem;

AddorActiveTabPage( childForm, true );

} else {

// 如果该文件已经被打开(在打开文件哈希表中),则激活相应标签页

// 哈希表中与文件名关联的值为此文件相关联的标签索引

tabPageManager.SelectedIndex = (int)openedFileHashtable[fileToOpenItem];

}

}

}

/// <summary>

/// 在MDI窗体中添加新标签页或者激活已经存在的标签页,true,则添加,false则激活

/// </summary>

/// <param name="childForm">待添加或者激活的TabableForm对象实例</param>

/// <param name="addPage">true,则添加,false则激活已存在标签页</param>

private void AddorActiveTabPage( RichTextChildTabForm childForm, bool addPage ) {

if( addPage ) {

childForm.MdiParent = this;

//child Form will now hold a reference value to the tab control

childForm.TabPageManager = tabPageManager;

//Add a Tabpage and enables it

TabPage tp = new TabPage();

tp.Parent = tabPageManager;

tp.Text = childForm.Text;

tp.Show();

//child Form will now hold a reference value to a tabpage

childForm.TabPageChild = tp;

//Activate the MDI child form

childForm.Show();

childForm.Activate();

//Activate the newly created Tabpage

tabPageManager.SelectedTab = tp;

} else {

//childForm.Show();

//childForm.Activate();

//TabableForm tpForm = this.ActiveMdiChild as TabableForm;

//this.viewerTabManager.SelectedTab = tpForm.ChildTabPage;

childForm.Select();

}

}

记住在“关闭所有窗口”时清空文件打开列表:

private void CloseAllToolStripMenuItem_Click( object sender, EventArgs e ) {

foreach( Form childForm in MdiChildren ) {

childForm.Close();

}

openedFileHashtable.Clear();

}

并且在子窗体关闭时删除文件打开列表中与该标签页对应的文件如下所示:

// 当子窗体关闭时销毁相应的TabPage,并删除其在父窗体打开文件列表中的相应项

private void RichTextChildTabForm_FormClosing( object sender, FormClosingEventArgs e ) {

if( !string.IsNullOrEmpty( this.OpenedFileNameOfThisTab ) ) {

// 关闭标签页时删除打开文件列表中的相应文件名

TabableMDIApp.OpenedFileHashtable.Remove( this.OpenedFileNameOfThisTab );

}

this.tabPageChild.Dispose();

// 如果没有TabPage对象则隐藏TabControl

if(!tabPageManager.HasChildren ) {

tabPageManager.Visible = false;

}

}

关闭标签页

现在我们可以通过两种方式关闭已经打开的标签页——直接关闭所有窗口或者,点击子窗口的关闭按钮,不过我们希望以一种更简单的方式关闭标签页——双击标签页则将其关闭,这也是GreenBrowser浏览器采取的方案。可以添加如下代码实现:

private void tabPageManager_MouseDoubleClick( object sender, MouseEventArgs e ) {

Form childForm  = this.ActiveMdiChild;

RichTextChildTabForm tabableChild = childForm as RichTextChildTabForm;

if( tabableChild != null && !string.IsNullOrEmpty( tabableChild.OpenedFileNameOfThisTab ) ) {

// 如果 Hashtable 不包含带有指定键的元素,则 Hashtable 保持不变。不引发异常。

// 此方法的运算复杂度是 O(1)。

openedFileHashtable.Remove( tabableChild.OpenedFileNameOfThisTab );

}

//Destroy the corresponding Tabpage when closing MDI child form

// 关闭当前标签页后自动激活前一个标签页

int preTabIndex = tabPageManager.SelectedIndex - 1;

this.tabPageManager.SelectedTab.Dispose();

if( childForm != null ) {

childForm.Dispose();

}

//If no Tabpage left

if( !tabPageManager.HasChildren ) {

tabPageManager.Visible = false;

} else {

tabPageManager.SelectedIndex = preTabIndex;

}

}

拖放打开多个文件

那么如何让用户可以通过将文件拖放到应用程序窗口的方式打开呢?在默认情况下当用户将文件拖放到程序窗口中的时侯鼠标呈现出“禁止”的形状。如下:

要想支持拖放式打开可以按如下步骤操作:

  1. 设置“TabableMDIApp”这个Form的AllowDrop属性为True;
  2. 为“TabableMDIApp”这个Form添加DragEnter事件处理方法:

// This event occurs when the user drags over the form with

// the mouse during a drag drop operation

private void TabableMDIApp_DragEnter( object sender, DragEventArgs e ) {

// Check if the Dataformat of the data can be accepted

// (we only accept file drops from Explorer, etc.)

if( e.Data.GetDataPresent( DataFormats.FileDrop ) )

e.Effect = DragDropEffects.Link; //.Copy Okay

else

e.Effect = DragDropEffects.None; // Unknown data, ignore it

}

  1. 为“TabableMDIApp”这个Form添加DragDrop事件处理方法:

/// <summary>

/// 支持用户拖放文件并将其打开操作

/// </summary>

private void TabableMDIApp_DragDrop( object sender, DragEventArgs e ) {

if( e.Data.GetDataPresent( DataFormats.FileDrop ) ) {

// 用户拖放文件列表

System.Array fileNames = (System.Array)e.Data.GetData( DataFormats.FileDrop );

// 取得用户拖放的所有文件的文件名

string[] dropedFileNames=new string[fileNames.Length];

for( int i = 0; i < fileNames.Length; i++ ) {

dropedFileNames[i] = fileNames.GetValue( i ).ToString();

}

// 在新标签页中打开拖放的文件,如果支持该格式的话

OpenSelectedFiles( dropedFileNames );

}

}

打开异构标签页

通常标签页承载的都是同一个对象的多个实例,也就是说从表面上看来这多个标签页中的基本控件都是一样的,比如UltraEdit的多个标签页只是打开不同的文本而已,实际上标签页里面都是一个编辑器,本人称之为“同构”的,不过Visual Studio 的标签也就不是“同构”的:他可以在一个标签页中编辑源代码,而在另一个标签页中配置项目属性,或者打开一个对象浏览器,而这些标签页都包含着不同的控件,姑且称之为“异构”的,这又是如何实现的呢?

其实也不难,我们可以创建一个BaseTabForm窗体类,这个类只包含MDI子窗体的一些基本属性、字段或者方法,然后让其余待创建的异构窗体继承这个类,然后在标签页中显示由BaseTableForm继承而来的之类,但是管理标签的时候我们统一将其当作BaseTableForm来对待即可,这其实是一种多态手法。具体做法如下:

新建一个BaseTabForm“Windows窗体“对象,该窗体中不含其他控件,然后将前面所创建的RichTextChildTabForm中除了OpenedFileNameOfThisTab属性和构造函数之外的代码剪切到BaseTabForm中,最后代码大致如下:

using System;

using System.Windows.Forms;

namespace TabableMDIApp {

public partial class BaseTabForm :Form {

private TabControl tabPageManager;

public TabControl TabPageManager {

set { tabPageManager = value; }

}

private TabPage tabPageChild;

public TabPage TabPageChild {

get { return tabPageChild; }

set { tabPageChild = value; }

}

public BaseTabForm() {

InitializeComponent();

}

// 当子窗体关闭时销毁相应的TabPage,并删除其在父窗体打开文件列表中的相应项

private void BaseTabForm_FormClosing( object sender, FormClosingEventArgs e ) {

this.tabPageChild.Dispose();

// 如果没有TabPage对象则隐藏TabControl

if( !tabPageManager.HasChildren ) {

tabPageManager.Visible = false;

}

}

// 子窗体激活时激活相应的标签页

private void BaseTabForm_Activated( object sender, EventArgs e ) {

tabPageManager.SelectedTab = tabPageChild;

if( !tabPageManager.Visible ) {

tabPageManager.Visible = true;

}

}

}

}

using System;

using System.Windows.Forms;

namespace TabableMDIApp {

public partial class RichTextChildTabForm : Form {   // 注意这里

private  string openedFileNameOfThisTab;

public string OpenedFileNameOfThisTab {

get { return openedFileNameOfThisTab; }

set { openedFileNameOfThisTab = value; }

}

public RichTextChildTabForm() {

InitializeComponent();

}

private void RichTextChildTabForm_FormClosing( object sender, FormClosingEventArgs e ) {

if( !string.IsNullOrEmpty( this.OpenedFileNameOfThisTab ) ) {

// 关闭标签页时删除打开文件列表中的相应文件名

TabableMDIApp.OpenedFileHashtable.Remove( this.OpenedFileNameOfThisTab );

}

}

}

}

注意相关的窗体事件处理委托也要做相应的修改,然后再手工将public partial class RichTextChildTabForm :Form 改为public partial class  RichTextChildTabForm :BaseTabForm,从此以后对于任意新创建的子窗体类都可以将其直接继承的Form基类改为BaseTabFrom,这样就可以创建异构的窗体对象而将其当作BaseTabForm对象处理了。

将父窗体中添加或者激活标签的方法的签名改为如下:

private void AddorActiveTabPage( BaseTabForm childForm, bool addPage )

改变标签页方法改为如下:

// 当父窗体的TabControl上被选中的标签页索引发生变化时激活相应的标签页

private void tabPageManager_SelectedIndexChanged( object sender, EventArgs e ) {

foreach(BaseTabForm childForm in this.MdiChildren ) {

//Check for its corresponding MDI child form

if( childForm.TabPageChild.Equals( tabPageManager.SelectedTab ) ) {

//Activate the MDI child form

childForm.Select();

}

}

}

下面我们可以在标签页中添加一个带Button的标签页,其类代码如下:

namespace TabableMDIApp {

public partial class ButtonTabForm :BaseTabForm {

public ButtonTabForm() {

InitializeComponent();

}

}

}

然后可以响应父窗体中添加Button标签页的方法:

private void addButtonTab_Click( object sender, EventArgs e ) {

ButtonTabForm btnTab = new ButtonTabForm();

btnTab.Text = "带按钮的标签页";

AddorActiveTabPage(btnTab,true);

}

创建带标签页的MDI WinForms应用程序的更多相关文章

  1. bootstrap-js(4)标签页

    实例 标签页(Tab)在 Bootstrap 导航元素 一章中介绍过.通过结合一些 data 属性,您可以轻松地创建一个标签页界面. 通过这个插件您可以把内容放置在标签页或者是胶囊式标签页甚至是下拉菜 ...

  2. Bootstrap 标签页(Tab)插件

    摘自: http://www.runoob.com/bootstrap/bootstrap-tab-plugin.html Bootstrap 标签页(Tab)插件 标签页(Tab)在 Bootstr ...

  3. Bootstrap-Plugin:标签页(Tab)插件

    ylbtech-Bootstrap-Plugin:标签页(Tab)插件 1.返回顶部 1. Bootstrap 标签页(Tab)插件 标签页(Tab)在 Bootstrap 导航元素 一章中介绍过.通 ...

  4. Bootstrap标签页(Tab)插件

    标签页(Tab)在Bootstrap导航元素一章中简介过,通过结合一些data属性,您可以轻松地创建一些标签页界面.通过这个插件您可以把内容放置在标签页或胶囊式标签页甚至是下拉菜单标签页中. 用法 您 ...

  5. Firefox在新标签页打开“书签”和“搜索栏”(无需插件)

    转自   初来灬炸到的博客 前言 每次打开书签前,都需要创建新标签页. 每次搜索前,都需要创建新标签页.  这个真滴很麻烦.下面介绍的方法非常简单,不需要任何插件,通过修改浏览器参数即可. 名词 设置 ...

  6. MFC MDI 主框架和标签页数据互操作

    ==================================声明================================== 本文原创,转载在正文中显要的注明作者和出处,并保证文章的完 ...

  7. Python之selenium创建多个标签页

    最近在做一个项目,需要用到cookies登录,想法是,在同一个浏览器下,打开两个标签页进行.让其自动获取cookies,先记录,不行的话,到时候再手动加载cookies. 1 ''' 2 #selen ...

  8. EasyUI创建异步树形菜单和动态添加标签页tab

    创建异步树形菜单 创建树形菜单的ul标签 <ul class="easyui-tree" id="treeMenu"> </ul> 写j ...

  9. jquery ui 常用(一)(自动完成 | 标签页 | 折叠面板 | 带图标的按钮 | 日期选择器| )

    条件,引用3个文件 jquery-ui.min.css; jquery.min.js; jquery-ui.min.js. 一.自动完成 http://www.w3cschool.cc/jqueryu ...

随机推荐

  1. CSS3——制作图片翻页的小动画

    觉得还挺有意思的嘻嘻~ 这里有一个问题要注意一下,图片反转过程中可能会遇到一种如下所示的跳变bug: 这玩意一出来吓我一大跳,显然这种用户体验是很不好的,解决方法: 在.pic, .info{...} ...

  2. Java面试之基础篇(4)

    31.String s = new String("xyz");创建了几个StringObject?是否可以继承String类? 两个或一个都有可能,”xyz”对应一个对象,这个对 ...

  3. SQL Server2008收缩日志文件

    查询状态语句: SELECT name,log_reuse_wait_desc FROM sys.databases where name='hrsystem'; 收缩日志文件: USE [maste ...

  4. Linux学习-利用inotify和rsync实现数据的实时同步

    一.inotify简介 1.inotify介绍 异步的文件系统事件监控机制,利用事件驱动机制,而无须通过诸如cron等的 轮询机制来获取事件,linux内核从2.6.13起支持 inotify,通过i ...

  5. celery在项目中的使用

    1 关于celery是一个处理异步耗时任务的框架 由 worker 和broker 和store 3部分组成 worker是来处理消息的工人 broker是来存储请求消息的仓库 store是用来存储结 ...

  6. Bugku web web基础$_GET

    web基础$_GET 打开网站后发现 $what=$_GET['what']; echo $what; if($what=='flag') echo 'flag{****}'; 根据这段话的意思是将w ...

  7. B/S超大文件上传与下载

    最近遇见一个需要上传百兆大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表 ...

  8. 洛谷p3956 棋盘(NOIP2017 t3)

    在noip考场上本来以为只能骗暴力分,没想到最后A了: 本蒟蒻的做法比较简(zhi)单(zhang):记忆化深搜(考场上本来是想打广搜的,但我深搜稳一点就这样打了): 具体:每个点用一个f数组记录当前 ...

  9. 特征提取算法(4)——LoG特征提取算法

    目录 1.介绍 2.LoG原理 3.数学原理 4.模板性质 1.介绍 LoG(DoG是一阶边缘提取)是二阶拉普拉斯-高斯边缘提取算法,先高斯滤波然后拉普拉斯边缘提取. Laplace算子对通过图像进行 ...

  10. ios8来了,屏幕更大,准备好使用 iOS Auto Layout了吗?

    引言: Auto Layout是iOS6发布后引入的一个全新的布局特性,其目的是弥补以往autoresizing在布局方面的不足之处,以及未来面对更多尺寸适配时界面布局可以更好的适应. 要完全掌握Au ...