WinForms graphics is pretty simple when you get the basic concepts. You need to do ALL your drawing on each Paint
event. If your code is in a form and you want to draw on a child control then that means handling that control's Paint
event and putting your drawing code in the event handler. In a control that draws on itself, you override the OnPaint
event and put the same code there. It is the base implementation of OnPaint
that actually invokes any registered event handler. That means that anything you put before the call to the base method gets drawn before anything in an event handler, i.e. underneath, and anything you put after that call gets drawn after, i.e. on top.
You should store the data that represents your drawing in one or more member variables. Your drawing code then gets that data and uses it to draw whatever is appropriate. For instance, you might have a field that refers to a List(Of Tuple(Of Point, Point))
, where each item in the list is a pair of points representing the start and end of a line. Your drawing code would then loop over that list and call e.Graphics.DrawLine
with each pair of Point
values.
As I said, your code needs to do ALL the drawing every time. That part is actually fast. The slow part of the process is the actual painting of pixels on the screen. For that reason, you want to keep that painting to a minimum. Whenever an area of your control has or may have changed, you should invalidate it. You can invalidate as many separate areas as you like and then, on the next Paint
event, all invalidated are will be repainted with the result of your drawing code. It is worth writing code that may seem somewhat complex in order to keep the invalidated area to a minimum because executing that code is still faster than painting the extra area that doing so will exclude. For instance, using my previous example of a collection of lines, if you were to add a new line to the list, you could calculate the smallest Rectangle
that contained that line and then pass that as an argument to Invalidate
. That will record that area as needing to be repainted and queue a Paint
event. Once the UI thread has finished doing what it's doing, it will then execute your drawing code to draw everything, then repaint the invalidated area of the control and the new line will appear in that area. The same goes if you were to remove a line, i.e. invalidate that area and execute your drawing code and then that area will get repainted without that line.
Calculating a Rectangle
is easy to do based on minimum and maximum X and Y coordinates. If your area is more complex though, it might be appropriate to calculate multiple Rectangles
and call Invalidate
multiple times or create a single Region
and pass that to Invalidate
instead.