2010-09-08 3 views
4

Я новичок в C# и пытаюсь научиться писать простые приложения, чтобы ознакомиться с синтаксисом и библиотекой .NET. Самый последний минипроект, который я взял, - полярные часы like the one found here.Почему у моего метода рисования C# заканчивается память?

Одна из проблем, которые я заметил на ранней стадии, заключалась в том, что приложение будет постоянно «мерцать», что действительно отвлекает от презентации, поэтому я читал онлайн о том, как реализовать двойной буфер, который устранил эту проблему, но может или возможно, не имеет никакого отношения к проблеме. Вот мой метод 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 http://www.ggot.org/inexplicable/pc2.jpg

EDIT: 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 внутри цикла.

+1

Пожалуйста, кадрировать снимки экрана, чтобы они не являются больше, чем необходимо. – Gabe

+0

@Gabe: Готово; Я использовал более крупный, чтобы показать дополнительный код, поэтому я сделал новый скриншот ссылкой на более старый. – Dereleased

+0

Согласно комментариям в http://msdn.microsoft.com/en-us/library/system.drawing.graphics.drawarc(VS.90).aspx есть ошибка в drawArc, когда arc i меньше 2 пикселей или 3.5 развертки градусов. –

ответ

6

Просто для кого-либо еще, найти эту страницу с помощью Google:

Возможной причиной System.OutOfMemoryException, если вы используете System.Drawing.DrawArc также может быть ошибка, если вы пытаетесь печатать небольшие углы.

Для углов < 1 эта ошибка произошла несколько раз в моем коде.

Смотрите также:

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

+0

Спасибо, он дал мне подсказку, как исправить исключение из памяти с помощью LinearGradientBrush, когда у него было меньше расстояния в 2 пикселя. –

3

Я не нашел ничего ужасного в вашем коде. Можете ли вы указать точную строку, на которой происходит OutOfMemoryException?

Просто, чтобы вы знали, мне действительно потребовалось несколько месяцев, чтобы понять: OutOfMemoryException не означает недостаток памяти. ;-) Это происходит в GDI +, когда что-то просто пошло не так (показывает плохой стиль кодирования внутри GDI, IMHO), например, вы пытались загрузить недопустимое изображение или изображение с недопустимым пиксельным форматом и т. Д.

+0

Я запустил его, пока он не выйдет из строя и не отправит трассировку стека. – Dereleased

+0

Кажется странным. Это либо имеет какое-то отношение к тому, что вы создаете новый объект каждые 33 мс, либо некоторые вычисления дуги приводят к недействительным результатам (например, 0). Попробуйте отладить проект, ожидая краха снова и проверьте значения, которые вы передаете DrawArc! Кроме того, я не могу дать вам никаких советов :-( – Michael

+0

По какой-то причине, когда он сбой во время работы в среде IDE, я не получаю возможность просматривать детали исключения, я просто получаю недопустимый растровый рендеринг и вот что, ничего не останавливается, и мои другие методы (например, перетаскивание и двойной щелчок) работают нормально. – Dereleased

2

Not действительно ответ почему, но возможное решение:

Вы не должны создавать новое растровое изображение каждый раз. Просто очищайте его каждый раз, когда вы рисуете новый кадр.

При изменении размера вы должны создать новое растровое изображение.

+0

Размер должен, на данный момент, никогда не меняться, но спасибо за подсказку! – Dereleased

+0

Это не проблема, но по-прежнему хороший совет. –

+0

О, и если вы измените его на работу таким образом, следите за тем, чтобы свести к минимуму ваше приложение, ваш элемент управления может получить размер 0x0, а растровое изображение не нравится создаваться с этим размером;) – Stormenet

8

Убедитесь, что вы также удаляете объекты Pen и Brush и используете блоки, чтобы убедиться, что вы удаляете объекты, даже если есть исключения.

В качестве примечания стороны: избегайте воссоздания и удаления _backBuffer каждый раз, когда вы рисуете. Либо поймайте событие изменения размера, либо удалите _backBuffer там, либо просто проверьте, имеет ли _backBuffer правильные размеры для каждого события Paint и удаляет и воссоздает, если размеры не совпадают.

+1

+1 - Забудьте «Шрифт». –

+0

Размер должен, на данный момент, никогда не меняться, но спасибо за подсказку! Я вернусь и начну убивать ручки, кисти, шрифты и т. Д. – Dereleased

+0

Как говорит Фредрик Мёрк, не забудьте также Шрифт. Windows (и .NET) очень разборчивы, когда дело доходит до объектов GDI. Всегда удаляйте все объекты GDI, которые можно сложить. –

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... 
} 
+0

Я изначально сделал это, чтобы убедиться, что я не использую ничего лишнего, сохраняя его, хотя подавляющее консенсус, похоже, является плохим идеей; с другой стороны, его можно воссоздать, если холст изменит размер, что является возможностью вниз по дороге, когда у меня была какая-то настройка; в этом случае его нужно будет переделать в любое время, когда оно будет изменено. – Dereleased

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 базового класса, чтобы зарегистрированные делегаты получили событие.

+0

Чтобы ответить на ваш вопрос, http://www.bobpowell.net/doublebuffer.htm – Dereleased

+0

Большое спасибо! Интересный материал. – Grif

Смежные вопросы