当前位置:首页 > WinForm

控件重绘 - 修改TextBox边框颜色且不闪烁的最佳做法

发表于 2015-11-08 15:59

在WinForm中.NET Framework提供的TextBox是不能修改边框的颜色的,在如今对界面要求越来越高的情况下只有黑色边框的TextBox显然不能满人们的需求。本文将介绍一种修改TextBox边框颜色且不会闪烁的最佳做法。


最开始的时候,我还是像重绘其他控件一样重写TextBox的OnPaint()方法,结果发现其根本就不走OnPaint()方法。因为有些控件是由系统进程绘制的,重写OnPaint()方法不起作用,TextBox就是其中之一。

面对以上问题,网上也有很多答案,自己都不太满意,但总体只有两种做法:

1. 隐藏TextBox的边框并将其放在一个容器中,设置容器的背景颜色来达到边框的效果。

2. 重写WndProc()方法,拦截系统消息来绘制自己想要颜色的边框。

这两种方法都有缺陷:

1. 每个TextBox都多一个容器控件,当TextBox较多的时候,加载较慢且浪费系统资源。

2. 鼠标在TextBox上来回滑动或者是点击TextBox的时候,会闪烁。


那么有没有一种方法既不占用系统资源又不闪烁呢?答案是有的。

我们只需要重写TextBox的CreateParams属性,给其加上一个边框,然后绘制自己想要的即可。以下是代码:

绘制过程中会用到一些API和消息常量等先声明

public class NativeMethods
    {
        internal const int WS_EX_CLIENTEDGE = 512 /*0x0200*/;
        internal const int WS_EX_WINDOWEDGE = 0x0100;
        internal const int WM_PAINT = 15; // 0x000f
        internal const int WM_NCPAINT = 133; // 0x0085

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;

            public RECT(Rectangle rect)
            {
                this.bottom = rect.Bottom;
                this.left = rect.Left;
                this.right = rect.Right;
                this.top = rect.Top;
            }

            public RECT(int left, int top, int right, int bottom)
            {
                this.bottom = bottom;
                this.left = left;
                this.right = right;
                this.top = top;
            }

            public static RECT FromXYWH(int x, int y, int width, int height)
            {
                return new RECT(x, y, x + width, y + height);
            }

            public int Width
            {
                get
                {
                    return this.right - this.left;
                }
            }
            public int Height
            {
                get
                {
                    return this.bottom - this.top;
                }
            }

            public override /*Object*/ string ToString()
            {
                return String.Concat(
                  "Left = ",
                  this.left,
                  " Top ",
                  this.top,
                  " Right = ",
                  this.right,
                  " Bottom = ",
                  this.bottom);
            }

            public static implicit operator Rectangle(RECT rect)
            {
                return Rectangle.FromLTRB(rect.left, rect.top, rect.right, rect.bottom);
            }

        }

        [DllImport("user32.dll")]
        internal static extern bool GetWindowRect(IntPtr hwnd, ref RECT lpRect);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
        internal static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2);

        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
        internal static extern IntPtr GetWindowDC(IntPtr hWnd);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
        internal static extern IntPtr CreateCompatibleDC(IntPtr hDC);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
        internal static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int width, int height);

        [DllImport("gdi32", CharSet = CharSet.Auto, ExactSpelling = true)]
        internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
        internal static extern int CombineRgn(IntPtr hRgn, IntPtr hRgn1, IntPtr hRgn2, int nCombineMode);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
        public static extern int OffsetRgn(IntPtr hrgn, int nXOffset, int nYOffset);

        [DllImport("gdi32")]
        internal static extern bool DeleteObject(IntPtr hObject);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
        internal static extern int SelectClipRgn(IntPtr hDC, IntPtr hRgn);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
        public static extern int ExcludeClipRect(IntPtr hdc, int nLeft, int nTop, int nRight, int nBottom);

        [DllImport("gdi32.dll")]
        internal static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, Int32 dwRop);

        [DllImport("gdi32", EntryPoint = "DeleteDC", CharSet = CharSet.Auto, ExactSpelling = true)]
        internal static extern bool DeleteDC(IntPtr hDC);
    }

添加一个组件CustomTextBox继承TextBox,实现自定义边框颜色

