I've a WinForms application on wich i have to draw some lines between controls. These lines need to be persistent, so i override the form OnPaint() event.
The problem is that, the re-draw of the lines aren't very smooth.
I'm creating the graphics as follows:
Graphics g;
g = this.CreateGraphics();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.White, this.ClientRectangle);
And drawing the lines as follows:
public void lineDraw(Control L1, Control L2) {
using (Pen pen = new Pen(Color.Black, 4)) {
pen.StartCap = System.Drawing.Drawing2D.LineCap.Flat;
pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
int x1, x2, y1, y2;
//choose x/y coordinates
g.DrawLine(pen, x1, y1, x2, y2);
}
}
Is there any property i can set to improve the smoothness of the drawn graphics?
Goal
An image is shown on a control (or form).
Invalidation
Any time the control (or form) is resized, minimalized/maximalized, partically obscured or moved around, it must be (partially) redrawn. When this happens the part of the control that must be redrawn is said to be invalidated.
When invalidated the control does something like this:
Call OnPaintBackground: this fills the invalidated region with the background color.
Call OnPaint: this draws the text and graphics on top of the background.
Why OnPaint causes flickering
You have overridden the OnPaint method of the control. Every time the control is redrawn you see a flash of the control with only its background color drawn in it. That is after OnPaintBackground has been called and before OnPaint has been called.
The solutions
If you have a static image (i.e. it never changes):
In the Load event: create a new Bitmap object.
Fill it with the background color and draw the lines and shapes on it.
Assign this Bitmap object to the control's BackgroundImage property.
If you have a static image that must resize when the control resizes:
Override the OnResize method and create the new Bitmap in there. Use the control's ClientSize property for the size of the Bitmap.
Fill it with the background color and draw the lines and shapes on it.
Assign this Bitmap object to the control's BackgroundImage property.
If you have an animated image:
In the Load event set the DoubleBuffered property of the control to true. Setting this prevents the flicker you saw as it uses an invisible buffer to draw the control.
Override the control's OnPaint method. Get the Graphics context of the control and draw the lines and shapes directly on the control.
Create and enable a new Timer object and in its callback method call the control's Invalidate method followed by the Update method (as shown here). Set the timer to fire, for example, every 40 ms.
You probably shouldn't use CreateGraphics here, and more importantly, don't use a local variable to store the graphic object. Use the graphic object obtained from the paint event and invalidate as needed.
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(Color.White);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen pen = new Pen(Color.Black, 4)) {
pen.StartCap = Drawing2D.LineCap.Flat;
pen.EndCap = Drawing2D.LineCap.ArrowAnchor;
int x1, x2, y1, y2;
//choose x/y coordinates
e.Graphics.DrawLine(pen, x1, y1, x2, y2);
}
base.OnPaint(e);
}
This's my way, its works for me
//FormMain.cs
private const float DisplayRatio = 6;
private Bitmap _bmpDisp; //use an in-memory bitmap to Persistent graphics
private Graphics _grpDisp4Ctl;
private Graphics _grpDisp4Bmp;
private Point _ptOldDsp;
private void FormMain_Shown(object sender, EventArgs e)
{
_grpDisp4Ctl = CreateGraphics();
_grpDisp4Ctl.SetHighQulity();
_bmpDisp = new Bitmap(ClientSize.Width, ClientSize.Height);
_grpDisp4Bmp = Graphics.FromImage(_bmpDisp);
_grpDisp4Bmp.SetHighQulity();
_ptOldDsp = new Point(
(int)((MousePosition.X - SystemInformation.VirtualScreen.Left) / DisplayRatio),
(int)((MousePosition.Y - SystemInformation.VirtualScreen.Top) / DisplayRatio)
);
}
private void UpdateDisplay(MouseHookEvent mhep) //your implement
{
var ptNew = mhep.Position;
ptNew.Offset(new Point(-SystemInformation.VirtualScreen.Left, -SystemInformation.VirtualScreen.Top));
ptNew.X = (int)(ptNew.X / DisplayRatio);
ptNew.Y = (int)(ptNew.Y / DisplayRatio);
_grpDisp4Ctl.DrawLine(Pens.White, _ptOldDsp, ptNew); //draw smooth lines to mem and ui
_grpDisp4Bmp.DrawLine(Pens.White, _ptOldDsp, ptNew);
_ptOldDsp = ptNew;
}
private void FormMain_Paint(object sender, PaintEventArgs e)
{
// like vb6's auto redraw :)
e.Graphics.DrawImage(_bmpDisp, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);
}
//common.cs
internal static void SetHighQulity(this Graphics g)
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
}
I know it's an older post, but you can also try setting DoubleBuffered property of the form to TRUE, read the following:
"Buffered graphics can reduce or eliminate flicker that is caused by
progressive redrawing of parts of a displayed surface. Buffered
graphics require that the updated graphics data is first written to a
buffer. The data in the graphics buffer is then quickly written to
displayed surface memory. The relatively quick switch of the displayed
graphics memory typically reduces the flicker that can otherwise
occur."
Related
I have a grid already drawn to a picturebox and I wish to draw a rectangle in the cell the mouse is clicked in. I'm trying to do this by drawing a rectangle in the top left corner of the cell and having it fill the entire cell.
However when I click the grid disappears.
How do I make it so the grid stays?
and is there an obvious better method for doing this?
The only way to redraw the grid is by pressing the button again but I want it to stay there.
Thanks.
You need to create a Graphics
Image bm;// Or Bitmap
using (var graphics = Graphics.FromImage(bm))
{
Draw(rects, graphics);
}
private static void Draw(List<Rectangle> rectangles, Graphics graphics)
{
Pen redPen = new Pen(Color.Red, 1);
foreach (var rect in rectangles)
{
graphics.DrawRectangle(redPen, rect);
}
}
At every mouse click in the mouse_click event at first you dispose the image of the picture box and then again assign the bitmap to the picturebox image otherwise you might face out of memory exception if you click on the grid too many times
private void InitializePictureBoxImage(PictureBox pictureBox)
{
if(pictureBox.Image != null)
{
var image = pictureBox.Image; // cache it to dispose
pictureBox.Image = null; // don't dispose an used object
image.Dispose(); // and dispose of it
}
Bitmap bitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
pictureBox.Image = bitmap; // assign bitmap to image
}
Then call the method in the mouse_click event which you used to draw the picture box when you press the Generate Grid button in your form. Afterwards use graphics inside the mouse_click event to colour the grid which you pressed taking the X-Y coordinate from MouseEventArgs.
Altogether I think it will be something like this
private void Mouse_ClickEvent(object sender, MouseEventArgs e)
{
InitializePictureBoxImage(pictureBox);
DrawThePictureBoxImage(pictureBox);
using (Graphics graphics = Graphics.FromImage(pictureBox.Image))
{
Color color = Color.Blue;
color = Color.FromArgb(150, color.R, color.G, color.B); // lower the opacity so the writing in the grid is visible
var brush = new SolidBrush(color);
// Calculate the starting X-Y coordinate and Width,Height of the grid to color
graphics.FillRectangle(brush, startX, startY,
width, height);
}
I have a sizable form and the location in which a pixel is drawn moves, I have a panel which is the desired image size 400,200 - how can I change individual pixels on this panel? I also need the fastest change possible.
GDI+ does not have a method to set a single pixel, however you can set a 1x1 rectangle to achieve the same effect in your OnPaint event. It feels somewhat like a hack, however there is no standard way of doing this.
SolidBrush brush = new SolidBrush(Color.White);
private void panel1_Paint(object sender, PaintEventArgs e)
{
SetPixel(brush, e.Graphics, 10, 10, Color.Red);
}
public void SetPixel(SolidBrush brush, Graphics graphics, int x, int y, Color color)
{
brush.Color = color;
graphics.FillRectangle(brush, x, y, 1, 1);
}
You could also access the bitmap directly and use it's GetPixel and SetPixel methods, however they are generally very slow if your pixels need to be updated quickly.
I have written this code, however, it doesn't work. Not only will this not work, but none of the methods I have tried for drawing have worked. I've spent more than an hour or two trying to solve this, but to no success. Ive tried simple programs where all it does is display a small line, but it wont work no matter what i do :c
What am I doing wrong, or what could cause this?
private void pictureBox1_MouseDown(object sender,
MouseEventArgs m,
EventArgs e,
PaintEventArgs q)
{
if (m.Button == System.Windows.Forms.MouseButtons.Left)
{
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
Graphics g = Graphics.FromImage(pictureBox1.Image);
g.DrawLine(selPen, 3, 3, 133, 133);
}
}
Try adding a
pictureBox1.Invalidate();
call.
This is not the right way to draw to a picture box:
private void pictureBox1_MouseDown(object sender,
MouseEventArgs m,
EventArgs e,
PaintEventArgs q)
{
if (m.Button == System.Windows.Forms.MouseButtons.Left)
{
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
using(Graphics g = pictureBox1.CreateGraphics()) // Use the CreateGraphics method to create a graphic and draw on the picture box. Use using in order to free the graphics resources.
{
g.DrawLine(selPen, 3, 3, 133, 133);
}
}
}
Btw, this method will create a temporary image which is reseted when the control is invalidated. For a more persistent drawing, you need to listen to the Paint event of the picture box and draw your graphics there.
You must draw it from image first. then attach it to pictureBox1
Bitmap canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Graphics g = Graphics.FromImage(canvas);
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
g.DrawLine(selPen, 3, 3, 133, 133);
pictureBox1.image = canvas;
This is an old question and if anyone else has a similar problem. See below. First let's examine the Ops code.
(1) See code: The first recommended change is to keep the Pen's format simple until we have a better understanding about how the Pen actually works when drawing to graphics. Look at the Op's line where we create graphics from image which is a perfectly good example of how to directly draw ("which means to write") to the supplied bitmap by use of the bitmap's graphics context. Next, the Op provides an excellent example of the Graphics DrawLine method which can draw the defined line to the supplied bitmap.
(2) Due to missing details we have to make the following assumptions about the Op's supplied bitmap and about their method for drawing a line to the bitmap. Assuming there already exists an image inside this pictureBox1; if an image is not set the graphics we get from image will be from a null image or that each pixel will be black just as a footnote:
(a) Is the Pen's color unique to the existing bitmap and is the alpha component of the color high enough to actually see the resultant color when it's drawn (when in doubt use a unique solid color or at least set the alpha channel to 255)?
(b) This line the Op wants to draw is starting Left 3, Top 3 to Left 133 and that is 3-pixels to the right of bitmap's left side where this line has a height of 133 and as such the Pen's line size was changed to a width = 3 for demonstration purposes.
(c) The final consideration, is the pictureBox1.Size sufficient for us to see this drawn line? The line's geometry forms a rectangle similar to this RectangleF(3, 3, 3, 133) structure, so if the pictureBox1 Bounds rectangle intersects with the derived line's rectangle then the area of that intersection is where the line could be drawn and considered visible.
Before we can draw to the pictureBox1 image from graphics we must first convert the pictureBox1 image data back to a usable image type like a bitmap for example. The reason is the picture box stores only pixel data in array format and is not directly usable by GDI/GDI+ without conversion to an image type ie. bitamp, jpeg, png etc..
One can avoid this messy conversion if you handle you own painting by the way of a custom user control and by properly handling the PaintEventArgs OnPaint implementation and/or by using related graphics screen buffer context scenarios.
For those who just want the answer about what's missing:
private void button1_Click(Object sender, EventArgs e)
{
Pen selPen = new Pen(Color.Red, 2); // The Op uses random color which is not good idea for testing so we'll choose a solid color not on the existing bitmap and we'll confine our Pen's line size to 2 until we know what we're doing.
// Unfortionately the picture box "image" once loaded is not directly usable afterwords.
// We need tp recreate the pictureBox1 image to a usable form, being the "newBmp", and for efficiency the bitmap type is chosen
Bitmap newBmp = new Bitmap(pictureBox1.Width, pictureBox1.Height, PixelFormat.Format32bppArgb); // Tip: Using System.Drawing.Imaging for pixel format which uses same pixel format as screen for speed
// We create the graphics from our new and empty bitmap container
Graphics g = Graphics.FromImage(newBmp);
// Next we draw the pictureBox1 data array to our equivelent sized bitmap container
g.DrawImage(pictureBox1.Image, 0, 0);
g.DrawLine(selPen, 3, 3, 3, 133); // Format: (pen, x1, y1, x2, y2)
pictureBox1.Image = newBmp;
// Don't forget to dispose of no longer needed resources
g.Dispose();
selPen.Dispose();
newBmp.Dispose(); // or save newBmp to file before dispose ie. newBmp.Save("yourfilepath", ImageFormat.Jpeg) or in whatever image type you disire;
}
The Op's code so far only draws a line to the bitmap's surface next if we are to "see" this change we must either save bitmap to file to be viewed later in an image viewer or we must draw the updated bitmap to our display monitor, the screen.
There are several methods with which to draw to your monitor's screen. The most common graphics contexts one could use are Control.CreateGraghics, graphics to screen method from (PaintEventArgs) and/or by using a graphics screen buffer sometimes called and used as a manual double buffered graphics context in which all is implemented by the way of DrawImage method from graphics.
The simplest solution, in this case based upon the Op's own code, is to display this newly updated bitmap using the pictureBox1 control. We'll simply update the control's image with the newly updated bitmap of course once first converted to a usage graphics image as seen in the above code descriptions.
Happy coding!
I drew some circles on the Form1 using GDI+, and the center of the circle is a small red rectangle of Custom Control which is derived from User Control, the BackgroundImage property ofForm1's a bitmap which is also drawn by GDI+ with several colors.
What I want is that when I move the red rectangle(the center of circle) with a mouse, the circle will also move following the red rectangle. Using the MouseDown, MouseMove event I could move the red rectangle smoothly with the mouse.
My problem is how to move the circle corresponding to the red rectangle(the center of circle).
I enabled the double buffering to solve the flicker problem. CircleCenter is an object of Custom Control class(e.g. the red rectangle). GObject is a Grahpics object.
Here is some key codes:
public Form1()
{
InitializeComponent();
this.SetStyle(ControlStyles.DoubleBuffer | //enables double-buffering
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint,
true);
}
Point CCenterPoint = new Point();
private int Diameter = 250;
private void CircleCenterMouseDown(object sender, MouseEventArgs e)
{
CCenterPoint = new Point(-e.X, -e.Y);
}
private void CircleCenterMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Point MousePos = CircleCenter.MousePosition;
MousePos.Offset(CCenterPoint.X, CCenterPoint.Y);
CircleCenter.Location = CircleCenter.Parent.PointToClient(MousePos);
CircleCenter.BringToFront();
CirclePen.Color = Color.Black;
GObject.DrawEllipse(CirclePen, CircleCenter.Left- Diameter/2, CircleCenter.Top - Diameter/2, Diameter, Diameter);
this.Invalidate();
}
}
How to remove the black circle drawn by GDI+ produced in the MouseMove proceed?
I googled several websites and didn't get a satisfied answer. Hope you could give me some hints,Thx!
Well, As I understand from your question, You just need to draw a circle around the red rectangle, This is quite easy.
In the Paint event of the Form, Add the following (assuming that your red rectangle control has the name "CircleCenter" and your Form was named "Form1"):
private void Form1_Paint(object sender, PaintEventArgs e)
{
// get the Graphics object of the form.
Graphics g = e.Graphics;
// create a think red pen for the circle drawing
Pen redPen = new Pen(Brushes.Red, 4.0f);
// drawing the circle
PointF ctrlCenter = GetCenterOfControl(CircleCenter);
g.DrawEllipse(redPen,
ctrlCenter.X - (Diameter / 2),
ctrlCenter.Y - (Diameter / 2),
Diameter, Diameter);
}
//The following little method to calculate the center point
PointF GetCenterOfControl(Control ctrl)
{
return new PointF(ctrl.Left + (ctrl.Width / 2),
ctrl.Top + (ctrl.Height / 2));
}
Any way, I know it looks long for such a simple task like Circle drawing! here is the ugly one line version of the above code:
e.Graphics.DrawEllipse(new Pen(Brushes.Red, 4.0f), (centerCircle.Left + (centerCircle.Width / 2)) - (Diameter / 2), (centerCircle.Top + (centerCircle.Height / 2)) - (Diameter / 2), Diameter, Diameter);
You will need to always reset the entire GObject (erase the images you drawn on it) and then redraw them all again.
This can be done by simply drawing a rectangle with the color of the object from which you obtained the Graphics object (although you didn't mentioned it, I think GObject is a Graphics object, obtained of some win control?).
So something like:
Control control = CircleCenter.Parent; // Parent control where you get Graphics object from.
System.Drawing.SolidBrush sBrush = new System.Drawing.SolidBrush(control.BackColor); // Brush to be used with the same color like the one of the parent control.
GObject.FilledRectangle(sBrush, new Rectangle(0, 0, control.Width, control.Height); // Erase background.
GObject.DrawEllipse(CirclePen, CircleCenter.Left- Diameter/2, CircleCenter.Top - Diameter/2, Diameter, Diameter); // Do your stuff.
should hide the old drawings and re-draw the circle on the new location.
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
NumberOfBets++;
if ((e.X >= 40 && e.X <= 125) && (e.Y >= 0 && e.Y <= 26))
{
bettingStatus[0]++;
for (int x; x < 10; x++)
{
Graphics graphic = (???)
}
}
I'm trying to draw an image in this MouseDown method. I have NO clue what goes in the "(???)" part.
// Create a Graphics object for the pictureBox1 control.
Graphics g = pictureBox1.CreateGraphics();
Read more: MSDN: Graphics Class
You should draw in the Paint event, because if you don't, you will loose your drawings if the control is repainted. The PaintEventArgs passed to the Paint event handler has a Property named Graphics (of type System.Drawing.Graphics) which you can draw on.
You need to get the Graphics from somewhere, probably pictureBox1 in your case:
Graphics graphic = pictureBox1.CreateGraphics();
...But are you sure you want to be drawing in a MouseDown event handler? It won't be repainted if any part gets redrawn. You'd probably be better off doing all your drawing in a Paint event handler and setting a flag in MouseDown instead. Then invalidate the region you want to be redrawn to draw the new image.
Or, if your images are going to be more static, you can create a Bitmap, draw on that, then set the Image of your PictureBox to point to the Bitmap instead. For example:
Bitmap bmp = new Bitmap(200, 100);
Graphics graphics = Graphics.FromImage(bmp);
//do drawing here
pictureBox1.Image = bmp;