0

I'm reading a lot about C# drawing and reading MSDN tutorial on GDI+ using Graphics handlers.

I want to be able to paint a graph, which nodes I have in a list but I can't use auto-placing, I need the nodes to be in a specified place and have specific appearance and so on, changing over time, that's why I stopped looking for graph libraries.

It all works great when painted for the first time but when I want something painted after something else happens in the code (not after clicking the control), I can't do it. For example:

if (somethingHappens) {
  // repaint the panel adding some things
}

All I got is either nothing new happens or my earlier painting disappears.

I found some examples on OnPaint overriding and drawings disappearing when minimalized. When I need to paint something additional when something happens in an application, do I have to override it or is it completely different?

wtrmeln
  • 121
  • 11
  • 2
    If you want to draw anything (persistent) onto a Panel you may only (read my lips: __only__) do it inside the Paint event or from methods you call from there (to which you hand the e.Graphics parm). Anything else is a mistake! - Whatever you draw must be first put into a (probably) series of draw actions stored at (probably ) class level, which you then can draw in the Paint event. - Whenever you want to draw more you must add more of those draw actions to the collection you have and then you call `yourPanel.Invalidate();`! - So, you learned how to make it look ok, now use that to do it right! – TaW Dec 16 '14 at 15:54
  • To get more concrete advice, please consider posting some code about what and how you are drawing so far..! – TaW Dec 16 '14 at 16:03
  • BTW: There is an alternative: You can also draw __into__ the Image shown in a PictureBox and succesively add more and more to it. Look at may answer [here for a short example](http://stackoverflow.com/questions/27337825/picturebox-paintevent-with-other-method/27341797#27341797)! - The main difference is that this way of cumulative drawing won't let you change thing you have done before. – TaW Dec 16 '14 at 16:07
  • Put another way, **everything** in the "drawing collection" gets drawn each time the Paint() event is called. To add something new, or change something, you add to or modify the collection somehow and then everything gets drawn again showing you the new "state". – Idle_Mind Dec 16 '14 at 16:41
  • Exactly. If the draw actions collection gets __really big__ and the drawing takes a while to update, like in a paint program after some serious doodling, one may take a snapshot at some point and only draw the things that come after.. – TaW Dec 16 '14 at 17:08
  • @TaW No, i need to be able to change things, some nodes and labels have to disappear. Painting the graph is easy (from what I understand I just need to call it everytime) but I need to handle additional small things appearing on that map so your first answer is probably good for me. – wtrmeln Dec 16 '14 at 23:09
  • @TaW From what I understand, I need to put EVERYTHING in this Paint event? So if some things are drawn conditionally, I need to put all the code there and have some flags? What do you mean my the "collection" I have? Is there a cleaner solution than flags? – wtrmeln Dec 16 '14 at 23:16
  • @parafiaraclawice: the act of drawing the control has to go in the `Paint` event handler. Assuming performance is sufficient, then you _should_ put everything in the handler, i.e. check conditional flags, etc. But it is common for performance reasons to cache some of the rendering to a bitmap, which itself is then drawn to the screen in the `Paint` handler instead of the graphics it represents, if for some reason drawing everything from scratch in the `Paint` event handler is too slow (it sounds like in your case that wouldn't be the case). – Peter Duniho Dec 17 '14 at 00:55
  • 1
    _if some things are drawn conditionally, I need to put all the code there and have some flags?_ No. If something is drawn conditionally you add it to the draw action collection on these conditions. The draw action often is a class, which contains data like points, brushes pens and the type of action like line, rectangle ellipse etc.. - In the apint event you then have somethig lioke `foreach (drawaction da in drawactions) if (da.type==..) e.Graphics.DrawLines(..)..` If you only draw lines between nodes you may get away without a class of draw actions, like in Peter's answer.. – TaW Dec 17 '14 at 01:33
  • @TaW oh, ok, I think I get it now! – wtrmeln Dec 17 '14 at 09:01
  • @TaW What about using a list of functions? A function would be a specific draw action for example DrawEllipse(). Could I store the delegates of static methods in a list? Or is this an overshoot and not really necessary here? I ask because when I started to write my DrawAction class it turned out that different actions need different parameters and I have to make a lot of constructors and pay a lot of attention to the fields I can use (the rest is null or empty) – wtrmeln Dec 17 '14 at 11:50
  • I don't think delegates will help; they would still need the params. Go for building a class and leave the unnecessary parameters empty. Write a type Enum and let it grow. The type will let you decide which members to use in the calls; I never had any problems there but I can understand hat you mean: for each new type a few things are added, a string and a font here and a brush and a region there.. but every empty slot only takes 4bytes so it doesn't matter.. – TaW Dec 17 '14 at 12:50

1 Answers1

2

I looked for another Q&A that would include the information you need. Frankly, it's a fundamental question, how to properly deal with drawing a Forms control. MSDN topics like Overriding the OnPaint Method and Custom Control Painting and Rendering provide some detail on the right way to do this. But I'm surprised I wasn't able to find any StackOverflow Q&A that at least addresses this basic question directly (there are many which address it tangentially of course).

Anyway…

This is the basic sequence for drawing in Forms code:

  1. Generate some data to be drawn
  2. Invalidate the control where the data will be drawn
  3. Handle the Paint event by actually drawing that data

Repeat the above as necessary, i.e. any time the data changes (such as "something happens", as in your question) you move back to step #1, adding whatever new data you want to your existing collection of data, then invalidating the control, and finally responding to the Paint event in your event handler.

In the case of drawing a graph, this might look something like this:

private List<Point> _points = new List<Point>();

void AddPoint(Point point)
{
    _points.Add(point);
    panel1.Invalidate();
}

void panel1_Paint(object sender, PaintEventArgs e)
{
    if (_points.Count < 2)
    {
        return;
    }

    Point previousPoint = _points[0];

    for (int i = 1; i < _points.Count; i++)
    {
        currentPoint = _points[i];

        e.Graphics.DrawLine(Pens.Black, previousPoint, currentPoint);
        previousPoint = currentPoint;
    }
}

Note that the panel1_Paint() event is an event handler. Normally you would create this via the Designer by selecting the Panel object, switching to the "Events" list in the "Properties" window for the control, and double-clicking in the edit field for the Paint event.

That is of course the simplest example. You can add things like updating the data in a batched manner (i.e. don't invalidate the control until you've added a group of points), use different colors or line styles to draw, draw other elements of the graph like axes, ticks, legend, etc. The above is simply meant to illustrate the basic technique.

One final note: depending on how many points in your graph you need to draw, the above may or may not be fast enough. It should be fine up to thousands of points or so, but if you start getting to tens or hundreds of thousands or more, you'll probably find that it's useful to cache the drawing into a bitmap and draw just the bitmap. But that's a whole separate question. First, you need to make sure you understand the Forms drawing model and are using it correctly.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thank you! I painted my form in a event but I was missing some things, obviously. I won't draw more than 50 nodes so it is perfect for me. – wtrmeln Dec 16 '14 at 23:13
  • 1
    Correct. Although for this kind of drawing I guess using one `DrawLines` would be better than many `DrawLine` calls, both for performance and for drawing quality for some other pens, not to speak of simplicity.. – TaW Dec 17 '14 at 02:59