PictureBox double buffer keypress lag? - c#

Sometimes, If I hold down for example the left arrow key, the pictureBox will stop drawing the coordinates of my character. A prime example is the yellow dot encircled in red. By holding down the left arrow key, the pictureBox did not constantly draw my characters location, until I hit another key/let go of the arrow key, then the picturebox started updating again (as you can see encircled in purple).
Also, another error I am experiencing is the fact that it should only be drawing one yellow dot and one 'blue whirlpool' per location; while it is drawing multiple (as you can see).
The code below is what I am using to replicate the picture results.
private Thread grabValues;
private Bitmap bm;
private Image image1;
private Image image2;
private Graphics gM;
void RGraphics()
{
image1 = Properties.Resources.image1;
image2 = Properties.Resources.image2;
}
void RInvokeMe()
{
while (true)
{
if (this.InvokeRequired)
{
this.Invoke(new UpdateControlsDelegate(MUpdate));
this.Invoke(new UpdateControlsDelegate(CUpdate));
}
else
{
MUpdate();
CUpdate();
}
}
}
void MUpdate()
{
pictureBox1.ImageLocation = "http://urlexample/r/we.png";
}
void CUpdate()
{
bm = new Bitmap(pictureBox1.Image);
gM = Graphics.FromImage(bm);
gM.CompositingMode = CompositingMode.SourceOver;
MDraw.DrawBlueDot(gM, image2, X2, Y2);
MDraw.DrawYellowDot(gM, image1, X1, Y1);
pictureBox1.Image = bm;
pictureBox1.Refresh();
}
public Form2()
{
InitializeComponent();
RGraphics();
grabValues = new Thread(new ThreadStart(RInvokeMe));
grabValues.IsBackground = true;
grabValues.Start();
}

Basically you start a thread (grabValues, implemented by function RInvokeMe) which endlessly invoke on your form methods MUpdate and CUpdate. When you invoke a method then you post a message on the windows message queue in order to let the window execute the given function. But when you press a key even the keypress message is posted to the windows queue... but maybe it is not served quickly because of the tons of invoke requests you are already executing on the form.
You could try to introduce a small delay (Thread.Sleep(500);) in your RInvokeMe loop just to see if the problem is there...
Then if so, I would consider to completely re-design your project in order to avoid such a scenario (if your characters are only moved only when pressing keys, why you simply do not redraw the scene only upon keypress but you do it continuously?)

Related

Small problems when dynamically resizing a picturebox (tooltip delay, 'blinking')

Help please elegantly solve a couple of problems in my small project. I'm developing a small program in c # (WinForms), which uses pictureboxes instead of buttons. I added a small feature like an 'animation' to them: when you mouse over them they decrease in size, when the mouse goes away they return to their original state. Actually, I have two problems here:
If the mouse is brought to the very edge, the picturebox will be decreased, but if the mouse is out of the current size of the picturebox, it's size will be returned back. And so on. Such a kind of 'blinking' turns out.
And more importantly, I have tooltips attached to these pictureboxes. And if you move the mouse over the picturebox, it will decrease in size and the tooltip will not be called. It will take to move the cursor a little without going beyond the picturebox so the tooltip will appear. And this behavior is counterintuitive.
Here's the code:
private void IconReduce(PictureBox picturebox)
{
originalLocation = picturebox.Location;
originalSize = picturebox.Size;
picturebox.Location = new Point(picturebox.Location.X + 2, picturebox.Location.Y + 2);
picturebox.Size = new Size(picturebox.Size.Width - 4, picturebox.Size.Height - 4);
}
private void IconNormal(PictureBox picturebox)
{
picturebox.Location = new Point(originalLocation.X, originalLocation.Y);
picturebox.Size = new Size(originalSize.Width, originalSize.Height);
}
private void pb_MouseEnter(object sender, EventArgs e)
{
IconReduce(sender as PictureBox);
}
private void pb_MouseLeave(object sender, EventArgs e)
{
IconNormal(sender as PictureBox);
}

PictureBox Image Painted two times issue

I am loading multiple images into a pictureBox and is trying to centre it at the bottom. I am using the below function:
public void toogleImage(Boolean visible, Bitmap img)
{
if (visible)
{
pict_statusCenter.Show();
pict_statusCenter.Image = img;
}
else
{
pict_statusCenter.Hide();
}
}
After reading several Q&A such as here, I have updated the paint event as below:
private void pict_statusCenter_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.DrawImage(pict_statusCenter.Image,
(pict_statusCenter.Width - pict_statusCenter.Image.Width) / 2,
pict_statusCenter.Height - pict_statusCenter.Image.Height);
}
The image is now painted at the right location of the picture box. The problem is that a duplicate of the image is painted in the top left corner. I am a bit lost, any clue?
Second question: my image is actually a gif. During debugging, I noticed that each gif update fires a paint event. Is that normal? Is this the right way to do it?
Thanks for your kind assistance.
Ok, I finally did a simple padding:
pict_statusCenter.Padding = new Padding(
(pict_statusCenter.Width - pict_statusCenter.Image.Width)/2,
(pict_statusCenter.Height- pict_statusCenter.Image.Height),
(pict_statusCenter.Width - pict_statusCenter.Image.Width)/2,
0);

