How to paint additional things on a drawn panel? - c#

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?

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:
Generate some data to be drawn
Invalidate the control where the data will be drawn
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.

Related

Controlling how, when, and if child controls are drawn (.NET)

I am writing an application in .NET that has a plugin interface. That plugin interface provides several ways to draw information (controls) onto the surface of the application window. While there are several reasons why I am doing this, the main reason is to provide custom colorization to text, either through the use of a graphic or directly manipulating the color of the text based on the background color. I do this through the use of a "text mask" which is a black and white bitmap that works as an "alpha" map to let the Paint method know where to apply the texture/color changes.
The plugin developer has the option of using regular text (such as with a label), mask text (which is drawn to the mask rather than as a regular control), OR letting the user decide. To go along with this, I have provided a modified label class that can either be drawn "normally' (when the text mask is not set for the control), or to the text mask when the User OR Developer decides (depending on what the plugin developer wishes to offer to the user). Here is the class's code so that you understand how this is being done:
public class MaskingLabel : Label
{
private static readonly SolidBrush maskBrush = new SolidBrush(Color.White);
public Bitmap Mask { get; set; }
public MaskingLabel() : base() { }
protected override void OnPaint(PaintEventArgs e)
{
if (Mask == null)
base.OnPaint(e);
else
{
Graphics g = Graphics.FromImage(Mask);
g.DrawString(Text, Font, maskBrush, Location);
}
}
}
The problem I am running into is that this approach requires that I handle controls in a very specific order so that the form is drawn correctly. I need to find the most efficient approach to get the tasks listed below done in the order given. I have thought of three possibilities discussed further down. For reference, this is the order in which tasks must be done:
All "MaskingLabel" controls that have the bitmap object set to the mask must be drawn first so that the mask is created before the next step.
The mask is applied to the background picture.
The resulting Bitmap is drawn in a way similar to the way a background would be drawn (except that it is modified first).
The rest of the controls are drawn as normal.
Is there a way for me to insure this happens without separating the controls manually? My first guess is no. As such, I have a few guesses below about how I should go about this. I was hoping someone with more in depth knowledge of GDI+ could offer some insight.
One idea that has occurred to me is to draw the masked controls during the OnPaintBackground method. However, I don't want to waste time by painting the controls twice. This means I would need to filter out which controls are drawn during the main Paint method which effectively leads us to option 2 (FAIK):
I can manually filter out the controls which draw to the mask so that they don't get added to the control. My question here though is would they get drawn at all? Can I manually force them to invoke the OnPaint method?
If doing that wouldn't work, then perhaps I can create a separate derived panel control to serve as a "backdrop" child control that acts as the background picture which can be forced to be drawn first?
EDIT (With Part of the answer):
I realized after posting this that I already have part of the solution built into my project. Still, I think it is a legitimate question to ask, so if anyone can add insight beyond what I have done in my description below, it is welcome.
Specifically, my project has only two controls that are added to the "root" form: a bar that goes to the top (docked at the top when it is shown), and a transparent panel that occupies the rest of the space (with a dock style set to fill). So my solution would be to add the mask controls to the main form and add all the rest to the panel. This only leaves one remaining issue to be resolved: How do I make sure that the panel and the bar are drawn last? (As part of step 4 in the first list?)

Assign event to custom drawn shape

How do I draw a line and assign events to it? I would like to draw a custom shape, but it should act like a normal Control, and have properties and events. E.g.:
button1_Click(object sender, EventArgs e)
{
DrawLine(width, height, location, location, color, panelToDrawShapeOn, nameThisShape);
}
nameThisShape_Click(object sender, EventArgs e)
{
MessageBox.Show("Click event raised.");
}
private void DrawLine(int width, int height, int location, int location, Color color, Panel panel, string controlName)
{
// Code to draw shape and setup events for it.
}
To confirm, I do know how to draw shapes using GDI+ but the problem is, they are static, and I can't "interact" with them, and no amount of searching has led me to the right place to find out how to interact with the shapes that I draw.
You're not going to be able to treat custom-drawn shapes as you would controls. As you've found, GDI+ is an immediate mode graphics system (as opposed a retained mode system). This means that if you want a persistent scene graph full of shapes to be rendered, you need to create and manage that yourself. Then, you would hook the events of interest on the control that's the drawing target and handle them by doing hit tests on your list of renderable objects (e.g., to find what shape, if any, the mouse is over).
Writing that code can be a lot of work, but you can find libraries to help you. For instance, in one of my work projects, we used a computational geometry library called JTS for the geometry representation and hit test code. If you want to avoid third-party libraries, you may get part of the way there with the Region class, which will at least give you hit tests.

Translate and rotate image outside of glControl1_Paint using OpenTK?

