简体   繁体   English

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

[英]ScrollableControl draw border around entire control

I'm building custom user control that is based on ScrollableControl . 我正在构建基于ScrollableControl自定义用户控件。
Right now I'm trying to add border around my control (similar to border that DataGridView has) 现在,我正在尝试在控件周围添加边框(类似于DataGridView的边框)

I'm able to draw border using: 我可以使用以下方法绘制边框:

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

but this draws border around ClientRectangle, not around whole control: 但这会在ClientRectangle周围绘制边框,而不是在整个控件周围绘制边框: 在此处输入图片说明

As you can see in the above picture, border isn't surrounding scrollbars as it does in DataGridView. 如上图所示,边框没有像DataGridView中那样环绕滚动条。

Can I draw border around entire control so that scrollbars get included in area surrounded by border? 我可以在整个控件周围绘制边框,以使滚动条包含在边框所包围的区域中吗?

EDIT: 编辑:
Based on Textbox custom onPaint I am able to draw custom border, by overriding WndProc but I get this weird looking border flickering: 基于Textbox的自定义onPaint,我可以通过覆盖WndProc来绘制自定义边框,但是我得到了看起来很奇怪的边框闪烁:

在此处输入图片说明

Here is full code I have so far: 这是我到目前为止的完整代码:

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

Can this flickering be fixed? 可以解决此闪烁问题吗?

I was able to solve my problem by overriding CreateParams : 我可以通过重写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;
    }
}

and here is required NativeMethods class: 这是必需的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;
}

below is the result: 结果如下:

在此处输入图片说明

You can simply derive from UserControl . 您可以简单地从UserControl派生。 It has built-in support for showing scrollbars and also has built-in support for showing borders. 它具有用于显示滚动条的内置支持,还具有用于显示边框的内置支持。

All of built-in features of UserControl can be added to your control which is derived from ScrollableControl but with some additional try and error effort or taking look at source code of UserControl . 可以将UserControl所有内置功能添加到从ScrollableControl派生的ScrollableControl但是需要进行一些额外的尝试和错误工作,或者着眼于UserControl 源代码 But using UserControl class you can simply have those features. 但是使用UserControl类可以简单地拥有这些功能。

Show Border 显示边框

To show border, it's enough to set its BorderStyle to FixedSingle to get desired feature: 要显示边框,只需将其BorderStyle设置为FixedSingle即可获得所需的功能:

在此处输入图片说明

Show Scrollbars 显示滚动条

To gain scroll feature, it's enough to set its AutoScroll to true and also set a suitable AutoScrollMinSize for control. 要获得滚动功能,只需将其AutoScroll设置为true,并设置合适的AutoScrollMinSize进行控制即可。 Then when the width or height of the control is less than width or height of given size, the suitable scrollbar will be shown. 然后,当控件的宽度或高度小于给定大小的宽度或高度时,将显示合适的滚动条。

Custom Border Color 自定义边框颜色

I also suppose you want to have different border color for the control, then it's enough to override WndProc and handle WM_NCPAINT and draw custom border for the control like this: 我还假设您希望控件具有不同的边框颜色,那么就足以覆盖WndProc并处理WM_NCPAINT并为控件绘制自定义边框,如下所示:

在此处输入图片说明

In above example, I've used the same technique which I used for Changing BorderColor of TextBox with a small change, here I checked if the BorderStyle equals to FixedSingle the I drew the border with desired color. 在上面的示例中,我使用了与更改TextBox的BorderColor相同的技术,只是做了很小的改动,在这里,我检查BorderStyle等于FixedSingle ,然后绘制了具有所需颜色的边框。

Enable the designer to act like a Parent Control at design time 使设计人员在设计时可以像父控件一样工作

If you want to enable it's designer to be able to drop some controls onto your UserControl , it's enough to decorate it with [Designer(typeof(ParentControlDesigner))] . 如果要使设计者能够将某些控件放到UserControl ,就可以用[Designer(typeof(ParentControlDesigner))]装饰它。 This way, when you drop your UserControl on form, it can host other controls like a panel control. 这样,当您将UserControl放在窗体上时,它可以承载其他控件,例如面板控件。 If you don't like this feature, just don't decorate it with that attribute and it will use Control designer by default which doesn't act like a parent control. 如果您不喜欢此功能,请不要使用该属性来装饰它,并且默认情况下它将使用Control设计器,该设计器的作用不像父控件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM