tags:

views:

15074

answers:

11
+4  A: 
MusiGenesis
It seems the Label has to be added to the panel first to be painted last. Playing with BringToFront() didn't seem to help much
dtroy
Instead of a label, you could use another panel (1 or 2 pixels wide). If you bring this to front, it will be in front of everything else on the panel no matter what.
MusiGenesis
Something I've noticed while playing with it:If the line is over a control that has "WS_CLIPSIBLINGS" disabled, the line won't be drawn on top of it. Otherwise, seems OK. But what will we do if we want a diagonal line ?
dtroy
BTW, why didn't my other answer work? (the one with the diagonal red line over the buttons)
MusiGenesis
It's not that it didn't work, it's just that I didn't want to hook to the control's event handler. I don't know how many controls there will be on the surface I am drawing on. What if we draw something more complicated than a line, like moving shapes etc.. You know what I mean?
dtroy
@dtroy: please see my new answer. I was halfway through a comment like "this is fundamentally impossible" when I decided to give it one more go.
MusiGenesis
+2  A: 

The only simple solution I can think of is to create Paint event handlers for each control you want to paint on top of. Then coordinate the line drawing between these handlers. This is not the most convenient solution, however this will give you the ability to paint on top of the controls.

Assuming button is a child control of panel:

panel.Paint += new PaintEventHandler(panel_Paint);
button.Paint += new PaintEventHandler(button_Paint);

protected void panel_Paint(object sender, PaintEventArgs e)
{
    //draw the full line which will then be partially obscured by child controls
}

protected void button_Paint(object sender, PaintEventArgs e)
{
    //draw the obscured line portions on the button
}
asponge
+2  A: 

A windows forms panel is a container for controls. If you want to draw something on top of other controls within a panel, then what you need is another control ( at the top of the z order ).

Luckily, you can create windows forms controls which have non-rectangular borders. Look at this technique: http://msdn.microsoft.com/en-us/library/aa289517(VS.71).aspx

To just draw something on the screen, use a label control, and turn AutoSize off. Then attach to the Paint event and set the Size and Region Properties.

Here's a code sample:

private void label1_Paint(object sender, PaintEventArgs e)
{
    System.Drawing.Drawing2D.GraphicsPath myGraphicsPath = new  System.Drawing.Drawing2D.GraphicsPath();
    myGraphicsPath.AddEllipse(new Rectangle(0, 0, 125, 125));
    myGraphicsPath.AddEllipse(new Rectangle(75, 75, 20, 20));
    myGraphicsPath.AddEllipse(new Rectangle(120, 0, 125, 125));
    myGraphicsPath.AddEllipse(new Rectangle(145, 75, 20, 20));
    //Change the button's background color so that it is easy
    //to see.
    label1.BackColor = Color.ForestGreen;
    label1.Size = new System.Drawing.Size(256, 256);
    label1.Region = new Region(myGraphicsPath);
}
Matt Brunell
A: 

I think the best way is to inherit the control of which you want to draw a line on. Override the OnPaint method, call base.Paint() from within, after that draw the line using the same graphic instance. At the same time, you can also have a parameter which specific at which point the line should be draw, so that you can control the line directly from your main form.

faulty
Done that. No good.As I mentioned in the question, The controls on top of it paint after the panel's "OnPaint()" has returned.
dtroy
You're overriding OnPaint of panel, but I'm referring to the OnPaint of the control itself (the one displaying the "wave"). If that still doesn't work out, then what control was it that you use to display the "wave"?
faulty
The control in the picture is inherited from UserControl, but the same goes for a Label, TextBox etc..The problem is.. there are 8 controls in the picuture, 4 of mine + 4 Splitter. Are you suggesting I'll draw a fragment of the line on each of them ?
dtroy
what if there are other controls (different types) on the panel ? Override all of their OnPaint()s ?While I know this is a possibility, I was hoping for a better solution.BTW, thanks for your help
dtroy
Sorry for the late reply. Yes I was referring to drawing fragment for each of them, and add a properly to each of them so you can easily set when you want the line to be from your usercontrol.Like:ControlA.LinePosition = new Point(100, 0);ControlB.LinePosition = new Point(100, 0);
faulty
A: 

Original code should be :

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp;
            cp = base.CreateParams;
            cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
            return cp;
        }
    }

This works !!

John, thanks for your reply.You are right about the mistake in the question and I've fixed it.This WILL let you draw on top of other controls, but since the controls within the panel are drawn after the Panel's OnPaint, they will simply be drawn on top of what you've drawn.
dtroy
+3  A: 
MusiGenesis
+2  A: 

Make a new LineControl : Control like this:

then call BringToFront() after the InitializeComponent

public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            this.simpleLine1.BringToFront();
        }
    }



using System;
using System.Windows.Forms;
using System.Drawing;
using System.Collections.Generic;

public class SimpleLine : Control
{
    private Control parentHooked;   
    private List<Control> controlsHooked;

