I have to create a graphic interface for a c# winform project. There is a background image and a set of small transparent pictures. The user must be able to put those little images over the backgroud, select them and move them freely (I have also to calculate distance between them but this is another step!).
I know I can do somthing like this:
Flicker free drawing using GDI+ and C#
I also found this:
http://cs-sdl.sourceforge.net/
My question is: is there a better or simplest solution to achieve this?
update
Now images are rectangular. There are no problems if images overlaps!
If small images are a problem I can switch with simple circles (DrawEllipse). The important point is that user can always click on and move them.
I have searched for a solution on how to remove flickering from pictureboxs, without finding something satisfying ... i ended up using some 2DVector from the XNA framework and spirits. it worked fine :)
http://www.xnadevelopment.com/ gives a good explanation for how to use it, it is explained in a game context.
The Windows Presentation Fo
Foundation (WPF) may be a better solution for this. It is more graphically inclined than GDI+, and is also much faster, as its powered by DirectX.
If you don't want flickr your best option is DirectX/XNA/OpenGL. Try to find a 2d framework with sprites for your application.
If you would use WPF then you should use a Canvas as your container control. To the images you have to add these event handlers in you code behind file:
private bool IsDragging = false;
private System.Windows.Point LastPosition;
private void MyImage_MouseDown(object sender, MouseButtonEventArgs e)
{
// Get the right MyImage
Image MyImage = sender as Image;
// Capture the mouse
if (!MyImage.IsMouseCaptured)
{
MyImage.CaptureMouse();
}
// Turn the drag mode on
IsDragging = true;
// Set the current mouse position to the last position before the mouse was moved
LastPosition = e.GetPosition(SelectionCanvas);
// Set this event to handled
e.Handled = true;
}
private void MyImage_MouseUp(object sender, MouseButtonEventArgs e)
{
// Get the right MyImage
Image MyImage = sender as Image;
// Release the mouse
if (MyImage.IsMouseCaptured)
{
MyImage.ReleaseMouseCapture();
}
// Turn the drag mode off
IsDragging = false;
// Set this event to handled
e.Handled = true;
}
private void MyImage_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
// Get the right MyImage
Image MyImage = sender as Image;
// Move the MyImage only when the drag move mode is on
if (IsDragging)
{
// Calculate the offset of the mouse movement
double xOffset = LastPosition.X - e.GetPosition(SelectionCanvas).X;
double yOffset = LastPosition.Y - e.GetPosition(SelectionCanvas).Y;
// Move the MyImage
Canvas.SetLeft(MyImage, (Canvas.GetLeft(MyImage) - xOffset >= 0.0) && (Canvas.GetLeft(MyImage) + MyImage.Width - xOffset <= SelectionCanvas.ActualWidth) ? Canvas.GetLeft(MyImage) - xOffset : Canvas.GetLeft(MyImage));
Canvas.SetTop(MyImage, (Canvas.GetTop(MyImage) - yOffset >= 0.0) && (Canvas.GetTop(MyImage) + MyImage.Height - yOffset <= SelectionCanvas.ActualHeight) ? Canvas.GetTop(MyImage) - yOffset : Canvas.GetTop(MyImage));
// Set the current mouse position as the last position for next mouse movement
LastPosition = e.GetPosition(SelectionCanvas);
}
}
I hope that helps, David.
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've done a mini test program to prototype a way to drag and drop an image (at the right)(using a button as a support) to a panel (left part)(The current panel background letter are only for test purpose) and to move it inside the panel perimeter.
The image on the moving control is manually drawn during the Paint event :
void bouton_Paint( object sender, PaintEventArgs e )
{
Button but = sender as Button;
Bitmap img = new Bitmap(WindowsFormsApplication1.Properties.Resources.triaxe);
img.MakeTransparent(Color.White);
e.Graphics.DrawImage(img, 0, 0, but.Width, but.Height);
}
As you can see below, during the moving process, the background of the moved control is frezzed. So it is not very pretty
Is it to make the progress smoother ?
Thank you.
This is the code to move my triaxe on the form :
void bouton_MouseUp( object sender, MouseEventArgs e )
{
Button button = sender as Button;
button.Tag = false;
button.BackColor = Color.Transparent;
}
void bouton_MouseMove( object sender, MouseEventArgs e )
{
Button button = sender as Button;
if (button.Tag != null && (bool)button.Tag == true)
{
button.Left += e.X - MouseDownLocation.X;
button.Top += e.Y - MouseDownLocation.Y;
}
}
private Point MouseDownLocation;
void bouton_MouseDown( object sender, MouseEventArgs e )
{
Button button = sender as Button;
MouseDownLocation = e.Location;
button.Tag = true;
button.BackColor = SystemColors.Control;
}
Let's assume you do not want to draw the graphics alone but do want a Control with transparency move on top of another Control.
You have to solve two problems:
Winforms doesn't support transparency well; you have two options:
Either let the system fake it for you
Or do the faking yourself.
For the first it is enough to have a control with a transparent backcolor and an Image or BackgroundImage (depending on the control type) with transparency and then nest the moving control in the background control.
For a simplistic test you can add if (button.Parent != panel1) button.Parent = panel1; to your mousedown code. This will look jumpy but should display transparency correctly. (Don't forget to make the Backcolor transparent; in your code your make it SystemColors.Control.
To fake it yourself you would do exactly what the system does:
You get the target suface in a bitmaps (with DrawToBitmap) and then combine the area the mover currently covers with the image; here getting that area is key.
Then you can set that image as backgroundimage or draw it onto the mover.
The other problem is with the moving code. Here the issue is that when you move the mouse the MouseMove event is triggered and you can move the control. But by moving it the mouse will have moved relativly to the new location and the event will be triggered again leading to flicker an jumpiness!
To avoid this you can test the numbers or use a flag or unregister the move event before setting the location and re-register afterwards.
Also setting the location in one go instead of the two coordinates is a good idea..: button.Location = new Point(button.Left + e.X - MouseDownLocation.X, button.Top + e.Y - MouseDownLocation.Y);
Imo you should still consider drawing just the graphics on On panel.MouseMove and panel.Paint. On panel.MouseUp you can still add a Button to the panel.Controls right there. Both issues are avoided and you are free to add the functionality you want as well..
I've seen few questions about this problem, I tried every solution but none of them worked for my case.
My code is working; this image shows what happens when I click on Draw button.
I need to zoom on that drawing.Is it possible to code something like autocad feature "zoom/extent"?
Pen myPen = new Pen(Color.Black);
int centerpointx, centerpointy;
private void pictureBoxDraw_Paint(object sender, PaintEventArgs e)
{
centerpointx = pictureBoxDraw.Size.Width/2;
centerpointy = pictureBoxDraw.Size.Height/2;
myPen.Width = 2;
if (binary > 0)
{
var sizecrestgeo = 40;
var distancearraycrestgeo = new float[sizecrestgeo];
var elevationarraycrestgeo = new float[sizecrestgeo];
for (int i = 0; i < sizecrestgeo; i++)
{
distancearraycrestgeo[i] = float.Parse(dataGridViewCrestGeo.Rows[i].Cells[0].Value.ToString());
elevationarraycrestgeo[i] = float.Parse(dataGridViewCrestGeo.Rows[i].Cells[1].Value.ToString())*-1;
}
for (int i=0; i < sizecrestgeo-1; i++)
{
e.Graphics.DrawLine(myPen, distancearraycrestgeo[i]+centerpointx, elevationarraycrestgeo[i]+centerpointy, distancearraycrestgeo[i + 1]+centerpointx, elevationarraycrestgeo[i + 1]+centerpointy);
}
}
else
{
}
}
private void buttonDraw_Click_1(object sender, EventArgs e)
{
if (Hd > 0.0001)
{
binary = 1;
pictureBoxDraw.Invalidate();
}
else
{
MessageBox.Show("No data to draw, perform analysis first.");
}
}
private void buttoncleardraw_Click(object sender, EventArgs e)
{
binary = 0;
pictureBoxDraw.Invalidate();
}
}
This is not so hard, provided you know all the puzzle pieces.
Let's start with the obvious one:
You can scale the Graphics object to create zoomed graphics with ScaleTransform.
As I mentioned, this will include the widths of pens, font sizes and also any images you draw (though not the hatches of a HatchBrush).
You also asked about keeping the drawing 'centered'. This is a non-obvious concept: Just what is the center of your drawing surface??
When zooming (just like rotating) you always need to know the center point of the zoom (or the rotation.) By default this is the origin (0,0). I chose the center of the Panel. You may want to pick some other point..
Once you do you can move the origin of the graphics viewport to this point with TranslateTransform.
Once you have achieved all this you almost certainly will want to allow scrolling.
To do so you have two options:
You can keep AutoScroll = false and nest the canvas control inside another control, usually a Panel, which has AutoScroll = true; next make the canvas control big enough to always hold your drawing and you're done.
Or you can turn on AutoScroll for the canvas control and also set a large enough AutoScrollMinSize. If you then add the current scrolling position to the translation you are also done. Let's see this solution in action:
This is the code in the Paint event:
Size sz = panel3.ClientSize;
Point center = new Point(sz.Width / 2, sz.Height / 2);
Graphics g = e.Graphics;
// center point for testing only!
g.DrawEllipse(Pens.Orange, center.X - 3, center.Y - 3, 6, 6);
// you determine the value of the zooming!
float zoom = (trackBar1.Value+1) / 3f;
// move the scrolled center to the origon
g.TranslateTransform(center.X + panel3.AutoScrollPosition.X,
center.Y + panel3.AutoScrollPosition.Y);
// scale the graphics
g.ScaleTransform(zoom, zoom);
// draw some stuff..
using(Pen pen = new Pen(Color.Yellow, 0.1f))
for (int i = -100; i < 100; i+= 10)
g.DrawEllipse(Pens.Yellow, i-22,i-22,44,44);
A few notes:
I draw an orange circle in the center to show this point is invariant.
My coordinates go from the negative to the positive so you can see that this works nicely.
I draw with a tiny pen width; so the width of the drawing only changes once the resulting pen goes over 1 pixel. Anything draw will always be draw with 1 pxiel width, though.
I first translate and then scale so I don't have to calculate scaled poitions.
The only line in the TrackBar's Scroll event is to trigger the Paint event: panel3.Invalidate();
The only settings needed for the Panel are
panel3.AutoScroll = true;
panel3.AutoScrollMinSize = new Size(500, 500); // use the size you want to allow!
However to avoid flicker it is highly recommended to use a DoubleBuffered control, maybe a Panel subclass like this:
class DrawPanel : Panel
{
public DrawPanel() { DoubleBuffered = true; }
}
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Graphics.ScaleTransform() is how you can zoom. Try using something like this inside your paint event handler:
e.Graphics.ScaleTransform(2.0F, 2.0F);
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....
}
I am a new C# developer and I want to create a hotspot in an image that I put in my winform application. I followed the solution posted HERE, but I did not know where I should put the coordinates to make this method works:
protected override void OnMouseMove(MouseEventArgs mouseEvent)
{
string X = mouseEvent.X.ToString();
string Y = mouseEvent.Y.ToString();
}
Where should I put the coordinates? I have two coordinates (X,Y): 110, 45
Hotspot I feel should be a small rectangular area rather than just a coordinate. Suppose you want it to be a small square area of width 20 then you would write something like this:
EDIT:
Suppose you have a PictureBox on your form called PictureBox1 and you want that a small rectangle of say 20x20 size starting from Top-Left corner of the picturebox become a hotspot (i.e. when you take mouse over it you would see a HAND cursor) then on the MouSeMove event of the PictureBox write this:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.X > 0 && e.X < 20 && e.Y > 0 && e.Y < 20)
this.Cursor = Cursors.Hand;
else
this.Cursor = Cursors.Default;
}
Please remember, we are just showing the Hand Cursor to denote a hotspot we have not yet handled a Click for that matter, to make it really a web kind hotspot. If you want to do something on Click, try using the MouseUp event, in the MouseUp event the same IF clause as above would give you the condition that user has clicked on the hotspot region.