I am making CAD type software in VS2010 Pro using a C# Windows Form Application and OpenTK. Nothing fancy; I just want to be able to read in some basic shapes and draw them. I'm not sure if this makes a difference to the answer, but I am drawing in 2D space using GL.Ortho();
To get familiar with graphics I've done a few OpenTK examples straight from the OpenTK documentation and have a basic understanding of it. From what I've learned so far I cannot move/rotate my primitives unless they were created within this event:
private void glControl1_Paint(object sender, PaintEventArgs e)
{
}
My program launches and waits for the user to select the CAD file to read in. After I read the file and break it down into primitives I draw it to the glControl1 form. So far it works as expected. However, I do not draw it in the "glControl1_Paint" event. Thus I have no control to translate/rotate it by using keyboard/mouse inputs.
I have read answers to other questions where the asker was directed to draw in the "glControl1_Paint" event. I would love to because it would solve my problem, but I am not sure how to do that since I don't have the primitives upon launch of the application, I wait for the user to provide the data.
I suppose I have a few questions that I would like to know the answers to:
1) When does the "glControl1_Paint" event happen in the program? I assumed it was part of initializing the glControl1 window and fired upon startup. Can I control when this happens so that I can draw my primitives here? If so, how do I control when this happens and how do I pass my geometry into this?
2) Is there a way to translate/rotate the my primitives outside of the "glControl1_Paint" event?
No you can not know when paint event will trigger. But you can manually trigger it via Invalidate() function.
The flow should be like this.
You should do the all the drawing in your paint event.
If something happened that effects the drawing, you should call Invalidate()
Keyboard events that moves objects or mouse events that rotates camera etc. all of them should call Invalidate()
If you like maximum frame rate. you should override application main loop and make it call Invalidate() if there are no other windows messages to process.
here is my programming loop
static void Main()
{
...
MainForm mainFrom = new MainForm();
mainFrom.FormClosed += QuitLoop;
mainFrom.Show();
do
{
Application.DoEvents();
mainFrom.glControl1.Invalidate(true); //actually may program is a lot more complex than this
if (mainFrom.IsRunning)
System.Threading.Thread.Sleep(0);
else
System.Threading.Thread.Sleep(1);
} while (!mQuit);
...

C# - storing objects drawn by graphicsObject

Im using System.Drawing.Graphics graphicsObj for drawing objects on System.Windows.Forms.Panel. What is wise method to store informations about old drawings? To remove last drawing during MouseMove event?
Do i have to remember Pen settings and cords?
Thanks :)
// edit. I think my question is not clear. I have a Panel. I treat it like a container for drawing.
I can draw multiple lines, circles, rectangles.
Every time i press the mouse (MouseDown), im want to start drawing new object. When i move mouse (MouseMove), object currently drawed has to change (for example line, like in paint). When i release the mouse (MouseUp), i want object i drawed to became constant.
Now, i tried to invoke graphicsObj.Clear(); method on MouseMove event, but it clears all drawed objects.
So i deduced, that i have to store informations about old drawed objects. And now, i have to make lists (vectors or so) and store informations about that objects? Or there is a method to avoid it?
You should handle all painting in the Panel's Paint event, that is exactly what it's there for.
The Paint event gives you in its PaintEventArgs the Graphics object you should use. This will make sure your instructions are painted on every refresh, not just once.
If you are deriving from Panel, override the OnPaint method instead.
Edit after original question has been expanded:
You should separate the logic of storing the data from the logic of displaying your data. The Graphics object's purpose is to display existing data, not to store data.
I think making Lists or other collection of the objects you are drawing is the best solution.
You will also need flags to handle all the logic of what is a "live" object, etc.
I think you can store your last x,y into an array of Points objects as history.
Not sure I get what you are after here, but if you want some sort of memory about what paint drew.
Draw to a collections of bitmaps, then have your paint routine draw one of the bitmaps on to the control.

Gradient Drawing Bug

I'm having trouble with a gradient drawing call. I have a Form that looks like this.
Screenshot http://img413.imageshack.us/img413/3570/30364682.png
The problem is every now and again the above gradient drawing bug will start happening. It should go right across of course. Sometimes it only takes some build-rebuild-mashing to fix and it'll simply just "start" after a build every now and again.
That control (the top white part) is a TableLayoutPanel. The BackColor is set to white and on the panel's Paint event I do this:
/// <summary>
/// Draws the background gradient.
/// </summary>
private void titleBarLayoutPanel_Paint(object sender, PaintEventArgs e)
{
Brush brush = new LinearGradientBrush(titleBarLayoutPanel.Bounds, TaskHeaderLeftColor, TaskHeaderRightColor, LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(brush, titleBarLayoutPanel.Bounds);
}
Should I be doing something else? The problem is that it works, and then without so much as a rebuild or build this will start happening!
EDIT I have since rebuilt the class library it is contained in (it's a generic Form) then rebuilt the app it's used in and the gradient is now filling across completely. This is bizarre.
Building and re-building your application, with no changes, normally doesn't solve this (or most any other bug for that matter) save the ones in which you run your application without doing a clean/rebuild first and then notice that the code you just wrote doesn't run (not sure that's possible these days with the IDEs). I see this a lot with newer devs when they keep rebuilding hoping that somehow the compiler will make the code "correct" or that maybe the compiler is simply not generating the correct code to begin with. (Please note that I do not mean the aforementioned statements to be taken disparagingly.)
To solve the issue at hand, you might try deriving your own TableLayoutPanel class in which you override the OnBackgroundPaint event, painting your own background, or simply returning if you don't want to paint your own background. (You seem to be painting the background in the Paint event). What you are doing in the code above is simply painting over the background already painted by the control, hence the "bug" you see, (double paint). It appears that the form is not resizable. Try making it resizable. Then resize it and watch it paint, or simply move other windows over it.
class CustomTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
Brush brush = new LinearGradientBrush(this.ClientRectangle, TaskHeaderLeftColor, TaskHeaderRightColor, LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(brush, this.ClientRectangle);
//base.OnPaintBackground(e);
}
}
By the way, you should replace Bounds with ClientRectangle.
Bounds is the control's rectangle relative to its parent; ClientRectangle is relative to the control itself.
In this particular case, it won't make a difference, since the control is at 0, 0.

Categories