Last active
July 24, 2023 18:29
-
-
Save CamelCaseName/e3d46c0d0fa8a600cdc907104ac97db8 to your computer and use it in GitHub Desktop.
Fully owner draw a TabControl tab title bar in WinForms c#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Runtime.InteropServices; | |
using System; | |
using System.Collections.Generic; | |
using System.Drawing; | |
using System.Runtime.Versioning; | |
using System.Windows.Forms; | |
namespace CustomComponents | |
{ | |
[StructLayout(LayoutKind.Sequential)] | |
public struct RECT | |
{ | |
public int Left, Top, Right, Bottom; | |
public RECT(int left, int top, int right, int bottom) | |
{ | |
Left = left; | |
Top = top; | |
Right = right; | |
Bottom = bottom; | |
} | |
public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { } | |
public int X | |
{ | |
get { return Left; } | |
set { Right -= (Left - value); Left = value; } | |
} | |
public int Y | |
{ | |
get { return Top; } | |
set { Bottom -= (Top - value); Top = value; } | |
} | |
public int Height | |
{ | |
get { return Bottom - Top; } | |
set { Bottom = value + Top; } | |
} | |
public int Width | |
{ | |
get { return Right - Left; } | |
set { Right = value + Left; } | |
} | |
public System.Drawing.Point Location | |
{ | |
get { return new System.Drawing.Point(Left, Top); } | |
set { X = value.X; Y = value.Y; } | |
} | |
public System.Drawing.Size Size | |
{ | |
get { return new System.Drawing.Size(Width, Height); } | |
set { Width = value.Width; Height = value.Height; } | |
} | |
public static implicit operator System.Drawing.Rectangle(RECT r) | |
{ | |
return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height); | |
} | |
public static implicit operator RECT(System.Drawing.Rectangle r) | |
{ | |
return new RECT(r); | |
} | |
public static bool operator ==(RECT r1, RECT r2) | |
{ | |
return r1.Equals(r2); | |
} | |
public static bool operator !=(RECT r1, RECT r2) | |
{ | |
return !r1.Equals(r2); | |
} | |
public bool Equals(RECT r) | |
{ | |
return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom; | |
} | |
public override bool Equals(object? obj) | |
{ | |
if (obj is RECT rect) | |
return Equals(rect); | |
else if (obj is System.Drawing.Rectangle rectangle) | |
return Equals(new RECT(rectangle)); | |
return false; | |
} | |
public override int GetHashCode() | |
{ | |
return ((System.Drawing.Rectangle)this).GetHashCode(); | |
} | |
public override string ToString() | |
{ | |
return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom); | |
} | |
} | |
[SupportedOSPlatform("Windows")] | |
public static partial class Winutils | |
{ | |
public const int WM_LBUTTONDOWN = 0x201; | |
public const int WM_LBUTTONDBLCLK = 0x203; | |
public const int WM_PAINT = 0x00f; | |
public const int WM_ERASEBKGND = 0x00e; | |
public const int WM_MOUSEMOVE = 0x200; | |
[LibraryImport("user32.dll")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static partial bool ValidateRect(IntPtr hWnd, ref RECT lpRect); | |
} | |
[SupportedOSPlatform("Windows")] | |
public class WinTabController : TabControl | |
{ | |
private readonly SolidBrush background = new(SystemColors.ControlDark); | |
private readonly SolidBrush greyedBackground = new(SystemColors.ControlDarkDark); | |
private readonly SolidBrush blackBackground = new(SystemColors.ControlText); | |
private int backgroundRedrawTabCount = 0; | |
private bool redrawNeeded = false; | |
public WinTabController() : base() | |
{ | |
DrawMode = TabDrawMode.OwnerDrawFixed; | |
DrawItem += DrawTabTitleCards; | |
Padding = Point.Empty; | |
Margin = System.Windows.Forms.Padding.Empty; | |
Resize += RedrawClean; | |
FontChanged += RedrawClean; | |
Layout += RedrawClean; | |
} | |
private void RedrawClean(object? sender, System.EventArgs e) | |
{ | |
redrawNeeded = true; | |
Invalidate(); | |
} | |
private void DrawTabTitleCards(object? sender, DrawItemEventArgs e) | |
{ | |
if (sender == null) return; | |
if (e.Index < 0) return; | |
Font font; | |
if (TabPages[e.Index].Text.Contains('*')) | |
font = new Font(Font, FontStyle.Bold); | |
else | |
font = new Font(Font, FontStyle.Regular); | |
//backgrounds | |
if (e.State == DrawItemState.Selected) | |
{ | |
var newBounds = new Rectangle(e.Bounds.X - 2, 0, e.Bounds.Width + 4, 24); | |
e.Graphics.FillRectangle(background, newBounds); | |
TextRenderer.DrawText(e.Graphics, TabPages[e.Index].Text, font, newBounds, SystemColors.ControlText); | |
} | |
else | |
{ | |
var newBounds = new Rectangle(e.Bounds.X, 4, e.Bounds.Width, 20); | |
//remove larger selection if we are the last or first box | |
if (e.Index == 0) | |
e.Graphics.FillRectangle(blackBackground, new Rectangle(e.Bounds.X - 2, 0, e.Bounds.Width, 24)); | |
else if (e.Index == TabCount - 1) | |
e.Graphics.FillRectangle(blackBackground, new Rectangle(e.Bounds.X, 0, e.Bounds.Width + 2, 24)); | |
else | |
e.Graphics.FillRectangle(blackBackground, new Rectangle(e.Bounds.X, 0, e.Bounds.Width, 24)); | |
e.Graphics.FillRectangle(greyedBackground, newBounds); | |
TextRenderer.DrawText(e.Graphics, TabPages[e.Index].Text, font, newBounds, SystemColors.ControlText); | |
} | |
} | |
protected override void WndProc(ref Message m) | |
{ | |
if (m.Msg == Winutils.WM_PAINT) | |
{ | |
var graphics = Graphics.FromHwnd(m.HWnd); | |
var rect = new RECT(ClientRectangle); | |
//only redraw if we add or remove pages | |
if (TabCount != backgroundRedrawTabCount || redrawNeeded) | |
{ | |
backgroundRedrawTabCount = TabCount; | |
graphics.Clear(SystemColors.ControlText); | |
redrawNeeded = false; | |
} | |
for (int i = 0; i < TabCount; i++) | |
{ | |
DrawTabTitleCards(this, new DrawItemEventArgs(graphics, Font, GetTabRect(i), i, SelectedIndex == i ? DrawItemState.Selected : DrawItemState.Default)); | |
} | |
m.Result = 0; | |
Winutils.ValidateRect(m.HWnd, ref rect); | |
} | |
else | |
{ | |
base.WndProc(ref m); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment