Avtem Ответов: 4

Почему функция createpolygonrgn() терпит неудачу?


Поэтому я пытаюсь создать цифровые часы, используя только WinAPI с C++ (см. скриншот)[^]
Функция drawDash() вызывается 7 раз, чтобы нарисовать одну цифру. я использую таймер, который перерисовывает цифры каждые 100 миллисекунд.
Все выглядит нормально, но по какой-то причине функция CreatePolygonRgn() начинает отказывать после определенного количества вызовов. Вот код, где все происходит:

void drawDash(HWND hwnd, const RECT& rect, UINT color) 
						 // this RECT is {left, top, width, height}!!!			
{
	// define some variables...
	HDC hdc = GetDC(hwnd);
	int trLen = min(rect.right, rect.bottom) *0.5;  // ◣ length of this triangle (there will be 4 of them)
	bool bHorizontal = rect.right > rect.bottom; // should we rotate the thing?
	
	// make clipping rgn that will make rgn look like a dash in digit clock
	POINT pointsVert[] = { trLen, 0,   trLen *2, trLen,    trLen *2, rect.bottom -trLen,
			trLen, rect.bottom,    0, rect.bottom -trLen,    0, trLen};
	POINT pointsHorz[] = { trLen, 0,   rect.right -trLen, 0,    rect.right, trLen,
			rect.right -trLen, rect.bottom,    trLen, rect.bottom,    0, trLen };
	
	POINT *ptArray = bHorizontal ? pointsHorz : pointsVert;

	// draw colored rgn!
	HRGN hrgnClip = CreatePolygonRgn(&ptArray[0], sizeof(pointsHorz) / sizeof(pointsHorz[0]), ALTERNATE);
	
	static int timesCalled = 0; // fails exactly at 9989-th time
	timesCalled++;

	OffsetRgn(hrgnClip, rect.left, rect.top);
	FillRgn(hdc, hrgnClip, CreateSolidBrush(color));

	DeleteObject(SelectObject(hdc, GetStockObject(WHITE_BRUSH)));
	ReleaseDC(hwnd, hdc);
	DeleteObject(hrgnClip);
}


Что я уже пробовал:

Так что CreatePolygonRgn() начинает давать сбой именно при 9989-м вызове. Он возвращает 0, поэтому после этого ничего не рисуется. я попробовал GetLastError(), но он возвращает 0 (документация msdn CreatePolygonRgn() не говорит, что я могу использовать GetLastError() для получения кода ошибки).
у меня было две одинаковые точки в массивах - документация говорит, что "каждая вершина может быть указана только один раз", поэтому я удалил их, но это ничего не изменило.

Gerry Schmitz

Вы указываете ptArray на разные массивы в разное время; но CreatePolygonRgn жестко закодирован в один.

Avtem

Но он меняет массивы, вы можете видеть это на скриншоте

4 Ответов

Рейтинг:
36

Richard MacCutchan

Единственное, что я вижу, что может быть проблемой, это следующее:

FillRgn(hdc, hrgnClip, CreateSolidBrush(color));

DeleteObject(SelectObject(hdc, GetStockObject(WHITE_BRUSH)));

Вы уверены в этом DeleteObject действительно ли вы удаляете твердую кисть?


Avtem

Ах, вот оно что! я знаю, что мне нужно освободить и удалить все с помощью GDI, но я пропустил эти строки!
Итак, я исправил это так:
"FillRgn(hdc, hrgnClip, hGlobalBrush);"
и конечно, вызов "DeleteObject()" больше не нужен, Но я не забыл удалить "hGlobalBrush" в WM_DESTROY.

Большое вам спасибо!

Richard MacCutchan

Рад помочь. Жаль, что GDI (и GDI+) так плохо говорят вам, что не так, когда он терпит неудачу.

Avtem

И я только что прочитал, что количество объектов GDI ограничено, так что, похоже, моей программе было разрешено создавать только 9989 кистей =)
Кстати, что он все время удалял? Если он удалял стандартные объекты, то должен был каким-то образом воссоздать их...

Richard MacCutchan

На самом деле вам не нужно удалять StockObjects, но это не повредит, если вы это сделаете.

Рейтинг:
2

Patrice T

ваш код очень простодушен, и это плохо.

Цитата:
я использую таймер, который перерисовывает цифры каждые 100 миллисекунд.

