Перейти к содержанию

GDI - Слова разного цвета в одном тексте


Рекомендуемые сообщения

2 часа назад, youneuoy сказал:

Это не мой код🙂Я его взял у какого-то пользователя с форума когда программированию учился только и по этому шаблону наделал патчей в память.

Понятно 🙂.

1 час назад, Xipho сказал:

Это на твоё усмотрение ) 

Мы же вместе собрались изобретать велосипед. Пусть простой, но зато свой.

Вчера я даже не смог скачать QT Creator. Хотел посмотреть как там устроены сигналы, слоты.

VPN не стал организовывать, из роликов и документации я примерно понял что там и к чему. Мне кажется WGW в этом плане попроще будет.

 

Я заметил что в QT в параметре метода события (например кнопка нажата), передается определенный для этого типа события объект. И чтобы получить какие-либо его характеристики, нужно вызвать его методы.

//QPushButton Class
virtual void 	keyPressEvent(QKeyEvent *e) override

И некоторые методы QKeyEvent

Спойлер
int 	count() const
bool 	isAutoRepeat() const
int 	key() const
QKeyCombination 	keyCombination() const
bool 	matches(QKeySequence::StandardKey key) const
Qt::KeyboardModifiers 	modifiers() const
quint32 	nativeModifiers() const
quint32 	nativeScanCode() const
quint32 	nativeVirtualKey() const
QString 	text() const

 

 

А в WGW немного по другому. Вместо QKeyEvent объекта передается тоже определенный для этого события объект , но он не имеет методов. Он имеет public поля с уже подсчитанными/полученными значениями (из wParam, lparam). Такой подход в сравнении с QT удобнее, как мне кажется. Но есть один минус, - даже если пользователю не нужны эти данные в методе события, они все равно подсчитываются/получаются. То есть холостая работа. Хотя опять же, она такая незначительная, что я подумал так и оставить, а не писать методы для их получения.

Либо разработчики гнались за максимальной производительностью, что даже такие мелочи оформили в отдельные методы. Либо это у них такой принцип. Не знаю.

Ссылка на комментарий
Поделиться на другие сайты

1 час назад, Antonshka сказал:

Он имеет public поля с уже подсчитанными/полученными значениями (из wParam, lparam)

Подход не очень. Тот, кто будет пользовать твою библиотеку, может задуматься, ошибиться и записать в это поле какое-то значение. А потом будет тратить часы на отладку не понимая, почему получает графические артефакты (или что-нибудь еще менее приятное)

Ссылка на комментарий
Поделиться на другие сайты

14 минут назад, Xipho сказал:

Подход не очень. Тот, кто будет пользовать твою библиотеку, может задуматься, ошибиться и записать в это поле какое-то значение. А потом будет тратить часы на отладку не понимая, почему получает графические артефакты (или что-нибудь еще менее приятное)

Графические артефакты? Я немного не понял тебя.

Если к примеру взять событие WM_PAINT.

Вот обычная функция для события рисования

Спойлер
LRESULT OnPaintFunctionName(WGW::OnPaintEvent* onPaintEvent)
{
	RECT rect{ 0 };
	GetClientRect(onPaintEvent->hWnd, &rect);
	FillRect(onPaintEvent->compDC, &rect, reinterpret_cast<HBRUSH>(GetClassLongPtrW(onPaintEvent->hWnd, GCLP_HBRBACKGROUND)));
	BitBlt(onPaintEvent->origDC, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, onPaintEvent->compDC, rect.left, rect.top, SRCCOPY);
    return TRUE;
}

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
	WGW::GUI::initializeWGWLibrary(hInstance);
	//-------------------------------------------
	WND_MAIN = new WGW::Window(TRUE, 0, 0, L"MainWindowName", 20, 20, 1000, 900);
	WND_MAIN->onPaint = OnPaintFunctionName;

 

 

Это класс передаваемого объекта в параметре (указателя на него, сам он находится в классе окна, постоянно). Как видно из класса, только поля объявлены как public. Пользователь не может создать или обратится к этому объекту вне функции события. Создать его может только класс окна/контрола.

