【问题标题】:Tabpage header stays below arrow buttons after moving移动后标签页标题保持在箭头按钮下方
【发布时间】:2017-09-21 08:16:41
【问题描述】:

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

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

图片展示:

我所知道的:
如果我手动调用.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 );

【问题讨论】:

    标签: c# winforms custom-controls tabcontrol


    【解决方案1】:

    我最终通过连接到 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
    

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

    【讨论】:

    • 太棒了!我计划根据msctls_updown32 发布答案。
    • @RezaAghaei 如果我没有找到它,我不会知道它:) 顺便说一句,如果您有更优雅的解决方案,请随时发布!
    • 我猜 Oscar Londono 在 CodeProject 中分享的解决方案已经足够好了。如果我有任何其他想法,我会分享。
    • @RezaAghaei 好的,在这种情况下,我将把它留给未来的读者(令人惊讶的是,我无法在任何地方的谷歌上找到我的问题)。感谢阅读:)
    • 如果你想知道当你点击那些滚动按钮时如何获取标签页眉的滚动事件,你可以处理TabControl的WM_HSCROLL事件,就像我做的那样在答案中。
    【解决方案2】:

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

    如果你想知道当你点击那些滚动按钮时如何获取标签页头的滚动事件,你可以处理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,并在调用基本方法后使控件无效。

    【讨论】:

    • 不客气。不,它不会发送WM_HSCROLL,而是需要覆盖OnSelectedIndexChanged 并使控件无效。
    • 你能详细说明这个问题吗?您在屏幕截图中显示的问题将根据我的测试使用上述代码解决。修复后我们可以在滚动按钮后面看到的背景颜色是预期的。
    • 除了我分享的代码之外,只需覆盖OnSelectedIndexChanged,并在方法调用中先调用base.OnSelectedIndexChanged(e);,然后再调用base.Invalidate();。
    • 哦,我明白你的意思了。事实上,WM_HSCROLL 只是在您单击这些滚动按钮的情况下发送。但是您所说的问题也是由于在不发送WM_HSCROLL消息的选项卡之间切换引起的,所以我也处理了。
    • 您可以使用Spy++ 监视控件的消息。我所描述的是基于我在监视控制消息后的理解:)
    猜你喜欢
    • 2016-01-26
    • 1970-01-01
    • 2018-11-30
    • 2019-05-03
    • 1970-01-01
    • 2015-01-18
    • 2022-07-29
    • 2017-05-23
    相关资源
    最近更新 更多