    public enum LineType
    {
        Horizontal,
        Vertical,
        ForwardsDiagonal,
        BackwardsDiagonal
    }

    public event EventHandler AppearanceChanged;
    private LineType appearance;
    public virtual LineType Appearance
    {
        get
        {
            return appearance;
        }
        set
        {
            if (appearance != value)
            {
                this.SuspendLayout();
                switch (appearance)
                {
                    case LineType.Horizontal:
                        if (value == LineType.Vertical)
                        {
                            this.Height = this.Width;
                        }

                        break;
                    case LineType.Vertical:
                        if (value == LineType.Horizontal)
                        {
                            this.Width = this.Height;
                        }
                        break;
                }
                this.ResumeLayout(false);

                appearance = value;
                this.PerformLayout();
                this.Invalidate();
            }
        }
    }
    protected virtual void OnAppearanceChanged(EventArgs e)
    {
        if (AppearanceChanged != null) AppearanceChanged(this, e);
    }

    public event EventHandler LineColorChanged;
    private Color lineColor;
    public virtual Color LineColor
    {
        get
        {
            return lineColor;
        }
        set
        {
            if (lineColor != value)
            {
                lineColor = value;
                this.Invalidate();
            }
        }
    }
    protected virtual void OnLineColorChanged(EventArgs e)
    {
        if (LineColorChanged != null) LineColorChanged(this, e);
    }

    public event EventHandler LineWidthChanged;
    private float lineWidth;
    public virtual float LineWidth
    {
        get
        {
            return lineWidth;
        }
        set
        {
            if (lineWidth != value)
            {
                if (0 >= value)
                {
                    lineWidth = 1;
                }
                lineWidth = value;
                this.PerformLayout();
            }
        }
    }
    protected virtual void OnLineWidthChanged(EventArgs e)
    {
        if (LineWidthChanged != null) LineWidthChanged(this, e);
    }

    public SimpleLine()
    {
        base.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Selectable, false);
        base.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        base.BackColor = Color.Transparent;

        InitializeComponent();

        appearance = LineType.Vertical;
        LineColor = Color.Black;
        LineWidth = 1;
        controlsHooked = new List<Control>();

        this.ParentChanged += new EventHandler(OnSimpleLineParentChanged);
    }

    private void RemoveControl(Control control)
    {
        if (controlsHooked.Contains(control))
        {
            control.Paint -= new PaintEventHandler(OnControlPaint);
            if (control is TextboxX)
            {
                TextboxX text = (TextboxX)control;
                text.DoingAPaint -= new EventHandler(text_DoingAPaint);
            }
            controlsHooked.Remove(control);
        }
    }

    void text_DoingAPaint(object sender, EventArgs e)
    {
        this.Invalidate();
    }

    private void AddControl(Control control)
    {
        if (!controlsHooked.Contains(control))
        {
            control.Paint += new PaintEventHandler(OnControlPaint);
            if (control is TextboxX)
            {
                TextboxX text = (TextboxX)control;
                text.DoingAPaint += new EventHandler(text_DoingAPaint);
            }
            controlsHooked.Add(control);
        }
    }

    private void OnSimpleLineParentChanged(object sender, EventArgs e)
    {
        UnhookParent();

        if (Parent != null)
        {

            foreach (Control c in Parent.Controls)
            {
                AddControl(c);
            }
            Parent.ControlAdded += new ControlEventHandler(OnParentControlAdded);
            Parent.ControlRemoved += new ControlEventHandler(OnParentControlRemoved);
            parentHooked = this.Parent;
        }
    }

    private void UnhookParent()
    {
            if (parentHooked != null)
            {
                foreach (Control c in parentHooked.Controls)
                {
                    RemoveControl(c);
                }
                parentHooked.ControlAdded -= new ControlEventHandler(OnParentControlAdded);
                parentHooked.ControlRemoved -= new ControlEventHandler(OnParentControlRemoved);
                parentHooked = null;
            }
    }

    private void OnParentControlRemoved(object sender, ControlEventArgs e)
    {
        RemoveControl(e.Control);
    }   

    private void OnControlPaint(object sender, PaintEventArgs e)
    {
        int indexa =Parent.Controls.IndexOf(this) , indexb = Parent.Controls.IndexOf((Control)sender);
        //if above invalidate on paint
        if(indexa < indexb)
        {
            Invalidate();
        }
    }

    private void OnParentControlAdded(object sender, ControlEventArgs e)
    {
        AddControl(e.Control);
    }

    private System.ComponentModel.IContainer components = null;
    private void InitializeComponent()
    {
        components = new System.ComponentModel.Container();
    }
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x20;  // Turn on WS_EX_TRANSPARENT
            return cp;
        }
    }

    protected override void OnLayout(LayoutEventArgs levent)
    {
        switch (this.Appearance)
        {
            case LineType.Horizontal:
                this.Height = (int)LineWidth;
                this.Invalidate();
                break;
            case LineType.Vertical:
                this.Width = (int)LineWidth;
                this.Invalidate();
                break;
        }

        base.OnLayout(levent);
    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
        //disable background paint
    }

    protected override void OnPaint(PaintEventArgs pe)
    {
        switch (Appearance)
        {
            case LineType.Horizontal:
                DrawHorizontalLine(pe);
                break;
            case LineType.Vertical:
                DrawVerticalLine(pe);
                break;
            case LineType.ForwardsDiagonal:
                DrawFDiagonalLine(pe);
                break;
            case LineType.BackwardsDiagonal:
                DrawBDiagonalLine(pe);
                break;
        }
    }

    private void DrawFDiagonalLine(PaintEventArgs pe)
    {
        using (Pen p = new Pen(this.LineColor, this.LineWidth))
        {
            pe.Graphics.DrawLine(p, this.ClientRectangle.X, this.ClientRectangle.Bottom,
                                    this.ClientRectangle.Right, this.ClientRectangle.Y);
        }
    }

    private void DrawBDiagonalLine(PaintEventArgs pe)
    {
        using (Pen p = new Pen(this.LineColor, this.LineWidth))
        {
            pe.Graphics.DrawLine(p, this.ClientRectangle.X, this.ClientRectangle.Y,
                                    this.ClientRectangle.Right, this.ClientRectangle.Bottom);
        }
    }

    private void DrawHorizontalLine(PaintEventArgs pe)
    {
        int  y = this.ClientRectangle.Height / 2;
        using (Pen p = new Pen(this.LineColor, this.LineWidth))
        {
            pe.Graphics.DrawLine(p, this.ClientRectangle.X, y,
                                    this.ClientRectangle.Width, y);
        }
    }

    private void DrawVerticalLine(PaintEventArgs pe)
    {
        int x = this.ClientRectangle.Width / 2;
        using (Pen p = new Pen(this.LineColor, this.LineWidth))
        {
            pe.Graphics.DrawLine(p,x, this.ClientRectangle.Y,
                                   x, this.ClientRectangle.Height);
        }
    }
}