Спойлер
	struct OnPaintEvent : public OnEvent {
		friend class Control;
		friend class Window;
	private:
		OnPaintEvent() {}
		OnPaintEvent(CONST OnPaintEvent&) = delete;
		~OnPaintEvent() {}
	public:
		HDC compDC{ NULL };
		HDC origDC{ NULL };
		INT menuHeight{ 0 };
		INT menuTopPosition{ 0 };
		INT titleBarHeight{ 0 };
	};
	using OnPaint = LRESULT(*)(OnPaintEvent* onPaintEvent);

 

 

А это то как заполняются поля при каждом сообщении WM_PAINT

Спойлер
	LRESULT Window::wmPaint(HWND& hWnd, UINT& msg, WPARAM& wParam, LPARAM& lParam) {
		if (onPaint != nullptr || titleBar.enabled.isTitleBarEnabled && !::GetParent(hWnd) || menu.enabled.isMenuEnabled) {
			if (GetUpdateRect(hWnd, NULL, FALSE)) {
				ValidateRect(hWnd, NULL);
				if (onPaint != nullptr) {
					onPaintEvent.compDC = compatibleDC.compDC;
					onPaintEvent.control = this;
					onPaintEvent.hWnd = hWnd;
					onPaintEvent.msg = msg;
					onPaintEvent.wParam = wParam;
					onPaintEvent.lParam = lParam;
					onPaintEvent.origDC = compatibleDC.origDC;
					if (titleBar.enabled.isTitleBarEnabled && !::GetParent(hWnd)) {
						onPaintEvent.titleBarHeight = titleBar.titleBarRect.bottom;
					}
					else {
						onPaintEvent.titleBarHeight = 0;
					}
					if (menu.enabled.isMenuEnabled)	{
						onPaintEvent.menuHeight = menu.menuHeight;
						onPaintEvent.menuTopPosition = menu.menuTopPosition;
					}
					else {
						onPaintEvent.menuHeight = 0;
						onPaintEvent.menuTopPosition = 0;
					}
					onPaint(&onPaintEvent);
				}
				if (titleBar.enabled.isTitleBarEnabled && !::GetParent(hWnd)) {
					titleBar.onOwnerWmPaint();
				}
				if (menu.enabled.isMenuEnabled) {
					menu.onOwnerWmPaint();
				}
				return 0;
			}
		}
		return DefWindowProcW(hWnd, msg, wParam, lParam);
	}

 

 

Поля объекта получают копии значений. Оригиналы не затрагиваются.

Если пользователь по ошибке запишет в поле compDC что-то свое, то да, код далее этого момента будет глючить. Но так подумать, это можно отнести вообще ко всем переменным.

 

Например

Спойлер
LRESULT OnPaintFunctionName(WGW::OnPaintEvent* onPaintEvent)
{
	HDC hDC = onPaintEvent->getMyCompatibleDC(); //Как в QT, получаем значение методом.
	//Продолжение кода.
	hDC = 0; //Пользователь ошибся, изменил значение определенной самим же им переменной.
	//Продолжение кода.
}

 

 

В WGW этот вызов

HDC hDC = onPaintEvent->getMyCompatibleDC();

выполняется за пользователя, автоматически и заранее.

Все ошибки какие пользователь может совершить с переменными в данном случае в WGW, равны тем что в QT.

 

Поэтому я не понял тебя.

Ссылка на комментарий
Поделиться на другие сайты

10 часов назад, Antonshka сказал:

Если пользователь по ошибке запишет в поле compDC что-то свое, то да, код далее этого момента будет глючить. Но так подумать, это можно отнести вообще ко всем переменным.

