为什么我的c#paint方法内存不足?

时间:2010-09-08 06:41:54

标签: c# gdi+ out-of-memory paint onpaint

我是c#的新手,并尝试通过编写一些简单的应用来学习语法和.NET库。我接受的最新的小型项目是极地时钟like the one found here

我之前注意到的一个问题是应用程序会不断地“闪烁”,这实际上已经从演示文稿中消失了,所以我在线阅读有关如何实现双缓冲区的消息,这可以消除这个问题,但可能会或者可能会与问题没有关系。这是我的onPaint方法;通过定时器控制每33ms(~30 FPS)调用一次。该应用程序的其余部分大多数只是拖动应用程序的处理程序(因为它是无框架并具有透明背景),双击退出等等。

    protected override void OnPaint(PaintEventArgs e) {
        DateTime now = DateTime.Now;

        float secondAngle = now.Second / 60F;
        secondAngle += (now.Millisecond / 1000F) * (1F / 60F);

        float minuteAngle = now.Minute / 60F;
        minuteAngle += secondAngle / 60F;

        float hourAngle = now.Hour / 24F;
        hourAngle += minuteAngle / 60F;

        float dayOfYearAngle = now.DayOfYear / (365F + (now.Year % 4 == 0 ? 1F : 0F));
        dayOfYearAngle += hourAngle / 24F;

        float dayOfWeekAngle = (float)(now.DayOfWeek + 1) / 7F;
        dayOfWeekAngle += hourAngle / 24F;

        float dayOfMonthAngle = (float)now.Day / (float)DateTime.DaysInMonth(now.Year, now.Month);
        dayOfMonthAngle += hourAngle / 24F;

        float monthAngle = now.Month / 12F;
        monthAngle += dayOfMonthAngle / (float)DateTime.DaysInMonth(now.Year, now.Month);

        float currentPos = brushWidth / 2F;

        float[] angles = {
            secondAngle, minuteAngle,
            hourAngle, dayOfYearAngle,
            dayOfWeekAngle, dayOfMonthAngle,
            monthAngle
        };

        SolidBrush DateInfo = new SolidBrush(Color.Black);
        SolidBrush background = new SolidBrush(Color.Gray);
        Pen lineColor = new Pen(Color.Blue, brushWidth);
        Font DateFont = new Font("Arial", 12);

        if (_backBuffer == null) {
            _backBuffer = new Bitmap(this.Width, this.Height);
        }

        Graphics g = Graphics.FromImage(_backBuffer);
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

        try {                
            g.Clear(Color.White);
            if (_mouseIsOver) {
                g.FillEllipse(background, new Rectangle(0, 0, this.Width, this.Height));
            }
            foreach (float angle in angles) {
                g.DrawArc(
                    lineColor,
                    currentPos, currentPos,
                    this.Height - currentPos * 2, this.Width - currentPos * 2,
                    startAngle, angle * 360F
                );

                currentPos += brushWidth + spaceStep;
            }

            // Text - Seconds

            g.DrawString(String.Format("{0:D2} s", now.Second), DateFont, DateInfo, new PointF(115F, 0F));
            g.DrawString(String.Format("{0:D2} m", now.Minute), DateFont, DateInfo, new PointF(115F, 20F));
            g.DrawString(String.Format("{0:D2} h", now.Hour), DateFont, DateInfo, new PointF(115F, 40F));
            g.DrawString(String.Format("{0:D3}", now.DayOfYear), DateFont, DateInfo, new PointF(115F, 60F));
            g.DrawString(now.ToString("ddd"), DateFont, DateInfo, new PointF(115F, 80F));
            g.DrawString(String.Format("{0:D2} d", now.Day), DateFont, DateInfo, new PointF(115F, 100F));
            g.DrawString(now.ToString("MMM"), DateFont, DateInfo, new PointF(115F, 120F));
            g.DrawString(now.ToString("yyyy"), DateFont, DateInfo, new PointF(115F, 140F));

            e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0);
        }
        finally {
            g.Dispose();
            DateInfo.Dispose();
            background.Dispose();
            DateFont.Dispose();
            lineColor.Dispose();
        }
        //base.OnPaint(e);
    }

    protected override void OnPaintBackground(PaintEventArgs e) {
        //base.OnPaintBackground(e);
    }

    protected override void OnResize(EventArgs e) {
        if (_backBuffer != null) {
            _backBuffer.Dispose();
            _backBuffer = null;
        }
        base.OnResize(e);
    }

