Tabpage header 移动后停留在箭头按钮下方
Tabpage header stays below arrow buttons after moving
情况如下,我有一个自定义的TabControl
画TabHeaders
本身,所以我可以选择颜色等
当我有更多 TabHeaders
然后 TabControl
可以容纳并且箭头 (spin control) 出现时,问题就出现了。每当我然后一直循环到右边(所以右边不再有 headers)时,箭头后面的制表符仍然部分存在。
图片演示:
我知道的:
如果我手动调用 .Invalidate()
以便重新绘制,问题就会消失,但是我找不到由箭头键触发的事件(它没有滚动事件)。
无效事件:
- 点击
- 布局
- 调整大小(甚至不要问)
I had WndProc in mind(它有 get 'spin' 的情况,听起来像自旋控制)
自定义选项卡控件代码:
class ColoredTabControl : TabControl
{
public Color ActiveTabColor
{
get
{
return _activeTabColor.Color;
}
set
{
_activeTabColor.Color = value;
this.Invalidate();
}
}
public Color DefaultTabColor
{
get
{
return _defaultTabColor.Color;
}
set
{
_defaultTabColor.Color = value;
this.Invalidate();
}
}
private SolidBrush _activeTabColor = new SolidBrush( Color.LightSteelBlue );
private SolidBrush _defaultTabColor = new SolidBrush( Color.LightGray );
private int _biggestTextHeight = 0;
private int _biggestTextWidth = 0;
public ColoredTabControl()
{
this.SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true );
}
protected override void OnPaint( PaintEventArgs e )
{
base.OnPaint( e );
var graphics = e.Graphics;
TabPage currentTab = this.SelectedTab;
if( TabPages.Count > 0 )
{
var biggestTextHeight = TabPages?.Cast<TabPage>()?.Max( r => TextRenderer.MeasureText( r.Text, r.Font ).Height );
var biggestTextWidth = TabPages?.Cast<TabPage>()?.Max( r => TextRenderer.MeasureText( r.Text, r.Font ).Width );
if( biggestTextHeight > _biggestTextHeight || biggestTextWidth > _biggestTextWidth )
{
_biggestTextWidth = ( int ) biggestTextWidth;
_biggestTextHeight = ( int ) biggestTextHeight;
this.ItemSize = new Size( _biggestTextWidth + 5, _biggestTextHeight + 10 );
}
}
for( int i = 0; i < TabPages.Count; i++ )
{
TabPage tabPage = TabPages[ i ];
Rectangle tabRectangle = GetTabRect( i );
SolidBrush brush = ( tabPage == currentTab ? _activeTabColor : _defaultTabColor );
graphics.FillRectangle( brush, tabRectangle );
TextRenderer.DrawText( graphics, tabPage.Text, tabPage.Font, tabRectangle, tabPage.ForeColor );
}
}
protected override void OnPaintBackground( PaintEventArgs e )
{
base.OnPaintBackground( e );
}
}
重现问题的一些表单代码:
var tabControl = new ColoredTabControl() { Width = 340, Height = 300, Top = 100 };
tabControl.TabPages.Add( "text" );
tabControl.TabPages.Add( "another tab");
tabControl.TabPages.Add( "yet another" );
tabControl.TabPages.Add( "another one" );
tabControl.TabPages.Add( "another" );
tabControl.DefaultTabColor = Color.Red; //To see the issue clearly
Controls.Add( tabControl );
我最终通过挂接到 msctls_updown32
class 的 WndProc
事件处理程序来修复它。我是通过使用从 a codeproject tabcontrol project by Oscar Londono 获得的 Win32 class 来完成此操作的,不过我不得不根据自己的需要对其进行一些调整。
在选项卡控件中添加代码:
protected override void OnSelectedIndexChanged( EventArgs e )
{
base.OnSelectedIndexChanged( e );
this.Invalidate();
}
protected override void OnControlAdded( ControlEventArgs e )
{
base.OnControlAdded( e );
FindUpDown();
}
protected override void Dispose( bool disposing )
{
if(scUpDown != null)
{
scUpDown.SubClassedWndProc -= scUpDown_SubClassedWndProc;
}
base.Dispose( disposing );
}
bool bUpDown = false;
SubClass scUpDown = null;
private void FindUpDown()
{
bool bFound = false;
// find the UpDown control
IntPtr pWnd =
Win32.GetWindow( this.Handle, Win32.GW_CHILD );
while( pWnd != IntPtr.Zero )
{
// Get the window class name
char[] fullName = new char[ 33 ];
int length = Win32.GetClassName( pWnd, fullName, 32 );
string className = new string( fullName, 0, length );
if( className == Win32.MSCTLS_UPDOWN32 )
{
bFound = true;
if( !bUpDown )
{
// Subclass it
this.scUpDown = new SubClass( pWnd, true );
this.scUpDown.SubClassedWndProc +=
new SubClass.SubClassWndProcEventHandler(
scUpDown_SubClassedWndProc );
bUpDown = true;
}
break;
}
pWnd = Win32.GetWindow( pWnd, Win32.GW_HWNDNEXT );
}
if( ( !bFound ) && ( bUpDown ) )
bUpDown = false;
}
private int scUpDown_SubClassedWndProc( ref Message m )
{
switch(m.Msg)
{
case Win32.WM_LCLICK:
//Invalidate to repaint
this.Invalidate();
return 0;
}
return 0;
}
此 class 的其他部分仍然与 OP
中的 100% 相同
Win32 class:
internal class Win32
{
/*
* GetWindow() Constants
*/
public const int GW_HWNDFIRST = 0;
public const int GW_HWNDLAST = 1;
public const int GW_HWNDNEXT = 2;
public const int GW_HWNDPREV = 3;
public const int GW_OWNER = 4;
public const int GW_CHILD = 5;
public const int WM_NCCALCSIZE = 0x83;
public const int WM_WINDOWPOSCHANGING = 0x46;
public const int WM_PAINT = 0xF;
public const int WM_CREATE = 0x1;
public const int WM_NCCREATE = 0x81;
public const int WM_NCPAINT = 0x85;
public const int WM_PRINT = 0x317;
public const int WM_DESTROY = 0x2;
public const int WM_SHOWWINDOW = 0x18;
public const int WM_SHARED_MENU = 0x1E2;
public const int WM_LCLICK = 0x201;
public const int HC_ACTION = 0;
public const int WH_CALLWNDPROC = 4;
public const int GWL_WNDPROC = -4;
//Class name constant
public const string MSCTLS_UPDOWN32 = "msctls_updown32";
[ DllImport("User32.dll",CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hwnd, char[] className, int maxCount);
[DllImport("User32.dll",CharSet = CharSet.Auto)]
public static extern IntPtr GetWindow(IntPtr hwnd, int uCmd);
}
#region SubClass Classing Handler Class
internal class SubClass : System.Windows.Forms.NativeWindow
{
public delegate int SubClassWndProcEventHandler(ref System.Windows.Forms.Message m);
public event SubClassWndProcEventHandler SubClassedWndProc;
private bool IsSubClassed = false;
public SubClass(IntPtr Handle, bool _SubClass)
{
base.AssignHandle(Handle);
this.IsSubClassed = _SubClass;
}
public bool SubClassed
{
get{ return this.IsSubClassed; }
set{ this.IsSubClassed = value; }
}
protected override void WndProc(ref Message m)
{
if (this.IsSubClassed)
{
if (OnSubClassedWndProc(ref m) != 0)
return;
}
base.WndProc(ref m);
}
private int OnSubClassedWndProc(ref Message m)
{
if (SubClassedWndProc != null)
{
return this.SubClassedWndProc(ref m);
}
return 0;
}
}
#endregion
如果有人有更好的答案或问题,请随时 answer/comment!
If anyone has a way or even thoughts on how to hook into the actual scroll event of the tabcontrol please answer/comment.
如果您想知道如何在单击那些滚动按钮时获取选项卡 header 的滚动事件,您可以处理 TabControl
的 WM_HSCROLL
事件:
const int WM_HSCROLL = 0x0114;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_HSCROLL)
this.Invalidate();
}
这样您就可以在使用这些按钮滚动后使控件无效。
对于用户使用键盘更改所选选项卡的情况,您可以重写 OnSelectedIndexChanged
并在调用基本方法后使控件无效。
情况如下,我有一个自定义的TabControl
画TabHeaders
本身,所以我可以选择颜色等
当我有更多 TabHeaders
然后 TabControl
可以容纳并且箭头 (spin control) 出现时,问题就出现了。每当我然后一直循环到右边(所以右边不再有 headers)时,箭头后面的制表符仍然部分存在。
图片演示:
我知道的:
如果我手动调用 .Invalidate()
以便重新绘制,问题就会消失,但是我找不到由箭头键触发的事件(它没有滚动事件)。
无效事件:
- 点击
- 布局
- 调整大小(甚至不要问)
I had WndProc in mind(它有 get 'spin' 的情况,听起来像自旋控制)
自定义选项卡控件代码:
class ColoredTabControl : TabControl
{
public Color ActiveTabColor
{
get
{
return _activeTabColor.Color;
}
set
{
_activeTabColor.Color = value;
this.Invalidate();
}
}
public Color DefaultTabColor
{
get
{
return _defaultTabColor.Color;
}
set
{
_defaultTabColor.Color = value;
this.Invalidate();
}
}
private SolidBrush _activeTabColor = new SolidBrush( Color.LightSteelBlue );
private SolidBrush _defaultTabColor = new SolidBrush( Color.LightGray );
private int _biggestTextHeight = 0;
private int _biggestTextWidth = 0;
public ColoredTabControl()
{
this.SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true );
}
protected override void OnPaint( PaintEventArgs e )
{
base.OnPaint( e );
var graphics = e.Graphics;
TabPage currentTab = this.SelectedTab;
if( TabPages.Count > 0 )
{
var biggestTextHeight = TabPages?.Cast<TabPage>()?.Max( r => TextRenderer.MeasureText( r.Text, r.Font ).Height );
var biggestTextWidth = TabPages?.Cast<TabPage>()?.Max( r => TextRenderer.MeasureText( r.Text, r.Font ).Width );
if( biggestTextHeight > _biggestTextHeight || biggestTextWidth > _biggestTextWidth )
{
_biggestTextWidth = ( int ) biggestTextWidth;
_biggestTextHeight = ( int ) biggestTextHeight;
this.ItemSize = new Size( _biggestTextWidth + 5, _biggestTextHeight + 10 );
}
}
for( int i = 0; i < TabPages.Count; i++ )
{
TabPage tabPage = TabPages[ i ];
Rectangle tabRectangle = GetTabRect( i );
SolidBrush brush = ( tabPage == currentTab ? _activeTabColor : _defaultTabColor );
graphics.FillRectangle( brush, tabRectangle );
TextRenderer.DrawText( graphics, tabPage.Text, tabPage.Font, tabRectangle, tabPage.ForeColor );
}
}
protected override void OnPaintBackground( PaintEventArgs e )
{
base.OnPaintBackground( e );
}
}
重现问题的一些表单代码:
var tabControl = new ColoredTabControl() { Width = 340, Height = 300, Top = 100 };
tabControl.TabPages.Add( "text" );
tabControl.TabPages.Add( "another tab");
tabControl.TabPages.Add( "yet another" );
tabControl.TabPages.Add( "another one" );
tabControl.TabPages.Add( "another" );
tabControl.DefaultTabColor = Color.Red; //To see the issue clearly
Controls.Add( tabControl );
我最终通过挂接到 msctls_updown32
class 的 WndProc
事件处理程序来修复它。我是通过使用从 a codeproject tabcontrol project by Oscar Londono 获得的 Win32 class 来完成此操作的,不过我不得不根据自己的需要对其进行一些调整。
在选项卡控件中添加代码:
protected override void OnSelectedIndexChanged( EventArgs e )
{
base.OnSelectedIndexChanged( e );
this.Invalidate();
}
protected override void OnControlAdded( ControlEventArgs e )
{
base.OnControlAdded( e );
FindUpDown();
}
protected override void Dispose( bool disposing )
{
if(scUpDown != null)
{
scUpDown.SubClassedWndProc -= scUpDown_SubClassedWndProc;
}
base.Dispose( disposing );
}
bool bUpDown = false;
SubClass scUpDown = null;
private void FindUpDown()
{
bool bFound = false;
// find the UpDown control
IntPtr pWnd =
Win32.GetWindow( this.Handle, Win32.GW_CHILD );
while( pWnd != IntPtr.Zero )
{
// Get the window class name
char[] fullName = new char[ 33 ];
int length = Win32.GetClassName( pWnd, fullName, 32 );
string className = new string( fullName, 0, length );
if( className == Win32.MSCTLS_UPDOWN32 )
{
bFound = true;
if( !bUpDown )
{
// Subclass it
this.scUpDown = new SubClass( pWnd, true );
this.scUpDown.SubClassedWndProc +=
new SubClass.SubClassWndProcEventHandler(
scUpDown_SubClassedWndProc );
bUpDown = true;
}
break;
}
pWnd = Win32.GetWindow( pWnd, Win32.GW_HWNDNEXT );
}
if( ( !bFound ) && ( bUpDown ) )
bUpDown = false;
}
private int scUpDown_SubClassedWndProc( ref Message m )
{
switch(m.Msg)
{
case Win32.WM_LCLICK:
//Invalidate to repaint
this.Invalidate();
return 0;
}
return 0;
}
此 class 的其他部分仍然与 OP
中的 100% 相同Win32 class:
internal class Win32
{
/*
* GetWindow() Constants
*/
public const int GW_HWNDFIRST = 0;
public const int GW_HWNDLAST = 1;
public const int GW_HWNDNEXT = 2;
public const int GW_HWNDPREV = 3;
public const int GW_OWNER = 4;
public const int GW_CHILD = 5;
public const int WM_NCCALCSIZE = 0x83;
public const int WM_WINDOWPOSCHANGING = 0x46;
public const int WM_PAINT = 0xF;
public const int WM_CREATE = 0x1;
public const int WM_NCCREATE = 0x81;
public const int WM_NCPAINT = 0x85;
public const int WM_PRINT = 0x317;
public const int WM_DESTROY = 0x2;
public const int WM_SHOWWINDOW = 0x18;
public const int WM_SHARED_MENU = 0x1E2;
public const int WM_LCLICK = 0x201;
public const int HC_ACTION = 0;
public const int WH_CALLWNDPROC = 4;
public const int GWL_WNDPROC = -4;
//Class name constant
public const string MSCTLS_UPDOWN32 = "msctls_updown32";
[ DllImport("User32.dll",CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hwnd, char[] className, int maxCount);
[DllImport("User32.dll",CharSet = CharSet.Auto)]
public static extern IntPtr GetWindow(IntPtr hwnd, int uCmd);
}
#region SubClass Classing Handler Class
internal class SubClass : System.Windows.Forms.NativeWindow
{
public delegate int SubClassWndProcEventHandler(ref System.Windows.Forms.Message m);
public event SubClassWndProcEventHandler SubClassedWndProc;
private bool IsSubClassed = false;
public SubClass(IntPtr Handle, bool _SubClass)
{
base.AssignHandle(Handle);
this.IsSubClassed = _SubClass;
}
public bool SubClassed
{
get{ return this.IsSubClassed; }
set{ this.IsSubClassed = value; }
}
protected override void WndProc(ref Message m)
{
if (this.IsSubClassed)
{
if (OnSubClassedWndProc(ref m) != 0)
return;
}
base.WndProc(ref m);
}
private int OnSubClassedWndProc(ref Message m)
{
if (SubClassedWndProc != null)
{
return this.SubClassedWndProc(ref m);
}
return 0;
}
}
#endregion
如果有人有更好的答案或问题,请随时 answer/comment!
If anyone has a way or even thoughts on how to hook into the actual scroll event of the tabcontrol please answer/comment.
如果您想知道如何在单击那些滚动按钮时获取选项卡 header 的滚动事件,您可以处理 TabControl
的 WM_HSCROLL
事件:
const int WM_HSCROLL = 0x0114;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_HSCROLL)
this.Invalidate();
}
这样您就可以在使用这些按钮滚动后使控件无效。
对于用户使用键盘更改所选选项卡的情况,您可以重写 OnSelectedIndexChanged
并在调用基本方法后使控件无效。