Твоя обязанность как разработчика библиотеки - максимально обезопасить пользователя от подобных ошибок. Пользователь НЕ должен иметь доступа на запись полей внутри класса. Да, пользователь может сделать переменную и ее переназначить - и вот это уже будет на совести пользователя. А вот открытые к изменению публичные поля, это еще одна точка отказа, за которую ответственность несешь ты, как разработчик библиотеки. В идеальном мире ни одно поле не должно быть публичным, а сеттер поля должен делать валидацию поступающих на вход значений. У того же HDC в OnPaintEvent можно сеттер вообще сделать приватным, как и само поле, и тогда снаружи его изменить будет нельзя. Не устаю говорить всегда о том, что пользователю библиотеки ни в коем случае нельзя доверять. Потому нужно стараться максимально закрыть от пользователя детали реализации, и открыть ему только те части, в которых не сможет накосячить. Ну или накосячит по минимуму. В данном конкретном случае я сходу не подскажу, как это можно сделать, надо посидеть, подумать.

Ссылка на комментарий
Поделиться на другие сайты

2 часа назад, Xipho сказал:

Твоя обязанность как разработчика библиотеки - максимально обезопасить пользователя от подобных ошибок. Пользователь НЕ должен иметь доступа на запись полей внутри класса. Да, пользователь может сделать переменную и ее переназначить - и вот это уже будет на совести пользователя. А вот открытые к изменению публичные поля, это еще одна точка отказа, за которую ответственность несешь ты, как разработчик библиотеки. В идеальном мире ни одно поле не должно быть публичным, а сеттер поля должен делать валидацию поступающих на вход значений. У того же HDC в OnPaintEvent можно сеттер вообще сделать приватным, как и само поле, и тогда снаружи его изменить будет нельзя. Не устаю говорить всегда о том, что пользователю библиотеки ни в коем случае нельзя доверять. Потому нужно стараться максимально закрыть от пользователя детали реализации, и открыть ему только те части, в которых не сможет накосячить. Ну или накосячит по минимуму. В данном конкретном случае я сходу не подскажу, как это можно сделать, надо посидеть, подумать.

Я понял тебя. Ты говоришь вообще в целом. С этим я согласен, это я держу во внимании.

Сейчас в библиотеке все так и есть. Все закрыто, ничего лишнего пользователь не то что изменить, даже и создать не может. Например никакой объект класса свойства контрола.

Все что ему открыто, все безопасно.

 

А я говорил про конкретную ситуацию с параметром функции события. Этот объект-параметр, специально имеет открытые поля, они безопасны, их можно изменять. Как я писал выше, по сути это все равно что вызвать метод такого объекта-параметра, просто библиотека делает это за пользователя.

LRESULT OnPaintFunctionName(WGW::OnPaintEvent* onPaintEvent) {
  	//Как в QT, получаем значение методом.
	HDC compDC = onPaintEvent->getMyCompatibleDC(); 
	FillRect(compDC, ...)

    //Как в WGW
    FillRect(onPaintEvent->compDС, ...)
}

 

Ссылка на комментарий
Поделиться на другие сайты

В 03.08.2022 в 09:24, Xipho сказал:

Твоя обязанность как разработчика библиотеки - максимально обезопасить пользователя от подобных ошибок. Пользователь НЕ должен иметь доступа на запись полей внутри класса.

А у тебя есть опыт работы с QT? В QT, как я понял, можно расширять классы контролов. Наследуясь от них.

В WGW же, создание интерфейса осуществляется как бы процедурно, не через наследование.

Спойлер
WND_MAIN = new WGW::Window(TRUE, 0, 0, L"MainWindowName", 20, 20, 900, 900); 
WND_MAIN->onClose = OnCloseFunction; // Это обычная функция, не метод какого-либо класса
WND_MAIN->color = RGB(188, 190, 220);
WND_MAIN->onPaint = OnPaintFunctionName; // Это обычная функция, не метод какого-либо класса
WND_MAIN->onEraseBkg = OnErase;
WND_MAIN->onClick = functionName1; // Это обычная функция, не метод какого-либо класса

BTN1 = new WGW::ButtonStandart(TRUE, 0, 0, L"Btn1", 10, 100, 100, 100, WND_MAIN);
BTN1->toolTip.enabled = TRUE;
BTN1->toolTip.titleText.append(L"ToolTipTitle ", RGB(10, 10, 200));
BTN1->toolTip.titleText.append(L"ToolTipTitleSecond1  \n", RGB(144, 140, 10));
BTN1->onClick = functionName2; // Это обычная функция, не метод какого-либо класса

 

 

