國際慣例,先上圖:web
總體效果、操做風格等 與Chrome 保持高度接近,實現了標籤新增、刪除、移動、自適應寬度等特性。
核心代碼,
1:建立Tab 頁邊框:ide
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:this
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; } }
使用方法:url
新增Tab頁,調用AddNewTab 方法,參數及方法源碼:spa
/// <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(); }
時間倉促,先寫到這。後續有時間會繼續完善,增長功能以及優化性能 ,源碼下載地址:code