Простой тест может разделить на 10 рабочую нагрузку перерисовки:
if (LastTime != NewTime) {
    redrawClock();
    LastTime = NewTime;
}

Вместо того чтобы каждый раз перестраивать 2 одинаковых полигона
int trLen = min(rect.right, rect.bottom) *0.5;  // ◣ length of this triangle (there will be 4 of them)
bool bHorizontal = rect.right > rect.bottom; // should we rotate the thing?

// make clipping rgn that will make rgn look like a dash in digit clock
POINT pointsVert[] = { trLen, 0,   trLen *2, trLen,    trLen *2, rect.bottom -trLen,
        trLen, rect.bottom,    0, rect.bottom -trLen,    0, trLen};
POINT pointsHorz[] = { trLen, 0,   rect.right -trLen, 0,    rect.right, trLen,
        rect.right -trLen, rect.bottom,    trLen, rect.bottom,    0, trLen };

POINT *ptArray = bHorizontal ? pointsHorz : pointsVert;

wgeb вы знаете что только 1 будет полезен
int trLen = min(rect.right, rect.bottom) *0.5;  // ◣ length of this triangle (there will be 4 of them)
// make clipping rgn that will make rgn look like a dash in digit clock
if (rect.right > rect.bottom) { // should we rotate the thing?
    POINT pointsHorz[] = { trLen, 0,   rect.right -trLen, 0,    rect.right, trLen,  rect.right -trLen, rect.bottom,    trLen, rect.bottom,    0, trLen };
    POINT *ptArray = pointsHorz;
}
else {
    POINT pointsVert[] = { trLen, 0,   trLen *2, trLen,    trLen *2, rect.bottom -trLen, trLen, rect.bottom,    0, rect.bottom -trLen,    0, trLen};
    POINT *ptArray = pointsVert;
}

Это перестраивает только полезный полигон.
Вы также можете использовать 2 полигона один раз для всех и сохранить их в постоянных переменных между вызовами.


Avtem

Спасибо Вам за ваш ответ! Это большое улучшение кода! мне это очень нравится.

Рейтинг:
2

Rick York

Поскольку вы используете C++, я рекомендую написать класс, чтобы справиться с этим для вас. Вот одна из возможностей :

template< typename T >
class handleobj
{
public:
   handleobj( T h )
   {
       m_handle = h;
   }

   virtual ~handleobj()
   {
       if( m_prevObj )  // restore previous object, for fonts and such
       {
          ::SelectObject( m_hDC, m_prevObj );
       }
       if( m_handle )
       {
          ::DeleteObject( m_handle );
          m_handle = nullptr;
       }
   }

   operator T()    { return m_handle; }

public:
    HDC m_hDC     = { nullptr };
    T   m_handle  = { nullptr };
    T   m_prevObj = { nullptr };   // to support selection
};

// usage example :

   // the brush object will delete itself automatically
   handleobj< HBRUSH > solidBrush( CreateSolidBrush( color ) );
   FillRgn(hdc, hrgnClip, solidBrush );
Я не пробовал это в действии, но он будет компилироваться. Если вы хотите, вы можете добавить методы в класс, который будет выбирать объект GDI и сохранять DC и предыдущий объект. Деструктор класса готов справиться с этим. Он также готов, если вы никогда этого не сделаете. Другой вариант-создать производные классы для таких вещей, как шрифты, которые их создают, и выбрать их в DC. Есть много возможностей.

Вы можете подумать, что это много работы, чтобы уничтожить ручку кисти, но как только вы получите это и используете его, вам никогда не придется помнить, чтобы уничтожить кисть снова, потому что этот класс сделает это за вас автоматически. На самом деле, он может быть использован с любым объектом GDI, который использует DeleteObject, поэтому удаление объектов GDI будет автоматическим.


Avtem

Спасибо Вам за ответ! я буду рассматривать это решение на будущее.

Рейтинг:
0

KarstenK

Ваша проблема здесь:

FillRgn(hdc, hrgnClip, CreateSolidBrush(color));
в создании кисти, а не в ее удалении.

Знамение его использовать ВАР и не удалять его позже, или если цвет является постоянным, вы должны создать его только один раз.

Вашем случае ошибка характерна для неправильно освободить объекты GUI ;-)


Richard MacCutchan

Вы видели мой ответ и комментарии ОП?

Avtem

Да, немного странно видеть такой ответ, когда все было понято и зафиксировано.