В QT события как я понял обрабатываются в методах класса, а не в простых функция как в WGW.

И еще я кажется видел что в QT можно создать контрол через оператор "new". Не через наследование. То есть как я понял просто используя то что QT дает стандартно .

 

- в QT есть расширение классов

- в QT события обрабатываются в методах класса, а не в простых функцияч, как в WGW.

 

Интересно, WGW от этого сильно проигрывает QT?

 

Ну хорошо, наследовал в QT класс QPushButton, расширин функционал, добавил что новый класс при нажатии на кнопку пиликает. И теперь все кнопки созданные этим классом будут пиликать.

А в WGW, вместо этого, можно создать файл PilicedButtons.h, там определить функцию на событие кнопка нажата, а в ней уже делать пиликанье. Затем все простые кнопки направить при нажатии на эту функцию. Получаем что и в QT.

Ссылка на комментарий
Поделиться на другие сайты

2 минуты назад, Antonshka сказал:

А у тебя есть опыт работы с QT?

Есть, но поверхностно - делал UI управления настройками устройства для депарафинизации нефтяных скважин. Там не было необходимости дорабатывать существующие контролы.

 

5 минут назад, Antonshka сказал:

В QT события как я понял обрабатываются в методах класса, а не в простых функция как в WGW.

И еще я кажется видел что в QT можно создать контрол через оператор "new". Не через наследование. То есть как я понял просто используя то что QT дает стандартно .

 

Да, всё так

 

7 минут назад, Antonshka сказал:

Ну хорошо, наследовал в QT класс QPushButton, расширин функционал, добавил что новый класс при нажатии на кнопку пиликает. И теперь все кнопки созданные этим классом будут пиликать.

А в WGW, вместо этого, можно создать файл PilicedButtons.h, там определить функцию на событие кнопка нажата, а в ней уже делать пиликанье. Затем все простые кнопки направить при нажатии на эту функцию. Получаем что и в QT.

Вопрос подхода. Qt в этом случае выбрали наследование, а ты выбираешь композицию. Сходу нельзя сказать, лучше что-то из этого, или хуже. Тут вопрос больше удобства кодинга. В случае с композицией у тебя ответственность смещается в сторону "композитора", то есть того, кто создает кнопку. В случае с наследованием всё, что связано с кнопками, инкапсулировано в классах кнопок.

По моему личному скромному мнению - всё, что связано с GUI контролами выглядит как вполне подчинающееся иерархии классов, и, следовательно, тут хорошо подходит наследование. Но наследование должно быть грамотным, продуманным.

Ссылка на комментарий
Поделиться на другие сайты

1 час назад, Xipho сказал:

Есть, но поверхностно - делал UI управления настройками устройства для депарафинизации нефтяных скважин. Там не было необходимости дорабатывать существующие контролы.

Получается это коммерческая ситуация? Нужно было платить как-то за использование библиотеки QT?

1 час назад, Xipho сказал:

Вопрос подхода. Qt в этом случае выбрали наследование, а ты выбираешь композицию. Сходу нельзя сказать, лучше что-то из этого, или хуже. Тут вопрос больше удобства кодинга. В случае с композицией у тебя ответственность смещается в сторону "композитора", то есть того, кто создает кнопку. В случае с наследованием всё, что связано с кнопками, инкапсулировано в классах кнопок.

По моему личному скромному мнению - всё, что связано с GUI контролами выглядит как вполне подчинающееся иерархии классов, и, следовательно, тут хорошо подходит наследование. Но наследование должно быть грамотным, продуманным.

Я сейчас посмотрел видео, где пишут калькулятор на QT. Мне понравилось. Что-то в этом наследование есть.

Нужно подумать, может получиться совместить то что сейчас в WGW с тем что в QT.

Ссылка на комментарий
Поделиться на другие сайты

