C#把MDI子窗体变为标签页面(不改写任何控件)

原创 2012年02月29日 22:38:11
     先给大家看下最终效果图如下:

这个是用vs2005写的,感觉那个关闭按钮图片支持不是那么好,在vs2008及其以上版里使用,效果更佳。

 

    首先我们先新建一个项目,默认有个Form1窗体,将ShowIcon、ShowInTaskbar属性设置为False,这样这个窗体就没有最大化、最小化按钮,和不显示左上角的图标了。

    接着,新建一个MDI父窗体,应该默认会有设置好的菜单栏、工具栏和状态栏,这里去掉工具栏和状态栏,并删除了大部分菜单,只留下一个新建功能,修改新建功能代码如下:

private void ShowNewForm(object sender, EventArgs e)
{
    // 创建此子窗体的一个新实例。
    Form1 childForm = new Form1();
    // 在显示该窗体前使其成为此 MDI 窗体的子窗体。
    childForm.MdiParent = this;
    childForm.Text = "窗口" + childFormNumber++;
    childForm.WindowState = FormWindowState.Maximized;
    childForm.Show();
}


 

运行后点击“新建”效果如下:

到这里,想必很多人都不希望菜单旁边的那个图标和右边的那几个按钮存在吧,这个问题比较容易解决,在窗体在再添加一个MenuStrip控件,然后将下面那个MenuStrip控件设置为不可见即可:

再次运行程序,嘿嘿,不想要的没有了吧!到现在,前驱工作才算完成,下面就要开始给子窗体添加标签了,这里使用TabControl来实现。继续在MDI窗体上添加一个TabControl,然后设置SizeMode为Fixed,ItemSize的Width为140,Height:20,设置Size的Height为24,Dock属性为Top。然后添加一个Label标签,用作关闭按钮,设置AutoSize为False,Size:w 15,h 13,然后给他设置一个关闭的图标:

到此,界面工作已经差不多完成了,现在开始代码实现子窗体的标签控制了。

 

在MDI的Form_Load中先把TabControl的页面全部清理了,然后把Label1隐藏:

private void MDIParent1_Load(object sender, EventArgs e)
{
    tabControl1.TabPages.Clear();
    tabControl1.Visible = false;    // 没有元素的时候隐藏自己
    label1.Visible = false;
}

 

接着,我们要做的是:当新建一个窗体的时候同时多一个TabPage:

/// <summary>
/// 添加一个标签
/// </summary>
/// <param name="frm"></param>
private void AddTabPage(Form frm)
{
    TabPage tp = new TabPage();
    tp.Tag = frm;  // 当前标签控制的窗体对象记录在Tag属性中
    tp.Text = frm.Text;
    tabControl1.TabPages.Add(tp);
    tabControl1.SelectedIndex = tabControl1.TabCount - 1;  // 默认选中最后一个新建的标签
    if (!tabControl1.Visible) tabControl1.Visible = true;  // 如果自己是隐藏的则显示自己
}

 

好了,接着修改原来新建的代码为:

private void ShowNewForm(object sender, EventArgs e)
{
    // 创建此子窗体的一个新实例。
    Form1 childForm = new Form1();
    // 在显示该窗体前使其成为此 MDI 窗体的子窗体。
    childForm.MdiParent = this;
    childForm.Text = "窗口" + childFormNumber++;
    childForm.WindowState = FormWindowState.Maximized;
    childForm.Show();
    AddTabPage(childForm);  // 新建窗体同时新建一个标签
}

 

这时候,虽然可以新建窗体,同时也有对应标签显示,但是标签的切换却没能同时将窗体切换到相应的窗体,因此,还需要一个方法来控制,添加TabControl的IndexChange事件,代码如下:

private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (tabControl1.SelectedIndex > -1)
        (tabControl1.TabPages[tabControl1.SelectedIndex].Tag as Form).Focus();
}

到此,效果图如下:




好了,新建窗体同时也可以新建标签了,许多人习惯了现在浏览器的操作,双击标签可以关闭当前页,每个标签的右边还有一个关闭按钮,所以咱们还需要给TabControl添加双击事件和关闭按钮,由于多处使用到,因此把关闭功能单独出一个函数:

/// <summary>
/// 删除一个标签
/// </summary>
/// <param name="selectedIndex"></param>
private void CloseTabPage(int selectedIndex)
{
    (tabControl1.TabPages[selectedIndex].Tag as Form).Close();
    tabControl1.TabPages.RemoveAt(selectedIndex);
    if (tabControl1.TabPages.Count == 0) tabControl1.Visible = false;
}


接着添加tabControl1_MouseDoubleClick事件,响应关闭功能:

