移动后,标题页标题位于箭头按钮下方

时间:2017-09-21 08:16:42

标签: c# winforms custom-controls tabcontrol

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

当我有更多TabHeaders然后TabControl可以适合并且箭头(spin control)出现时,就会出现问题。每当我然后一直向右循环(因此没有更多的标题在右边)箭头后面的tabheader仍然部分存在。

要展示的图片:

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

2 个答案:

答案 0 :(得分:2)

我最终通过挂钩WndProc类的msctls_updown32事件处理程序来修复它。我通过使用从a codeproject tabcontrol project by Oscar Londono获得的Win32类来完成此操作。但我必须根据我的需要对其进行一些调整。

在tabcontrol中添加了代码:

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

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

答案 1 :(得分:1)

  

如果有人对如何挂钩tabcontrol的实际滚动事件有任何想法或想法,请回答/评论。

如果您想知道在单击这些滚动按钮时如何获取标签页眉的滚动事件,则可以处理WM_HSCROLL的{​​{1}}事件:

TabControl

这样,您可以在使用这些按钮滚动后使控件无效。

同样,对于用户使用键盘更改选定选项卡的情况,您可以覆盖const int WM_HSCROLL = 0x0114; protected override void WndProc(ref Message m) { base.WndProc(ref m); if (m.Msg == WM_HSCROLL) this.Invalidate(); } ,并在调用基本方法后,使控件无效。