Winform 自定义TabControl实现浏览器标签

实现效果
作者:Gavin(daisong.michelangelo@gmail.com)
时间: Nov, 2015
封面图片为Gavin原创,请勿未经允许私自引

最近因为工作需要,要做一个桌面浏览器,和大多数浏览器一样,我的这个浏览器也需要有标签栏,效果就像这样:目标效果图
在网上查了很多资料,大多数做法都是自定义Winform中TabControl控件,具体的做法有多种。
首先让我们来见识一下winform原生的TabControl是什么样子的:这里写图片描述
怎么样,有没有让你有种思考人生的冲动?
下面我们来分析怎么将它改造成我们想要的模样这里写图片描述
TabControl有由一个个TabPage组成,每个TabPage有一个TabItem(也就是页签)
让我们从这个页签开始,它由以下三部分组成:

  1. 标签页左边是一个图标
  2. 中间是网页的Title
  3. 右边是标签的关闭按钮

要实现这三个部分,我们可以重写TabControl.DrawItem 事件,在这之前我们当然要继承Winform 的TabControl 组件:
public partial class TabControl1 : TabControl
然后还要记得将DrawMode 设置成 TabDrawMode.OwnerDrawFixed,这样我们就可以自己画TabItem了,否则你在DrawItem里面干的事可就白干了。
然后,我们就可以重写DrawItem事件,开始画也签了

this.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.MainTab_DrawItem);

首先,我们可以先设置标签页的背景,而且标签在选中和未选中的时候背景是不一样的。

// 获取当前选项卡区域
 Rectangle bounds = GetTabRect(e.Index); 
 Brush br = SystemBrushes.ActiveCaptionText;
 if (e.Index == this.SelectedIndex)    //当前选中的Tab页,设置不同的样式以示选中  
 {
   e.Graphics.FillRectangle(Brushes.White, bounds); //改变选中选项卡标签的背景色   
 }
 else
  {
e.Graphics.FillRectangle(Brushes.DarkRed, bounds); //改变非选中的选项卡标签的背景色
    br = SystemBrushes.GrayText;    //置灰非选中项
}

注意,一定要先画背景,在画其他的东西,不然后画背景会将之前画的东西擦除掉。
接着我们就可以来画图标了,TabControl 有一个ImageList可以存放图片,可以通过key和index获取到。原生的TabControl已经实现了这个功能,但是我们重写了整个TabItem的绘制方法,所以我们还是要自己实现。

 //绘制图标  
 if (this.ImageList != null)
 {
     int index = this.TabPages[e.Index].ImageIndex;
     string key = this.TabPages[e.Index].ImageKey;
     Image icon = new Bitmap(icoSize, icoSize);
     if (index > -1)
     {
         icon = this.ImageList.Images[index];
     }
     if (!string.IsNullOrEmpty(key))
     {
         icon = this.ImageList.Images[key];
     }
     e.Graphics.DrawImage(icon, bounds.X + startX, bounds.Top + 2);
}

好了,我们快要完成了,Graphics有绘制各种形状的方法,参数大多含有坐标,坐标控制就不多说了,MSDN里的方法说的很清楚了。
接下来,我们要画文字了,很简单,TabPages[i].Text就是我们的Title

 e.Graphics.DrawString(this.TabPages[i].Text, font, br, bounds.X + startX, bounds.Y + 6);

最后,我们还是一样的方法画出关闭按钮。closeImage可以先导入到资源文件

bounds.Offset(bounds.Width - (CLOSE_SIZE + 5), 2);
e.Graphics.DrawImage(closeImage, bounds.X, bounds.Y);

好了,页签的外观我们已经大工告成了,接下来我们要响应关闭按钮来关闭页签。
方法是监听鼠标点击事件,如果点击区域是在我们画关闭按钮的区域呢,我们就冲tabPages中将这个Page移除。

 //计算关闭区域  
 Rectangle bounds = this.GetTabRect(this.SelectedIndex);
 bounds.Offset(bounds.Width - (CLOSE_SIZE + 5), 2);
 bounds.Width = CLOSE_SIZE;
 bounds.Height = CLOSE_SIZE;
 //如果鼠标在区域内就关闭选项卡  
 bool isClose = x > bounds.X && x < bounds.Right
  && y > bounds.Y && y < bounds.Bottom;
 if (isClose == true)
 {
     int index = this.SelectedIndex;
     TabPage tab = this.SelectedTab;
     this.TabPages.Remove(tab);
     tab.Dispose();
 }

好了,看来我们已经完美地做出了一个美观的TabItem了,如果你没有别的需要的话,这样的组件就可以使用了。
但如我之前所说,实现方法有多种,因为需求总是在变,所以我们还是需要一种更全面的方法。
如果我需要改变整个TabControl 的背景色透明要怎么办呢?
仅仅重写DrawTabItem是远远不够的。

好了,下面进入今天的主题,怎么重写TabControl的OnPaint()方法来实现我们之前所实现的功能。
首先,我们当然要继承TabControl,还要设置几个属性

 this.SetStyle(
     ControlStyles.UserPaint |                      // 控件将自行绘制,而不是通过操作系统来绘制  
    ControlStyles.OptimizedDoubleBuffer |  // 该控件首先在缓冲区中绘制,而不是直接绘制到屏幕上,这样可以减少闪烁  
    ControlStyles.AllPaintingInWmPaint |           // 控件将忽略 WM_ERASEBKGND 窗口消息以减少闪烁  
    ControlStyles.ResizeRedraw |                   // 在调整控件大小时重绘控件  
    ControlStyles.SupportsTransparentBackColor,    // 控件接受 alpha 组件小于 255 的 BackColor 以模拟透明  
       true);                                         // 设置以上值为 true  
this.UpdateStyles();

最终要的一个就是要设置ControlStyles.UserPaint 为True,不然你就思考人生吧!但是设置了这个属性我们就要告别过去,从一张白纸开始了,当然这也意味着我们就可以尽情发挥了。
首先,我们就可以重写OnPaint方法了,这里我们绘制背景就很方便了,只要画一张图片就好了。

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Rectangle rec = this.ClientRectangle;           // 获取TabControl主控件的工作区域
Image backImage = Properties.Resources.bgImage; //获取背景图片,我的背景图片在项目资源文件中。
e.Graphics.DrawImage(backImage, 0, 0, this.Width, this.Height);  //绘制主控件的背景
//绘制标签样式
for (int i = 0; i < this.TabPages.Count; i++)
{
  //....这里和绘制上面的标签页类似
}
}

好了,先写到这吧,一个简单的自定义TabControl就实现了。
参考资料:

一步一步玩控件:自定义TabControl——从山寨Safari开始

 

posted @ 2015-11-23 19:55  InkGenius  阅读(1945)  评论(0编辑  收藏  举报