代码之家  ›  专栏  ›  技术社区  ›  Dereleased

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

  •  4
  • Dereleased  · 技术社区  · 14 年前

    我对c#还很陌生,我试图通过编写一些简单的应用程序来学习,以熟悉语法和.NET库。我最近接的一个小项目是一个极钟 like the one found here .

    我早期注意到的一个问题是,应用程序会不断“闪烁”,这确实会影响演示文稿的效果,因此我在网上阅读了有关如何实现双缓冲区的内容,它消除了这个问题,但可能与问题有关,也可能与问题无关。这是我的 onPaint 方法;计时器控件每33毫秒(~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
    (来源: ggot.org )

    编辑: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 回复  |  直到 5 年前
        1
  •  8
  •   buhlara    13 年前

    对于其他人来说,通过谷歌找到这个页面:

    可能的原因System.OutOfMemoryException异常如果你使用系统图.绘图弧如果你试着打印小角度的话也可能是个错误。

    对于angles<1,此错误在我的代码中出现过多次。

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

        2
  •  8
  •   Andreas Paulsson    14 年前

    确保同时处理笔和笔刷对象,并使用using blocks确保即使存在异常也处理对象。

    另请注意:每次绘图时,请避免重新创建和处理backBuffer。在那里捕获调整大小事件并释放\u backBuffer,或者只检查\u backBuffer在每个绘制事件上的尺寸是否正确,如果尺寸不匹配,则释放并重新创建。

        3
  •  3
  •   Michael    14 年前

    OutOfMemoryException 发生了什么?

    我花了几个月才明白: OutOfMemoryException异常 并不意味着没有记忆。;-)它发生在GDI+中,当某些事情发生错误时(显示GDI、IMHO内部的错误编码样式),比如您试图加载无效的图像,或者具有无效像素格式的图像,等等。

        4
  •  2
  •   Stormenet    14 年前

    不是真正的答案 为什么? ,但可能的解决方案是:

    每次你画新画框的时候都要把它清除。

    但是,当大小更改时,应该创建一个新位图。

        5
  •  1
  •   Cipi    14 年前

    为什么每次你想用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...
    }
    
        6
  •  0
  •   Grif    14 年前

    不是对你的问题的回答,也许你这样做有一个很好的理由(我可能会学到一些东西),但是为什么要先创建一个位图,在位图上绘制,然后在窗体上绘制位图呢? 直接在表格上画不是更有效吗? 沿着这条线:

    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方法,以便已注册的委托接收事件。