C# – Drawing on top of controls inside a panel (C# WinForms)


I know this question had been asked more than a few times, but so far I haven't been able to find a good solution for it.

I've got a panel with other control on it.
I want to draw a line on it and on top of all the controls in the panel

I came across 3 types of solutions (non of them worked the way I wanted) :

  1. Get the desktop DC and Draw on the screen.
    This will draw on other applications if they overlap the form.

  2. Overriding the panel's "CreateParams":


protected override CreateParams CreateParams {  
  get {  
    CreateParams cp;  
    cp = base.CreateParams;  
    cp.Style &= ~0x04000000; //WS_CLIPSIBLINGS
    cp.Style &= ~0x02000000; //WS_CLIPCHILDREN
    return cp;  

//NOTE I've also tried disabling WS_CLIPSIBLINGS

and then drawing the line OnPaint().
But… Since the panel's OnPaint is called before the OnPaint of the controls in it,
the drawing of the controls inside simply paints on top of the line.
I've seen someone suggest using a message filter to listen to WM_PAINT mesages, and use a timer, but I don't think this solution is either "good practice" or effective.
What would you do ? Decide that the controls inside have finished drawing after X ms, and set the timer to X ms ?

This screen shot shows the panel with WS_CLIPSIBLINGS and WS_CLIPCHILDREN turned off.
The Blue line is painted at the Panel's OnPaint, and simply being painted on by the textboxes and label.
The Red line is painted on top only because it's not being painted from the panel's OnPaint (It's actually painted as a result of a Button being clicked)
alt text

3rd: Creating a transparent layer and drawing on top of that layer.
I've created a transparent control using:

protected override CreateParams CreateParams {  
  get {  
    CreateParams cp = base.CreateParams;  
    cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT  
    return cp;  

The problem is still, putting the transparent control on top of the Panel and all its controls.
I've tried bringing it to the front using: "BringToFront()" , but it didn't seem to help.
I've put it in the Line control's OnPaint() handler.
Should I try putting it somewhere else ??
– This also creates issue with having another control on top of the panel. (catching the mouse clicks etc..)

Any help would be greatly appreciated!

The black line is a sample of what I was trying to do. (used windows paint to paint it)

alt text

Best Solution

Turns out this is a whole lot easier than I thought. Thanks for not accepting any of my other answers. Here is the two-step process for creating a Fline (floating line - sorry, it's late):

alt text

Step 1: Add a UserControl to your project and name it "Fline". Add the following to the using statements:

using System.Drawing.Drawing2D;

Step 2: Add the following to the Fline's Resize event:

int wfactor = 4; // half the line width, kinda
// create 6 points for path
Point[] pts = {
    new Point(0, 0), 
    new Point(wfactor, 0), 
    new Point(Width, Height - wfactor),
    new Point(Width, Height) ,
    new Point(Width - wfactor, Height),
    new Point(0, wfactor) };
// magic numbers! 
byte[] types = {
    0, // start point
    1, // line
    1, // line
    1, // line
    1, // line
    1 }; // line 
GraphicsPath path = new GraphicsPath(pts, types);
this.Region = new Region(path);

Compile, and then drag a Fline onto your form or panel. Important: the default BackColor is the same as the form, so change the Fline's BackColor to Red or something obvious (in the designer). One weird quirk about this is that when you drag it around in the designer it shows as a solid block until you release it - not a huge deal.

This control can appear in front of or behind any other control. If you set Enabled to false, it will still be visible but will not interfere with mouse events on the controls underneath.

You'll want to enhance this for your purposes, of course, but this shows the basic principle. You can use the same technique for creating a control of whatever shape you like (my initial test of this made a triangle).

Update: this makes a nice dense one-liner, too. Just put this in your UserControl's Resize event:

this.Region=new Region(new System.Drawing.Drawing2D.GraphicsPath(new Point[]{new Point(0,0),new Point(4,0),new Point(Width,Height-4),new Point(Width,Height),new Point(Width-4,Height),new Point(0,4)},new byte[]{0,1,1,1,1,1}));
Related Question