ScrollableControl在整个控件周围绘制边框

时间:2017-04-11 12:38:58

标签: c# .net winforms user-controls

我正在构建基于ScrollableControl的自定义用户控件 现在我试图在我的控件周围添加边框(类似于DataGridView的边框)

我可以使用以下方式绘制边框:

e.Graphics.TranslateTransform(AutoScrollPosition.X*-1, AutoScrollPosition.Y*-1);
ControlPaint.DrawBorder(e.Graphics, ClientRectangle, Color.DarkBlue, ButtonBorderStyle.Dashed);

但是这会在ClientRectangle周围绘制边框,而不是围绕整个控件: enter image description here

正如您在上图中所看到的,边框不像DataGridView中那样包围滚动条。

我可以在整个控件周围绘制边框,以便滚动条包含在由边框包围的区域中吗?

编辑:
基于Textbox custom onPaint,我可以通过覆盖WndProc来绘制自定义边框,但是我看到这个奇怪的边框闪烁:

enter image description here

这是我到目前为止的完整代码:

internal class TestControl : ScrollableControl
{
    private int _tileWidth = 100;
    private int _tileHeight = 100;
    private int _tilesX = 20;
    private int _tilesY = 20;

    public TestControl()
    {
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.UserPaint, true);
        SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        SetStyle(ControlStyles.Opaque, true);
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        UpdateStyles();
        ResizeRedraw = true;
        AutoScrollMinSize = new Size(_tilesX*_tileWidth, _tilesY*_tileHeight);
    }

    private bool _test = true;
    [DefaultValue(true)]
    public bool Test
    {
        get { return _test; }
        set
        {
            if(_test==value) return;
            _test = value;
            Update();
        }
    }

    [DllImport("user32")]
    private static extern IntPtr GetWindowDC(IntPtr hwnd);
    struct RECT
    {
        public int left, top, right, bottom;
    }
    struct NCCALSIZE_PARAMS
    {
        public RECT newWindow;
        public RECT oldWindow;
        public RECT clientWindow;
        IntPtr windowPos;
    }
    int clientPadding = 1;
    int actualBorderWidth = 1;
    Color borderColor = Color.Black;

    protected override void WndProc(ref Message m)
    {
        //We have to change the clientsize to make room for borders
        //if not, the border is limited in how thick it is.
        if (m.Msg == 0x83 && _test) //WM_NCCALCSIZE   
        {
            if (m.WParam == IntPtr.Zero)
            {
                RECT rect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
                rect.left += clientPadding;
                rect.right -= clientPadding;
                rect.top += clientPadding;
                rect.bottom -= clientPadding;
                Marshal.StructureToPtr(rect, m.LParam, false);
            }
            else
            {
                NCCALSIZE_PARAMS rects = (NCCALSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALSIZE_PARAMS));
                rects.newWindow.left += clientPadding;
                rects.newWindow.right -= clientPadding;
                rects.newWindow.top += clientPadding;
                rects.newWindow.bottom -= clientPadding;
                Marshal.StructureToPtr(rects, m.LParam, false);
            }
        }
        if (m.Msg == 0x85 && _test) //WM_NCPAINT    
        {
            base.WndProc(ref m);

            IntPtr wDC = GetWindowDC(Handle);
            using (Graphics g = Graphics.FromHdc(wDC))
            {
                ControlPaint.DrawBorder(g, new Rectangle(0, 0, Size.Width, Size.Height), borderColor, actualBorderWidth, ButtonBorderStyle.Solid,
                    borderColor, actualBorderWidth, ButtonBorderStyle.Solid, borderColor, actualBorderWidth, ButtonBorderStyle.Solid,
                    borderColor, actualBorderWidth, ButtonBorderStyle.Solid);
            }
            return;
        }
        base.WndProc(ref m);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
        e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y);

        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

        var offsetX = (AutoScrollPosition.X*-1)/_tileWidth;
        var offsetY = (AutoScrollPosition.Y*-1)/_tileHeight;

        var visibleX = Width/_tileWidth + 2;
        var visibleY = Height/_tileHeight + 2;

        var x = Math.Min(visibleX + offsetX, _tilesX);
        var y = Math.Min(visibleY + offsetY, _tilesY);

        for (var i = offsetX; i < x; i++)
        {
            for (var j = offsetY; j < y; j++)
            {
                e.Graphics.FillRectangle(Brushes.Beige, new Rectangle(i*_tileWidth, j*_tileHeight, _tileWidth, _tileHeight));
                e.Graphics.DrawString(string.Format("{0}:{1}", i, j), Font, Brushes.Black, new Rectangle(i*_tileWidth, j*_tileHeight, _tileWidth, _tileHeight));
            }
        }

        using (var p = new Pen(Color.Black))
        {
            for (var i = offsetX + 1; i < x; i++)
            {
                e.Graphics.DrawLine(p, i*_tileWidth, 0, i*_tileWidth, y*_tileHeight);
            }

            for (var i = offsetY + 1; i < y; i++)
            {
                e.Graphics.DrawLine(p, 0, i*_tileHeight, x*_tileWidth, i*_tileHeight);
            }
        }

        e.Graphics.FillRectangle(Brushes.White, AutoScrollPosition.X*-1 + 10, AutoScrollPosition.Y*-1 + 10, 35, 14);
        e.Graphics.DrawString("TEST", DefaultFont, new SolidBrush(Color.Red), AutoScrollPosition.X*-1 + 10, AutoScrollPosition.Y*-1 + 10);

        e.Graphics.TranslateTransform(AutoScrollPosition.X*-1, AutoScrollPosition.Y*-1);

        ControlPaint.DrawBorder(e.Graphics, ClientRectangle, Color.Red, actualBorderWidth, ButtonBorderStyle.None,
                    Color.Red, actualBorderWidth, ButtonBorderStyle.None, Color.Red, actualBorderWidth, ButtonBorderStyle.Solid,
                    Color.Red, actualBorderWidth, ButtonBorderStyle.Solid);
    }

    protected override void OnScroll(ScrollEventArgs e)
    {
        if (DesignMode)
        {
            base.OnScroll(e);
            return;
        }

        if (e.Type == ScrollEventType.First)
        {
            LockWindowUpdate(Handle);
        }
        else
        {
            LockWindowUpdate(IntPtr.Zero);
            Update();
            if (e.Type != ScrollEventType.Last) LockWindowUpdate(Handle);
        }
    }

    protected override void OnMouseWheel(MouseEventArgs e)
    {
        if (VScroll && (ModifierKeys & Keys.Shift) == Keys.Shift)
        {
            VScroll = false;
            LockWindowUpdate(Handle);
            base.OnMouseWheel(e);
            LockWindowUpdate(IntPtr.Zero);
            Update();
            VScroll = true;
        }
        else
        {
            LockWindowUpdate(Handle);
            base.OnMouseWheel(e);
            LockWindowUpdate(IntPtr.Zero);
            Update();
        }
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool LockWindowUpdate(IntPtr hWnd);
}