public partial class CustomTextBox : System.Windows.Forms.TextBox
    {

        private Rectangle m_rcClient = Rectangle.Empty;
        private Color _coBorder = Color.Red;

        public CustomTextBox()
        {
            InitializeComponent();
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cparams = base.CreateParams;
                BorderStyle border = this.BorderStyle;

                if (!(border == BorderStyle.Fixed3D))
                {
                    cparams.ExStyle &= ~NativeMethods.WS_EX_CLIENTEDGE;
                    cparams.Style &= ~8388608;

                    switch (border)
                    {
                        // Unlike other controls, text box doesn't draw its single border in the NC area!
                        case BorderStyle.Fixed3D:
                        case BorderStyle.FixedSingle:
                            // 使一个视窗具有凹陷边框
                            cparams.ExStyle = cparams.ExStyle | NativeMethods.WS_EX_CLIENTEDGE | NativeMethods.WS_EX_WINDOWEDGE;
                            break;
                    }
                }

                return cparams;
            }
        }

        public Color BorderColor
        {
            get { return this._coBorder; }
            set { this._coBorder = value; }
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            base.OnPaint(pe);
        }

        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case NativeMethods.WM_PAINT:
                    this.OnWmNcPaint(ref m);
                    break;
                case NativeMethods.WM_NCPAINT:
                    this.OnWmNcPaint(ref m);
                    return;
            }


            base.WndProc(ref m);
        }

        private void OnWmNcPaint(ref Message m)
        {
            NativeMethods.RECT rcWnd = new NativeMethods.RECT();
            NativeMethods.GetWindowRect(m.HWnd, ref rcWnd);

            int intWidth = rcWnd.Width;
            int intHeight = rcWnd.Height;

            int width = rcWnd.Width;
            int height = rcWnd.Height;

            IntPtr hRgn = NativeMethods.CreateRectRgn(0, 0, width, height);
            if (hRgn != IntPtr.Zero)
            {
                IntPtr hDc = NativeMethods.GetWindowDC(m.HWnd);
                if (hDc != IntPtr.Zero)
                {
                    IntPtr memDc = NativeMethods.CreateCompatibleDC(hDc);
                    if (memDc != IntPtr.Zero)
                    {
                        IntPtr hBmp = NativeMethods.CreateCompatibleBitmap(hDc, width, height);
                        if (hBmp != IntPtr.Zero)
                        {
                            IntPtr hObj = NativeMethods.SelectObject(memDc, hBmp);

                            if (m.WParam.ToInt32() != 1)
                            {
                                IntPtr tmpRgn = NativeMethods.CreateRectRgn(0, 0, 0, 0);

                                NativeMethods.CombineRgn(tmpRgn, m.WParam, IntPtr.Zero, NativeMethods.RGN_COPY);
                                NativeMethods.OffsetRgn(tmpRgn, -rcWnd.left, -rcWnd.top);
                                NativeMethods.CombineRgn(hRgn, hRgn, tmpRgn, NativeMethods.RGN_AND);

                                NativeMethods.DeleteObject(tmpRgn);
                            }

                            using (Graphics g = Graphics.FromHdc(memDc))
                            {
                                RenderNCArea(g, width, height, true);
                            }

                            NativeMethods.SelectClipRgn(hDc, hRgn);
                            NativeMethods.ExcludeClipRect(hDc, m_rcClient.X, m_rcClient.Y, m_rcClient.Right, m_rcClient.Bottom);

                            NativeMethods.BitBlt(hDc, 0, 0, rcWnd.Width, rcWnd.Height, memDc, 0, 0, 0x00CC0020/*SRCCOPY*/ );

                            NativeMethods.SelectObject(memDc, hObj);
                            NativeMethods.DeleteObject(hBmp);
                        }

                        NativeMethods.DeleteDC(memDc);
                    }
                    NativeMethods.DeleteDC(hDc);
                }

                IntPtr hTmp = NativeMethods.CreateRectRgn(m_rcClient.Left, m_rcClient.Top, m_rcClient.Right, m_rcClient.Bottom);
                if (hTmp != IntPtr.Zero)
                {
                    NativeMethods.CombineRgn(hRgn, hRgn, hTmp, NativeMethods.RGN_AND);
                    NativeMethods.OffsetRgn(hRgn, rcWnd.left, rcWnd.top);
                    NativeMethods.DeleteObject(hTmp);

                    Message msg = new Message();

                    msg.Msg = m.Msg;
                    msg.HWnd = m.HWnd;
                    msg.WParam = hRgn;

                    base.WndProc(ref msg);
                }
                NativeMethods.DeleteObject(hRgn);
            }

            m.Result = IntPtr.Zero;
        }

        private void RenderNCArea(Graphics g, int width, int height, bool bDrawBorder)
        {
            Rectangle rc = new Rectangle(0, 0, width, height);

            using (Brush brush = new SolidBrush(this.BackColor))
            {
                g.FillRectangle(brush, 0, 0, width, height);
            }

            if (bDrawBorder)
            {
                DrawBorders(rc, g);
            }

        }

        private void DrawBorders(Rectangle rc, Graphics g)
        {
            ControlPaint.DrawBorder(g, rc, this.BorderColor, ButtonBorderStyle.Solid);
        }

    }

效果如图:

控件重绘 - 修改TextBox边框颜色且不闪烁的最佳做法 图1


本文章由创风网原创,转载请注明出处:http://www.windite.com/article/details/ftw0yz5