c# - 移动后标签页标题保持在箭头按钮下方

标签 c# winforms custom-controls tabcontrol

情况如下,我有一个自定义的 TabControl 绘制 TabHeaders 本身,所以我可以选择颜色等。

当我有更多 TabHeaders 然后 TabControl 可以容纳并且箭头 ( spin control ) 出现时,就会出现问题。每当我然后一直向右循环(因此右侧不再有标题)时,箭头后面的制表符仍然部分存在。

图片展示:

enter image description here

我所知道的:
如果我手动调用 .Invalidate() 以便重新绘制,问题就会消失,但是我找不到由箭头键触发的事件(它没有滚动事件)。

无效的事件:

  • 点击
  • 布局
  • 调整大小(甚至不要问)

I had WndProc in mind (它有 get 'spin' 的情况,听起来像 spincontrol)


自定义标签控件代码:

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 类的 WndProc 事件处理程序来修复它。我通过使用从 a codeproject tabcontrol project by Oscar Londono 获得的 Win32 类来做到这一点不过,我必须根据自己的需要对其进行一些调整。

在选项卡控件中添加代码:

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;
}

该类的其他部分仍然与 OP 中的 100% 相同

Win32 类:

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

如果有人有更好的答案或问题,请随时回答/评论!

关于c# - 移动后标签页标题保持在箭头按钮下方,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46338808/

相关文章:

C# HttpWebRequest to Socket 写入Request Stream延迟3秒

jquery - 页面加载时滚动到给定的 jCarousel 幻灯片

asp.net - 从自定义 ASP Net 服务器控件发送动态 CSS

c# - 正在为每个子控件调用 OnPaint 方法

winforms - WinForm TabControl : How to hide/show tab headers dynamically?

android seekbar thumb (scrubber control) 按下时剪裁

c# - ASP.NET MVC 网格显示解决方案

c# - 使用 Daydream Controller 作为指针与 Unity UI 交互

c# - 有没有办法自动捕获应用程序中 C# 中的所有 FormClosed 事件?

ruby - 用于自动化 Windows 窗体应用程序和 Web 应用程序的 GUI 测试的免费工具