1 час назад, Antonshka сказал:

Получается это коммерческая ситуация? Нужно было платить как-то за использование библиотеки QT?

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

Ссылка на комментарий
Поделиться на другие сайты

5 часов назад, Xipho сказал:

Есть, но поверхностно - делал UI управления настройками устройства для депарафинизации нефтяных скважин. Там не было необходимости дорабатывать существующие контролы.

У тебя только с QT мало опыта, или вообще с GUI?

Ссылка на комментарий
Поделиться на другие сайты

24 минуты назад, Xipho сказал:

С Qt

Понятно.

Кстати я сейчас смотрел исходники QT. Там тоже есть интересные моменты в коде, как и у меня

Спойлер
void QGridLayoutPrivate::setupSpacings(QList<QLayoutStruct> &chain, QGridBox *grid[],
                                       int fixedSpacing, Qt::Orientation orientation)
{
    Q_Q(QGridLayout);
    int numRows = rr;       // or columns if orientation is horizontal
    int numColumns = cc;    // or rows if orientation is horizontal

    if (orientation == Qt::Horizontal) {
        qSwap(numRows, numColumns);
    }

    QStyle *style = nullptr;
    if (fixedSpacing < 0) {
        if (QWidget *parentWidget = q->parentWidget())
            style = parentWidget->style();
    }

    for (int c = 0; c < numColumns; ++c) {
        QGridBox *previousBox = nullptr;
        int previousRow = -1;       // previous *non-empty* row

        for (int r = 0; r < numRows; ++r) {
            if (chain.at(r).empty)
                continue;

            QGridBox *box = gridAt(grid, r, c, cc, orientation);
            if (previousRow != -1 && (!box || previousBox != box)) {
                int spacing = fixedSpacing;
                if (spacing < 0) {
                    QSizePolicy::ControlTypes controlTypes1 = QSizePolicy::DefaultType;
                    QSizePolicy::ControlTypes controlTypes2 = QSizePolicy::DefaultType;
                    if (previousBox)
                        controlTypes1 = previousBox->item()->controlTypes();
                    if (box)
                        controlTypes2 = box->item()->controlTypes();

                    if ((orientation == Qt::Horizontal && hReversed)
                            || (orientation == Qt::Vertical && vReversed))
                        qSwap(controlTypes1, controlTypes2);

                    if (style)
                        spacing = style->combinedLayoutSpacing(controlTypes1, controlTypes2,
                                             orientation, nullptr, q->parentWidget());
                } else {
                    if (orientation == Qt::Vertical) {
                        QGridBox *sibling = vReversed ? previousBox : box;
                        if (sibling) {
                            if (sibling->item()->isEmpty()) {
                                spacing = 0;
                            } else {
                                QWidget *wid = sibling->item()->widget();
                                if (wid)
                                    spacing = qMax(spacing, sibling->item()->geometry().top() - wid->geometry().top());
                            }
                        }
                    }
                }

                if (spacing > chain.at(previousRow).spacing)
                    chain[previousRow].spacing = spacing;
            }

            previousBox = box;
            previousRow = r;
        }
    }
}

 

 

Я заметил что в WGW Layout система уступает той что в QT. Теперь хочу доработать.

Подумал, посмотрю как там сделано в QT. Но при виде кода решил что легче придумать самому. Пока разберешься, что там и к чему.

Как было с Docking.

Ссылка на комментарий
Поделиться на другие сайты

  • 2 месяца спустя...

Сегодня обнаружил интересный момент, связанный с утечкой GDI объектов.

Известно, что после того как созданный нами объект GDI нам больше не нужен, он должен быть удален специальной WinAPI функцией DeleteObject. Если этого не сделать, то произойдет утечка.

Для определения текущего количества GDI объектов для процесса, есть также специальная функция, GetGuiResources. Есть в интернете и специальная программа, которая позволяет определить не только общее количество GDI объектов, но и распределить их по типу. Название программы - GDIView.

 

