The control below draws a string in a rectangle. On mouse move there is a hit test on the string rectangle, and the string is redrawn via CreateGraphics. The irritating problem is that the text is not drawn the same as in the Paint handler; it appears to be displaced by about 1 pixel, and the effect is like a bold font. How can I create a graphics object exactly like the one in the Paint handler so the text is drawn the same way? Ordinarily you would invalidate and redraw everything in the Paint event, but I have potentially hundreds of of other drawing items and only want to draw the string. Should I try to do any drawing outside of the Paint event or is this a mistake?
Example control:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Test.TestModes
{
public partial class ExampleControl: UserControl
{
private const string testString = "0123456789";
private RectangleF stringRect = new RectangleF(10, 10, 100, 20);
public ExampleControl()
{
InitializeComponent();
}
private void ExampleControl_Paint(object sender, PaintEventArgs e)
{
Font font = new Font("Arial", 12, FontStyle.Regular);
e.Graphics.DrawString(testString, font, Brushes.Black, stringRect);
font.Dispose();
}
private void DrawString(bool hit)
{
Font font = new Font("Arial", 12, FontStyle.Regular);
using(Graphics g = CreateGraphics())
{
g.SetClip(ClientRectangle);
if(hit)
g.DrawString(testString, font, Brushes.Red, stringRect);
else
g.DrawString(testString, font, Brushes.Black, stringRect);
}
font.Dispose();
}
private void ExampleControl_MouseMove(object sender, MouseEventArgs e)
{
if(stringRect.Contains(e.Location))
DrawString(true);
else
DrawString(false);
}
private void button1_Click(object sender, EventArgs e)
{
Invalidate();
}
}
}
It is the CreateGraphics() call that is getting you in trouble, indirectly. The problem is anti-aliasing of the text. A normal painting cycle erases the background before drawing something on top. That doesn't happen in your case, your draw text on top of existing text. The side effect is that the pixels uses to create the aliasing get darker each time your draw. The end result is bold looking, and noticeably jagged text outlines.
The fix is easy: start with a clean slate before you draw:
using (Graphics g = CreateGraphics()) {
g.Clear(this.BackColor); <=== added
g.SetClip(ClientRectangle);
// etc..
}
You'll now also get to encounter a problem in drawing known as "flicker". It might not yet be that noticeable yet, but it will when you do more drawing. Flicker is suppressed with double-buffering. A feature supported by Windows Forms, but only if you use standard drawing techniques. In other words: no CreateGraphics().
Related
I'm learning how to draw in Winforms. I've created a Form, with a panel with scrollbars. Upone Event Paint I draw an ellipse. This is fairly straightforward:
this.panel1.AutoScroll = true;
this.panel1.AutoScrollMinSize = = new System.Drawing.Size(500, 300);
private void OnPaint(object sender, PaintEventArgs e)
{
Rectangle ellipse = new Rectangle(Point.Empty, new Size(400, 400));
ellipse.Offset(this.panel1.AutoScrollPosition);
using (Pen myPen = new System.Drawing.Pen(System.Drawing.Color.Red))
{
e.Graphics.DrawEllipse(myPen, ellipse);
}
}
private void OnPanelScroll(object sender, ScrollEventArgs e)
{
this.panel1.Invalidate();
}
This works fine, but the complete image is redrawn when resizing or scrolling the panel.
A long time ago, in the time of MFC there was the notion of ViewPort / SetViewPortOrg / mapping modes / etc.. Scrolling and resizing did not require a recalculation of the complete image. Once you had drawn the image you didn't have to redraw as long as the complete image was not changed. All you had to do was move the viewport, or change the mapping mode
Does .NET have something similar that can do the scrolling for me? Maybe I should not draw on a panel, but on another subclass of a ScrollableControl?
I have a simple Photoshop-made grid and i would like to use it as progress bar, i need to draw round ellipses from 1 to 100 (then probably about 100 times in x time).
If I use System.Graphic I have not persistent result.
Then I found the code to use the PaintEventArgs method by inserting instructions in the Paint Event of the form.
Unfortunately in my mind this is not a solution because I need to draw only when I need and only where I want.... in other word I need a simple Function able to draw desired ellipses when I need...
I tried also to override the OnPaint-base but I really don't understand how to use it and if may help to reach my goal.
Here some code:
With the paint event:
private void Main_Paint(object sender, PaintEventArgs e)
{
// Create pen.
Pen blackPen = new Pen(Color.Yellow, 3);
// Create rectangle for ellipse.
Rectangle rect = new Rectangle(355, 282, 9, 9);
e.Graphics.DrawEllipse(blackPen, rect);
}
With the Graphic mode:
private void lbl_help_Click(object sender, EventArgs e)
{
//SetStatus("BURNING PROCESS COMPLETED SUCCESSFULLY");
Graphics g = this.CreateGraphics();
// Create pen.
Pen blackPen = new Pen(Color.Yellow, 3);
// Create rectangle for ellipse.
Rectangle rect = new Rectangle(355, 282, 9, 9);
// Draw ellipse to screen.
g.DrawEllipse(blackPen, rect);
System.Threading.Thread.Sleep(3000);
}
And this one I found to override OnPaint(), but I really don't know how to use it, how to override the form-paint event or how to call it only when needed and passing values:
private void lbl_help_Click(object sender, EventArgs e)
{
//SetStatus("BURNING PROCESS COMPLETED SUCCESSFULLY",);
Graphics g = this.CreateGraphics();
// Create pen.
Pen blackPen = new Pen(Color.Yellow, 3);
// Create rectangle for ellipse.
Rectangle rect = new Rectangle(355, 282, 9, 9);
// Draw ellipse to screen.
g.DrawEllipse(blackPen, rect);
System.Threading.Thread.Sleep(3000);
}
Something other other:
I imagine if I use variables to store the percentage and call a paint-refresh of the form (maybe invalidate?) to update the result should work but I will lose any sort of animation, elsewhere I come-back to a non persistent state again... I need to use the grid as a progress bar, adding circles only at desired time, without losing the back drawings...
The grid I need to fill is very simple, here a screenshot:
Sry I should not post image for the reputation (i'm a new user!), here the link
EDIT:
I solved the smoothing problem (at least with the Graphic Mode):
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
I have UserControl : Panel. When I add to Form own UserControl. UserControl.Anchor = Left|Right|Top|Bottom. When I resize Form Rectangle is blinking. How can you make that do not blink?
public partial class UserControl1 : Panel
{
public UserControl1()
{
InitializeComponent();
this.ResizeRedraw = true;
}
private void UserControl1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = this.CreateGraphics())
{
Pen pen = new Pen(Color.Black, 1);
Brush brush = new SolidBrush(Color.Black);
g.DrawRectangle(pen, 0, 0, this.Width - 1, this.Height - 1);
pen.Dispose();
}
}
}
There are many things you can do to reduce flicker:
make your paint handler as fast as possible by moving unnecessary work out of the method (e.g. create brushes and pens outside the method and cache them to avoid the creation cost on every paint; clear the background using gfx.Clear rather than filling the rectangle; redesign your display so you don't have to draw so much stuff; cache parts of the image in bitmaps so they can be redrawn faster)
avoid drawing each pixel more than once (if you fill the background with white and then draw over it with black, it will flicker. But if you draw awhite rectangle and then draw a frame around it in black you can avoid the flicker)
only draw the visible part of your graphics by checking the clip rectangle, to avoid the work of drawing stuff that isn't currently visible)
ensure that the framework is not filling the background for you (usually in white) by overriding the erase background handling.
enable double buffering so that any remaining flicker is eliminated. This uses a lot more resources than the other approaches, although these days that's not usually much of a problem.
use red existing e.Graphics to draw with rather than calling CreateGraphics
Try set doubleBuffered = true and you do not have to create graphics object in pain event. you can get that from the event args. You have to make sure you do minimum amount of task in a paint event.
public partial class UserControl1 : Panel
{
public UserControl1()
{
InitializeComponent();
this.ResizeRedraw = true;
this.DoubleBuffered = true;
}
private void UserControl1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
Pen pen = new Pen(Color.Black, 1);
Brush brush = new SolidBrush(Color.Black);
g.DrawRectangle(pen, 0, 0, this.Width - 1, this.Height - 1);
}
}
I have a Windows Forms application with a GroupBox, and a PictureBox as background image, and several clickable OvalShapes from the PowerPack.
Now I need some labels for the OvalShapes, so I put a EventHandler on my GroupBox that on every repaint, the following should be drawn
this.groupBoxTest.Paint += new System.Windows.Forms.PaintEventHandler(this.groupBoxVirtualView_Paint);
private void groupBoxVirtualView_Paint(object sender, PaintEventArgs e)
{
Graphics g = groupBoxVirtualView.CreateGraphics();//e.Graphics;
g.DrawString("01", new Font("Arial", 12), new SolidBrush(Color.Black), 240, 115);
}
But the string 01 never gets drawn; all I see are the oval shapes that are at the same position - disabling them for testing purpose doesn't do it either.
What's happening to my string?
Any other way to label my PoweredOval?
You have to use
groupBoxVirtualView.Invalidate();
to repaint the groupBox!
The paint event should use the graphic object from the sender:
private void groupBoxVirtualView_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawString("01", new Font("Arial", 12), new SolidBrush(Color.Black), 240, 115);
}
Also, if there is a PictureBox inside the GroupBox, that control will not show any of the GroupBox paintings to come through the control. It will hide it.
I googled for "Drawing text on picturebox C#" ,but I couldnt find anything useful.Then I googled for "Drawing text on form C#" and I found some code,but it doesnt work the way I want it to work.
private void DrawText()
{
Graphics grf = this.CreateGraphics();
try
{
grf.Clear(Color.White);
using (Font myFont = new Font("Arial", 14))
{
grf.DrawString("Hello .NET Guide!", myFont, Brushes.Green, new PointF(2, 2));
}
}
finally
{
grf.Dispose();
}
}
When I call the function,the background color of the form becomes white(it's black by default).
My questions:
1:Will this work on a picturebox?
2:How to fix the problem?
You don't want that call to Clear() - that's why it's turning the background white, and it will cover up your picture.
You want to use the Paint event in the PictureBox. You get the graphics reference from e.Graphics, and then use the DrawString() that you have in your sample.
Here's a sample. Just add a picture box to your form, and add an event handler for the Paint event:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
using (Font myFont = new Font("Arial", 14))
{
e.Graphics.DrawString("Hello .NET Guide!", myFont, Brushes.Green, new Point(2, 2));
}
}
(Note that you won't see the text at design time - you'll have to run the program for it to paint).