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源码下载

相关文章推荐

.net c# winform带验证功能的TextBox,支持正则和自定义验证函数

该控件使用的是visual studio2010开发,对TextBox进行了改写,附带了验证功能,不需要开发人员再次对TextBox的内容进行验证,也不需要在相关的按钮里写判断语句,节省了对内容验证的...
  • ziyouli
  • ziyouli
  • 2012年05月19日 23:17
  • 5988

单例模式——解决MDI子窗体实例化的问题

机房收费系统进行有一段时间了,但是始终有些历史遗留问题。比如,如何MDI子窗体如何显示在上层的问题和MDI子窗体实例化的问题。         对于如何显示在上层的问题,我这次采用的还是SetPare...

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

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

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

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

C#MDI打开子窗体去掉自动生成的菜单栏

C#在DMI中打开子窗体时自动生成了菜单栏,怎么去掉菜单栏呢呢? 先在父窗体的加载事件中打开子窗体并使它最大化 private void MDIParent1_Load(object send...

编程技巧:C# mdi子窗体简单教程

一、参照自带例子做个mdi窗体  1、加入父窗体MainForm,并加入一个菜单栏,new form1 form2 windows  2、给new加命令      private int childF...

C# 子窗体向父窗体的控件传值

  • 2013年07月15日 00:04
  • 932B
  • 下载

MDI窗体与子窗体的显示问题--(如何让主窗体是被控件挡住的子窗体显示)

机房收费系统热火朝天的进行着,于此同时问题也是毫不留情的就来了,在MDI窗体中添加了picture控件后,子窗体不能显示就是我遇到的第一个问题。刚刚着手做系统遇到这样的问题,挺棘手的,当时在网上查过很...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C#把MDI子窗体变为标签页面(不改写任何控件)
举报原因:
原因补充:

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