Skip to content

Instantly share code, notes, and snippets.

@CamelCaseName
Last active July 24, 2023 18:29
Show Gist options
  • Save CamelCaseName/e3d46c0d0fa8a600cdc907104ac97db8 to your computer and use it in GitHub Desktop.
Save CamelCaseName/e3d46c0d0fa8a600cdc907104ac97db8 to your computer and use it in GitHub Desktop.
Fully owner draw a TabControl tab title bar in WinForms c#
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