How to draw custom borders on .Net WinForms elements

I am trying to draw custom borders for existing .Net WinForms elements. I tried to do this by creating a class which from the control I want to change the border color, and then try a few things while drawing. I tried the following:

1. Catch WM_NCPAINT . This works somewhat. The problem with the code below is that when you resize the control, the border will be cut off on the right and bottom. Not good.

 protected override void WndProc(ref Message m) { if (m.Msg == NativeMethods.WM_NCPAINT) { WmNcPaint(ref m); return; } base.WndProc(ref m); } private void WmNcPaint(ref Message m) { if (BorderStyle == BorderStyle.None) { return; } IntPtr hDC = NativeMethods.GetWindowDC(m.HWnd); if (hDC != IntPtr.Zero) { using (Graphics g = Graphics.FromHdc(hDC)) { ControlPaint.DrawBorder(g, new Rectangle(0, 0, this.Width, this.Height), _BorderColor, ButtonBorderStyle.Solid); } m.Result = (IntPtr)1; NativeMethods.ReleaseDC(m.HWnd, hDC); } } 

2. Override void OnPaint . This works for some controls, but not for all. It also requires that you set BorderStyle to BorderStyle.None , and you need to manually clear the background from the paint, otherwise you will get it when you size.

 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); ControlPaint.DrawBorder(e.Graphics, new Rectangle(0, 0, this.Width, this.Height), _BorderColor, ButtonBorderStyle.Solid); } 

3. Overriding void OnResize and void OnPaint (as in method 2). Thus, it draws well with resizing, but not when the panel has AutoScroll turned on, in which case it will look like this when scrolling down. If I try to use WM_NCPAINT to draw a border, Refresh() has no effect.

 protected override void OnResize(EventArgs eventargs) { base.OnResize(eventargs); Refresh(); } 

Suggestions are more than welcome. I would like to know what the best way to do this is for several types of controls (I will have to do this for several WinForms controls by default).

+7
c # controls winforms paintevent
source share
2 answers

EDIT: So, I realized what caused my initial problems. After a very long time fiddling, experimenting and studying the source code of the .Net framework, here you can make the final method (given that you have a control that inherits the control you want to draw a custom frame on):

 [DllImport("user32.dll")] public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RedrawWindowFlags flags); [Flags()] public enum RedrawWindowFlags : uint { Invalidate = 0X1, InternalPaint = 0X2, Erase = 0X4, Validate = 0X8, NoInternalPaint = 0X10, NoErase = 0X20, NoChildren = 0X40, AllChildren = 0X80, UpdateNow = 0X100, EraseNow = 0X200, Frame = 0X400, NoFrame = 0X800 } // Make sure that WS_BORDER is a style, otherwise borders aren't painted at all protected override CreateParams CreateParams { get { if (DesignMode) { return base.CreateParams; } CreateParams cp = base.CreateParams; cp.ExStyle &= (~0x00000200); // WS_EX_CLIENTEDGE cp.Style |= 0x00800000; // WS_BORDER return cp; } } // During OnResize, call RedrawWindow with Frame|UpdateNow|Invalidate so that the frame is always redrawn accordingly protected override void OnResize(EventArgs e) { base.OnResize(e); if (DesignMode) { RecreateHandle(); } RedrawWindow(this.Handle, IntPtr.Zero, IntPtr.Zero, RedrawWindowFlags.Frame | RedrawWindowFlags.UpdateNow | RedrawWindowFlags.Invalidate); } // Catch WM_NCPAINT for painting protected override void WndProc(ref Message m) { if (m.Msg == NativeMethods.WM_NCPAINT) { WmNcPaint(ref m); return; } base.WndProc(ref m); } // Paint the custom frame here private void WmNcPaint(ref Message m) { if (BorderStyle == BorderStyle.None) { return; } IntPtr hDC = NativeMethods.GetWindowDC(m.HWnd); using (Graphics g = Graphics.FromHdc(hDC)) { g.DrawRectangle(new Pen(_BorderColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); } NativeMethods.ReleaseDC(m.HWnd, hDC); } 

So, leave OnPaint as it is, make sure WS_BORDER set, then catch WM_NCPAINT and draw a border through hDC and make sure RedrawWindow is called in OnResize .

Perhaps it can even be extended to draw a custom scrollbar, because this is the part of the window frame that you can use during WM_NCPAINT .

I removed my old answer from this.

EDIT 2: For ComboBox you need to catch WM_PAINT in WndProc() , because for some reason .Net source for drawing ComboBox does not use OnPaint() , but WM_PAINT . So something like this:

 protected override void WndProc(ref Message m) { base.WndProc(ref m); if (m.Msg == NativeMethods.WM_PAINT) { OnWmPaint(); } } private void OnWmPaint() { using (Graphics g = CreateGraphics()) { if (!_HasBorders) { g.DrawRectangle(new Pen(BackColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); return; } if (!Enabled) { g.DrawRectangle(new Pen(_BorderColorDisabled), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); return; } if (ContainsFocus) { g.DrawRectangle(new Pen(_BorderColorActive), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); return; } g.DrawRectangle(new Pen(_BorderColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); } } 
+1
source share

In fact, you can use the WPF functionality controls to create any border you need.

  • Create form
  • Install ElementHost control (from WPF interaction) in the form
  • Create a WPF user control (or use an existing panel) with a custom border
  • Place the WindowsFormsHost control inside the WPF user control (this control will be used later to host your control)
  • Set the ElementHost Child property using the WPF user control from the previous step

    I agree that my solution contains many nested controls, but from my point of view it significantly reduces the number of problems associated with OnPaint nested controls WPF + WinForm

-2
source share

All Articles