简体   繁体   English

在带有背景图像的表单上有效地移动WinForms按钮

[英]Efficiently moving a WinForms button on a form with a background image

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. 如果不是这样,那么我将非常感谢有关如何继续在屏幕上创建和移动此矩形的指示,因为我很难找到有关如何在优质的Google上实现此信息的良好信息资源。

Update, 26/11/13 更新,13/11/26

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. 我正在尝试Luaan关于覆盖表单的OnPaint的建议,但是不确定如何在此代码中添加按钮的呈现方式。 Any ideas? 有任何想法吗?

protected override void OnPaint(PaintEventArgs e)
{
    if (_IsBeingDragged)
    {
        e.Graphics.DrawImage(this._FormPaintBuffer, new Point(0, 0));
    }
    else
    {
        base.OnPaint(e);
    }
}

This is a standard case of Winforms being too programmer-friendly. 这是Winforms对程序员过于友好的标准情况。 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. 它允许您设置BackgroundImage,并且可以处理您扔给它的任何东西 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: 您需要做两件事以使其绘制快50倍:

  • 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. 自行调整位图的大小以适合表单的ClientSize,因此不必在每次需要绘制图像时重复进行。 The default Graphics.InterpolationMode property value produces very nice looking images but it is not cheap. 默认的Graphics.InterpolationMode属性值生成非常漂亮的图像,但它并不便宜。 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. 那就是过去10多年使用的所有视频适配器上的PixelFormat.Format32bppPArgb。 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. 就像Winforms设计器中使用的那样。 You use layers , an extra borderless transparent window on top of the original. 您可以使用layers ,它是原始图层上方额外的无边界透明窗口。 You get one with the TransparencyKey property. 您可以使用TransparencyKey属性获得一个。

1) If you want to keep everything the way it is, you might want to implement some sort of double buffering for the background. 1)如果您希望一切都保持原样,则可能需要为后台实现某种双重缓冲。 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). 重新绘制表单时,实际上必须做一些逻辑工作(例如,JPG / PNG的绘制速度比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. 如果将“ Paint”画布存储在位图中,则可以绘制整个表单,除了“位图”中的那个按钮外,在拖动按钮时仅将其绘制为背景-这样,您就可以避开表单的所有绘制逻辑及其控制,应该更快。

2) You can only draw an outline of the button being moved. 2)您只能绘制要移动的按钮的轮廓。 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. 诀窍是在XOR模式下绘制线-第一次绘制矩形会添加轮廓,而下次在相同位置绘制轮廓时,轮廓会消失。 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. 在C#中对XOR行的支持并不是最好的,但是您可以使用ControlPaint.DrawReversibleLine方法。

To drag a control you need to use the .DrawToBitmap function of the control and set the appropriate styles of the form. 若要拖动控件,您需要使用控件的.DrawToBitmap函数并设置表单的适当样式。 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. 对于此示例,创建一个标准的C#Windows窗体应用程序(Form1),并将一个按钮(button1)放到窗体上,然后将此代码放置在Form类中的Form1构造函数之后。 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();
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM