C# drawing on custom controls - c#

so I've created a custom control and I wanted to draw on that control
now the problem is that I can't use OnPaint event because I want to
draw at different times with different conditions.
here is the custom control function to draw a rectangle
public void DrawARectangle(int x,int y,int height,int width)
{
Graphics g = this.CreateGraphics();
g.DrawRectangle(Pens.Black, x, y, height, width);
g.FillRectangle(Brushes.Black, x, y, height, width);
}
and I basically call it from my form , but it doesn't draw anything even after using the update() method.

You must use Control.OnPaint to do your custom drawing. Otherwise all of your drawings will be erased after the next repaint of your control.
The idea is you may store your rectangles in a list. Then in your Control.OnPaint, you do the drawing based on that list.

Graphics g = this.CreateGraphics(); This is almost always wrong.
Use the Paint/OnPaint event and its e.Graphics parameter! Store the coordinates somewhere and be prepared to always draw everthing..
I can't use OnPaint event because I want to draw at different times with different conditions. Yes. But you must!
This is how graphics work in winforms. This only sounds wasteful but..: The system also needs to call this event when the window must be restored, so there is no way around it if you want your drawing to persist..
Only non-persistent graphics operations like displaying a dynamic rubber-band rectangle or a line that follows the mouse, are ok with a Graphics object you get from control.CreateGraphics(). And measurements without drawing...
Trigger re-drawing by calling Invalidate on your control whenever your data have changed.

Related

DrawToBitmap returning blank image

