双缓冲C#

6
我正在尝试实现以下方法:
void Ball::DrawOn(Graphics g); 该方法应绘制球的所有先前位置(存储在队列中),最后是当前位置。我不知道这是否重要,但我使用g.DrawEllipse(...)打印先前的位置,并使用g.FillEllipse(...)打印当前位置。
问题是,由于需要进行大量绘制,因此显示器开始出现闪烁。我已经搜索了双缓冲的方法,但我只能找到以下两种:
1. System.Windows.Forms.Control.DoubleBuffered = true; 2. SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true); 当我尝试使用第一种方法时,我会收到一个错误,解释说在该方法中,属性DoubleBuffered由于其保护级别而无法访问。而我无法弄清如何使用SetStyle方法。
当我只有作为输入参数的Graphics对象的所有访问权限时,是否有可能使用双缓冲?
提前感谢,
编辑: 我创建了以下类
namespace doubleBuffer
{
    class BufferedBall : System.Windows.Forms.Form
    {
        private Ball ball;
        public BufferedBall(Ball ball)
        {
            this.ball = ball;
        }

        public void DrawOn(Graphics g)
        {
            this.DoubleBuffered = true;
            int num = 0;
            Rectangle drawArea1 = new Rectangle(5, 35, 30, 100);
            LinearGradientBrush linearBrush1 =
            new LinearGradientBrush(drawArea1, Color.Green, Color.Orange, LinearGradientMode.Horizontal);
            Rectangle drawArea2 = new Rectangle(5, 35, 30, 100);
            LinearGradientBrush linearBrush2 =
               new LinearGradientBrush(drawArea2, Color.Black, Color.Red, LinearGradientMode.Vertical);
            foreach (Point point in ball.previousLocations)
            {
                Pen myPen1;
                if (num % 3 == 0)
                    myPen1 = new Pen(Color.Yellow, 1F);
                else if (num % 3 == 1)
                    myPen1 = new Pen(Color.Green, 2F);
                else
                    myPen1 = new Pen(Color.Red, 3F);
                num++;
                myPen1.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                myPen1.StartCap = System.Drawing.Drawing2D.LineCap.RoundAnchor;
                myPen1.EndCap = System.Drawing.Drawing2D.LineCap.AnchorMask;
                g.DrawEllipse(myPen1, (float)(point.X - ball.radius), (float)(point.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
            }
            if ((ball.Host.ElapsedTime * ball.Host.FPS * 10) % 2 == 0)
            {
                g.FillEllipse(linearBrush1, (float)(ball.Location.X - ball.radius), (float)(ball.Location.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
            }
            else
            {
                g.FillEllipse(linearBrush2, (float)(ball.Location.X - ball.radius), (float)(ball.Location.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
            }
        }
    }
}

而球drawOn的样子如下:
new BufferedBall(this).DrawOn(g);

你的意思是这样吗?因为它仍然在闪烁?

好的,SetStyle() 的方法是正确的,你正在正确地使用它。据我所知,不应该使用 DoubleBuffered 属性(优先使用 SetStyle),但你可以始终子类化 Control/Form/任何其他控件并在那里使用它(它是受保护的)。 - sunside
我看到了一些使用双缓冲的工作代码,展示了一个动画球,链接在这里:https://stackoverflow.com/a/4923613/1016343 - Matt
5个回答

4

2
是的,但您正在从 Form 派生。就像当您创建基本的 winforms 应用程序时,它会创建来自 Form 的 Form1。您可以从那里访问它。如果不行 - 您始终可以创建一个包装器,公开此属性。 - Andrey

3
this.DoubleBuffered = true;

这没问题,但你需要把这个语句移到构造函数中。双缓冲需要设置,Windows Forms必须创建缓冲区,在绘制事件运行之前必须完成。构造函数是最理想的位置。


这是您唯一需要做的:将表单填写完整,其余操作由表单完成。 - John Lord

2

但是那需要访问我正在绘制的from对象,对吗? 我所拥有的所有访问权限都是作为参数提供给函数的Graphics对象。在这种情况下反射是否可行? - LmSNe
可以通过反射仅用几行代码来实现:https://dev59.com/yXVD5IYBdhLWcg3wI36L - Paul Sasik

1

在重新绘制时,您不需要每次将DoubleBuffered设置为true。当绘制完成后,它并未被禁用。只需从DrawOn中删除该行,并在构造函数或表单设计器中设置它并检查结果即可。将值设置为false会产生明显的闪烁,而将其设置为true则不会。

我在一个定时器强制每毫秒重新绘制的窗体中尝试了您的代码,并注意到当DoubleBuffered为true时没有闪烁:

private int yDir = 1,xDir=1;
int step = 1;

private void timer1_Tick(object sender, EventArgs e)
{
    if (ball.Location.Y >= this.Height-50)
        yDir =   -1 ;
    if (ball.Location.X >= this.Width-50) 
        xDir= -1 ;

    ball.MoveBy(xDir*step,yDir*step);
    ball.Host.ElapsedTime++;
    this.Invalidate();
 }

private void DoubleBufferedBall_Paint(object sender, PaintEventArgs e)
{                      
        DrawOn(e.Graphics);
}

0

我为你提供另一个选项,就是将所有的绘图都绘制到位图上,然后在OnPaint方法中,你只需将该位图绘制到窗体上即可。

虽然这种方式比较繁琐,但它可以让你完全掌控。我曾在我的一些小项目中使用过它,并取得了一定的成功。

你也可以考虑使用XNA——虽然它可能对你的项目来说有点大材小用,但你可以将XNA作为渲染引擎在WinForms中使用。


这是你的意思吗?public void DrawOn(Graphics g){ Bitmap bitmap = new Bitmap(800, 600, g); Graphics g1 = Graphics.FromImage(bitmap); // 绘制到g1... g.DrawImage(bitmap, new Point(0, 0)); }因为这仍然会闪烁。 - LmSNe
是的,确实是这样,但我不确定为什么它在你那里还是会闪烁。 - Nate
因为它在绘制之前会擦除,所以需要开启双缓冲。将以下代码放入构造函数中:this.DoubleBuffered = true; 但不要在其他地方使用。 - John Lord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接