我想通过在方法结束时处理所有东西我会安全,但它似乎没有帮助。此外,运行时和OutOfMemoryException之间的间隔不是常量;一旦发生只有几秒钟,但通常需要一两分钟。这是一些类范围的变量声明。

    private Bitmap _backBuffer;

    private float startAngle = -91F;
    private float brushWidth = 14;
    private float spaceStep = 6;

截图(编辑:截图链接到包含某些代码的视图):

Screenshot http://www.ggot.org/inexplicable/pc2.jpg

编辑:Stacktrace!

System.OutOfMemoryException: Out of memory.
   at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
   at System.Drawing.Graphics.DrawArc(Pen pen, Single x, Single y, Single width, Single height, Single startAngle, Single sweepAngle)
   at PolarClock.clockActual.OnPaint(PaintEventArgs e) in C:\Redacted\PolarClock\clockActual.cs:line 111
   at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs)
   at System.Windows.Forms.Control.WmPaint(Message& m)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

似乎是它上次崩溃的同一行,循环内的主drawArc

6 个答案:

答案 0 :(得分:8)

确保您也处理Pen和Brush对象,并使用块来确保即使有例外也要处置对象。

作为旁注:避免每次绘制时重新创建和处理_backBuffer。要么捕获resize事件并在那里处理_backBuffer,要么只检查_backBuffer是否在每个Paint事件上都有正确的尺寸,并且如果尺寸不匹配则处理并重新创建。

答案 1 :(得分:6)

仅限其他人,通过Google查找此页面:

如果您尝试打印小角度,如果使用System.Drawing.DrawArc,可能是System.OutOfMemoryException的可能原因。

对于角度< 1这个错误在我的代码中出现过几次。

另见:

http://connect.microsoft.com/VisualStudio/feedback/details/121532/drawarc-out-of-memory-exception-on-small-arcs

答案 2 :(得分:3)

我没有发现你的代码有任何可怕的错误。你能提供OutOfMemoryException正在发生的确切行吗?

你知道这真的花了我几个月才明白:OutOfMemoryException并不意味着记忆力不足。 ;-)当GDI +发生错误时(在GDI内部显示错误的编码风格,恕我直言),就像你试图加载无效图像,或者像素图像格式无效等等。

答案 3 :(得分:2)

不是为什么的答案,而是一个可能的解决方案:

您不应每次都创建新的位图。 每次绘制新框架时都要清除它。

但是,当您的尺寸发生变化时,您应该创建一个新的位图。

答案 4 :(得分:1)

为什么每次想要使用OnPaint绘制的东西时都需要新的位图?!你需要完全1.尝试这样的事情:

private Bitmap _backBuffer = new Bitmap(this.Width, this.Height);

protected override void OnPaint(PaintEventArgs e) { 

    Graphics g = Graphics.FromImage(_backBuffer);

    //Clear back buffer with white color...
    g.Clear(Color.White);

    //Draw all new stuff...
}

答案 5 :(得分:0)

不是你的问题的答案,也许有一个很好的理由为什么你这样做(我可能会学到一些东西),但为什么先创建一个位图,在位图上绘图然后在表格上绘制位图? 直接在表格上绘制会不会更有效率? 有点像这样:

protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    //_backBuffer = new Bitmap(this.Width, this.Height);
    Graphics g = Graphics.FromImage(_backBuffer);

    //Rest of your code
    //e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0);

    //g.Dispose();
    //e.Dispose();
    //base.OnPaint(e);

    //_backBuffer.Dispose();
    //_backBuffer = null;
}

同样根据MSDN

  

在派生类中重写OnPaint时,请务必调用基类的OnPaint方法,以便已注册的委托接收事件。