I have a problem on creating bitmap image out of my winform application.
Situation:
I have a UserControl named as "CanvasControl" that accepts OnPaint method acting as canvas for my Draw Pad application. Inside this user control I have a function "PrintCanvas()" that will create a screenshot image of the UserControl into PNG file. Below is the PrintCanvas() function:
public void PrintCanvas(string filename = "sample.png")
{
Graphics g = this.CreateGraphics();
//new bitmap object to save the image
Bitmap bmp = new Bitmap(this.Width, this.Height);
//Drawing control to the bitmap
this.DrawToBitmap(bmp, new Rectangle(0, 0, this.Width, this.Height));
bmp.Save(Application.StartupPath +
#"\ExperimentFiles\Experiment1" + filename, ImageFormat.Png);
bmp.Dispose();
}
This user control (CanvasControl) is called out inside my main form where user will draw something and have an option to save afterwards using a save button. The save button will call out the "PrintCanvas()" function of the UserControl.
I get the output image file as expected, but the problem is it was a blank image.
What I have tried so far:
To test that it is not a syntax issue, I tried to transfer the PrintCanvas() function into my main form and surprisingly I get an image of the whole main form on file but the UserControl is not visible there.
Is there any other setup i missed out to make a winform UserControl printable?
UPDATE: (DRAWING ROUTINES)
User control acting as canvas - code here
The code in the question gave a first hint but the code in the link showed the source of the problem: You use a 'wrong' instance of the Graphics object for drawing:
protected override void OnPaint(PaintEventArgs e)
{
// If there is an image and it has a location,
// paint it when the Form is repainted.
Graphics graphics = this.CreateGraphics();
..
This is one of the most common mistakes with winforms graphics! Never use CreateGraphics ! You always should draw onto the control surface with the Graphics object in a Paint or DrawXXX event. These events have a parameter e.Graphics which is the only one that can draw persistent graphics.
Persistent means that it will always be refreshed when necessary, not just when you trigger it. This is a nasty error because everything seems to work until you come upon a situation when an outside event makes redrawing necessary:
Minimizing and then maximizing the form
Moving it off the screen and back again
Calling DrawToBitmap
...
Note that all will only really work if you use the valid and current Graphics object from the PaintEventArgs e parameter.
So, the solution is simple:
protected override void OnPaint(PaintEventArgs e)
{
// If there is an image and it has a location,
// paint it when the Form is repainted.
Graphics graphics = e.Graphics(); // << === !!
..
But what is the CreateGraphics good for? It is only good for luring newbies into that error??
Not quite; here are some uses for it:
Drawing non-persistent graphics like a rubber-band rectangle or a special mouse cursor
Measuring text sizes without actually drawing it with a TextRenderer or the MeasureString method
Querying the screen or Bitmap resolution with Graphics.DpiX/Y
and probably some others I can't think of at the moment..
So for normal drawing onto controls always use the e.Grapahics object! You can pass it on to subroutines to make the code more structured, but do not try to cache it; it needs to be current!

Redraw panel contents after ClientSizeChanged

So my application runs in fixed size window and in full screen. The problem I'm facing is how to properly scale the current contents of the panel (which depend on the application use) when the window is resized. This is my current code:
private void Form1_ClientSizeChanged(object sender, EventArgs e)
{
System.Drawing.Drawing2D.Matrix transformMatrix = new System.Drawing.Drawing2D.Matrix();
float px = panel2.Width;
float py = panel2.Height;
panel2.Width = this.Width / 2;
panel2.Height = panel2.Width;
panel2.Location = new Point(this.Width - panel2.Width - 30, 30);
transformMatrix.Scale(panel2.Width / px, panel2.Height / py);
panel2.Region.Transform(transformMatrix);
//Rest of the code
}
But the drawn content doesn't scale, and if I use Invalidate() or Refresh() the drawn content gets cleared (the panel is redrawn empty). What am I missing?
.NET doesn't remember what's drawn on the panel, as simple as that. As soon as anything invalidates the windows bitmap buffer (causing a WM_PAINT), it's going to be repainted again. So, you have to draw what you want to draw using the Paint event (or overriding OnPaint).
However, there is another way that might be easier to implement - don't paint into a Panel. Instead, paint into a PictureBox (or rather, a Bitmap assigned to the Image property of the PictureBox). The Bitmap will be reused when invalidating (and redrawing) the picture box, so nothing will be lost. By using PictureBox.ScaleMode, you can define how you want the picture box to scale the bitmap, and it will do so as well as it can.
In any case, transforming the Region property doesn't do anything useful - you're simply changing the region, not doing anything to the drawing itself. To use 2D transformation matrices, you want to apply them on a Graphics object during the drawing (in Paint handler or OnPaint override) - drawing anything on the Graphics object will then transform everything you're trying to draw, which in your case means scaling the painting.
So you have to decide: do you want to just scale a stored bitmap with the painted image, or do you want to redraw it all from scratch (which also means you can pick any level of detail you can provide)?
I think that you're mistaking what the Region property is meant for. According to the MSDN docs (empasis mine, replace 'window' with 'control' when reading):
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region. The coordinates of a control's region are relative to the upper-left corner of the control, not the client area of the control.
All that you're doing is changing the region that the OS will allow painting, which explains why you're not seeing anything. I think that you should be resizing the control when the form is resized, either through Anchor, or through my preference of Dock with several controls, or a panel like TableLayoutPanel where it will handle scaling and relative sizing for you.
Thank you for your answers, but I wrote my own function and logic that serves the purpose for this application. Basically the function checks for the state of the application variables, and calls the appropriate function that originally drew the content, and since those functions use the panel width and height as arguments they properly scale the drawn content and retain the drawing composition.
P.S. I'll accept Luaan's answers since it offers a valid alternative and is complete.

What is the easiest way to draw a cube in Windows Forms?

I am making a Windows Forms app in C# for homework that accepts the length of a cube. It then displays the surface area and volume of the cube. That's easy enough but it also needs to draw the cube.
What I'd like to know is the easiest way to draw the cube. I must do this with the Graphics class.
My thoughts on how to do it so far:
paper = myPicBox.CreateGraphics();
myPen = new Pen(Color.Black);
myPen.Width = 3;
paper.DrawRectangle(myPen, xCoord, yCoord, width, height);
paper.DrawLine(myPen, pointOne, pointTwo); // Then repeat this line for the four lines on the Z-axis
paper.DrawRectangle(myPen, xCoord, yCoord, width, height); // Where xCoord and yCoord have been changed to be placed at the end of the lines I've drawn
This is pretty bulky, so I was wondering if there was an easier or simpler way to achieve the same thing?
As mentioned, that's probably the best you're going to get with WinForms. The best you can do is encapsulate your functionality into its own method such that you can draw more than one cube at once. So your DrawCube() method might take in an origin, length, and Graphics object and then draw it. The CreateGraphics call would come before any calls to DrawCube.
Also, after you are done with your Graphics object, you should dispose of it by calling paper.Dispose() (see thispage) or stick it in a using block. Also, check out this website that explains when to use CreateGraphics (basically when you are drawing outside of a Paint event handler, which you are doing)
In WinForms what you have done is only your best bet, if you are searching for somthing as DrawCube() method then sorry you don't have it in .Net. Any thing you do will involve using those primitive types like line and rectangle.

How to delete a drawn circle in c# windows form?

I have drawn a circle in windows form
Pen pen = new Pen(Color.Black, 3);
Graphics gr = this.CreateGraphics();
gr.DrawEllipse(pen, 5,5,20,20);
How to delete it...
You have to clear your Graphic:
Graphics.Clear();
But all drawn figures will be cleared. Simply, you will then need to redraw all figures except that circle.
Also, you can use the Invalidate method:
Control.Invalidate()
It indicates a region to be redrawn inside your Graphics. But if you have intersecting figures you will have to redraw the figures you want visible inside the region except the circle.
This can become messy, you may want to check out how to design a control graph or use any graph layout library.
You can invalidate the draw region you want to refresh for example:
this.Invalidate();
on the form...
Assuming you're subscribing to the Paint event or overriding the protected OnPaint routine, then you will need to perform something like this:
bool paint = false;
protected override void OnPaint(object sender, PaintEventArgs e)
{
if (paint)
{
// Draw circle.
}
}
Then when you want to stop painting a circle:
paint = false;
this.Invalidate(); // Forces a redraw
You can make a figure of same dimensions using the backColor of your control in which you are drawing
use after your code to clear your figure.
Pen p = new Pen(this.BackColor);
gr.DrawEllipse(p, 5,5,20,20);
In fact, you can delete your circle and nothing but your circle.
Everything you need is something like a screenshot of the "before state" of the area you want to clear, to make a TextureBrush from it. You can achieve that step by something like this:
Bitmap _Background = new Bitmap(this.Width, this.Height);
Graphics.FromImage(_Background).CopyFromScreen(this.Left, this.Top, 0, 0, this.Size);
The first line will give you a bitmap in your windows forms size. The second line will save a screenshot of it in the _Background-bitmap.
Now you create a TextureBrush out of it:
Brush brsBackground = new TextureBrush(_Background);
The next thing you need are the dimensions of your circle, so you should save them into a variable, if they are not a fix value. When you got them at hand, you can clear the specific area like this:
Graphics gr = this.CreateGraphics();
gr.FillEllipse(brsBackground, 5, 5, 20, 20); // values referred to your example
Done!
Even complex figures are able to be deleted by this, like a GraphicsPath for example:
GraphicsPath gp = new GraphicsPath(); // any kind of GraphicsPath
gr.FillRegion(brsBackground, new Region(gp));
You don't "delete" it per se, there's nothing to delete. It's a drawing, you draw something else over it or you can call the Graphics.Clear() method.
If u are using Invalidate() and is not working, make a panel.Refresh().
That will work on you.
just make another control with the attributes etc. that you want, make the visibility to false and set the region of the control to the other control like this:
pen.Region = pen2.Region;
It is very simple to delete a drawn circle from c.
There is only four steps:-
Open turbo app
go to the command where you had drawn the circle
drag the command
click on delete button

c# - clear surface when resizing

I'm trying to build my own custom control for a windows forms application in C#.Net. Currently I paint some rectangles and other graphic elements using the paint event.
When I now resize the app form to fit the desktop size, all elements are repainted (which is exactly the behaviour I need) but the old one's are shown in the background.
Here's what I'm doing by now:
Pen penDefaultBorder = new Pen(Color.Wheat, 1);
int margin = 5;
private void CustomControl_Paint(object sender, PaintEventArgs e) {
CustomControl calendar = (CustomControl)sender;
Graphics graphics = e.Graphics;
graphics.Clear(Color.WhiteSmoke);
graphics.DrawRectangle(penDefaultBorder, margin, margin, calendar.Width - margin * 2, calendar.Height - margin * 2);
//...
}
Neither the graphics.Clear, nor adding a graphics.FillRectangle(...) will hide the old rectangle from the surface.
Ideas? Thank you all.
Paint events usually don't request an update for the entire canvas, just the area specified in the PaintEventArgs. I'm guessing what's happening is that only the newly-exposed regions of the canvas are being passed in the PaintEventArgs.
This one of the reasons that you shouldn't do any rendering in the Paint event. You should render to an offscreen bitmap - a buffer - and copy from that buffer to the control's canvas in the Paint event.
Searching for "double buffering" here or on Google will give you many examples of the technique.
Have you tried .Invalidate() to cause the form to redraw?

Categories