Так вот, интересный момент связан с кистями и регионами. Даже если удалить их с помощью функции DeleteObject, количество объектов, возвращаемое функцией GetGuiResources, не измениться. Все потому, что система их кеширует.

Это не значит, что не нужно вызывать DeleteObject. Нет, ее нужно вызывать, но нужно просто всегда учитывать такую особенность реализации.

 

Например, вызовем функцию CreateRectRgn. Эта функция создаст для нас регион, увеличив при этом количество GDI объектов на 1. Позднее, вызвав для этого региона DeleteObject, количество объектов GDI не измениться. Однако, если вызвать функцию CreateRoundRectRgn, а затем DeleteObject, то в таком случае, количество GDI объектов все же уменьшиться на 1.

 

Получается, что это правило, правило кеширования и не уменьшения количества, относиться к кистям и регионам, созданным лишь определенными WinAPI функциями. Какие именно это функции, для кистей и регионов, нужно выяснять своими тестами.

 

GdiFlush никак не изменяет кеш таких объектов.

Скорее всего, Microsoft реализовала такую особенность не просто так. Но, это конечно не айс, с точки зрения определения утечки. Впрочем, реальная утечка объектов, проявляет себя как постоянное увеличения количества, когда как при кешировании, увеличение происходит всего один раз, в момент первого создания. По этому признаку, можно смело определить, что утечки, как таковой, нет.

 

Сколько еще таких особенностей скрывает Micosoft...

Ссылка на комментарий
Поделиться на другие сайты

3 часа назад, Antonshka сказал:

Сколько еще таких особенностей скрывает Micosoft...

ImageList, также имеет подобный "остаточный" эффект. После одного такого ImageList'a, остается 2 битмапа и две кисти. И это нормально.

В общем, при поиске утечек GDI объектов, нужно смотреть на постоянное увеличение их количества, а не на единоразовое.

Изменено пользователем Antonshka
Ссылка на комментарий
Поделиться на другие сайты

9 часов назад, Xipho сказал:

А если после вызова DeleteObject обнулить переменную, в которой этот объект лежал, счетчик уменьшится?

Нет, не уменьшится.

Мысль у тебя хорошая, я помню где-то в интернете мне попадалась она уже. Но я не помню, для какого она была контекста.

В каких случаях такие обнуления имеют эффект?

 

По поводу счетчика и кеша, вот ответ от Raymond Chen

Спойлер

As noted in this article, GDI caches solid color brushes. What you're seeing is that the brush was logically deleted, but is still physically present in the cache

The behavior is not documented because it is not contractual. It is an implementation detail. (Some versions of Windows do not have a solid color brush cache.)

 

Ссылка на комментарий
Поделиться на другие сайты

Насчет кистей со сплошной заливкой я в курсе. Есть даже набор кистей по умолчанию типа BLACK_BRUSH, WHITE_BRUSH и иже с ними.

Обнуление переменной, по идее, должно уничтожить дескриптор GDI объекта и пометить память как свободную. 

Ссылка на комментарий
Поделиться на другие сайты

Вот выписка с MSDN Resource leak

Спойлер

This could be due to the fact that, behind the scenes, when an application requests a pure color brush, Windows not only creates it, but it also stores its handle in a dedicated cache. In this case, even if the application calls DeleteObject on the handle it receives for the brush, Windows doesn't really delete it. Therefore, if the application requests a brush of the same color again, the same handle is returned.

Мы можем обнулить созданный нами дескриптор кисти, но не можем обнулить тот дескриптор кисти, который помешен в dedicated cache.

Обнуление также никак не влияет на удаление самого объекта кисти в кеше. Он там будет сидеть до следующего вызова CreateSolidBrush, с таким же цветом как и у нее.

Это все что я понял на данный момент, из собственных тестов, и из интернета.

 

Я нигде не нашел функцию очистки такого специального кеша. Впрочем, я долго не искал. Если это норма для WIndows, то пусть так и будет.

Просто для обнаружения утечки GDI объектов, приходится в обязательном порядке несколько раз создавать и удалять какой-либо контрол.

Изменено пользователем Antonshka
Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.