Form1
Form2
Introduction
The GraphicalOverlay
component draws on top of a form's controls. It is essentially like laying a piece of glass over the form and drawing on the glass. I use this component to draw lines between controls to help users make sense of complex relationships in the UI. I use it sparingly, but sometimes you just need a big red arrow that says: This affects that! When done correctly, the result can be quite elegant.
Using the Code
To use the GraphicalOverlay
component, follow these steps:
- Copy the following two files to your project's directory: GraphicalOverlay.cs, GraphicalOverlay.designer.cs.
- Add GraphicalOverlay.cs to your project. Visual Studio will automatically include GraphicalOverlay.designer.cs.
Compile the project. This will create a GraphicalOverlay
component in the Visual Studio toolbox.
- Drag a
GraphicalOverlay
component from the toolbox onto your form's design surface. The component will automatically be named graphicalOverlay1
.
- In your form, create a paint event handler for
graphicalOverlay1
(graphicalOverlay1_Paint()
).
- In your form's constructor, after the
InitializeComponents();
line, add the following line:
public Form1()
{
InitializeComponent();
graphicalOverlay1.Owner = this;
}
- In
graphicalOverlay1_Paint()
(see step 5), use e.Graphics
to draw anything you like, using form-relative coordinates.
private void graphicalOverlay1_Paint(object sender, PaintEventArgs e)
{
Rectangle rect = this.ClientRectangle;
rect.Inflate(-10, -10);
using(Pen pen = new Pen(Color.Red, 5))
e.Graphics.DrawEllipse(pen, rect);
using(Font font = new Font("Arial", 14))
e.Graphics.DrawString("Now is the time.",
font, Brushes.Green, 60, 110);
[...]
}
- To draw control-relative graphics, use the
Coordinates()
method to get the control's form-relative coordinates. See the Form1.graphicalOverlay1_Paint()
event handler in the demo code for an example.
private void graphicalOverlay1_Paint(object sender, PaintEventArgs e)
{
[...]
using (Pen pen = new Pen(Color.Blue, 3))
e.Graphics.DrawEllipse(pen, pictureBox1.Coordinates());
}
Points of Interest
The component contains the following event handlers:
Form_Resize
Control_Paint
When the graphicalOverlay1.Owner
property is set, the component connects its Form_Resize
event handler to the owner form's Resize
event. Then, the component attaches its Control_Paint
event handler to the Paint
event for each of the owner form's controls (including the owner form itself).
As each control is repainted, the component handles the Paint
event, transforms the coordinates of the e.Graphics
object, then fires its own Paint
event which will be handled by the form's graphicalOverlay1_Paint
event handler.
Because the e.Graphics
object received by graphicalOverlay1_Paint()
has been transformed to use the form's coordinate system, all of the drawing logic is form-relative. Just draw as if you're drawing within the form's client area.
This approach makes it difficult to draw relative to the controls, however. So, to make control-relative drawing easier, I have added an extension method called Coordinates()
to the System.Windows.Forms.Control
class. The Coordinates()
method converts the control's location to form-relative coordinates. Just draw using Coordinates(control)
instead of control.Location
.
Because the graphical overlay can be drawn over all of the form's controls, redrawing it requires the entire form to be invalidated. Calling the component's Invalidate()
method will invalidate the form and each of the form's controls.
Limitations
The component responds to each control's Paint
event. The TextBox
control does not fire a Paint
event, so the component cannot draw over TextBox
controls. It may be possible to use text box controls from a third party, but I have not tested any of them.
The component can only draw over a control's client area. Some controls include a border that is not part of their client area, so the component cannot draw over the border. One solution to this problem is to turn off the control's border and draw it using the component.
The component can only draw within a form's client area. It cannot draw over the form's title bar or borders, and it cannot draw between forms. To simulate drawing over the form's borders, you would need to turn off the form's border and draw it with the component--and re-implement all of the lost functionality.
Demos
I have included two demo forms to help you understand how to implement your own graphicalOverlay1_Paint
event handler.
Form1.cs is the one with the picture with red and blue rings and green text.
Note: While the Form1
demo is running, resize the form a few times.
Form2.cs is the demo with the group boxes and radio buttons.
Note: In order to switch between the Form1
and Form2
demos, you must modify the Program.cs file to run Form1
or Form2
. By default, the demo will run Form2
.
In the Form2
demo, the code in graphicalOverlay1_Paint()
contains a lot of hard coded values. I could have calculated these values, but it would only have made it harder to read the code. The hard coded values may not work exactly right if your UI culture is different from mine or if your fonts are configured differently from mine. The screenshots at the beginning of the article will show you what they are supposed to look like.