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