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);
}
Related
I am creating my own app in Winforms where I want to be able to create a map and put icons on it. Those icons will reveal information when clicked. The map is a simple image and the icons are bitmaps. The user can drag an icon onto the map, and afterwards drag it again or remove it.
The problem is that when I drag the icon (a bitmap) and release it, it will flicker for a moment. This will happen regardless of the speed, and regardless of wether it is the very first placement of the icon.
My repo for the code: https://github.com/Foxion7/MapMarker
A screenshot of the panels, icons etc.: https://imgur.com/a/pKMJ4lX To the right is the icon you can click to drag a new icon to the map.
I have already tried using Images with transparancy (didnt work for issues unrelated to the flicker), doublebuffering, checking excessive use of Invalidate().
I would also appreciate advice on other ways besides bitmaps and multiple panels to perhaps circumvent the issue, but I've gotten pretty far with this so far.
Below is an excerpt for the 'in-panel' moving of an icon.
// Selecting an existing icon.
private void mapPictureBox_MouseDown(object sender, MouseEventArgs e)
{
Icon selectedIcon = map.CheckCollision(e.Location);
if (selectedIcon != null)
{
SelectIcon(selectedIcon);
currentOffset = new Point((e.X - selectedIcon.center.X) * -1, (e.Y - selectedIcon.center.Y) * -1);
holdingAnIcon = true;
}
}
// Only hides icon on old position when you start dragging to prevent flashing.
private void mapPictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (holdingAnIcon)
{
if(oneTimePerMoveActionsDone)
{
selectedIcon.hidden = true;
mapPictureBox.Invalidate();
oneTimePerMoveActionsDone = false;
}
var dragImage = ResizeImage(selectedIcon.bitmap, iconWidth, iconHeight);
IntPtr icon = dragImage.GetHicon();
Cursor.Current = new Cursor(icon);
}
}
// Releasing after dragging an existing icon.
private void mapPictureBox_MouseUp(object sender, MouseEventArgs e)
{
if (holdingAnIcon)
{
selectedIcon.location = mapPanel.PointToClient(new Point(e.X - (int)(iconWidth / 3.75) + currentOffset.X, e.Y + iconHeight / 5 + currentOffset.Y));
selectedIcon.UpdateCenterPosition();
selectedIcon.hidden = false;
holdingAnIcon = false;
oneTimePerMoveActionsDone = true;
mapPictureBox.Invalidate();
}
}
The flicker (almost always) happens on release of the mousebutton. There is only a very tiny chance the flicker doesn't appear sometimes.
I am having a problem with this code. When I start the program Ruler is in the center of the page. When I mousemove when MouseDown is true, the Rectangle (Ruler) is dragable as I want. However, this only works on the first drag. The next time I go to drag it the Ruler jumps back to it's original position, then when you mouse over it the distance from where it was to where it jumped back is calculated and it jumps off the screen as the mouseup event doesn't fire as the rectangle has moved. I basically want to be able to drag the object around the screen however many times I want, but the XStart and YStart need to take the new rendered values on each click.
I think the reason has to do with the e.GetPosition(this).X; as 'this' refers to the Grid that is the rulers parent.
Do I need to commit the RenderTransform to the program? or is there an error in my logic?
It would honestly make more sense if it didn't work at all, but to work perfectly once, then screw up makes no sense.
Here is the code:
private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
XStart = e.GetPosition(this).X;
YStart = e.GetPosition(this).Y;
Console.WriteLine("X: " + XStart + ", Y: " + YStart);
MouseDown = true;
}
private void Rectangle_MouseMove(object sender, MouseEventArgs e)
{
if(MouseDown)
{
X = e.GetPosition(this).X - XStart;
Y = e.GetPosition(this).Y - YStart;
Ruler.RenderTransform = new TranslateTransform(X, Y);
}
}
private void Ruler_MouseUp(object sender, MouseButtonEventArgs e)
{
MouseDown = false;
}
Looks like Mouse.GetPosition doesn't work when dragging like you would expect.
This example seems relevant, but he uses the DragOver event instead of MouseMove, so I'm not entirely sure if it's the same situation.
In my program I added this code so when I move my mouse all over the screen I will get the mouse cursor coordinates in real time:
Form1 Load:
private void Form1_Load(object sender, EventArgs e)
{
System.Windows.Forms.Timer t1 = new System.Windows.Forms.Timer();
t1.Interval = 50;
t1.Tick += new EventHandler(timer1_Tick);
t1.Enabled = true;;
}
Then the method that get the mouse position:
public static Point GetMousePosition()
{
var position = System.Windows.Forms.Cursor.Position;
return new Point(position.X, position.Y);
}
Then the timer1 tick event:
private void timer1_Tick(object sender, EventArgs e)
{
label1.Text = string.Format("X={0}, Y={1}", GetMousePosition().X, GetMousePosition().Y);
}
Then I ran some application and moved the mouse over a specific location on the screen where the application window is and I found this coordinates:
358, 913
Now I have in my program a listBox with items each item present application screenshot. And if I click on the pictureBox for example in this case on the BATTLEFIELD 3 area I get the mouse cursor coordinates according to the pictureBox area.
So I did:
Point screenCoordinates;
Point pictureBoxSnapCoordinates;
private void pictureBoxSnap_MouseDown(object sender, MouseEventArgs e)
{
screenCoordinates = pictureBoxSnap.PointToScreen(e.Location);
pictureBoxSnapCoordinates = e.Location;
}
Now when I click in the pictureBox at the same location as I found the coordinates 358, 913 but on the pictureBox so the results are:
screenCoordinates 435, 724
pictureBoxSnapCoordinates 23,423
The screenCoordinates isn't the same coordinates as I found with the mouse move 358, 913 it's not even close. There is a big difference between 358,913 and 437,724
e.Location is relative to the Control's top left corner. If you want to use e.Location to get the screen coordinates, then you have to first do pictureBoxSnap.PointToScreen(Point.Empty); and then offset by the e.Location.
Also, Cursor.Position returns a Point object, so making a new Point(...) is pointless.
I must add, if you are dealing with images, and you need to interact with mouse, and do any task related with offset, scroll, etc, I recommend you that library, it is open source and have a lot of examples and methods that will help you
https://github.com/cyotek/Cyotek.Windows.Forms.ImageBox
I have a growing rectangle drawn on top of a TableLayoutPanel but when it grows it causes a horrible flicker even with Double Buffer.
I am using e.Graphics.FillRectangle and a global variable that increases the size of the rectangle. I set up a timer to make it grow every 1/10th of a second by 1 pixel. Why does it flicker so much and how can I stop the flicker?
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width-grow)/2, (tableLayoutPanel1.Height-grow)/2, grow,grow);
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Refresh();
}
In order to rule out all other possibilities I removed everything from my program and started from scratch with just a growing rectangle and it still creates this horrible flicker.
Ok, here is the code. You first need to make background buffer Bitmap with the size of your control. After that, you will need to draw everything on the Bitmap, and than draw that bitmap onto the control.
Bitmap backBuffer = null;
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
if (backBuffer == null)
backBuffer = new Bitmap(tableLayoutPanel1.Width, tableLayoutPanel1.Height);
Graphics g = Graphics.FromImage(backBuffer);
g.Clear(tableLayoutPanel1.BackColor);
g.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width - grow) / 2, (tableLayoutPanel1.Height - grow) / 2, grow, grow);
e.Graphics.DrawImage(backBuffer, 0, 0, backBuffer.Width, backBuffer.Height);
g.Dispose();
}
private void tableLayoutPanel1_Resize(object sender, EventArgs e)
{
backBuffer = null;
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Invalidate();
}
Note that you will need to create new Bitmap each time you resize the TableLayoutPanel. In addition I suggest using Invalidate() instead of Refresh().
But this will still include some potential flickering. In order to completely avoid flicker, in addition to the previous code, you will need to subclass the TableLayoutPanel and override the OnPaintBackground() method in such a way that base.OnPaintBackground is never called. This way way won't have any flickering at all. The reason why you have the flickering is because the background is redrawn before your Rectangle, any time you draw it. Switch the original TableLayoutPanel class with this BackgroundlessTableLayoutPanel
public class BackgroundlessTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
}
}
Most controls have a Paint event where you can implement any custom drawing you need to do. It is also possible to implement your own control where you override the OnPaint method. See the article here.
Both these should give ok results.
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....
}