Reputation: 2159
I am creating an application for an industrial touch screen computer with no hardware to brag about. The operator of this touch screen computer is among other things supposed to be able to unlock and drag buttons around on a form with a background image.
However, as many of you already might know, moving controls on a parent control with a background image isn't pretty. The dragging is slow and instead of experiencing a smooth dragging, the operator will see a button jumping after through hoops in the wake of the mouse pointer as you move the pointer across the screen.
This is the current code for moving the button:
private void btnButton_MouseMove(object sender, MouseEventArgs e)
{
// Do not proceed unless it is allowed to move buttons
if (!this._AllowButtonsToBeMoved)
return;
if (this._IsBeingDragged)
{
var btn = (sender as Button);
var newPoint = btn.PointToScreen(new Point(e.X, e.Y));
newPoint.Offset(this._ButtonOffset);
btn.Location = newPoint;
}
}
I am not looking to solve this exact problem, I'd rather eliminate it to save some time. What I wish to implement in order to eliminate this, is a more resource efficient way to move the box around. I'm thinking that moving a dotted rectangle instead of the button, then dropping it where I want the button must be way more efficient than dragging the button around the screen, causing who knows how many repaint operations.
Does anyone have any better suggestions? If not, then I would very much appreciate pointers on how to proceed with creating and moving this rectangle around the screen, as I am having some difficulty finding good sources of information regarding how to approach this on good ol' Google.
Update, 26/11/13
I'm attempting Luaan's suggestion regarding overriding the form's OnPaint, however I am unsure as to how exactly I can add the rendering of the button in this code. Any ideas?
protected override void OnPaint(PaintEventArgs e)
{
if (_IsBeingDragged)
{
e.Graphics.DrawImage(this._FormPaintBuffer, new Point(0, 0));
}
else
{
base.OnPaint(e);
}
}
Upvotes: 0
Views: 1455
Reputation: 145
To drag a control you need to use the .DrawToBitmap function of the control and set the appropriate styles of the form. I haven't done it in the sample code but you'll need a "design mode" and a "normal mode". To drag the control you simply click it, drag it and click again. You can get fancy and make the Bitmap holding the control transparent so as to accommodate rounded edges.
For this example, make a standard C# Windows Forms application (Form1) and drop a button (button1) onto the form then place this code after the Form1 constructor in your Form class. Make sure to change the location of the background bitmap in code.
private Bitmap b = null;
private bool IsDragging = false;
private Point down = Point.Empty;
private Point offset = Point.Empty;
private void button1_MouseUp(object sender, MouseEventArgs e)
{
IsDragging = true;
button1.Visible = false;
down = button1.PointToScreen(e.Location);
offset = e.Location;
this.Invalidate();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (IsDragging)
{
IsDragging = false;
down = new Point(down.X - offset.X, down.Y - offset.Y);
button1.Location = down;
button1.Visible = true;
down = Point.Empty;
this.Invalidate();
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (IsDragging)
{
down.X += (e.X - down.X);
down.Y += (e.Y - down.Y);
this.Invalidate();
}
}
private void Form1_Load(object sender, EventArgs e)
{
try
{
b = new Bitmap(button1.Width, button1.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
button1.DrawToBitmap(b, new Rectangle(0, 0, button1.Width, button1.Height));
button1.MouseUp += new MouseEventHandler(button1_MouseUp);
this.MouseUp += new MouseEventHandler(Form1_MouseUp);
this.MouseMove += new MouseEventHandler(Form1_MouseMove);
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
this.UpdateStyles();
this.BackgroundImage = Image.FromFile(@"C:\Users\Public\Pictures\Sample Pictures\desert.jpg");
this.BackgroundImageLayout = ImageLayout.Stretch;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (IsDragging)
{
e.Graphics.DrawImage(b, new Point(down.X - offset.X, down.Y - offset.Y));
}
base.OnPaint(e);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (b != null)
{
b.Dispose();
}
}
Upvotes: 0
Reputation: 941317
This is a standard case of Winforms being too programmer-friendly. Details that any game programmer pays careful attention to but are way too easy to miss. It allows you to set a BackgroundImage and it will take anything you throw at it. That usually works just fine, except when you need the image to render quickly. Like you do in this case.
Two things you need to do to make it draw ~fifty times quicker:
Resize the bitmap yourself to fit the form's ClientSize so it doesn't have to be done repeatedly every time the image needs to be painted. The default Graphics.InterpolationMode property value produces very nice looking images but it is not cheap. Do note that this can take a significant memory hit, the reason it isn't done automatically.
Pay attention to the pixel format of the image. There's only one that draws fast, the one that can be blitted directly to the video adapter without having the value of every single pixel converted to the frame buffer format. That is PixelFormat.Format32bppPArgb on all video adapters in use in the past 10+ years. Big difference, it is ten times faster than all the other ones. You never get that format out of a bitmap that you created with a painting program so explicitly converting it is required. Again heed the memory hit.
It takes but a little scrap of code to get both:
private static Bitmap Resample(Image img, Size size) {
var bmp = new Bitmap(size.Width, size.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (var gr = Graphics.FromImage(bmp)) {
gr.DrawImage(img, new Rectangle(Point.Empty, size));
}
return bmp;
}
In the somewhat unlikely case you still have painting trails or stuttering you need to consider a very different approach. Like the one that's used in the Winforms designer. You use layers, an extra borderless transparent window on top of the original. You get one with the TransparencyKey property.
Upvotes: 6
Reputation: 63722
1) If you want to keep everything the way it is, you might want to implement some sort of double buffering for the background. When the form is redrawn, it always has to redraw pretty much the whole thing, while actually doing some logic (ie. JPG/PNG is slower to draw than a BMP). If you store the Paint canvas in a Bitmap, you can draw the whole form except for that one button in a Bitmap and draw only that as background while you're dragging the button - this way you get around all the draw logic of the form and its controls, which should be vastly faster.
2) You can only draw an outline of the button being moved. The trick is that you draw lines in XOR mode - the first time you draw the rectangle it adds the outline, and the next time you draw it in the same location, it disappears. This way you don't have to redraw the form all the time, just the few pixels that form the rectangle. The support for XOR lines in C# isn't the best, but you can use the ControlPaint.DrawReversibleLine method.
Upvotes: 1