To extend the TabControl with Close and Add Button solution. Create a new class and inherit from the TabControl
, set the control styles shown below in the constructor to mainly draw the control and the tabs the way you want it.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
namespace YourProjectNamespace
{
[DesignerCategory("Code")]
[ToolboxBitmap(typeof(TabControl))]
public class TabControlEx : TabControl
{
public TabControlEx() : base()
{
SetStyle(ControlStyles.AllPaintingInWmPaint
| ControlStyles.ResizeRedraw
| ControlStyles.OptimizedDoubleBuffer
| ControlStyles.UserPaint, true);
UpdateStyles();
Padding = new Point(14, 4);
}
private Color backColor = Color.FromArgb(40, 40, 40);
/// <inheritdoc cref="Control.BackColor"/>
[DefaultValue(typeof(Color), "40, 40, 40"),
Browsable(true),
EditorBrowsable(EditorBrowsableState.Always),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public new Color BackColor
{
get => backColor;
set
{
if (backColor != value)
{
backColor = value;
Invalidate();
}
}
}
private Image addImage;
[DefaultValue(null)]
public Image AddButtonImage
{
get => addImage;
set
{
addImage = value;
Invalidate();
}
}
private Image closeImage;
[DefaultValue(null)]
public Image CloseButtonImage
{
get => closeImage;
set
{
closeImage = value;
Invalidate();
}
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SendMessage(Handle, TCM_SETMINTABWIDTH, IntPtr.Zero, (IntPtr)16);
if (TabCount > 0) TabPages[TabCount - 1].Text = "";
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.Clear(BackColor);
if (TabCount == 0) return;
for (int i = 0; i < TabCount; i++)
{
var tp = TabPages[i];
var r = Rectangle.Inflate(GetTabRect(i), -4, 0);
if (i == TabCount - 1)
{
if (AddButtonImage != null)
g.DrawImage(AddButtonImage,
r.X + (r.Width - AddButtonImage.Width) / 2,
r.Y + (r.Height - AddButtonImage.Height) / 2,
AddButtonImage.Width, AddButtonImage.Height);
}
else
{
if (i == SelectedIndex)
using (var brSel = new SolidBrush(Color.FromArgb(105, 105, 105)))
g.FillRectangle(brSel, Rectangle.Inflate(r, 4, 1));
if (ImageList != null && tp.ImageIndex >= 0)
ImageList.Draw(g,
r.X, r.Y + (r.Height - ImageList.ImageSize.Height) / 2,
tp.ImageIndex);
if (CloseButtonImage != null)
{
var closeRect = new Rectangle(new Point(
r.Right - CloseButtonImage.Width,
r.Y + (r.Height - CloseButtonImage.Height) / 2),
CloseButtonImage.Size);
g.DrawImage(CloseButtonImage, closeRect,
0, 0, CloseButtonImage.Width, CloseButtonImage.Height,
GraphicsUnit.Pixel);
}
TextRenderer.DrawText(g, tp.Text, tp.Font, r, Color.White,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter);
}
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
var lastIndex = TabCount - 1;
if (GetTabRect(lastIndex).Contains(e.Location))
{
var args = new AddingTabEventArgs();
OnAddingTab(args);
if (args.Cancel) return;
var newTp = new TabPage
{
Text = "New Tab",
BackColor = BackColor
};
TabPages.Insert(lastIndex, newTp);
SelectedIndex = lastIndex;
}
else
{
for (var i = 0; i < TabPages.Count; i++)
{
var r = Rectangle.Inflate(GetTabRect(i), -4, 0);
if (new Rectangle(r.Right - 16, r.Y + (r.Height - 16), 16, 16)
.Contains(e.Location))
{
var args = new ClosingTabEventArgs(TabPages[i]);
OnClosingTab(args);
if (args.Cancel) return;
TabPages[i].Dispose();
break;
}
}
}
}
protected override void OnSelecting(TabControlCancelEventArgs e)
{
if (e.TabPageIndex == TabCount - 1)
e.Cancel = true;
else
base.OnSelecting(e);
}
public event EventHandler<ClosingTabEventArgs> ClosingTab;
protected virtual void OnClosingTab(ClosingTabEventArgs e) =>
ClosingTab?.Invoke(this, e);
public event EventHandler<AddingTabEventArgs> AddingTab;
protected virtual void OnAddingTab(AddingTabEventArgs e) =>
AddingTab?.Invoke(this, e);
const int TCM_SETMINTABWIDTH = 0x1300 + 49;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
public class ClosingTabEventArgs : EventArgs
{
public ClosingTabEventArgs(TabPage tp) => TabPage = tp;
public TabPage TabPage { get; }
public bool Cancel { get; set; }
}
public class AddingTabEventArgs : EventArgs
{
public AddingTabEventArgs() { }
public bool Cancel { get; set; }
}
}
Note, I've added two more events, AddingTab
and ClosingTab
to trap them in your implementation and do whatever you need, including cancelation.
private void tabControlEx1_AddingTab(object sender, AddingTabEventArgs e)
{
if (MessageBox.Show("Add new TabPage?", "Alert",
MessageBoxButtons.YesNo) == DialogResult.No)
e.Cancel = true;
}
private void tabControlEx1_ClosingTab(object sender, ClosingTabEventArgs e)
{
if (MessageBox.Show($"Remove {e.TabPage.Text}?", "Alert",
MessageBoxButtons.YesNo) == DialogResult.No)
e.Cancel = true;
}
Of course, you can extend the control more and add more drawing properties like the back/foreground colors of the normal and selected tabs. Also, you can try the LinearGradientBrush
to fill the backgrounds instead of the solid brushes.
Rebuild, on the top of the ToolBox
window, find and drop an instance. Try and go back to the code to add your touch.