private void tabControl1_MouseDoubleClick(object sender, MouseEventArgs e)
{
    if (e.Button == System.Windows.Forms.MouseButtons.Left) // 只有左键双击才响应关闭
        CloseTabPage(tabControl1.SelectedIndex);
}


到此,以上操作已经实现了简单的子窗体变为标签页面显示了。下面,就是给标签添加上一个关闭按钮,就是还没用上的Label,这个就有些麻烦了,因为tabControl没有提供直接获取当前鼠标所在的是哪个TabPage的属性或者方法,因此只能通过坐标计算来得到当前是在哪个项上:

/// <summary>
/// 从菜单弹出位置得到当前所在的标签索引
/// </summary>
/// <returns></returns>
private int GetPageIndexWidthPoint(int pointX)
{
    int x = 0;
    for (int i = 0; i < tabControl1.TabPages.Count; ++i)
    {
        if (pointX >= x && pointX <= x + tabControl1.ItemSize.Width)
            return i;
        x += tabControl1.ItemSize.Width;
    }
    return tabControl1.TabPages.Count - 1;
}
/// <summary>
/// 计算从第一个可见项到当前项的宽度
/// </summary>
/// <param name="nowItemIndex"></param>
/// <returns></returns>
private int GetItemWidth(int nowItemIndex)
{
    int w = 0;
    for (int i = 0; i <= nowItemIndex; i++)
    {
        w += tabControl1.ItemSize.Width;
    }
    return w;
}


然后给tabControl添加MouseMove事件,得到当前是在第几个项上,并在相应的项上显示关闭按钮:

private void tabControl1_MouseMove(object sender, MouseEventArgs e)
{
    int i = GetPageIndexWidthPoint(e.X); // 获取当前鼠标所在标签位置
    if (i > -1)
    {
        int posX = GetItemWidth(i) - label1.Width - 4;    // 计算label1的x坐标
        //定位label1位置
        label1.Left = posX;
        label1.Top = tabControl1.Top + 6;
        label1.Visible = true;
    }
    else
    {
        label1.Visible = false;
    }
    label1.Tag = i;
}


当鼠标离开tabControl范围则隐藏关闭按钮:

private void tabControl1_MouseLeave(object sender, EventArgs e)
{
    label1.Visible = false;
}


运行后效果如下如:

 

然后给Label1添加上MouseClick事件,响应关闭功能:

private void label1_MouseClick(object sender, MouseEventArgs e)
{
    if (label1.Tag != null && (int)label1.Tag > -1)
    {
        CloseTabPage((int)label1.Tag);
        label1.Visible = false;  // 关闭后将自己隐藏
    }
}

 

一般来说在标签除了双击、和点击关闭按钮可以关闭相应标签和窗体了,还有就是鼠标右键有个关闭菜单,因此我们在添加一个右键菜单contextMenuStrip,添加一个项菜单,名为“关闭”,然后将tabControl的contextMenuStrip属性设置为这个菜单,接着双击菜单的“关闭”,编写代码如下:

private void 关闭ToolStripMenuItem_Click(object sender, EventArgs e)
{
    int index = GetPageIndexWidthPoint(contextMenuStrip1.Left - this.Left);  // 这里也需要通过弹出菜单的位置来得到当前是哪个项弹出的,注意菜单位置是针对屏幕左边的距离
    CloseTabPage(index);
}


好了,到此完成了MDI窗体标签化的设计了,运行下查看效果,理论上一切都OK了,但是细心的朋友会发现...NO!!Label标签的关闭按钮没有达到预期效果!点击压根没反应,想必是因为当前鼠标是被tabControl给捕获了导致Label没有捕获到鼠标,因此失去了事件响应。

接下来的处理才是Label能够相应MouseClick的关键,还好label有提供一个属性:Capture。因此我们在tabControl的MouseMove上做一些处理,如果鼠标进入label区域,则强制让Label捕获鼠标:

private void tabControl1_MouseMove(object sender, MouseEventArgs e)
{
    int i = GetPageIndexWidthPoint(e.X); // 获取当前鼠标所在标签位置
    if (i > -1)
    {
        int posX = GetItemWidth(i) - label1.Width - 4;    // 计算lblClose的x坐标
        //定位label1位置
        label1.Left = posX;
        label1.Top = tabControl1.Top + 6;
       //如果在关闭标签范围则强制标签捕获鼠标
        if (label1.Left <= e.X && label1.Left + label1.Width >= e.X &&
            e.Y >= 6 && e.Y <= 6 + label1.Height)
            label1.Capture = true;
        label1.Visible = true;
    }
    else
    {
        label1.Visible = label1.Capture || false;  // 只有当label没有捕获鼠标的时候隐藏关闭按钮
    }
    label1.Tag = i;
}
private void tabControl1_MouseLeave(object sender, EventArgs e)
{
    label1.Visible = label1.Capture || false;
    if (!label1.Visible)
        label1.Tag = -1;
}


好了,再运行下,发现还是不对劲,鼠标到了标签上后就感觉有些反应迟钝了,只有点击了鼠标才会正常过来!这是因为在上面代码中强制让label捕获鼠标后没是否了,不管鼠标移动在哪个位置,当前还是由label控制鼠标消息的,因此还需要添加label的MouseMove事件来及时释放鼠标:

private void label1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.X < 0 || e.X > label1.Width || e.Y < 0 || e.Y > label1.Height)  // 超出label范围释放鼠标
        label1.Capture = false;
}


最后运行下程序,OK了!这样就可以实现MDI子窗体标签样式显示了。

Demo源码下载

c#开发中,mdi子窗体如何调用父窗体中控件

http://hi.baidu.com/ivanbobo/blog/item/63571d1744ea941bc83d6ddd.html在子窗口中声明父窗口对象即可进行调用例如,父窗口为MDIPare...
  • plean
  • plean
  • 2011年07月18日 14:49
  • 2465

.NET C# MDI窗体菜单,隐藏子窗体ICON图标

最近在做项目的时候,发现MDI父窗体的主菜单的左边,当子窗体最大化的时候,显示了子窗体的图标ICON。 解决办法,在主菜单的ItemAdded事件编写如下代码:   1 ...
  • liuruxin
  • liuruxin
  • 2012年06月18日 19:38
  • 2872

C# Winform MDI窗体,父窗体控件覆盖子窗体的解决办法

问题:MDI窗体中,父窗体控件会覆盖子窗体,网上很多解决方案表面上解决了问题,但失去了MDI窗体的基本特性,并不实用。比较赞成使用子窗体“代替”父窗体控件的方案。思路:不将控件放置在父窗体上,而是放在...
  • lj22377
  • lj22377
  • 2015年09月14日 15:37
  • 2284

MDI子窗体ICON显示问题

在vs2005多文档设计过程中,被一个小问题给犯难了--MDI子窗体显示最大化后主窗体菜单栏左上角的图标显示不正常。一般情况下,只要设置Form的Icon属性就可以了,但在VS2005 .net设计中...
  • juanna_ding
  • juanna_ding
  • 2010年04月21日 11:47
  • 1958

子窗子显示在MDI主窗体panel控件之上

机房收费系统中,需要将子窗子显示在MDI主窗体panel容器控件之上。查了资料。大概网上有两种方法。一种是是可以将子窗体上的控件直接内嵌到panel控件之上,还有一种是子窗子显示在MDI主窗体pane...
  • suneqing
  • suneqing
  • 2014年02月15日 14:25
  • 3018

【C#】MDI窗体中,将子窗体置于父窗体控件之上的方法

问题 这几天在优化程序的时候发现了一个问题,就是在MDI窗体中的子窗体被激活显示出来之后,总是被主窗体中的控件遮挡(各种控件)。解决的方法也试了很多但是都不是很满意,由于C#的WINFROM窗体没...
  • SugaryoTT
  • SugaryoTT
  • 2016年05月08日 16:03
  • 2301

C#中MDI子窗体的创建方法

C#中去掉了窗体的MDIChild属性,如果要为MDI主窗体添加一个子窗体的话,现在要用代码来实现假如新建了一个名为NewForm的窗体来做为子窗体,那么要在显示这个窗体的地方加入如下代码:NewFo...
  • xiaoxinghappy
  • xiaoxinghappy
  • 2006年12月18日 06:49
  • 5764

C# MDI编程中子窗体调用子窗体的问题

在MDI编程中,从父窗体调用了子窗体,会出现子窗体隶属父窗体。但当我们要从一子窗体调用另一个子窗体,并且,调用过后,这个被子窗体调用出来的子窗体,会出现隶属于父窗体的情况是什么实现的呢? 下面是针对...
  • zhu1991_
  • zhu1991_
  • 2016年12月07日 10:25
  • 1730

WinForm(C#)中MDI子窗体最大化的问题

 “用MDI方式打开一个子窗口体后,总是不能最大化显示,明明子窗口体的WindowState设置为Maximized?”,相信有很多人会遇到这的样问题,请按下面的方法设置即可使MDI子窗体最大化...
  • xsfqh
  • xsfqh
  • 2015年06月15日 16:19
  • 1793

C# 实现MDI子窗体只打开一个(打开新的窗口,关闭其他窗口)

C# 实现MDI子窗体只打开一个(打开新的窗口,关闭其他窗口)2010年05月04日 12:331.private Form m_CurrentMdiChild;//声明窗体             ...
  • tq84020519
  • tq84020519
  • 2010年06月15日 15:38
  • 4483
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C#把MDI子窗体变为标签页面(不改写任何控件)
举报原因:
原因补充:

(最多只允许输入30个字)