Moving a transparent image in a picturebox

For a project, I'm making a game and in it I have a scrolling map. The map moves left and right and is redrawn in a picturebox so that I can have a large map in a small picturebox. The top portion of the map is transparent so that I can change the sky colour later on. However when I move the map, the transparent part glitches out.
Original map before moving
After moving the map a bit
As you can see, everything above the tree line gets stretched, that is because that is where the transparency starts. The picturebox's parent is the form and the form is light blue, which is why the background is light blue.
Here is my code for moving the picture/redrawing it onto the picturebox:
private void timerTick_Tick(object sender, EventArgs e)
{
move();
//Draws new portion of the map
g.DrawImage(image, new Rectangle(0, 0, pbMap.Width, pbMap.Height), new Rectangle(imageX, imageY, pbMap.Width, pbMap.Height), GraphicsUnit.Pixel);
//Refreshes
pbMap.Image = bmp;
}
private void move()
{
//Right arrow events
if (right)
{
imageX += mapSpeed;
//Makes sure the picture stays within borders
if (imageX >= (imageWidth - pbMap.Width))
{
imageX = imageWidth - pbMap.Width;
}
}
//Left arrow events
if (left)
{
imageX -= mapSpeed;
//Makes sure the picture stays within borders
if (imageX <= 0)
{
imageX = 0;
}
}
}
Can anyone help explain the glitching?
Try calling g.Clear() with your sky color before the g.DrawImage() call. I think it's just drawing on top of itself and that's causing the smearing.
To me it seems like you are redrawing over and over without clearing the display from the previous draw! What type of framework are you using to develop that? Does it have a custom drawing class? As tesserex suggested more specifically call g.Clear() and u will be fine.
You shouldn't need g.Clear in this case because you're Re-Drawing a new image every time.
My bet is that imageX is greater then (imageWidth - pbMap.Width) so it will not enter the IF, therefore it will redraw the same as before.
Note: I don't know how you create g but if you use .CreateGraphics() don't forget to Dispose()
Cheers

Graphics.DrawImage speed

