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
}
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);
}
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.
Original code should be :
protected override CreateParams CreateParams
{
get
{
CreateParams cp;
cp = base.CreateParams;
cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
return cp;
}
}
This works !!
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
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 !!
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.
How about this take on solution #1 (Get the desktop DC and Draw on the screen):
- Get the desktop DC and the graphics object for that DC [Graphics.fromHDC(...)]
- 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)
- Do your graphics rendering.