How to Change the colours of a Tabcontrols Header Item.
The first thing to do is to set the TabControls DrawMode to OwnerDrawFixed as shown above. Then simply add the code shown below.
I'll leave you the challenge of modifying the code to show an image.
Private Sub TabControl1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles TabControl1.DrawItem
'Firstly we'll define some parameters.
Dim CurrentTab As TabPage = TabControl1.TabPages(e.Index)
Dim ItemRect As Rectangle = TabControl1.GetTabRect(e.Index)
Dim FillBrush As New SolidBrush(Color.Red)
Dim TextBrush As New SolidBrush(Color.White)
Dim sf As New StringFormat
sf.Alignment = StringAlignment.Center
sf.LineAlignment = StringAlignment.Center
'If we are currently painting the Selected TabItem we'll
'change the brush colors and inflate the rectangle.
If CBool(e.State And DrawItemState.Selected) Then
FillBrush.Color = Color.White
TextBrush.Color = Color.Red
ItemRect.Inflate(2, 2)
End If
'Set up rotation for left and right aligned tabs
If TabControl1.Alignment = TabAlignment.Left Or TabControl1.Alignment = TabAlignment.Right Then
Dim RotateAngle As Single = 90
If TabControl1.Alignment = TabAlignment.Left Then RotateAngle = 270
Dim cp As New PointF(ItemRect.Left + (ItemRect.Width \ 2), ItemRect.Top + (ItemRect.Height \ 2))
e.Graphics.TranslateTransform(cp.X, cp.Y)
e.Graphics.RotateTransform(RotateAngle)
ItemRect = New Rectangle(-(ItemRect.Height \ 2), -(ItemRect.Width \ 2), ItemRect.Height, ItemRect.Width)
End If
'Next we'll paint the TabItem with our Fill Brush
e.Graphics.FillRectangle(FillBrush, ItemRect)
'Now draw the text.
e.Graphics.DrawString(CurrentTab.Text, e.Font, TextBrush, RectangleF.op_Implicit(ItemRect), sf)
'Reset any Graphics rotation
e.Graphics.ResetTransform()
'Finally, we should Dispose of our brushes.
FillBrush.Dispose()
TextBrush.Dispose()
End Sub
private void tabControl1_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
TabPage CurrentTab = tabControl1.TabPages[e.Index];
Rectangle ItemRect = tabControl1.GetTabRect(e.Index);
SolidBrush FillBrush = new SolidBrush(Color.Red);
SolidBrush TextBrush = new SolidBrush(Color.White);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
//If we are currently painting the Selected TabItem we'll
//change the brush colors and inflate the rectangle.
if (System.Convert.ToBoolean(e.State & DrawItemState.Selected))
{
FillBrush.Color = Color.White;
TextBrush.Color = Color.Red;
ItemRect.Inflate(2, 2);
}
//Set up rotation for left and right aligned tabs
if (tabControl1.Alignment == TabAlignment.Left || tabControl1.Alignment == TabAlignment.Right)
{
float RotateAngle = 90;
if (tabControl1.Alignment == TabAlignment.Left)
RotateAngle = 270;
PointF cp = new PointF(ItemRect.Left + (ItemRect.Width / 2), ItemRect.Top + (ItemRect.Height / 2));
e.Graphics.TranslateTransform(cp.X, cp.Y);
e.Graphics.RotateTransform(RotateAngle);
ItemRect = new Rectangle(-(ItemRect.Height / 2), -(ItemRect.Width / 2), ItemRect.Height, ItemRect.Width);
}
//Next we'll paint the TabItem with our Fill Brush
e.Graphics.FillRectangle(FillBrush, ItemRect);
//Now draw the text.
e.Graphics.DrawString(CurrentTab.Text, e.Font, TextBrush, RectangleF.op_Implicit(ItemRect), sf);
//Reset any Graphics rotation
e.Graphics.ResetTransform();
//Finally, we should Dispose of our brushes.
FillBrush.Dispose();
TextBrush.Dispose();
}
A Completely OwnerDraw TabControl.
A little more work involved here than in the previous case, but definately worth it. Add a new UserControl to your project and replace the code with that shown below. As you can see we have made the BackColor property of the TabControl Visible in the Designer and put a custom background behind Transparent Tabpages.
This control has been updated to incorporate a modified version of the SelectedIndexChanging event in Matt Hinz and Ken Tuckers Tabcontrol source.
The project needs a custom designer to get the Design time painting to behave properly, but that's something for a later project.
You'll find TabControlEx on my Controls page with all the work already done.
Modify the appearance to reflect the style you want.
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Reflection
Public Class TabControl
Inherits System.Windows.Forms.TabControl
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
setstyle(ControlStyles.AllPaintingInWmPaint Or _
ControlStyles.DoubleBuffer Or _
ControlStyles.ResizeRedraw Or _
ControlStyles.UserPaint, True)
End Sub
'UserControl1 overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container
End Sub
#End Region
#Region " InterOP "
<StructLayout(LayoutKind.Sequential)> _
Private Structure NMHDR
Public HWND As Int32
Public idFrom As Int32
Public code As Int32
Public Overloads Function ToString() As String
Return String.Format("Hwnd: {0}, ControlID: {1}, Code: {2}", HWND, idFrom, code)
End Function
End Structure
Private Const TCN_FIRST As Int32 = &HFFFFFFFFFFFFFDDA&
Private Const TCN_SELCHANGING As Int32 = (TCN_FIRST - 2)
Private Const WM_USER As Int32 = &H400&
Private Const WM_NOTIFY As Int32 = &H4E&
Private Const WM_REFLECT As Int32 = WM_USER + &H1C00&
#End Region
#Region " BackColor Manipulation "
'As well as exposing the property to the Designer we want it to behave just like any other
'controls BackColor property so we need some clever manipulation.
Private m_Backcolor As Color = Color.Empty
<Browsable(True), _
Description("The background color used to display text and graphics in a control.")> _
Public Overrides Property BackColor() As Color
Get
If m_Backcolor.Equals(Color.Empty) Then
If Parent Is Nothing Then
Return Control.DefaultBackColor
Else
Return Parent.BackColor
End If
End If
Return m_Backcolor
End Get
Set(ByVal Value As Color)
If m_Backcolor.Equals(Value) Then Return
m_Backcolor = Value
Invalidate()
'Let the Tabpages know that the backcolor has changed.
MyBase.OnBackColorChanged(EventArgs.Empty)
End Set
End Property
Public Function ShouldSerializeBackColor() As Boolean
Return Not m_Backcolor.Equals(Color.Empty)
End Function
Public Overrides Sub ResetBackColor()
m_Backcolor = Color.Empty
Invalidate()
End Sub
#End Region
Protected Overrides Sub OnParentBackColorChanged(ByVal e As System.EventArgs)
MyBase.OnParentBackColorChanged(e)
Invalidate()
End Sub
Protected Overrides Sub OnSelectedIndexChanged(ByVal e As System.EventArgs)
MyBase.OnSelectedIndexChanged(e)
Invalidate()
End Sub
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
e.Graphics.Clear(BackColor)
Dim r As Rectangle = Me.ClientRectangle
If TabCount <= 0 Then Return
'Draw a custom background for Transparent TabPages
r = SelectedTab.Bounds
Dim sf As New StringFormat
sf.Alignment = StringAlignment.Center
sf.LineAlignment = StringAlignment.Center
Dim DrawFont As New Font(Font.FontFamily, 24, FontStyle.Regular, GraphicsUnit.Pixel)
ControlPaint.DrawStringDisabled(e.Graphics, "Micks Ownerdraw TabControl", DrawFont, BackColor, RectangleF.op_Implicit(r), sf)
DrawFont.Dispose()
'Draw a border around TabPage
r.Inflate(3, 3)
Dim tp As TabPage = TabPages(SelectedIndex)
Dim PaintBrush As New SolidBrush(tp.BackColor)
e.Graphics.FillRectangle(PaintBrush, r)
ControlPaint.DrawBorder(e.Graphics, r, PaintBrush.Color, ButtonBorderStyle.Outset)
'Draw the Tabs
For index As Integer = 0 To TabCount - 1
tp = TabPages(index)
r = GetTabRect(index)
Dim bs As ButtonBorderStyle = ButtonBorderStyle.Outset
If index = SelectedIndex Then bs = ButtonBorderStyle.Inset
PaintBrush.Color = tp.BackColor
e.Graphics.FillRectangle(PaintBrush, r)
ControlPaint.DrawBorder(e.Graphics, r, PaintBrush.Color, bs)
PaintBrush.Color = tp.ForeColor
'Set up rotation for left and right aligned tabs
If Alignment = TabAlignment.Left Or Alignment = TabAlignment.Right Then
Dim RotateAngle As Single = 90
If Alignment = TabAlignment.Left Then RotateAngle = 270
Dim cp As New PointF(r.Left + (r.Width \ 2), r.Top + (r.Height \ 2))
e.Graphics.TranslateTransform(cp.X, cp.Y)
e.Graphics.RotateTransform(RotateAngle)
r = New Rectangle(-(r.Height \ 2), -(r.Width \ 2), r.Height, r.Width)
End If
'Draw the Tab Text
If tp.Enabled Then
e.Graphics.DrawString(tp.Text, Font, PaintBrush, RectangleF.op_Implicit(r), sf)
Else
ControlPaint.DrawStringDisabled(e.Graphics, tp.Text, Font, tp.BackColor, RectangleF.op_Implicit(r), sf)
End If
e.Graphics.ResetTransform()
Next
PaintBrush.Dispose()
End Sub
<Description("Occurs as a tab is being changed.")> _
Public Event SelectedIndexChanging As SelectedTabPageChangeEventHandler
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = (WM_REFLECT + WM_NOTIFY) Then
Dim hdr As NMHDR = DirectCast(Marshal.PtrToStructure(m.LParam, GetType(NMHDR)), NMHDR)
If hdr.code = TCN_SELCHANGING Then
Dim tp As TabPage = TestTab(Me.PointToClient(Cursor.Position))
If Not tp Is Nothing Then
Dim e As New TabPageChangeEventArgs(Me.SelectedTab, tp)
RaiseEvent SelectedIndexChanging(Me, e)
If e.Cancel OrElse tp.Enabled = False Then
m.Result = New IntPtr(1)
Return
End If
End If
End If
End If
MyBase.WndProc(m)
End Sub
Private Function TestTab(ByVal pt As Point) As TabPage
For index As Integer = 0 To TabCount - 1
If GetTabRect(index).Contains(pt.X, pt.Y) Then
Return TabPages(index)
End If
Next
Return Nothing
End Function
End Class
#Region " EventArgs Class's "
Public Class TabPageChangeEventArgs
Inherits EventArgs
Private _Selected As TabPage
Private _PreSelected As TabPage
Public Cancel As Boolean = False
Public ReadOnly Property CurrentTab() As TabPage
Get
Return _Selected
End Get
End Property
Public ReadOnly Property NextTab() As TabPage
Get
Return _PreSelected
End Get
End Property
Public Sub New(ByVal CurrentTab As TabPage, ByVal NextTab As TabPage)
_Selected = CurrentTab
_PreSelected = NextTab
End Sub
End Class
Public Delegate Sub SelectedTabPageChangeEventHandler(ByVal sender As Object, ByVal e As TabPageChangeEventArgs)
#End Region
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Dotnetrix_Samples
{
/// <summary>
/// Summary description for TabControl.
/// </summary>
public class TabControl : System.Windows.Forms.TabControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public TabControl()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
// TODO: Add any initialization after the InitializeComponent call
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
#region Interop
[StructLayout(LayoutKind.Sequential)]
private struct NMHDR
{
public IntPtr HWND;
public uint idFrom;
public int code;
public override String ToString()
{
return String.Format("Hwnd: {0}, ControlID: {1}, Code: {2}", HWND, idFrom, code);
}
}
private const int TCN_FIRST = 0 - 550;
private const int TCN_SELCHANGING = (TCN_FIRST - 2);
private const int WM_USER = 0x400;
private const int WM_NOTIFY = 0x4E;
private const int WM_REFLECT = WM_USER + 0x1C00;
#endregion
#region BackColor Manipulation
//As well as exposing the property to the Designer we want it to behave just like any other
//controls BackColor property so we need some clever manipulation.
private Color m_Backcolor = Color.Empty;
[Browsable(true),Description("The background color used to display text and graphics in a control.")]
public override Color BackColor
{
get
{
if (m_Backcolor.Equals(Color.Empty))
{
if (Parent == null)
return Control.DefaultBackColor;
else
return Parent.BackColor;
}
return m_Backcolor;
}
set
{
if (m_Backcolor.Equals(value)) return;
m_Backcolor = value;
Invalidate();
//Let the Tabpages know that the backcolor has changed.
base.OnBackColorChanged(EventArgs.Empty);
}
}
public bool ShouldSerializeBackColor()
{
return !m_Backcolor.Equals(Color.Empty);
}
public override void ResetBackColor()
{
m_Backcolor = Color.Empty;
Invalidate();
}
#endregion
protected override void OnParentBackColorChanged(EventArgs e)
{
base.OnParentBackColorChanged (e);
Invalidate();
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged (e);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint (e);
e.Graphics.Clear(BackColor);
Rectangle r = ClientRectangle;
if (TabCount <= 0) return;
//Draw a custom background for Transparent TabPages
r = SelectedTab.Bounds;
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
Font DrawFont = new Font(Font.FontFamily, 24, FontStyle.Regular, GraphicsUnit.Pixel);
ControlPaint.DrawStringDisabled(e.Graphics, "Micks Ownerdraw TabControl", DrawFont, BackColor, (RectangleF)r, sf);
DrawFont.Dispose();
//Draw a border around TabPage
r.Inflate(3, 3);
TabPage tp = TabPages[SelectedIndex];
SolidBrush PaintBrush = new SolidBrush(tp.BackColor);
e.Graphics.FillRectangle(PaintBrush, r);
ControlPaint.DrawBorder(e.Graphics, r, PaintBrush.Color, ButtonBorderStyle.Outset);
//Draw the Tabs
for (int index = 0; index <= TabCount - 1; index++)
{
tp = TabPages[index];
r = GetTabRect(index);
ButtonBorderStyle bs = ButtonBorderStyle.Outset;
if (index == SelectedIndex) bs = ButtonBorderStyle.Inset;
PaintBrush.Color = tp.BackColor;
e.Graphics.FillRectangle(PaintBrush, r);
ControlPaint.DrawBorder(e.Graphics, r, PaintBrush.Color, bs);
PaintBrush.Color = tp.ForeColor;
//Set up rotation for left and right aligned tabs
if (Alignment == TabAlignment.Left || Alignment == TabAlignment.Right)
{
float RotateAngle = 90;
if (Alignment == TabAlignment.Left) RotateAngle = 270;
PointF cp = new PointF(r.Left + (r.Width >> 1), r.Top + (r.Height >> 1));
e.Graphics.TranslateTransform(cp.X, cp.Y);
e.Graphics.RotateTransform(RotateAngle);
r = new Rectangle(-(r.Height >> 1), -(r.Width >> 1), r.Height, r.Width);
}
//Draw the Tab Text
if (tp.Enabled)
e.Graphics.DrawString(tp.Text, Font, PaintBrush, RectangleF.op_Implicit(r), sf);
else
ControlPaint.DrawStringDisabled(e.Graphics, tp.Text, Font, tp.BackColor, RectangleF.op_Implicit(r), sf);
e.Graphics.ResetTransform();
}
PaintBrush.Dispose();
}
[Description("Occurs as a tab is being changed.")]
public event SelectedTabPageChangeEventHandler SelectedIndexChanging;
protected override void WndProc(ref Message m)
{
if (m.Msg == (WM_REFLECT + WM_NOTIFY))
{
NMHDR hdr = (NMHDR)(Marshal.PtrToStructure(m.LParam, typeof(NMHDR)));
if (hdr.code == TCN_SELCHANGING)
{
TabPage tp = TestTab(PointToClient(Cursor.Position));
if (tp != null)
{
TabPageChangeEventArgs e = new TabPageChangeEventArgs(SelectedTab, tp);
SelectedIndexChanging(this, e);
if (e.Cancel || tp.Enabled == false)
{
m.Result = new IntPtr(1);
return;
}
}
}
}
base.WndProc (ref m);
}
private TabPage TestTab(Point pt)
{
for (int index = 0; index <= TabCount - 1; index++)
{
if (GetTabRect(index).Contains(pt.X, pt.Y))
return TabPages[index];
}
return null;
}
}
public class TabPageChangeEventArgs : EventArgs
{
private TabPage _Selected = null;
private TabPage _PreSelected = null;
public bool Cancel = false;
public TabPage CurrentTab
{
get
{
return _Selected;
}
}
public TabPage NextTab
{
get
{
return _PreSelected;
}
}
public TabPageChangeEventArgs(TabPage CurrentTab, TabPage NextTab)
{
_Selected = CurrentTab;
_PreSelected = NextTab;
}
}
public delegate void SelectedTabPageChangeEventHandler(Object sender, TabPageChangeEventArgs e);
}
Associate a ContextMenu with the TabItem headers of a Tabcontrol.
A nice little trick. At least I think it is.
'Variable to store the tabpage which belongs to the headeritem
'over which the cursor is currently hovering.
Private CurrentTabItem As TabPage
Private Sub TabControl1_MouseMove(ByVal sender As Object,ByVal e As System.Windows.Forms.MouseEventArgs)Handles TabControl1.MouseMove
Dim HoverTab As Integer = TestTab(New Point(e.X, e.Y))
If HoverTab >= 0 Then
CurrentTabItem = TabControl1.TabPages(HoverTab)
End If
If TabControl1.ContextMenu Is Nothing Then
TabControl1.ContextMenu = Me.ContextMenu1
End If
End Sub
Private Function TestTab(ByVal pt As Point) As Integer
Dim returnIndex As Integer = -1
For index As Integer = 0 To TabControl1.TabCount - 1
If TabControl1.GetTabRect(index).Contains(pt.X, pt.Y) Then
returnIndex = index
End If
Next
Return returnIndex
End Function
Private Sub TabControl1_MouseLeave(ByVal sender As Object,ByVal e As System.EventArgs)Handles TabControl1.MouseLeave
TabControl1.ContextMenu = Nothing
End Sub
//Variable to store the tabpage which belongs to the headeritem
//over which the cursor is currently hovering.
private TabPage CurrentTabItem;
private void tabControl1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
int HoverTab = TestTab(new Point(e.X, e.Y));
if (HoverTab >= 0)
CurrentTabItem = tabControl1.TabPages[HoverTab];
if (tabControl1.ContextMenu == null)
tabControl1.ContextMenu = contextMenu1;
}
private int TestTab(Point pt)
{
int returnIndex = -1;
for (int index = 0; index <= tabControl1.TabCount - 1; index++)
{
if (tabControl1.GetTabRect(index).Contains(pt.X, pt.Y))
returnIndex = index;
}
return returnIndex;
}
private void tabControl1_MouseLeave(object sender, System.EventArgs e)
{
tabControl1.ContextMenu = null;
}
Hide and Show Tabpages in a Tabcontrol.
The Visible property has not been implemented on the TabControl, and there is no Insert method. The following functions are a workaround.
Private Sub HideTabPage(ByVal tp As TabPage)
If TabControl1.TabPages.Contains(tp) Then TabControl1.TabPages.Remove(tp)
End Sub
Private Sub ShowTabPage(ByVal tp As TabPage)
ShowTabPage(tp, TabControl1.TabPages.Count)
End Sub
Private Sub ShowTabPage(ByVal tp As TabPage, ByVal index As Integer)
If TabControl1.TabPages.Contains(tp) Then Return
InsertTabPage(tp, index)
End Sub
Private Sub InsertTabPage(ByVal [tabpage] As TabPage, ByVal [index] As Integer)
If [index] < 0 Or [index] > TabControl1.TabCount Then
Throw New ArgumentException("Index out of Range.")
End If
TabControl1.TabPages.Add([tabpage])
If [index] < TabControl1.TabCount - 1 Then
Do While TabControl1.TabPages.IndexOf([tabpage]) <> [index]
SwapTabPages([tabpage], (TabControl1.TabPages(TabControl1.TabPages.IndexOf([tabpage]) - 1)))
Loop
End If
TabControl1.SelectedTab = [tabpage]
End Sub
Private Sub SwapTabPages(ByVal tp1 As TabPage, ByVal tp2 As TabPage)
If TabControl1.TabPages.Contains(tp1) = False Or TabControl1.TabPages.Contains(tp2) = False Then
Throw New ArgumentException("TabPages must be in the TabCotrols TabPageCollection.")
End If
Dim Index1 As Integer = TabControl1.TabPages.IndexOf(tp1)
Dim Index2 As Integer = TabControl1.TabPages.IndexOf(tp2)
TabControl1.TabPages(Index1) = tp2
TabControl1.TabPages(Index2) = tp1
'Uncomment the following section to overcome bugs in the Compact Framework
'TabControl1.SelectedIndex = TabControl1.SelectedIndex
'Dim tp1Text, tp2Text As String
'tp1Text = tp1.Text
'tp2Text = tp2.Text
'tp1.Text=tp2Text
'tp2.Text=tp1Text
End Sub
private void HideTabPage(TabPage tp)
{
if (tabControl1.TabPages.Contains(tp))
tabControl1.TabPages.Remove(tp);
}
private void ShowTabPage(TabPage tp)
{
ShowTabPage(tp, tabControl1.TabPages.Count);
}
private void ShowTabPage(TabPage tp , int index)
{
if (tabControl1.TabPages.Contains(tp)) return;
InsertTabPage(tp, index);
}
private void InsertTabPage(TabPage tabpage, int index)
{
if (index < 0 || index > tabControl1.TabCount)
throw new ArgumentException("Index out of Range.");
tabControl1.TabPages.Add(tabpage);
if (index < tabControl1.TabCount - 1)
do
{
SwapTabPages(tabpage, (tabControl1.TabPages[tabControl1.TabPages.IndexOf(tabpage) - 1]));
}
while (tabControl1.TabPages.IndexOf(tabpage) != index);
tabControl1.SelectedTab = tabpage;
}
private void SwapTabPages(TabPage tp1, TabPage tp2)
{
if (tabControl1.TabPages.Contains(tp1) == false || tabControl1.TabPages.Contains(tp2) == false)
throw new ArgumentException("TabPages must be in the TabControls TabPageCollection.");
int Index1 = tabControl1.TabPages.IndexOf(tp1);
int Index2 = tabControl1.TabPages.IndexOf(tp2);
tabControl1.TabPages[Index1] = tp2;
tabControl1.TabPages[Index2] = tp1;
//Uncomment the following section to overcome bugs in the Compact Framework
//tabControl1.SelectedIndex = tabControl1.SelectedIndex;
//string tp1Text, tp2Text;
//tp1Text = tp1.Text;
//tp2Text = tp2.Text;
//tp1.Text=tp2Text;
//tp2.Text=tp1Text;
}
An Alternative approach to TabPage Navigation.
As well as a customised Navigation System this option allows you to Enable Disable TabPages without the need to use a modified TabControl.
This tip is mainly property manipulation.
For a better solution see the PanelManager control on my Custom Controls page.
Enjoy!
'TabControl Settings:
' TabControl.Appearance = Button
' TabControl.SizeMode = Fixed.
' TabControl.ItemSize = 0,1
' TabControl.Alignment = Top
'Now add controls for Navigation.
'You can use any control of your choice i.e. Toolbar or Buttons.
'For the purpose of this tip I have a TabControl with 5 Tabpages and for
'navigation I am using a Horizontal Scrollbar with the following settings:
' Minimum = 0
' Maximum = 4
' SmallChange = 1
' LargeChange = 1
'a scrollbar is not really a good choice for navigation, but it serves to show how versatile this method is.
Private Sub HScrollBar1_Scroll(ByVal sender As System.Object, ByVal e As System.Windows.Forms.ScrollEventArgs) Handles HScrollBar1.Scroll
TabControl1.SelectedIndex = HScrollBar1.Value
End Sub
//TabControl Settings:
// TabControl.Appearance = Buttons
// TabControl.SizeMode = Fixed.
// TabControl.ItemSize = 0,1
// TabControl.Alignment = Top
//Now add controls for Navigation.
//You can use any control of your choice i.e. Toolbar or Buttons.
//For the purpose of this tip I have a TabControl with 5 Tabpages and for
//navigation I am using a Horizontal Scrollbar with the following settings:
// Minimum = 0
// Maximum = 4
// SmallChange = 1
// LargeChange = 1
//a scrollbar is not really a good choice for navigation, but it serves to show how versatile this method is.
private void hScrollBar1_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e)
{
tabControl1.SelectedIndex = hScrollBar1.Value;
}
Tabpage order has changed.
This is not random. The tabpages appear in their zOrder.
I do not know why the zOrder changes but it does.
The simple solution to reapplying the zOrder is, starting from the first tabitem and working your way to the last, select each tabpage in turn, in the order in which they should appear, (make sure the tabpage is selected and not the tabcontrol) and right click the tabpage. A menu will appear and you should select SendToBack. You may need to close and reopen the form designer to see the result in the IDE but it will be correct at runtime.
Reposition TabItems at Runtime.
If you want the ability to Drag Tabs around at runtime then this code will do it for you.
Add a TabControl (with the AllowDrop property set to True) and the code below to your form.
This code is based upon code submitted to CodeProject by Paul Auger. The original is here.
Assumes your Tabcontrol is named TabControl1.
Private DragStartPosition As Point = Point.Empty
Private Sub TabControl1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TabControl1.MouseDown
DragStartPosition = New Point(e.X, e.Y)
End Sub
Private Sub TabControl1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TabControl1.MouseMove
If e.Button <> MouseButtons.Left Then Return
Dim r As Rectangle = New Rectangle(DragStartPosition, Size.Empty)
r.Inflate(SystemInformation.DragSize)
Dim tp As TabPage = HoverTab()
If Not tp Is Nothing Then
If Not r.Contains(e.X, e.Y) Then
TabControl1.DoDragDrop(tp, DragDropEffects.All)
End If
End If
DragStartPosition = Point.Empty
End Sub
Private Sub TabControl1_DragOver(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles TabControl1.DragOver
Dim hover_Tab As TabPage = HoverTab()
If hover_Tab Is Nothing Then
e.Effect = DragDropEffects.None
Else
If e.Data.GetDataPresent(GetType(TabPage)) Then
e.Effect = DragDropEffects.Move
Dim drag_tab As TabPage = DirectCast(e.Data.GetData(GetType(TabPage)), TabPage)
If hover_Tab Is drag_tab Then Return
Dim TabRect As Rectangle = TabControl1.GetTabRect(TabControl1.TabPages.IndexOf(hover_Tab))
TabRect.Inflate(-3, -3)
If TabRect.Contains(TabControl1.PointToClient(New Point(e.X, e.Y))) Then
SwapTabPages(drag_tab, hover_Tab)
TabControl1.SelectedTab = drag_tab
End If
End If
End If
End Sub
Private Function HoverTab() As TabPage
For index As Int32 = 0 To TabControl1.TabCount - 1
If TabControl1.GetTabRect(index).Contains(TabControl1.PointToClient(Cursor.Position)) Then
Return TabControl1.TabPages(index)
End If
Next
End Function
Private Sub SwapTabPages(ByVal tp1 As TabPage, ByVal tp2 As TabPage)
Dim Index1 As Integer = TabControl1.TabPages.IndexOf(tp1)
Dim Index2 As Integer = TabControl1.TabPages.IndexOf(tp2)
TabControl1.TabPages(Index1) = tp2
TabControl1.TabPages(Index2) = tp1
End Sub
private Point DragStartPosition = Point.Empty;
private void tabControl1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
DragStartPosition = new Point(e.X, e.Y);
}
private void tabControl1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
Rectangle r = new Rectangle(DragStartPosition, Size.Empty);
r.Inflate(SystemInformation.DragSize);
TabPage tp = HoverTab();
if (tp != null)
{
if (!r.Contains(e.X, e.Y))
tabControl1.DoDragDrop(tp, DragDropEffects.All);
}
DragStartPosition = Point.Empty;
}
private void tabControl1_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
TabPage hover_Tab = HoverTab();
if (hover_Tab == null)
e.Effect = DragDropEffects.None;
else
{
if (e.Data.GetDataPresent(typeof(TabPage)))
{
e.Effect = DragDropEffects.Move;
TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage));
if (hover_Tab == drag_tab) return;
Rectangle TabRect= tabControl1.GetTabRect(tabControl1.TabPages.IndexOf(hover_Tab));
TabRect.Inflate(-3, -3);
if (TabRect.Contains(tabControl1.PointToClient(new Point(e.X, e.Y))))
{
SwapTabPages(drag_tab, hover_Tab);
tabControl1.SelectedTab = drag_tab;
}
}
}
}
private TabPage HoverTab()
{
for (int index = 0; index <= tabControl1.TabCount - 1; index++)
{
if (tabControl1.GetTabRect(index).Contains(tabControl1.PointToClient(Cursor.Position)))
return tabControl1.TabPages[index];
}
return null;
}
private void SwapTabPages(TabPage tp1, TabPage tp2)
{
int Index1 = tabControl1.TabPages.IndexOf(tp1);
int Index2 = tabControl1.TabPages.IndexOf(tp2);
tabControl1.TabPages[Index1] = tp2;
tabControl1.TabPages[Index2] = tp1;
}
Add Mnemonic support to TabPages.
You must resort to drawing the Tabitems yourself to get the underlined character to show.
This is a very basic ownerdraw example.
Set the Forms KeyPreview Property to True.
Set the TabControls DrawMode Property to OwnerDrawFixed and add the code below to your form.
Assumes your Tabcontrol is named TabControl1.
Private Sub TabControl1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles TabControl1.DrawItem
Dim tc As TabControl = DirectCast(sender, TabControl)
Dim sf As New StringFormat
sf.Alignment = StringAlignment.Center
sf.LineAlignment = StringAlignment.Center
sf.HotkeyPrefix = Drawing.Text.HotkeyPrefix.Show
If tc.SelectedIndex = e.Index Then
e.Graphics.FillRectangle(SystemBrushes.Control, e.Bounds)
End If
e.Graphics.DrawString(tc.TabPages(e.Index).Text, tc.Font, SystemBrushes.ControlText, RectangleF.op_Implicit(e.Bounds), sf)
sf.Dispose()
End Sub
Protected Overrides Function ProcessMnemonic(ByVal charCode As Char) As Boolean
For Each tp As TabPage In TabControl1.TabPages
If IsMnemonic(charCode, tp.Text) Then
TabControl1.SelectedTab = tp
Return True
End If
Next
Return MyBase.ProcessMnemonic(charCode)
End Function
TabControl using Custom TabPages.
This was one of the first projects I tackled in dotnet. The object was to have the tabpages support WindowsXP Visual Style. Adding visual style to an inherited TabPage was simple and after a few attempts I got the TabControl to accept the custom Tabpages via the Collection Editor.
Well since then I have learned a lot and I decided that I would rewrite the control. My goal this time was to get the DesignerVerbs to add the customised Tabpages as well as add an Insert verb. This turned out to be quite a challenge, since the TabControl Designer would not detect mouse clicks on any part of the control that was not a TabPage or TabItem. When I finally found the solution to this problem, as is usually the case, it was very simple to overcome. While I was at it I added an OnselectedIndexChanging event so that Tabpage changes may be cancelled, and a HotTab variable so you can check which tabitem the cursor is currently over. It would have been nice to add Mnemonic support, but that would involve taking full responsibility for painting the TabControl. If I'm going to do this then I may as well write the control from scratch.
Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.ComponentModel.Design
Imports System.Runtime.InteropServices
Namespace Controls
#Region " TabControlEx Class "
<ToolboxBitmap(GetType(System.Windows.Forms.TabControl)), _
Designer(GetType(Designers.TabControlExDesigner))> _
Public Class TabControlEx
Inherits System.Windows.Forms.TabControl
Public Event SelectedIndexChanging As TabControlExEventHandler
Public HotTab As TabPage = Nothing
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'UserControl1 overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
components = New System.ComponentModel.Container
End Sub
#End Region
#Region " Properties "
<Editor(GetType(TabpageExCollectionEditor), GetType(UITypeEditor))> _
Public Shadows ReadOnly Property TabPages() As TabPageCollection
Get
Return MyBase.TabPages
End Get
End Property
#End Region
#Region " TabpageExCollectionEditor "
Friend Class TabpageExCollectionEditor
Inherits CollectionEditor
Public Sub New(ByVal type As System.Type)
MyBase.new(type)
End Sub
Protected Overrides Function CreateCollectionItemType() As System.Type
Return GetType(TabPageEx)
End Function
End Class
#End Region
#Region " Interop for SelectedIndexChanging event "
<StructLayout(LayoutKind.Sequential)> _
Private Structure NMHDR
Public HWND As Int32
Public idFrom As Int32
Public code As Int32
Public Overloads Function ToString() As String
Return String.Format("Hwnd: {0}, ControlID: {1}, Code: {2}", HWND, idFrom, code)
End Function
End Structure
Private Const TCN_FIRST As Int32 = &HFFFFFFFFFFFFFDDA&
Private Const TCN_SELCHANGING As Int32 = (TCN_FIRST - 2)
Private Const WM_USER As Int32 = &H400&
Private Const WM_NOTIFY As Int32 = &H4E&
Private Const WM_REFLECT As Int32 = WM_USER + &H1C00&
#End Region
#Region " SelectedIndexChanging event Implementation "
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = (WM_REFLECT + WM_NOTIFY) Then
Dim hdr As NMHDR = DirectCast(Marshal.PtrToStructure(m.LParam, GetType(NMHDR)), NMHDR)
If hdr.code = TCN_SELCHANGING Then
If Not HotTab Is Nothing Then
Dim e As New TabControlExEventArgs(HotTab, Me.Controls.IndexOf(HotTab))
RaiseEvent SelectedIndexChanging(Me, e)
If e.Cancel OrElse HotTab.Enabled = False Then
m.Result = New IntPtr(1)
Return
End If
End If
End If
End If
MyBase.WndProc(m)
End Sub
#End Region
#Region " HotTab Immplementation "
Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
MyBase.OnMouseMove(e)
HotTab = TestTab(New Point(e.X, e.Y))
End Sub
#End Region
#Region " Custom Methods "
Public Sub InsertTabPage(ByVal [tabpage] As TabPage, ByVal [index] As Integer)
If [index] < 0 Or [index] > TabCount Then
Throw New ArgumentException("Index out of Range.")
End If
TabPages.Add([tabpage])
If [index] < TabCount - 1 Then
Do
SwapTabPages([tabpage], (TabPages(TabPages.IndexOf([tabpage]) - 1)))
Loop Until TabPages.IndexOf([tabpage]) = [index]
End If
SelectedTab = [tabpage]
End Sub
Public Sub SwapTabPages(ByVal tp1 As TabPage, ByVal tp2 As TabPage)
If TabPages.Contains(tp1) = False Or TabPages.Contains(tp2) = False Then
Throw New ArgumentException("TabPages must be in the TabCotrols TabPageCollection.")
End If
Dim Index1 As Integer = TabPages.IndexOf(tp1)
Dim Index2 As Integer = TabPages.IndexOf(tp2)
TabPages(Index1) = tp2
TabPages(Index2) = tp1
End Sub
Private Function TestTab(ByVal pt As Point) As TabPage
For index As Integer = 0 To TabCount - 1
If GetTabRect(index).Contains(pt.X, pt.Y) Then
Return TabPages(index)
End If
Next
Return Nothing
End Function
#End Region
End Class
#Region " SelectedIndexChanging EventArgs Class/Delegate "
Public Class TabControlExEventArgs
Inherits EventArgs
Private m_TabPage As TabPage = Nothing
Private m_TabPageIndex As Integer = -1
Public Cancel As Boolean = False
Public ReadOnly Property [TabPage]() As TabPage
Get
Return m_TabPage
End Get
End Property
Public ReadOnly Property [TabPageIndex]() As Integer
Get
Return m_TabPageIndex
End Get
End Property
Public Sub New(ByVal [TabPage] As TabPage, ByVal [TabPageIndex] As Integer)
m_TabPage = [TabPage]
m_TabPageIndex = [TabPageIndex]
End Sub
End Class
Public Delegate Sub TabControlExEventHandler(ByVal sender As Object, ByVal e As TabControlExEventArgs)
#End Region
#End Region
#Region " TabPageEx Class "
Public Class TabPageEx
Inherits TabPage
#Region " API Declares "
<DllImport("Comctl32.dll", CallingConvention:=CallingConvention.Cdecl)> _
Private Overloads Shared Function DllGetVersion(ByRef pdvi As DLLVERSIONINFO) As Integer
End Function
<DllImport("uxtheme.dll", CallingConvention:=CallingConvention.Cdecl)> _
Private Overloads Shared Function IsAppThemed() As Boolean
End Function
<DllImport("uxtheme.dll", CallingConvention:=CallingConvention.Cdecl, CharSet:=CharSet.Unicode)> _
Private Overloads Shared Function OpenThemeData(ByVal hwnd As IntPtr, ByVal pszClassList As String) As IntPtr
End Function
<DllImport("uxtheme.dll", CallingConvention:=CallingConvention.Cdecl)> _
Private Overloads Shared Function GetThemePartSize(ByVal hTheme As IntPtr, ByVal hdc As IntPtr, ByVal iPartId As Integer, ByVal iStateId As Integer, ByRef prc As Rectangle, ByVal eSize As THEMESIZE, ByRef psz As Size) As Integer
End Function
<DllImport("uxtheme.dll", CallingConvention:=CallingConvention.Cdecl)> _
Private Overloads Shared Function DrawThemeBackground(ByVal hTheme As IntPtr, ByVal hdc As IntPtr, ByVal iPartId As Integer, ByVal iStateId As Integer, ByRef pRect As Rectangle, ByVal pClipRect As IntPtr) As Integer
End Function
<DllImport("uxtheme.dll", CallingConvention:=CallingConvention.Cdecl)> _
Private Overloads Shared Function CloseThemeData(ByVal htheme As IntPtr) As Integer
End Function
Private Structure DLLVERSIONINFO
Friend cbSize As Integer
Friend dwMajorVersion As Integer
Friend dwMinorVersion As Integer
Friend dwBuildNumber As Integer
Friend dwPlatformID As Integer
Public Sub New(ByVal ctrl As Control)
cbSize = Marshal.SizeOf(GetType(DLLVERSIONINFO))
End Sub
End Structure
Private Enum THEMESIZE As Integer
TS_MIN
TS_TRUE
TS_DRAW
End Enum
Private Const TABP_BODY As Integer = 10
Private Const WM_THEMECHANGED As Integer = &H31A
#End Region
#Region " Properties "
Private bStyled As Boolean = True
Private m_Brush As Brush
Private ReadOnly Property AppIsXPThemed() As Boolean
'IsAppThemed will return True if the App is not using visual
'Styles but It's TitleBar is drawn with Visual Style(i.e. a
'manifest resource has not been supplied). To overcome this
'problem we must also check which version of ComCtl32.dll is
'being used. Since ComCtl32.dll version 6 is exclusive to
'WindowsXP, we do not need to check the OSVersion.
Get
Dim dllVer As New DLLVERSIONINFO(Me)
DllGetVersion(dllVer)
If dllVer.dwMajorVersion >= 6 Then Return IsAppThemed()
End Get
End Property
<Category("Appearance"), _
Description("Enables/Disables Visual Styles on the TabPage. Valid only in WidowsXP."), _
DefaultValue(True)> _
Public Property EnableVisualStyles() As Boolean
Get
Return bStyled
End Get
Set(ByVal Value As Boolean)
If bStyled = Value Then Return
bStyled = Value
Invalidate(True)
End Set
End Property
#End Region
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
Public Sub New(ByVal Text As String)
MyBase.New()
MyBase.Text = Text
End Sub
'UserControl1 overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
components = New System.ComponentModel.Container
End Sub
#End Region
Protected Overrides Sub OnPaintBackground(ByVal pevent As System.Windows.Forms.PaintEventArgs)
If EnableVisualStyles And AppIsXPThemed Then
If m_Brush Is Nothing Then SetTabBrush()
'Paint the TabPage with our Brush.
pevent.Graphics.FillRectangle(m_Brush, Me.ClientRectangle)
Else
'Call the default Paint Event.
MyBase.OnPaintBackground(pevent)
End If
End Sub
Private Sub SetTabBrush()
Dim hdc As IntPtr
Dim hTheme As IntPtr
Dim sz As Size
Dim bmp As Bitmap
Dim lColor As Integer
Dim h As Integer = Height
'Open the theme data for the Tab Class.
hTheme = OpenThemeData(Handle, "TAB")
'Get the size of the Active Theme's TabPage Bitmap.
GetThemePartSize(hTheme, IntPtr.Zero, TABP_BODY, 0, DisplayRectangle, THEMESIZE.TS_TRUE, sz)
'If the TabPage is taller than the bitmap then we'll get a
'nasty block efect so we'll check for that and correct.
If h > sz.Height Then sz.Height = h
'Create a new bitmap of the correct size.
bmp = New Bitmap(sz.Width, sz.Height)
'Create a Graphics object from our bitmap so we can
'draw to it.
Dim g As Graphics = Graphics.FromImage(bmp)
'Get the handle to the Graphics Object's DC for API usage.
hdc = g.GetHdc 'Hidden member of Graphics
Dim bmpRect As New Rectangle(0, 0, sz.Width, sz.Height)
'Draw to the Bitmaps Graphics Object.
DrawThemeBackground(hTheme, hdc, TABP_BODY, 0, bmpRect, IntPtr.Zero)
'Release the DC to Windows.
g.ReleaseHdc(hdc) 'Hidden member of Graphics'Close the theme data for the Tab Class.
CloseThemeData(hTheme)
'Create a BitmapBrush.
m_Brush = New TextureBrush(bmp)
'Clean Up
bmp.Dispose()
g.Dispose()
End Sub
Private Sub TabpageEx_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Disposed
'Get rid of the brush if we created one.
If Not m_Brush Is Nothing Then
m_Brush.Dispose()
End If
End Sub
Protected Overrides Sub OnResize(ByVal eventargs As System.EventArgs)
MyBase.OnResize(eventargs)
If AppIsXPThemed Then SetTabBrush()
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
If m.Msg = WM_THEMECHANGED Then
SetTabBrush()
End If
End Sub
''Have to take responsibility for drawing TabItems for this method to be useful.
'Protected Overrides Function ProcessMnemonic(ByVal charCode As Char) As Boolean
' If IsMnemonic(charCode, Text) Then
' DirectCast(Parent, TabControl).SelectedTab = Me
' Return True
' End If
' Return False
'End Function
End Class
#End Region
End Namespace
Namespace Designers
Friend Class TabControlExDesigner
Inherits System.Windows.Forms.Design.ParentControlDesigner
#Region " Private Instance Variables "
Private m_verbs As DesignerVerbCollection = New DesignerVerbCollection
Private m_DesignerHost As IDesignerHost
Private m_SelectionService As ISelectionService
#End Region
Public Sub New()
MyBase.New()
Dim verb1 As New DesignerVerb("Add Tab", AddressOf OnAddPage)
Dim verb2 As New DesignerVerb("Insert Tab", AddressOf OnInsertPage)
Dim verb3 As New DesignerVerb("Remove Tab", AddressOf OnRemovePage)
m_verbs.AddRange(New DesignerVerb() {verb1, verb2, verb3})
End Sub
#Region " Properties "
Public Overrides ReadOnly Property Verbs() As System.ComponentModel.Design.DesignerVerbCollection
Get
If m_verbs.Count = 3 Then
Dim MyControl As Controls.TabControlEx = CType(Me.Control, Controls.TabControlEx)
If MyControl.TabCount > 0 Then
m_verbs(1).Enabled = True
m_verbs(2).Enabled = True
Else
m_verbs(1).Enabled = False
m_verbs(2).Enabled = False
End If
End If
Return m_verbs
End Get
End Property
Public ReadOnly Property DesignerHost() As IDesignerHost
Get
If m_DesignerHost Is Nothing Then
m_DesignerHost = DirectCast(GetService(GetType(IDesignerHost)), IDesignerHost)
End If
Return m_DesignerHost
End Get
End Property
Public ReadOnly Property SelectionService() As ISelectionService
Get
If m_SelectionService Is Nothing Then
m_SelectionService = DirectCast(getservice(GetType(ISelectionService)), ISelectionService)
End If
Return m_SelectionService
End Get
End Property
#End Region
Sub OnAddPage(ByVal sender As Object, ByVal e As EventArgs)
Dim ParentControl As Controls.TabControlEx = DirectCast(Control, Controls.TabControlEx)
Dim oldTabs As Control.ControlCollection = ParentControl.Controls
RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)("TabPages"))
Dim P As Controls.TabPageEx = DirectCast(DesignerHost.CreateComponent(GetType(Controls.TabPageEx)), Controls.TabPageEx)
P.Text = P.Name
ParentControl.TabPages.Add(P)
RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)("TabPages"), oldTabs, ParentControl.TabPages)
ParentControl.SelectedTab = P
SetVerbs()
End Sub
Sub OnInsertPage(ByVal sender As Object, ByVal e As EventArgs)
Dim ParentControl As Controls.TabControlEx = DirectCast(Control, Controls.TabControlEx)
Dim oldTabs As Control.ControlCollection = ParentControl.Controls
Dim Index As Integer = ParentControl.SelectedIndex
RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)("TabPages"))
Dim P As Controls.TabPageEx = DirectCast(DesignerHost.CreateComponent(GetType(Controls.TabPageEx)), Controls.TabPageEx)
P.Text = P.Name
Dim tpc(ParentControl.TabCount) As TabPage
'Starting at our Insert Position, store and remove all the tabpages.
For i As Integer = Index To ParentControl.TabCount - 1
tpc(i) = ParentControl.TabPages(Index)
ParentControl.TabPages.Remove(ParentControl.TabPages(Index))
Next
'add the tabpage to be inserted.
ParentControl.TabPages.Add(P)
'then re-add the original tabpages.
For i As Integer = Index To UBound(tpc) - 1
ParentControl.TabPages.Add(tpc(i))
Next
RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)("TabPages"), oldTabs, ParentControl.TabPages)
ParentControl.SelectedTab = P
SetVerbs()
End Sub
Sub OnRemovePage(ByVal sender As Object, ByVal e As EventArgs)
Dim ParentControl As Controls.TabControlEx = DirectCast(Control, Controls.TabControlEx)
Dim oldTabs As Control.ControlCollection = ParentControl.Controls
If ParentControl.SelectedIndex < 0 Then Return
RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)("TabPages"))
DesignerHost.DestroyComponent(ParentControl.TabPages(ParentControl.SelectedIndex))
RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)("TabPages"), oldTabs, ParentControl.TabPages)
SelectionService.SetSelectedComponents(New IComponent() {ParentControl}, SelectionTypes.Normal)
SetVerbs()
End Sub
Private Sub SetVerbs()
Dim ParentControl As Controls.TabControlEx = DirectCast(Control, Controls.TabControlEx)
Select Case ParentControl.TabPages.Count
Case 0
Verbs(1).Enabled = False
Verbs(2).Enabled = False
Case 1
Verbs(1).Enabled = False
Verbs(2).Enabled = True
Case Else
Verbs(1).Enabled = True
Verbs(2).Enabled = True
End Select
End Sub
Private Const WM_NCHITTEST As Integer = &H84
Private Const HTTRANSPARENT As Integer = -1
Private Const HTCLIENT As Integer = 1
Protected Overrides Sub WndProc(ByRef m As Message)
MyBase.WndProc(m)
If m.Msg = WM_NCHITTEST Then
'select tabcontrol when Tabcontrol clicked outside of TabItem.
If m.Result.ToInt32 = HTTRANSPARENT Then
m.Result = IntPtr.op_Explicit(HTCLIENT)
End If
End If
End Sub
Private Enum TabControlHitTest
TCHT_NOWHERE = 1
TCHT_ONITEMICON = 2
TCHT_ONITEMLABEL = 4
TCHT_ONITEM = TCHT_ONITEMICON Or TCHT_ONITEMLABEL
End Enum
Private Const TCM_HITTEST As Int32 = &H130D
Private Structure TCHITTESTINFO
Public pt As Point
Public flags As TabControlHitTest
End Structure
Protected Overrides Function GetHitTest(ByVal point As System.Drawing.Point) As Boolean
If (Me.SelectionService.PrimarySelection Is Me.Control) Then
Dim hti As New TCHITTESTINFO
hti.pt = Me.Control.PointToClient(point)
Dim m As New Message
m.HWnd = Me.Control.Handle
m.Msg = TCM_HITTEST
Dim lparam As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(hti))
Marshal.StructureToPtr(hti, lparam, False)
m.LParam = lparam
MyBase.WndProc(m)
Marshal.FreeHGlobal(lparam)
If m.Result.ToInt32 <> -1 Then
Return hti.flags <> TabControlHitTest.TCHT_NOWHERE
End If
End If
Return False
End Function
Protected Overrides Sub OnPaintAdornments(ByVal pe As System.Windows.Forms.PaintEventArgs)
'Don't want DrawGrid dots.
End Sub
'Fix the AllSizable selectiorule on DockStyle.Fill
Public Overrides ReadOnly Property SelectionRules() As System.Windows.Forms.Design.SelectionRules
Get
If Me.Control.Dock = DockStyle.Fill Then
Return System.Windows.Forms.Design.SelectionRules.Visible
End If
Return MyBase.SelectionRules
End Get
End Property
End Class
End Namespace
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Drawing.Design;
using System.ComponentModel.Design;
using System.Runtime.InteropServices;
namespace Dotnetrix_Samples
{
#region TabControlEx Class
/// <summary>
/// Summary description for TabControlEx.
/// </summary>
[ToolboxBitmap(typeof(System.Windows.Forms.TabControl)),
Designer(typeof(Designers.TabControlExDesigner))]
public class TabControlEx : System.Windows.Forms.TabControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public TabControlEx()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
// TODO: Add any initialization after the InitializeComponent call
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
public event TabControlExEventHandler SelectedIndexChanging;
public TabPage HotTab = null;
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
#region Properties
[Editor(typeof(TabpageExCollectionEditor), typeof(UITypeEditor))]
public new TabPageCollection TabPages
{
get
{
return base.TabPages;
}
}
#endregion
#region TabpageExCollectionEditor
internal class TabpageExCollectionEditor : CollectionEditor
{
public TabpageExCollectionEditor(System.Type type): base(type)
{
}
protected override Type CreateCollectionItemType()
{
return typeof(TabPageEx);
}
}
#endregion
#region Interop for SelectedIndexChanging event
[StructLayout(LayoutKind.Sequential)]
private struct NMHDR
{
public IntPtr HWND;
public uint idFrom;
public int code;
public override String ToString()
{
return String.Format("Hwnd: {0}, ControlID: {1}, Code: {2}", HWND, idFrom, code);
}
}
private const int TCN_FIRST = 0 - 550;
private const int TCN_SELCHANGING = (TCN_FIRST - 2);
private const int WM_USER = 0x400;
private const int WM_NOTIFY = 0x4E;
private const int WM_REFLECT = WM_USER + 0x1C00;
#endregion
#region SelectedIndexChanging event Implementation
protected override void WndProc(ref Message m)
{
if (m.Msg == (WM_REFLECT + WM_NOTIFY))
{
NMHDR hdr = (NMHDR)(Marshal.PtrToStructure(m.LParam, typeof(NMHDR)));
if (hdr.code == TCN_SELCHANGING)
{
if (HotTab != null)
{
TabControlExEventArgs e = new TabControlExEventArgs(HotTab, Controls.IndexOf(HotTab));
if (SelectedIndexChanging != null)
SelectedIndexChanging(this, e);
if (e.Cancel || !HotTab.Enabled)
{
m.Result = new IntPtr(1);
return;
}
}
}
}
base.WndProc (ref m);
}
#endregion
#region HotTab Immplementation
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove (e);
HotTab = TestTab(new Point(e.X, e.Y));
}
#endregion
#region Custom Methods
public void InsertTabPage(TabPage tabpage, int index)
{
if (index < 0 || index > TabCount)
throw new ArgumentException("Index out of Range.");
TabPages.Add(tabpage);
if (index < TabCount - 1)
{
do
SwapTabPages(tabpage, (TabPages[TabPages.IndexOf(tabpage) - 1]));
while (TabPages.IndexOf(tabpage) != index);
}
SelectedTab = tabpage;
}
public void SwapTabPages(TabPage tp1, TabPage tp2)
{
if (!TabPages.Contains(tp1) || !TabPages.Contains(tp2) )
throw new ArgumentException("TabPages must be in the TabCotrols TabPageCollection.");
int Index1 = TabPages.IndexOf(tp1);
int Index2 = TabPages.IndexOf(tp2);
TabPages[Index1] = tp2;
TabPages[Index2] = tp1;
}
private TabPage TestTab(Point pt)
{
for (int index = 0; index <= TabCount - 1; index++)
{
if (GetTabRect(index).Contains(pt.X, pt.Y))
return TabPages[index];
}
return null;
}
#endregion
}
#region SelectedIndexChanging EventArgs Class/Delegate
public class TabControlExEventArgs : EventArgs
{
private TabPage m_TabPage = null;
private int m_TabPageIndex = -1;
public bool Cancel = false;
public TabPage tabPage
{
get
{
return m_TabPage;
}
}
public int TabPageIndex
{
get
{
return m_TabPageIndex;
}
}
public TabControlExEventArgs(TabPage tabPage, int TabPageIndex)
{
m_TabPage = tabPage;
m_TabPageIndex = TabPageIndex;
}
}
public delegate void TabControlExEventHandler(Object sender, TabControlExEventArgs e);
#endregion
#endregion
#region TabPageEx Class
public class TabPageEx : TabPage
{
#region API Declares
[DllImport("Comctl32.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int DllGetVersion(ref DLLVERSIONINFO pdvi);
[DllImport("uxtheme.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern bool IsAppThemed();
[DllImport("uxtheme.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Unicode)]
private static extern IntPtr OpenThemeData(IntPtr hwnd, String pszClassList);
[DllImport("uxtheme.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int GetThemePartSize(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, ref Rectangle prc, THEMESIZE eSize, ref Size psz);
[DllImport("uxtheme.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int DrawThemeBackground(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, ref Rectangle pRect, IntPtr pClipRect);
[DllImport("uxtheme.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern int CloseThemeData(IntPtr htheme);
private struct DLLVERSIONINFO
{
public int cbSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformID;
public DLLVERSIONINFO(Control ctrl)
{
cbSize = Marshal.SizeOf(typeof(DLLVERSIONINFO));
dwMajorVersion = 0;
dwMinorVersion = 0;
dwBuildNumber = 0;
dwPlatformID = 0;
}
}
private enum THEMESIZE
{
TS_MIN,
TS_TRUE,
TS_DRAW
}
private const int TABP_BODY = 10;
private const int WM_THEMECHANGED= 0x31A;
#endregion
#region Properties
private bool bStyled = true;
private Brush m_Brush;
private bool AppIsXPThemed
{
//IsAppThemed will return True if the App is not using visual
//Styles but It's TitleBar is drawn with Visual Style(i.e. a
//manifest resource has not been supplied). To overcome this
//problem we must also check which version of ComCtl32.dll is
//being used. Since ComCtl32.dll version 6 is exclusive to
//WindowsXP, we do not need to check the OSVersion.
get
{
DLLVERSIONINFO dllVer = new DLLVERSIONINFO(this);
DllGetVersion(ref dllVer);
if (dllVer.dwMajorVersion >= 6) return IsAppThemed();
return false;
}
}
[Category("Appearance")]
[Description("Enables/Disables Visual Styles on the TabPage. Valid only in WidowsXP.")]
[DefaultValue(true)]
public bool EnableVisualStyles
{
get
{
return bStyled;
}
set
{
if (bStyled == value) return;
bStyled = value;
Invalidate(true);
}
}
#endregion
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public TabPageEx()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
// TODO: Add any initialization after the InitializeComponent call
}
public TabPageEx(String Text) : base()
{
base.Text = Text;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.Disposed += new EventHandler(TabpageEx_Disposed);
}
#endregion
protected override void OnPaintBackground(PaintEventArgs pevent)
{
if (EnableVisualStyles && AppIsXPThemed)
{
if (m_Brush == null) SetTabBrush();
//Paint the TabPage with our Brush.
pevent.Graphics.FillRectangle(m_Brush, ClientRectangle);
}
else
//Call the default Paint Event.
base.OnPaintBackground (pevent);
}
private void SetTabBrush()
{
IntPtr hdc;
IntPtr hTheme;
Size sz = new Size(0,0);
Bitmap bmp;
int h = Height;
//Open the theme data for the Tab Class.
hTheme = OpenThemeData(Handle, "TAB");
//Get the size of the Active Theme's TabPage Bitmap.
Rectangle displayrect = DisplayRectangle;
GetThemePartSize(hTheme, IntPtr.Zero, TABP_BODY, 0, ref displayrect, THEMESIZE.TS_TRUE, ref sz);
//If the TabPage is taller than the bitmap then we'll get a
//nasty block efect so we'll check for that and correct.
if (h > sz.Height) sz.Height = h;
//Create a new bitmap of the correct size.
bmp = new Bitmap(sz.Width, sz.Height);
//Create a Graphics object from our bitmap so we can
//draw to it.
Graphics g = Graphics.FromImage(bmp);
//Get the handle to the Graphics Object's DC for API usage.
hdc = g.GetHdc(); //Hidden member of Graphics
Rectangle bmpRect = new Rectangle(0, 0, sz.Width, sz.Height);
//Draw to the Bitmaps Graphics Object.
DrawThemeBackground(hTheme, hdc, TABP_BODY, 0,ref bmpRect, IntPtr.Zero);
//Release the DC to Windows.
g.ReleaseHdc(hdc); //Hidden member of Graphics//Close the theme data for the Tab Class.
CloseThemeData(hTheme);
//Create a BitmapBrush.
m_Brush = new TextureBrush(bmp);
//Clean Up
bmp.Dispose();
g.Dispose();
}
private void TabpageEx_Disposed(Object sender, System.EventArgs e)
{
//Get rid of the brush if we created one.
if (m_Brush != null) m_Brush.Dispose();
}
protected override void OnResize(EventArgs e)
{
base.OnResize (e);
if (AppIsXPThemed) SetTabBrush();
}
protected override void WndProc(ref Message m)
{
base.WndProc (ref m);
if (m.Msg == WM_THEMECHANGED)
SetTabBrush();
}
//Have to take responsibility for drawing TabItems for this method to be useful.
//Protected Overrides Function ProcessMnemonic(ByVal charCode As Char) As Boolean
// If IsMnemonic(charCode, Text) Then
// DirectCast(Parent, TabControl).SelectedTab = Me
// Return True
// End If
// Return False
//End Function
}
#endregion
}
namespace Designers
{
internal class TabControlExDesigner : System.Windows.Forms.Design.ParentControlDesigner
{
#region Private Instance Variables
private DesignerVerbCollection m_verbs = new DesignerVerbCollection();
private IDesignerHost m_DesignerHost;
private ISelectionService m_SelectionService;
#endregion
public TabControlExDesigner():base()
{
DesignerVerb verb1 = new DesignerVerb("Add Tab", new EventHandler(OnAddPage));
DesignerVerb verb2 = new DesignerVerb("Insert Tab", new EventHandler(OnInsertPage));
DesignerVerb verb3 = new DesignerVerb("Remove Tab", new EventHandler(OnRemovePage));
m_verbs.AddRange(new DesignerVerb[] {verb1, verb2, verb3});
}
#region Properties
public override DesignerVerbCollection Verbs
{
get
{
if (m_verbs.Count == 3)
{
Dotnetrix_Samples.TabControlEx MyControl = (Dotnetrix_Samples.TabControlEx)Control;
if (MyControl.TabCount > 0 )
{
m_verbs[1].Enabled = true;
m_verbs[2].Enabled = true;
}
else
{
m_verbs[1].Enabled = false;
m_verbs[2].Enabled = false;
}
}
return m_verbs;
}
}
public IDesignerHost DesignerHost
{
get
{
if (m_DesignerHost == null)
m_DesignerHost = (IDesignerHost)(GetService(typeof(IDesignerHost)));
return m_DesignerHost;
}
}
public ISelectionService SelectionService
{
get
{
if (m_SelectionService == null)
m_SelectionService = (ISelectionService)(this.GetService(typeof(ISelectionService)));
return m_SelectionService;
}
}
#endregion
void OnAddPage(Object sender, EventArgs e)
{
Dotnetrix_Samples.TabControlEx ParentControl = (Dotnetrix_Samples.TabControlEx)Control;
Control.ControlCollection oldTabs = ParentControl.Controls;
RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)["TabPages"]);
Dotnetrix_Samples.TabPageEx P = (Dotnetrix_Samples.TabPageEx)(DesignerHost.CreateComponent(typeof(Dotnetrix_Samples.TabPageEx)));
P.Text = P.Name;
ParentControl.TabPages.Add(P);
RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)["TabPages"], oldTabs, ParentControl.TabPages);
ParentControl.SelectedTab = P;
SetVerbs();
}
void OnInsertPage(Object sender, EventArgs e)
{
Dotnetrix_Samples.TabControlEx ParentControl = (Dotnetrix_Samples.TabControlEx)Control;
Control.ControlCollection oldTabs = ParentControl.Controls;
int Index = ParentControl.SelectedIndex;
RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)["TabPages"]);
Dotnetrix_Samples.TabPageEx P = (Dotnetrix_Samples.TabPageEx)(DesignerHost.CreateComponent(typeof(Dotnetrix_Samples.TabPageEx)));
P.Text = P.Name;
Dotnetrix_Samples.TabPageEx[] tpc = new Dotnetrix_Samples.TabPageEx[ParentControl.TabCount];
//Starting at our Insert Position, store and remove all the tabpages.
for (int i = Index; i <= tpc.Length-1; i++)
{
tpc[i] = (Dotnetrix_Samples.TabPageEx)ParentControl.TabPages[Index];
ParentControl.TabPages.Remove(ParentControl.TabPages[Index]);
}
//add the tabpage to be inserted.
ParentControl.TabPages.Add(P);
//then re-add the original tabpages.
for (int i = Index; i <= tpc.Length-1; i++)
{
ParentControl.TabPages.Add(tpc[i]);
}
RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)["TabPages"], oldTabs, ParentControl.TabPages);
ParentControl.SelectedTab = P;
SetVerbs();
}
void OnRemovePage(Object sender, EventArgs e)
{
Dotnetrix_Samples.TabControlEx ParentControl = (Dotnetrix_Samples.TabControlEx)Control;
Control.ControlCollection oldTabs = ParentControl.Controls;
if (ParentControl.SelectedIndex < 0) return;
RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)["TabPages"]);
DesignerHost.DestroyComponent(ParentControl.TabPages[ParentControl.SelectedIndex]);
RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)["TabPages"], oldTabs, ParentControl.TabPages);
SelectionService.SetSelectedComponents(new IComponent[] {ParentControl}, SelectionTypes.Normal);
SetVerbs();
}
private void SetVerbs()
{
Dotnetrix_Samples.TabControlEx ParentControl = (Dotnetrix_Samples.TabControlEx)Control;
switch (ParentControl.TabPages.Count)
{
case 0:
Verbs[1].Enabled = false;
Verbs[2].Enabled = false;
break;
case 1:
Verbs[1].Enabled = false;
Verbs[2].Enabled = true;
break;
default:
Verbs[1].Enabled = true;
Verbs[2].Enabled = true;
break;
}
}
private const int WM_NCHITTEST = 0x84;
private const int HTTRANSPARENT = -1;
private const int HTCLIENT = 1;
protected override void WndProc(ref Message m)
{
base.WndProc (ref m);
if (m.Msg == WM_NCHITTEST)
{
//select tabcontrol when Tabcontrol clicked outside of TabItem.
if (m.Result.ToInt32() == HTTRANSPARENT)
m.Result = (IntPtr)HTCLIENT;
}
}
private enum TabControlHitTest
{
TCHT_NOWHERE = 1,
TCHT_ONITEMICON = 2,
TCHT_ONITEMLABEL = 4,
TCHT_ONITEM = TCHT_ONITEMICON | TCHT_ONITEMLABEL
}
private const int TCM_HITTEST = 0x130D;
private struct TCHITTESTINFO
{
public Point pt;
public TabControlHitTest flags;
}
protected override bool GetHitTest(Point point)
{
if (this.SelectionService.PrimarySelection == this.Control)
{
TCHITTESTINFO hti =new TCHITTESTINFO();
hti.pt = this.Control.PointToClient(point);
hti.flags = 0;
Message m = new Message();
m.HWnd = this.Control.Handle;
m.Msg = TCM_HITTEST;
IntPtr lparam = Marshal.AllocHGlobal(Marshal.SizeOf(hti));
Marshal.StructureToPtr(hti, lparam, false);
m.LParam = lparam;
base.WndProc(ref m);
Marshal.FreeHGlobal(lparam);
if (m.Result.ToInt32() != -1)
return hti.flags != TabControlHitTest.TCHT_NOWHERE;
}
return false;
}
protected override void OnPaintAdornments(PaintEventArgs pe)
{
//Don't want DrawGrid dots.
}
//Fix the AllSizable selectiorule on DockStyle.Fill
public override System.Windows.Forms.Design.SelectionRules SelectionRules
{
get
{
if (Control.Dock == DockStyle.Fill)
return System.Windows.Forms.Design.SelectionRules.Visible;
return base.SelectionRules;
}
}
}
}
Mirrored TabControl.
Enable the display of TabItems on your TabControl from RightToLeft.
Imports System.ComponentModel
Public Class MirroredTabControl
Inherits System.Windows.Forms.TabControl
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'UserControl overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container
End Sub
#End Region
Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
Get
Dim cp As CreateParams = MyBase.CreateParams
Const WS_EX_LAYOUTRTL As Integer = &H400000
Const WS_EX_NOINHERITLAYOUT As Integer = &H100000
If Me.Mirror Then
cp.ExStyle += WS_EX_LAYOUTRTL Or WS_EX_NOINHERITLAYOUT
End If
Return cp
End Get
End Property
Private m_Mirror As Boolean = False
<DefaultValue(False)> _
Public Property Mirror() As Boolean
Get
Return m_Mirror
End Get
Set(ByVal Value As Boolean)
If m_Mirror = Value Then Return
m_Mirror = Value
MyBase.UpdateStyles()
End Set
End Property
End Class
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
namespace Dotnetrix_Samples
{
/// <summary>
/// Summary description for MirroredTabControl.
/// </summary>
public class MirroredTabControl : System.Windows.Forms.TabControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public MirroredTabControl()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
// TODO: Add any initialization after the InitializeComponent call
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
protected override CreateParams CreateParams
{
get
{
const int WS_EX_LAYOUTRTL = 0x400000;
const int WS_EX_NOINHERITLAYOUT = 0x100000;
CreateParams cp = base.CreateParams;
if (this.Mirror)
cp.ExStyle = cp.ExStyle|WS_EX_LAYOUTRTL|WS_EX_NOINHERITLAYOUT;
return cp;
}
}
private bool m_Mirror = false;
[DefaultValue(false)]
public bool Mirror
{
get
{
return m_Mirror;
}
set
{
if (m_Mirror == value) return;
m_Mirror = value;
base.UpdateStyles();
}
}
}
}
Prevent users navigating TabControl via Ctrl+Tab and Ctrl+Shift+Tab.
Add the following code to your Form.
Protected Overrides Function ProcessCmdKey(ByRef msg As Message, _
ByVal keyData As Keys) As Boolean
If TypeOf ActiveControl Is TabControl Then
If CBool(keyData And Keys.Control Or Keys.Tab) Then
Return True
End If
End If
Return MyBase.ProcessCmdKey(msg, keyData)
End Function
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (ActiveControl is TabControl)
{
if (System.Convert.ToBoolean(keyData & Keys.Tab | Keys.Control))
return true;
}
return base.ProcessCmdKey (ref msg, keyData);
}
Add SelectedIndexChanging Event.
The following class adds a SelectedIndexChanging event to a TabControl. As well as dissallowing selection of a disabled tab via keyboard or mouse, you can now cancel a change of tabpage.
Does not include code to draw disabled tabs.
Imports System.ComponentModel
Public Class TabControlEX
Inherits System.Windows.Forms.TabControl
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'UserControl overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container
End Sub
#End Region
<Description("Occurs as a tab is being changed.")> _
Public Event SelectedIndexChanging As SelectedTabPageChangeEventHandler
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
'Tab Selection via mouse button occurs before OnMouseDown()
'so we need to catch it here instead.
If m.Msg = &H201 Then 'WM_MouseDown
Dim tp As TabPage = TestTab(New Point(m.LParam.ToInt32))
If Not tp Is Nothing Then
If tp.Enabled = False Then
Return
Else
Dim ev As New TabPageChangeEventArgs(SelectedTab, tp)
OnSelectedIndexChanging(ev)
Return
End If
End If
End If
MyBase.WndProc(m)
End Sub
Protected Overrides Sub OnKeyDown(ByVal ke As System.Windows.Forms.KeyEventArgs)
If ke.KeyValue >= 37 AndAlso ke.KeyValue <= 40 Then
ke.Handled = True
Dim tp As TabPage = Nothing
Dim r As Rectangle = GetTabRect(SelectedIndex)
Dim pt As Point
Dim foundNextTab As Boolean = False
Do
If ke.KeyCode = Keys.Left Then
pt = New Point(r.Left - 3, r.Top)
ElseIf ke.KeyCode = Keys.Up Then
pt = New Point(r.Left, r.Top - 3)
ElseIf ke.KeyCode = Keys.Right Then
pt = New Point(r.Right + 3, r.Top)
Else
pt = New Point(r.Left, r.Bottom + 3)
End If
tp = TestTab(pt)
If Not tp Is Nothing Then
foundNextTab = tp.Enabled
r = GetTabRect(TabPages.IndexOf(tp))
End If
Loop While Not tp Is Nothing AndAlso foundNextTab = False
If tp Is Nothing Then
tp = GetNextEnabledTab(ke.KeyValue > 38, False)
End If
If Not tp Is Nothing AndAlso Not tp Is SelectedTab Then
Dim ev As New TabPageChangeEventArgs(SelectedTab, tp)
OnSelectedIndexChanging(ev)
End If
End If
MyBase.OnKeyDown(ke)
End Sub
Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
If keyData = (Keys.Tab Or Keys.Control) OrElse keyData = (Keys.Tab Or Keys.Shift Or Keys.Control) Then
Dim tp As TabPage = GetNextEnabledTab((keyData And Keys.Shift) = Keys.None, True)
If Not tp Is Nothing Then
If tp Is SelectedTab = False Then
Dim ev As New TabPageChangeEventArgs(SelectedTab, tp)
OnSelectedIndexChanging(ev)
End If
Return True
End If
End If
Return MyBase.ProcessCmdKey(msg, keyData)
End Function
Protected Overridable Sub OnSelectedIndexChanging(ByVal e As TabPageChangeEventArgs)
RaiseEvent SelectedIndexChanging(Me, e)
If e.Cancel = False Then
SelectedTab = e.NextTab
End If
End Sub
Private Function TestTab(ByVal pt As Point) As TabPage
For index As Int32 = 0 To TabCount - 1
If GetTabRect(index).Contains(pt.X, pt.Y) Then
Return TabPages(index)
End If
Next
Return Nothing
End Function
Private Function GetNextEnabledTab(ByVal forward As Boolean, ByVal wrap As Boolean) As TabPage
If forward Then
For index As Int32 = SelectedIndex + 1 To TabCount - 1
If TabPages(index).Enabled Then
Return TabPages(index)
End If
Next
If wrap Then
For index As Int32 = 0 To SelectedIndex
If TabPages(index).Enabled Then
Return TabPages(index)
End If
Next
End If
Else
For index As Int32 = SelectedIndex - 1 To 0 Step -1
If TabPages(index).Enabled Then
Return TabPages(index)
End If
Next
If (wrap) Then
For index As Int32 = TabCount - 1 To SelectedIndex Step -1
If TabPages(index).Enabled Then
Return TabPages(index)
End If
Next
End If
End If
Return Nothing
End Function
End Class
Public Class TabPageChangeEventArgs : Inherits EventArgs
Private _Selected As TabPage
Private _PreSelected As TabPage
Public Cancel As Boolean = False
Public ReadOnly Property CurrentTab() As TabPage
Get
Return _Selected
End Get
End Property
Public ReadOnly Property NextTab() As TabPage
Get
Return _PreSelected
End Get
End Property
Public Overrides Function ToString() As String
Return String.Format("CurrentTab: {0}, NextTab: {1}", _Selected.ToString(), _PreSelected.ToString())
End Function
Public Sub New(ByVal CurrentTab As TabPage, ByVal NextTab As TabPage)
_Selected = CurrentTab
_PreSelected = NextTab
End Sub
End Class
Public Delegate Sub SelectedTabPageChangeEventHandler(ByVal sender As Object, ByVal e As TabPageChangeEventArgs)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Dotnetrix_Samples
{
/// <summary>
/// Summary description for TabControlEX.
/// </summary>
public class TabControlEX : System.Windows.Forms.TabControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public TabControlEX()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
// TODO: Add any initialization after the InitializeComponent call
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
[Description("Occurs as a tab is being changed.")]
public event SelectedTabPageChangeEventHandler SelectedIndexChanging;
protected override void WndProc(ref Message m)
{
//Tab Selection via mouse button occurs before OnMouseDown()
//so we need to catch it here instead.
if (m.Msg == 0x201)//WM_MOUSEDOWN
{
TabPage tp = TestTab(new Point(m.LParam.ToInt32()));
if (tp != null)
{
if (tp.Enabled == false)
return;
else
{
TabPageChangeEventArgs ev = new TabPageChangeEventArgs(SelectedTab, tp);
OnSelectedIndexChanging(ev);
return;
}
}
}
base.WndProc (ref m);
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyValue >=37 && e.KeyValue <=40)
{
e.Handled=true;
TabPage tp = null;
Rectangle r = GetTabRect(SelectedIndex);
Point pt;
bool foundNextTab = false;
do
{
if (e.KeyCode == Keys.Left)
pt = new Point(r.Left - 3, r.Top);
else if (e.KeyCode == Keys.Up)
pt = new Point(r.Left, r.Top - 3);
else if (e.KeyCode == Keys.Right)
pt = new Point(r.Right + 3, r.Top);
else
pt = new Point(r.Left, r.Bottom + 3);
tp = TestTab(pt);
if (tp != null)
{
foundNextTab = tp.Enabled;
r = GetTabRect(TabPages.IndexOf(tp));
}
}while (tp != null && !foundNextTab);
if (tp == null)
tp = GetNextEnabledTab(e.KeyValue > 38, false);
if (tp != null && tp != SelectedTab)
{
TabPageChangeEventArgs ev = new TabPageChangeEventArgs(SelectedTab, tp);
OnSelectedIndexChanging(ev);
}
}
base.OnKeyDown(e);
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if ((keyData == (Keys.Tab | Keys.Control)) || (keyData == (Keys.Tab | Keys.Shift | Keys.Control)))
{
TabPage tp = GetNextEnabledTab((keyData & Keys.Shift) == Keys.None ,true);
if (tp != null)
if (tp != SelectedTab)
{
TabPageChangeEventArgs ev = new TabPageChangeEventArgs(SelectedTab, tp);
OnSelectedIndexChanging(ev);
}
return true;
}
return base.ProcessCmdKey (ref msg, keyData);
}
protected virtual void OnSelectedIndexChanging(TabPageChangeEventArgs e)
{
if (SelectedIndexChanging != null)
SelectedIndexChanging(this, e);
if( !e.Cancel)
SelectedTab = e.NextTab;
}
private TabPage TestTab(Point pt)
{
for (int index = 0; index <= TabCount - 1; index++)
if (GetTabRect(index).Contains(pt.X, pt.Y))
return TabPages[index];
return null;
}
private TabPage GetNextEnabledTab(bool forward, bool wrap)
{
if (forward)
{
for (int index = SelectedIndex + 1; index < TabCount; index++)
if (TabPages[index].Enabled)
return TabPages[index];
if (wrap)
for (int index = 0; index < SelectedIndex; index++)
if (TabPages[index].Enabled)
return TabPages[index];
}
else
{
for (int index = SelectedIndex - 1; index >=0; index--)
if (TabPages[index].Enabled)
return TabPages[index];
if (wrap)
for (int index = TabCount - 1; index > SelectedIndex; index--)
if (TabPages[index].Enabled)
return TabPages[index];
}
return null;
}
}
public class TabPageChangeEventArgs : EventArgs
{
private TabPage _Selected = null;
private TabPage _PreSelected = null;
public bool Cancel = false;
public TabPage CurrentTab
{
get{return _Selected;}
}
public TabPage NextTab
{
get{return _PreSelected;}
}
public override String ToString()
{
return String.Format("CurrentTab: {0}, NextTab: {1}",_Selected.ToString(),_PreSelected.ToString());
}
public TabPageChangeEventArgs(TabPage CurrentTab, TabPage NextTab)
{
_Selected = CurrentTab;
_PreSelected = NextTab;
}
}
public delegate void SelectedTabPageChangeEventHandler(Object sender, TabPageChangeEventArgs e);
}
Add a HideTabs property to turn on/off the Tabs
I don't know why I didn't think to try this earlier. This also fixes the problem of tabpages not being displayed correctly when Alignment is set to anything other than Top and Appearance is not Normal. I have left the Border around the edge at 4 (when HideTabs is False), but you may wish to change this for the Button Appearance.
Imports System.ComponentModel
Namespace Dotnetrix.Samples.VB
<ToolboxBitmap(GetType(System.Windows.Forms.TabControl))> _
Public Class TabControl
Inherits System.Windows.Forms.TabControl
Private m_HideTabs As Boolean
<DefaultValue(False), RefreshProperties(RefreshProperties.All)> _
Public Property HideTabs() As Boolean
Get
Return m_HideTabs
End Get
Set(ByVal Value As Boolean)
If m_HideTabs = Value Then Return
m_HideTabs = Value
If Value = True Then Me.Multiline = True
Me.UpdateStyles()
End Set
End Property
<RefreshProperties(RefreshProperties.All)> _
Public Overloads Property Multiline() As Boolean
Get
If Me.HideTabs Then Return True
Return MyBase.Multiline
End Get
Set(ByVal Value As Boolean)
If Me.HideTabs Then
MyBase.Multiline = True
Else
MyBase.Multiline = Value
End If
End Set
End Property
Public Overrides ReadOnly Property DisplayRectangle() As System.Drawing.Rectangle
Get
If Me.HideTabs Then
Return New Rectangle(0, 0, Width, Height)
Else
Dim tabStripHeight, itemHeight As Int32
If Me.Alignment <= TabAlignment.Bottom Then
itemHeight = Me.ItemSize.Height
Else
itemHeight = Me.ItemSize.Width
End If
If Me.Appearance = TabAppearance.Normal Then
tabStripHeight = 5 + (itemHeight * Me.RowCount)
Else
tabStripHeight = (3 + itemHeight) * Me.RowCount
End If
Select Case Me.Alignment
Case TabAlignment.Top
Return New Rectangle(4, tabStripHeight, Width - 8, Height - tabStripHeight - 4)
Case TabAlignment.Bottom
Return New Rectangle(4, 4, Width - 8, Height - tabStripHeight - 4)
Case TabAlignment.Left
Return New Rectangle(tabStripHeight, 4, Width - tabStripHeight - 4, Height - 8)
Case TabAlignment.Right
Return New Rectangle(4, 4, Width - tabStripHeight - 4, Height - 8)
End Select
End If
End Get
End Property
End Class
End Namespace
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Dotnetrix.Samples.CSharp
{
[ToolboxBitmap(typeof(System.Windows.Forms.TabControl))]
public class TabControl : System.Windows.Forms.TabControl
{
private bool m_HideTabs = false;
[DefaultValue(false)]
[RefreshProperties(RefreshProperties.All)]
public bool HideTabs
{
get{return m_HideTabs;}
set
{
if (m_HideTabs == value) return;
m_HideTabs = value;
if (value == true) this.Multiline = true;
this.UpdateStyles();
}
}
[RefreshProperties(RefreshProperties.All)]
public new bool Multiline
{
get
{
if (this.HideTabs) return true;
return base.Multiline;
}
set
{
if (this.HideTabs)
base.Multiline = true;
else
base.Multiline = value;
}
}
public override System.Drawing.Rectangle DisplayRectangle
{
get
{
if (this.HideTabs)
return new Rectangle(0, 0, Width, Height);
else
{
int tabStripHeight, itemHeight;
if (this.Alignment <= TabAlignment.Bottom)
itemHeight = this.ItemSize.Height;
else
itemHeight = this.ItemSize.Width;
if (this.Appearance == TabAppearance.Normal)
tabStripHeight = 5 + (itemHeight * this.RowCount);
else
tabStripHeight = (3 + itemHeight) * this.RowCount;
switch (this.Alignment)
{
case TabAlignment.Bottom:
return new Rectangle(4, 4, Width - 8, Height - tabStripHeight - 4);
case TabAlignment.Left:
return new Rectangle(tabStripHeight, 4, Width - tabStripHeight - 4, Height - 8);
case TabAlignment.Right:
return new Rectangle(4, 4, Width - tabStripHeight - 4, Height - 8);
default:
return new Rectangle(4, tabStripHeight, Width - 8, Height - tabStripHeight - 4);
}
}
}
}
}
}