使用.net ,打造一款类似 Chrome 风格的 TabControl

国际惯例,先上图:

 

整体效果、操作风格等 与Chrome 保持高度接近,实现了标签新增、删除、移动、自适应宽度等特性。 

核心代码,
1:创建Tab 页边框:

public void drawRect(Graphics g, Rectangle rect)
 {
            GraphicsPath path = new GraphicsPath();            

            path = new GraphicsPath();
            path.AddBezier(
                new Point(rect.X, rect.Bottom),
                new Point(rect.X + 3, rect.Bottom - 2),
                new Point(rect.X + 3, rect.Bottom - 2),
                new Point(rect.X + 4, rect.Bottom - 4));
            //path.AddLine(rect.X + 4, rect.Bottom - 4, rect.Left + 15 - 4, rect.Y + 4);
            path.AddBezier(
                new Point(rect.Left + 15 - 4, rect.Y + 4),
                new Point(rect.Left + 15 - 3, rect.Y + 2),
                new Point(rect.Left + 15 - 3, rect.Y + 2),
                new Point(rect.Left + 15, rect.Y));
            //path.AddLine(rect.Left + 15, rect.Y, rect.Right - 15, rect.Y);
            path.AddBezier(
                new Point(rect.Right - 15, rect.Y),
                new Point(rect.Right - 15 + 3, rect.Y + 2),
                new Point(rect.Right - 15 + 3, rect.Y + 2),
                new Point(rect.Right - 15 + 4, rect.Y + 4));
            //path.AddLine(rect.Right - 15 + 4, rect.Y + 4, rect.Right - 4, rect.Bottom - 4);
            path.AddBezier(
                new Point(rect.Right - 4, rect.Bottom - 4),
                new Point(rect.Right - 3, rect.Bottom - 3),
                new Point(rect.Right - 3, rect.Bottom - 3),
                new Point(rect.Right, rect.Bottom));

            region = new System.Drawing.Region(path);

            g.DrawPath(new Pen(Color.Black), path);

            g.FillPath(new SolidBrush(Selected ? Color.White : noSelectedColor), path);
            g.DrawLine(new Pen(Selected ? Color.White : BottomLineColor, 1), rect.X + 2, rect.Bottom - 1, rect.Right - 2, rect.Bottom - 1);
 }

 

2:绘制图标, 标签的图标有两种,一种为静态,一种为动态图,比如当状态为Loading 时,则显示动态图,之前考虑过使用GIF,效果不太理想,所以改为Timer 的方式,循环显示一组图片,实现GIF的效果

public void drawTabIcon(Graphics g, Rectangle rect)
{
            if (webPageState == WebPageState.Loading)
            {
                if(iCurFrame == 0)
                    g.DrawImage((Image)Resources.ResourceManager.GetObject("Marty_000" + (0).ToString("00")), rect);
                else
                    g.DrawImage((Image)Resources.ResourceManager.GetObject("Marty_000" + (iCurFrame - 1).ToString("00")), rect);
            }
            else
                g.DrawImage(HeaderIcon, rect);
 }

void tmAnimation_Tick(object sender, EventArgs e)
{
            iCurFrame = (iCurFrame) % iFrameCount + 1;
            this.paintType = PaintType.PaintHeaerIcon;
            paintRequest();
}

3:绘制Tab 的文本, 仔细看Chrome 的实现,就会发现当文字超出Tab宽度时, 接近Tab边缘的区域,文字颜色会逐级变淡,这里也使用线性画刷实现了该效果:

public void drawString(Graphics g, Rectangle rect, Rectangle rectFontLinearBrush, string title, Font font)
 {
            g.DrawString(title, font, brushFont, rect);

            using (LinearGradientBrush brush = new LinearGradientBrush(rectFontLinearBrush, Color.Transparent, Selected ? Color.White : noSelectedColor, 0, false))
            {
                g.FillRectangle(brush, rectFontLinearBrush);
            }
}

 

4:实现Tab页顺序调整:

protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (e.Button == System.Windows.Forms.MouseButtons.Left && thMouseDown != null)
            {
                if (!slided)
                {
                    if (Math.Abs(e.X - pMouseDown.X) > 15)
                    {
                        slided = true;
                    }
                }
                else
                {
                    btnAddNew.Visible = false;

                    Point newPos = thMouseDown.Rect.Location;
                    newPos.X += e.Location.X - pMouseDown.X;

                    // 是否在父窗体范围内移动
                    if (newPos.X < 0)
                        newPos.X = 0;
                    if (newPos.X > this.Width - thMouseDown.Rect.Width)
                        newPos.X = this.Width - thMouseDown.Rect.Width;

                   

                    // 判断移动方向,向左或向右
                    if (e.Location.X - pMouseDown.X > 0)
                    {
                        // 判断是否已经是最后一个Tab
                        if (thMouseDown.TabIndex != lstTabHeader.Count - 1)
                        {
                            TabHeader thRight = lstTabHeader[thMouseDown.TabIndex + 1];

                            // 向右移动时,判断是否与后一Tab 交换位置:当前Tab的 Right ,超过后一Tab 位置的一半
                            if (newPos.X + tabWidth > thRight.Rect.X + tabWidth / 2)
                            {
                                thRight.TabIndex --;
                                thMouseDown.TabIndex ++;
                                lstTabHeader.Sort();
                            }
                        }
                    }
                    else
                    {
                        // 判断是否已经是第0个Tab
                        if (thMouseDown.TabIndex != 0)
                        {
                            TabHeader thLeft = lstTabHeader[thMouseDown.TabIndex - 1];

                            // 向右移动时,判断是否与后一Tab 交换位置:当前Tab的 Right ,超过后一Tab 位置的一半
                            if (newPos.X < thLeft.Rect.X + tabWidth / 2)
                            {
                                thLeft.TabIndex ++;
                                thMouseDown.TabIndex --;
                                lstTabHeader.Sort();
                            }
                        }
                    }

                    thMouseDown.Rect.X = newPos.X;
                    pMouseDown = e.Location;
                    this.Invalidate();
                }
            }
            else
            {
                this.Invalidate();
            }
        }

 

5:重载Control 的 OnPaint:

protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            e.Graphics.CompositingQuality = CompositingQuality.HighQuality;

            e.Graphics.DrawLine(new Pen(TabHeader.BottomLineColor), new Point(0, this.Bottom - 1), new Point(this.Width, this.Bottom - 1));

            // 判断重绘区域大小,解决由最小化还原后,无法绘制Tab的问题
            if (currPaintTh == null || e.ClipRectangle.Size.Width > TabHeader.Left_Offset)
            {
                // 被选中的Tab 需要处于顶层,因此最后绘制
                TabHeader thSelected = null;
                foreach (TabHeader th in lstTabHeader)
                {
                    if (th.Selected)
                        thSelected = th;
                    else
                        th.DrawAll(e.Graphics, th.Rect);
                }
                // 最后绘制
                if (thSelected != null)
                    thSelected.DrawAll(e.Graphics, thSelected.Rect);
            }
            else
            {
                // 绘制完整的TabHeader,如果仅绘制指定区域,可能会出现白色背景
                currPaintTh.DrawAll(e.Graphics, currPaintTh.Rect);
                currPaintTh = null;
            }
        }

 

使用方法:

新增Tab页,调用AddNewTab 方法,参数及方法源码:

/// <summary>
        /// 新增Tab
        /// </summary>
        /// <param name="title">标题</param>
        /// <param name="font">字体</param>
        /// <param name="url">网址</param>
        public void AddNewTab(string title, Font font, string url = "")
        {
            widthCalculate();

            TabHeader newTh = new TabHeader(lstTabHeader.Count, title, font, tabWidth, this, url);
            newTh.Selected = true;

            foreach (TabHeader th in lstTabHeader)
            {
                th.Selected = false;
            }
            
            lstTabHeader.Add(newTh);
            newTh.OnPaintRequest += newTh_OnPaintRequest;

            this.Invalidate();
        }

 

 

时间仓促,先写到这。后续有时间会继续完善,增加功能以及优化性能 ,源码下载地址:

https://files.cnblogs.com/files/perpetual/BroswerX.zip

posted on 2016-06-30 16:12  yu.恒  阅读(3495)  评论(5编辑  收藏  举报

导航