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. SpringCloud学习系列-Eureka自我保护模式(5)

    什么是自我保护模式? 默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒).但是当网络分区故障发生时,微服务与Eur ...

  2. 【leetcode】1156. Swap For Longest Repeated Character Substring

    题目如下: Given a string text, we are allowed to swap two of the characters in the string. Find the leng ...

  3. vue的生产环境dependencies 和开发环境devDependencies,二者的理解和区别

  4. Bugku web 计算器

    计算器 打开网页,想输入正确的计算结果发现只输进去一位数??? 遇事不决先F12看一眼源码,发现flag

  5. 最短路(模板)【CodeChef CLIQUED,洛谷P3371】

    自TG滚粗后咕咕咕了这么久,最近重新开始学OI,也会慢慢开始更博了.... 最短路算法经典的就是SPFA(Bellman-Ford),Dijkstra,Floyd: 本期先讲两个经典的单源最短路算法: ...

  6. Ubuntu 16.04下使用docker部署MySQL主从复制

    (以下docker相关的命令,需要在root用户环境下或通过sudo提升权限来进行操作.) 首先更新 软件源 https://mirrors.tuna.tsinghua.edu.cn/help/ubu ...

  7. C++ 对象间通信框架 V2.0 ××××××× 之(三)

    类定义:CSignalSlot ======================================================================= // SignalSlo ...

  8. Java测试笔记(ATM)

    本次Java测试主要是做一个与ATM相似的系统,用文本文件来作为用户数据库,实现存款.取款.转账.修改密码.查询余额的功能.在做这次测试之前老师并没有讲解与Java相关的知识,所以这就需要我们自学Ja ...

  9. Beyond Compare4 激活

    当你使用过一段时间后会提示有问题,需要激活或者什么. 解决办法: 找到这个路径并删除其下Beyond Compare 4文件夹即可正常使用. C:\Users\******\AppData\Roami ...

  10. 关于【C++项目:无法解析的外部符号】

    1,基本原因,[链接器]->[附加库目录]没有填写相关库的路径.或没有在[链接器]->[输入]->[附加依赖项]中填写相关库的名称 2,高级原因:如果不是1的原因,那就有可能是平台与 ...