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.
The Source Code available both in VB net and CSharp, you can try this:
VB net Sample:
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 "
<Designer(GetType(System.Windows.Forms.Design.ScrollableControlDesigner))> _
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
Csharp Sample:
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
[Designer(typeof(System.Windows.Forms.Design.ScrollableControlDesigner))]
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;
}
}
}
}
By: Mick Doherty's













0 comments:
Post a Comment