这种闪烁可以修复吗?

2 个答案:

答案 0 :(得分:2)

我能够通过覆盖CreateParams来解决我的问题:

protected override CreateParams CreateParams
{
    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= NativeMethods.WS_EX_CONTROLPARENT;

        cp.ExStyle &= (~NativeMethods.WS_EX_CLIENTEDGE);
        cp.Style &= (~NativeMethods.WS_BORDER);

                cp.Style |= NativeMethods.WS_BORDER;

        return cp;
    }
}

这里需要NativeMethods class:

internal static class NativeMethods
{
    public const int WS_EX_CONTROLPARENT = 0x00010000;
    public const int WS_EX_CLIENTEDGE = 0x00000200;
    public const int WS_BORDER = 0x00800000;
}

下面是结果:

enter image description here

答案 1 :(得分:1)

您可以简单地从UserControl派生。它内置支持显示滚动条,并且内置支持显示边框。

UserControl的所有内置功能都可以添加到您的控件中,该功能源自ScrollableControl,但需要一些额外的尝试和错误工作,或者查看source code {{} 1}}。但是使用UserControl类,您可以简单地使用这些功能。

显示边框

要显示边框,只需将其UserControl设置为BorderStyle即可获得所需的功能:

enter image description here

显示滚动条

要获得滚动功能,只需将其FixedSingle设置为true,并为控件设置合适的AutoScroll即可。然后,当控件的宽度或高度小于给定尺寸的宽度或高度时,将显示合适的滚动条。

自定义边框颜色

我还假设您希望控件具有不同的边框颜色,然后它就足以覆盖AutoScrollMinSize并处理WndProc并为控件绘制自定义边框,如下所示:

enter image description here

在上面的示例中,我使用了与Changing BorderColor of TextBox相同的技术进行了一些小改动,在这里我检查了WM_NCPAINT是否等于BorderStyle我画了边框具有所需的颜色。

让设计师在设计时充当父控件

如果你想让它的设计师能够将一些控件放到FixedSingle上,那么用UserControl来装饰它就足够了。这样,当您将[Designer(typeof(ParentControlDesigner))]放在表单上时,它可以托管其他控件,如面板控件。如果您不喜欢此功能,请不要使用该属性进行装饰,默认情况下它将使用UserControl设计器,它不像父控件那样。