C# 在具有背景图像的窗体上高效地移动WinForms按钮

C# 在具有背景图像的窗体上高效地移动WinForms按钮,c#,winforms,C#,Winforms,我正在为一台工业触摸屏计算机创建一个应用程序,它没有硬件可炫耀。这个触摸屏计算机的操作员应该能够解锁和拖动带有背景图像的表单上的按钮 然而,正如许多人可能已经知道的,在具有背景图像的父控件上移动控件并不漂亮。拖动速度很慢,当您在屏幕上移动鼠标指针时,操作员将看到一个按钮在鼠标指针跟随下穿过环圈后跳跃,而不是平滑拖动 这是移动按钮的当前代码: private void btnButton_MouseMove(object sender, MouseEventArgs e) { // Do

我正在为一台工业触摸屏计算机创建一个应用程序,它没有硬件可炫耀。这个触摸屏计算机的操作员应该能够解锁和拖动带有背景图像的表单上的按钮

然而,正如许多人可能已经知道的,在具有背景图像的父控件上移动控件并不漂亮。拖动速度很慢,当您在屏幕上移动鼠标指针时,操作员将看到一个按钮在鼠标指针跟随下穿过环圈后跳跃,而不是平滑拖动

这是移动按钮的当前代码:

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;
    }
}
我不想解决这个问题,我宁愿消除它以节省一些时间。为了消除这种情况,我希望实现的是一种更节省资源的方法来移动盒子。我在想,移动一个带点的矩形而不是按钮,然后把它放在我想要的地方,这个按钮肯定比在屏幕上拖动按钮更有效,因为谁知道会有多少重绘操作

有谁有更好的建议吗?如果没有,我将非常感谢关于如何继续在屏幕上创建和移动这个矩形的指针,因为我很难找到关于如何在好的ol'Google上实现这一点的好信息源

更新,2013年11月26日

我正在尝试Luaan关于覆盖表单OnPaint的建议,但是我不确定如何在代码中添加按钮的呈现。有什么想法吗

protected override void OnPaint(PaintEventArgs e)
{
    if (_IsBeingDragged)
    {
        e.Graphics.DrawImage(this._FormPaintBuffer, new Point(0, 0));
    }
    else
    {
        base.OnPaint(e);
    }
}
1) 如果您想保持一切原样,您可能需要为后台实现某种双缓冲。当表单被重绘时,它总是需要重绘几乎整个内容,同时实际执行一些逻辑(例如,JPG/PNG的绘制速度比BMP慢)。如果将绘制画布存储在位图中,则可以绘制除位图中的一个按钮之外的整个表单,并在拖动按钮时仅绘制该按钮作为背景-这样可以绕过表单及其控件的所有绘制逻辑,这应该要快得多


2) 您只能绘制正在移动的按钮的轮廓。诀窍是你在XOR模式下画线——第一次画矩形时,它会添加轮廓,下一次你在同一位置画它时,它会消失。这样,您就不必一直重新绘制形状,只需绘制构成矩形的几个像素。C#中对XOR行的支持不是最好的,但您可以使用ControlPaint.DrawReversibleLine方法。

这是Winforms对程序员过于友好的标准情况。任何一个游戏程序员都会注意到一些细节,但这些细节太容易被忽略。它允许你设置一个背景图像,它会接受你扔给它的任何东西。这通常效果很好,除非需要快速渲染图像。就像你在这种情况下所做的那样

您需要做两件事才能使绘图速度快50倍:

  • 自行调整位图的大小以适应窗体的ClientSize,这样就不必在每次需要绘制图像时重复执行。默认的Graphics.InterpolationMode属性值可以生成非常好看的图像,但并不便宜。请注意,这可能会对内存造成重大影响,这是它不能自动完成的原因

  • 注意图像的像素格式。只有一种画图速度很快,它可以直接点显到视频适配器,而无需将每个像素的值转换为帧缓冲区格式。这就是过去10多年中使用的所有视频适配器上的PixelFormat.Format32bppPArgb。差别很大,它比其他所有的都快十倍。您永远不会从使用绘图程序创建的位图中获得该格式,因此需要显式转换它。再次注意记忆的打击

只需少量代码即可实现这两种功能:

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;
}

在一些不太可能的情况下,你仍然有绘画痕迹或口吃,你需要考虑一个非常不同的方法。就像Winforms设计器中使用的一样。您可以使用图层,这是一个额外的无边界透明窗口,位于原始图层的顶部。您将获得一个具有TransparencyKey属性的控件。

要拖动控件,您需要使用该控件的.DrawToBitmap函数并设置适当的表单样式。我在示例代码中没有这样做,但是您需要一个“设计模式”和一个“正常模式”。要拖动控件,只需单击它,拖动它,然后再次单击。您可以想象一下,使保持控件的位图透明,以便容纳圆角

对于本例,制作一个标准的C#Windows窗体应用程序(Form1),并在窗体上放置一个按钮(button1),然后将此代码放在form类中Form1构造函数之后。确保在代码中更改背景位图的位置

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();
  }
}

它还可能有助于覆盖PaintBackground方法-如果将背景图像保留在位图中,则可以只绘制实际重画的矩形,这应该比获取PNG、将其转换为BMP快得多,从中画出一个小矩形,然后再次扔掉BMP。我不知道JPG/PNG和BMP的问题。我想我想先试试。你能详细说明一下这个方法吗?你说“除了位图中的一个按钮之外,绘制整个表单”是什么意思?关于PaintBackground方法,您指的是哪个对象?只需将背景图像更改为BMP就可以显著提高性能,至少它以前(在Pocket PC上)对我有所帮助。另一种方法要求您重写表单的OnPaint和/或OnPaintBackground方法。当您开始拖动时,您会隐藏正在拖动的按钮,将整个表单存储在位图(DrawToB)中