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

标签 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);
}

这个闪烁问题可以解决吗?

最佳答案

我能够通过重写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类:

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

关于c# - ScrollableControl 在整个控件周围绘制边框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43346169/

相关文章:

.net - 滚动超过 DataGridView 的底行

c# - 面板滚动 c#

c# - 选择后重置组合框中的文本

C# SqlCeException : One Line SQL to determine table exists

c# - 检查属性是否存在于自定义类列表中

c# - 向基于模型的列表添加值

c# - 如何将包含数组的结构转换为字节数组?

c# - 如何从 jpg 图像中删除嵌入的颜色配置文件?

c# - EPPlus 能否区分 Excel 工作表中的空白单元格和空文本单元格?

c# - 将具有值的 XElement 转换为空元素(其中 XElement.IsEmpty = true)