In my program, I'm coding a basic image editor. Part of this allows the user to draw a rectangular region and I pop up a display that shows that region zoomed by 3x or so (which they can adjust further with the mouse wheel). If they right click and drag this image, it will move the zoom region around on the original image, basically acting as a magnifying glass.
The problem is, I'm seeing some serious performance issues even on relatively small bitmaps. If the bitmap showing the zoomed region is around 400x400 it's still updating as fast as mouse can move and is perfectly smooth, but if I mouse wheel the zoom up to around 450x450, it immediately starts chunking, only down to around 2 updates per second, if that. I don't understand why such a small increase incurs such an enormous performance problem... it's like I've hit some internal memory limit or something. It doesn't seem to matter the size of the source bitmap that is being zoomed, just the size of the zoomed bitmap.
The problem is that I'm using Graphics.DrawImage and a PictureBox. Reading around this site, I see that the performance for both of these is typically not very good, but I don't know enough about the inner workings of GDI to improve my speed. I was hoping some of you might know where my bottlenecks are, as I'm likely just using these tools in poor ways or don't know of a better tool to use in its place.
Here are some snippets of my mouse events and related functions.
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
// slide the zoomed part to look at a different area of the original image
if (zoomFactor > 1)
{
isMovingZoom = true;
// try saving the graphics object?? are these settings helping at all??
zoomingGraphics = Graphics.FromImage(displayImage);
zoomingGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
zoomingGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
zoomingGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
zoomingGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
}
}
}
private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (isMovingZoom)
{
// some computation on where they moved mouse ommitted here
zoomRegion.X = originalZoomRegion.X + delta.X;
zoomRegion.Y = originalZoomRegion.Y + delta.Y;
zoomRegionEnlarged = scaleToOriginal(zoomRegion);
// overwrite the existing displayImage to prevent more Bitmaps being allocated
createZoomedImage(image.Bitmap, zoomRegionEnlarged, zoomFactor, displayImage, zoomingGraphics);
}
}
private void createZoomedImage(Bitmap source, Rectangle srcRegion, float zoom, Bitmap output, Graphics outputGraphics)
{
Rectangle destRect = new Rectangle(0, 0, (int)(srcRegion.Width * zoom), (int)(srcRegion.Height * zoom));
outputGraphics.DrawImage(source, destRect, srcRegion, GraphicsUnit.Pixel);
if (displayImage != originalDisplayImage && displayImage != output)
displayImage.Dispose();
setImageInBox(output);
}
// sets the picture box image, as well as resizes the window to fit
void setImageInBox(Bitmap bmp)
{
pictureBox.Image = bmp;
displayImage = bmp;
this.Width = pictureBox.Width + okButton.Width + SystemInformation.FrameBorderSize.Width * 2 + 25;
this.Height = Math.Max(450, pictureBox.Height) + SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height * 2 + 20;
}
private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
if (isMovingZoom)
{
isMovingZoom = false;
zoomingGraphics.Dispose();
}
}
}
As you can see, I'm not declaring a new Bitmap every time I want to draw something, I'm reusing an old Bitmap (and the Bitmap's graphics object, though I don't know if there is much cost with calling Graphics.FromImage repeatedly). I tried adding Stopwatches around to benchmark my code, but I think DrawImage passes functionality to another thread so the function claims to be done relatively quickly. I'm trying to Dispose all my Bitmap and Graphics objects when I'm not using them, and avoid repeated calls to allocate/deallocate resources during the MouseMove event. I'm using a PictureBox but I don't think that's the problem here.
Any help to speed up this code or teach me what's happening in DrawImage is appreciated! I've trimmed some excess code to make it more presentable, but if I've accidentally trimmed something important, or don't show how I'm using something which may be causing problems, please let me know and I'll revise the post.
The way I handle issues like that is when receiving the Paint event, I draw the whole image to a memory bitmap, and then BLT it to the window.
That way, all visual flash is eliminated, and it looks fast, even if it actually is not.
To be more clear, I don't do any painting from within the mouse event handlers.
I just set up what's needed for the main Paint handler, and then do Invalidate.
So the painting happens after the mouse event completes.
ADDED: To answer Tom's question in a comment, here's how I do it. Remember, I don't claim it's fast, only that it looks fast, because the _e.Graphics.DrawImage(bmToDrawOn, new Point(0,0)); appears instantaneous. It just bips from one image to the next.
The user doesn't see the window being cleared and then repainted, thing by thing.
It gives the same effect as double-buffering.
Graphics grToDrawOn = null;
Bitmap bmToDrawOn = null;
private void DgmWin_Paint(object sender, PaintEventArgs _e){
int w = ClientRectangle.Width;
int h = ClientRectangle.Height;
Graphics gr = _e.Graphics;
// if the bitmap needs to be made, do so
if (bmToDrawOn == null) bmToDrawOn = new Bitmap(w, h, gr);
// if the bitmap needs to be changed in size, do so
if (bmToDrawOn.Width != w || bmToDrawOn.Height != h){
bmToDrawOn = new Bitmap(w, h, gr);
}
// hook the bitmap into the graphics object
grToDrawOn = Graphics.FromImage(bmToDrawOn);
// clear the graphics object before drawing
grToDrawOn.Clear(Color.White);
// paint everything
DoPainting();
// copy the bitmap onto the real screen
_e.Graphics.DrawImage(bmToDrawOn, new Point(0,0));
}
private void DoPainting(){
grToDrawOn.blahblah....
}

Creating a custom graph in Winforms

I am trying to make a simple graph for my application which shows real-time data for every 100 ms. So I thought I could draw the graph line using the DrawCurve method and started with the following code:
class BufferedPanel : Panel
{
public BufferedPanel()
{
this.DoubleBuffered = true; //to avoid flickering of the panel
}
}
class Form2: Form
{
BufferedPanel panel1 = new BufferedPanel();
List<Point> graphPoints = new List<System.Drawing.Point>();
private void Form2_Load(object sender, EventArgs e)
{
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Point[] points = graphPoints.ToArray();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
if (points.Length > 1)
g.DrawCurve(graphPen, points);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
graphPoints.Add(new System.Drawing.Point(counter * steps, (int)(float)e.UserState)); //x - regular interval decided by a constant; y - simulated by background worker
panel1.Refresh();
counter++;
}
}
As of now, I am simulating the values of graphPoints from a background worker thread. My problem is that, I could not get the graph lines visible when I doublebuffer my panel. It works well when I set doublebuffering to false. I am new to drawing using Graphics. So I am not very sure of how it works. Please help me in this.
Also, I would like to achieve AutoScrolling when the graphlines reach to end of the panel. Could you suggest an idea on how to do it?
This is an image of my working graph:
using (Graphics g = e.Graphics)
That's bad. That destroys the Graphics object that was passed to your Paint event handler. Nothing can be done with that object when your event handler returns, it is a dead parrot. So don't expect anything to work afterwards, including what needs to happen when you turn on double-buffering, the buffer needs to be drawn to the screen to be visible.
There's a simple rule to using the using statement or the Dispose() method correctly. If you create an object then you own it and it is yours to destroy it. Hands off it you didn't create it.
Some evidence that you are also getting it wrong with the "graphPen" variable. Pens are definitely an object that you create and destroy in a Paint event handler. Don't store one, that just needlessly occupies space in the GDI heap, that isn't worth the few microseconds needed to create one. You'd definitely use the using statement for the pen.
So the quick fix is:
var g = e.Graphics;

Categories