Edit: Added diagonal support

I've added some support for controls that repaint when they get the focus.

textboxes and comboboxs wont work as is you will need to make your own and hook there paintish commands like so:

public class TextboxX : TextBox
{
    public event EventHandler DoingAPaint;
    protected override void WndProc(ref Message m)
    {
        switch ((int)m.Msg)
        {
            case (int)NativeMethods.WindowMessages.WM_PAINT:
            case (int)NativeMethods.WindowMessages.WM_ERASEBKGND:
            case (int)NativeMethods.WindowMessages.WM_NCPAINT:
            case 8465: //not sure what this is WM_COMMAND?
                if(DoingAPaint!=null)DoingAPaint(this,EventArgs.Empty);
                break;
        }           
        base.WndProc(ref m);
    }
}

Its not tested and i'm sure you can improve on it

Hath
Hath, This could have been a nice solution, but.. look (see the image below) what happens when you hover to mouse under a button under the line , or a control under the line gets the focus. http://i73.photobucket.com/albums/i201/sdjc1/temp/screen4.png
dtroy
@dtroy - i've added code that will invalidate when another control on in the parents.controls collection paints.. it won't work as well for textbox or comboboxs but you can see what i've done to cover them. not sure if that will do for your needs but you could make it work for what your discribing.
Hath
dtroy
@dtroy - added the detach for parentHooked. Just noticed this has a few problems if you do a diagonal.. say if you want to select a control thats been covered by the line you can't because the line control is covering it.. i suppose you could relay the mouse messages somehow but i dunno..bit dirty.
Hath
This might be the most code ever written for drawing a single line. :)
MusiGenesis
A: 

Hi,

Very interesting post ! i tryed all the solutions. They work well on pc ... but i hope that somebody could have a solution to do exactly the same but with the compact framework (windows mobile 5.0) ? - I can't ovveride createparam - and buttons don't have paint handler

I hope somebody will have the idea that will save me !!

+3  A: 
MusiGenesis
MusiGenesis, thanks for this answer. It helped a lot. But.. I was playing with it again today and I was trying to create a 1 pixel wide dashed line similar to the red line in your answer above. Since I couldn't have an open GraphicsPath, I was trying to make the control transparent
dtroy
cont...and then draw a line on it. But I couldn't find a way that worked with all the controls under it etc.. Have you got any ideas ?? THANKS!
dtroy
A: 

This article might help with this subject, but not sure if it's the solution: http://www.codeproject.com/KB/GDI-plus/WindowGraphics.aspx

I thought I'll add it in case it helps others.

dtroy
A: 

How about this take on solution #1 (Get the desktop DC and Draw on the screen):

  1. Get the desktop DC and the graphics object for that DC [Graphics.fromHDC(...)]
  2. Set the Clip property of the resulting Graphics object to be the currently visible region of your form. (I have not researched yet how to find the visible region of a form)
  3. Do your graphics rendering.
Ski