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

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


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

Привет всем, никто не пробовал реализовать такое?

Есть желание в ToolTip контроле отображать текст как в Visual Studio. С подсветкой синтаксиса.

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

 

Вначале была мысль использовать стиль похожий на HTML

BTN1->ToolTip.DescriptionText = L"<fontColor:#ff0000> January 30, 2011\n </fontColor>"
									L"<fontColor:#ff0010> Feb 1, 1999\n </fontColor>"
									L"DesriptionTextExample3 text4";
									L"<fontColor:#ff0010> March 11, 2014\n </fontColor>";

, и затем в методе класса разбирать эту строку.

 

Сейчас есть мысль использовать иной подход

BTN1->ToolTip.DescriptionText.Append(L"TextOne", RGB(112, 254, 177));
BTN1->ToolTip.DescriptionText.Append(L"TextTwo"); //Цвет по дефолту.
BTN1->ToolTip.DescriptionText.Append(L"TextThree \n"); //Цвет по дефолту.
BTN1->ToolTip.DescriptionText.Append(L"TextFour \n and TextFive", RGB(77, 11, 22));

 

Затем в цикле вызывать DrawText.

Вся сложность в расчетах положения следующей строки для DrawText. Так как текст может быть многострочный.

Думаю создать вектор структур. Структура имеет в себе поле строки, цвета, и положения. Осталось придумать алгоритм расчета положений. Скорее всего он будет основан на DrawText с флагом DT_CALCRECT.

Как получится рабочий вариант, обязательно напишу.

 

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

Ну мне пришел в голову такой вариант - написать свой парсер и на его основе уже использовать функции рисования.

Если хочешь сделать разметку для текста вроде HTML, то придется написать тебе обработчик данной строки.
Допустим, ты передаешь в свою функцию следующее (Ну соотв. класс для всего этого)

CustomGDIDraw->DrawTextbyHTML("<fontColor:#ff0000> January 30, 2011\n </fontColor><fontColor:#ff0010> Feb 1, 1999\n </fontColor><fontColor:#ff0050> Feb 2, 1970\n </fontColor>")

Первый твой этап - сделать парсинг, который ты уже будешь обрабатывать по своей логике. Сначала определять текст, потом цвет, потом символы переноса и т.д.
Самый простой вариант - регулярка (кликабельно)
 

Совпадения по группам, это будет то, что тебе нужно в данном случае.
Группа 1 - выдаст тебе hex цвета. Далее уже сам преобразуешь
Группа 2 - выдаст тебе текст, который нужен для отображения. Далее уже сам обработаешь перенос строки.

Ну и по итогу получишь то, что хотел.

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


По поводу переноса текста - берешь начальные координаты отрисовки, и высчитываешь от этой координаты вниз некоторое количество пикселей (Допустим шрифт 14 + отступ текста 8 = 22). Примерно такая логика для переноса текста. Ну а далее стандартная отрисовка по координатам.

Идею для реализации я тебе описал, дальше все за тобой.
 

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

13 часов назад, roma912 сказал:

Ну мне пришел в голову такой вариант - написать свой парсер и на его основе уже использовать функции рисования.
Если хочешь сделать разметку для текста вроде HTML, то придется написать тебе обработчик данной строки.

HTML версия мне не нравится. В сравнению с методом Append.

С Append ясно видно какой цвет и для чего установлен. Быстрее ищется глазами, быстрее редактируется. Я остановился на нем.

 

13 часов назад, roma912 сказал:

По поводу переноса текста - берешь начальные координаты отрисовки, и высчитываешь от этой координаты вниз некоторое количество пикселей (Допустим шрифт 14 + отступ текста 8 = 22). Примерно такая логика для переноса текста. Ну а далее стандартная отрисовка по координатам.

Все так. Главное вовремя добавить Y офсет при встрече \n.

13 часов назад, roma912 сказал:

 (Допустим шрифт 14 + отступ текста 8 = 22).

Отступ текста 8 = 22 по идее не нужен, если использовать для Y офсета поле LOGFONT lfHeight и функцию ExtTextOutW.

 

13 часов назад, roma912 сказал:

Идею для реализации я тебе описал, дальше все за тобой.

Спасибо за идею.  В интернете тоже советовали что-то подобное. Плюс сам я бывало задумывался на ручным переносом.

 

Так выглядит разноцветная версия ToolTip'a. Самодельного, на основе простого окна.

Спойлер

 

spacer.png

 

 

 

Это два метода для рисования

Спойлер
VOID ToolTip::DrawToolTip()
	{
		if (!isThreadLocalMustBeTerminated)
		{
			SetLayeredWindowAttributes(hWnd, 0, 1, LWA_ALPHA);
			GetCursorPos(&cursorPosAbsoluteCurrent);
			if (hasLinkText)
				SetWindowPos(hWnd, HWND_TOPMOST, cursorPosAbsoluteCurrent.x + setting.offsetXBetweenCursorAndLinkedTTip,
					cursorPosAbsoluteCurrent.y + setting.offsetYBetweenCursorAndLinkedTTip, toolTipSize.cx, toolTipSize.cy,
					SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOOWNERZORDER | SWP_NOZORDER);
			else
				SetWindowPos(hWnd, HWND_TOPMOST, cursorPosAbsoluteCurrent.x + setting.offsetXBetweenCursorAndNotLinkedTTip,
					cursorPosAbsoluteCurrent.y + setting.offsetYBetweenCursorAndNotLinkedTTip, toolTipSize.cx, toolTipSize.cy,
					SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOOWNERZORDER | SWP_NOZORDER);
			FillRect(CompatibleDC.compDC, &toolTipClientRect, backgroundBrush);
			FrameRect(CompatibleDC.compDC, &toolTipClientRect, borderBrush);
			if (Icon.iconNew)
				DrawIconEx(CompatibleDC.compDC, iconRect.left, iconRect.top, Icon.iconNew, Icon.IconWidth, Icon.IconHeight, NULL, NULL, DI_NORMAL);
			if (hasTitleText)
			{
				CompatibleDC.ApplyFont(titleFont);
				dynamicTextYOffset = titleTextRect.top;
				MoveToEx(CompatibleDC.compDC, titleTextRect.left, titleTextRect.top, NULL);
				DrawToolTipTexts(titleTextData);
			}
			if (hasDescriptionText)
			{
				CompatibleDC.ApplyFont(descriptionFont);
				dynamicTextYOffset = descriptionTextRect.top;
				MoveToEx(CompatibleDC.compDC, descriptionTextRect.left, descriptionTextRect.top, NULL);
				DrawToolTipTexts(descriptionTextData);
			}
			if (hasLinkText)
			{
				CompatibleDC.ApplyFont(linkFont);
				dynamicTextYOffset = linkTextRect.top;
				MoveToEx(CompatibleDC.compDC, linkTextRect.left, linkTextRect.top, NULL);
				DrawToolTipTexts(linkTextData);
			}
			BitBlt(CompatibleDC.origDC, 0, 0, toolTipSize.cx, toolTipSize.cy, CompatibleDC.compDC, 0, 0, SRCCOPY);
		}
	}

	VOID ToolTip::DrawToolTipTexts(std::vector<ToolTipTextProperties*> toolTipTextComponentTextData)
	{
		std::wstring currentString;
		std::size_t stringsLength = 0;
		std::wstringstream stringStream;
		std::size_t textLength = 0;
		for (auto& textData : toolTipTextComponentTextData)
		{
			CompatibleDC.ApplyFontColor(textData->textColor);
			if (textData->text.find(L'\n') == textData->text.npos)
				ExtTextOutW(CompatibleDC.compDC, 0, 0, ETO_OPAQUE, NULL, textData->text.c_str(), textData->text.length(), NULL);
			else
			{
				stringsLength = 0;
				stringStream.clear();
				stringStream << textData->text;
				textLength = textData->text.length();
				while (std::getline(stringStream, currentString, L'\n'))
				{
					ExtTextOutW(CompatibleDC.compDC, 0, 0, ETO_OPAQUE, NULL, currentString.c_str(), currentString.length(), NULL);
					stringsLength += currentString.length();
					if (stringsLength == textLength - 1)
					{
						if (textData->text[textLength - 1] == L'\n')
						{
							dynamicTextYOffset += setting.titleTextLogFont.lfHeight;
							MoveToEx(CompatibleDC.compDC, titleTextRect.left, dynamicTextYOffset, NULL);
						}
					}
					else
					{
						if (textData->text[stringsLength] == L'\n')
						{
							stringsLength++;
							dynamicTextYOffset += setting.titleTextLogFont.lfHeight;
							MoveToEx(CompatibleDC.compDC, titleTextRect.left, dynamicTextYOffset, NULL);
						}
					}
				}
			}
		}
	}

 

Второй метод паралельно рисованию занимается вертикальным переносом. SetTextAlign установлен заранее в TA_UPDATECP. Для автоматического расчета смещения по Х, для ExtTextOutW.

 

Если кому-то интересно посмотреть весь код, то проект скоро будет выложен на Github, после небольшого рефаторинга. Чтобы хоть что-то было рабочее.

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

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

Ёшки-матрёшки! А говорил, что "Чистый код" прочитал...

Нет обработки исключений? Вроде все как по книге. Смысловые названия переменных. Короткие функции, занимающиеся только рисованием. Нет комментариев. Объявления переменных в одном месте, в начале.

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

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

Короткие функции, занимающиеся только рисованием.

Пробегусь чисто поверхностно:

 

image.png

 

1 - нет фигурных скобок. Ухудшается чтение. Можно вынести все условие целиком в отдельный действительно короткий метод.

2, 3, 4 - вынести в отдельный метод, завести сущность, характеризующую рисование для этого метода, и сразу три условия схлопнется.

 

image.png

Типичный перегруз. Ошибки:

1. Опять местами отсутствуют операторские скобки. Да, там, где в условии одно действие, они не являются необходимыми, но для упрощения читаемости кода их всё равно рекомендуется указывать
2. Условие, вложенное в условие, вложенное в цикл, вложенный в условие, вложенное в цикл - мозг не сломался, не? Разбить на маленькие приватные методы для упрощения читаемости.

3. Если у тебя везде такой код, не предлагай мне его ревьюить, такой код в нашей платформе я бы ни за что не пропустил.

 

Добавлю: насчет переменных в одном месте - ты точно не путаешь их с полями класса? Потому как поля класса должны быть в одном месте, а вот переменные должны быть рядом с местами, где используются. Основной посыл в том, что код метода должен в идеале читаться как книга - сверху вниз. А как это так получится, если ты все переменные метода соберешь в начале метода? 

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

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

1 - нет фигурных скобок. Ухудшается чтение.

И правда, меня это тоже напрягало. Ставил по упомянутой тобой возможности.  Считал что так нужно делать обязательно, а делать как говоришь ты, это плохой тон. Теперь буду ставить везде

 

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

Добавлю: насчет переменных в одном месте - ты точно не путаешь их с полями класса? Потому как поля класса должны быть в одном месте, а вот переменные должны быть рядом с местами, где используются. Основной посыл в том, что код метода должен в идеале читаться как книга - сверху вниз. А как это так получится, если ты все переменные метода соберешь в начале метода? 

Опять же, прочитав где-то что переменные (не поля класса) нужно объявлять все в одном месте наверху (это я точно помню), я так и делал. Нужно теперь переучивать себя. Думаю это пройдет быстро, так как мне не нравилось собирать их все наверху.

 

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

2. Условие, вложенное в условие, вложенное в цикл, вложенный в условие, вложенное в цикл - мозг не сломался, не? Разбить на маленькие приватные методы для упрощения читаемости.

Есть такое. Но здесь не однозначно. Как мне кажется сейчас.

Ведь так видна сразу вся картина, все перед глазами. А если разбить это на небольшие методы, то для понимания придется часто бегать по ним, и запоминать, как они внутри себя выполняют операции.

 

Я помню как в Чистом коде читал про эту вложенность и как она вредна. И помню, как я не мог понять как без нее то. Если такая настоит необходимость, что нужно проверить кучу условий.

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

3. Если у тебя везде такой код, не предлагай мне его ревьюить, такой код в нашей платформе я бы ни за что не пропустил.

У меня весь код такой, и еще хуже. К сожалению.

Можешь не ревьювить его. Эту кодо-помойку я не вправе кому-то предлагать на рассмотрение. И вообще отнимать у кого-то время принудительно. Так, если просто будет у тебя желание и интерес. Пробежаться быстрым взглядом.

За советы спасибо. Без советом и опыта других далеко не уедешь, к счастью я это понимаю.

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

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

2. Условие, вложенное в условие, вложенное в цикл, вложенный в условие, вложенное в цикл - мозг не сломался, не? Разбить на маленькие приватные методы для упрощения читаемости.

 

Спойлер
int main(void)
{
	char* error = "";
	ALLEGRO_DISPLAY *display = NULL;
	ALLEGRO_FONT *font = NULL;
	ALLEGRO_TIMER *timer = NULL;
	ALLEGRO_EVENT_QUEUE *event_queue = NULL;

	HANDLE mutex = CreateMutex(NULL, FALSE, "Chapter3_CloseMutex-Mutex");
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
		MessageBoxA(GetForegroundWindow(), "Only one instance of the game can run at a time.", "Chapter3_CloseMutex", MB_OK);
        return 0;
    }

	do
	{
		loadHighscore();
		if(!al_init())
		{
			error = "failed to initialize allegro!";
			break;
		}
		if(!al_init_primitives_addon())
		{
			error = "failed to initialize primitives!";
			break;
		}
		al_init_font_addon();
		if(!al_init_ttf_addon())
		{
			error = "failed to initialize ttfs!";
			break;
		}
		if(!al_install_keyboard())
		{
			error = "failed to initialize keyboard!";
			break;
		}
		if(!(font = al_load_ttf_font("arial.ttf", 18, 0)))
		{
			error = "failed to initialize font!";
			break;
		}
		if(!(timer = al_create_timer(1.0 / 20)))
		{
			error = "failed to initialize timer!";
			break;
		}
		if(!(display = al_create_display(640, 480)))
		{
			error = "failed to initialize display!";
			break;
		}
		if(!(event_queue = al_create_event_queue()))
		{
			error = "failed to initialize event_queue!";
			break;
		}

		al_register_event_source(event_queue, al_get_display_event_source(display));
		al_register_event_source(event_queue, al_get_keyboard_event_source());
		al_register_event_source(event_queue, al_get_timer_event_source(timer));

		al_start_timer(timer);

		while(1)
		{
			ALLEGRO_EVENT ev;
			al_wait_for_event(event_queue, &ev);

			if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
				break;
			else if (ev.type == ALLEGRO_EVENT_KEY_DOWN)
			{
				if (ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
					break;
				else if (ev.keyboard.keycode <= 255)
					keys[ev.keyboard.keycode] = true;
			}
			else if (ev.type == ALLEGRO_EVENT_KEY_UP)
			{
				if (ev.keyboard.keycode <= 255)
					keys[ev.keyboard.keycode] = false;
			}
			else if (ev.type == ALLEGRO_EVENT_KEY_CHAR)
				keyboardEvent();

			if (al_is_event_queue_empty(event_queue) && ev.type == ALLEGRO_EVENT_TIMER)
				drawFrame(font);
		}
	} while (0);

	if (mutex)
        CloseHandle(mutex);

	if (strlen(error) > 0)
	{
		al_show_native_message_box(NULL, NULL, "error", error, NULL, NULL);
		return -1;
	}

	al_destroy_timer(timer);
	al_destroy_display(display);
	al_destroy_event_queue(event_queue);
	saveHighscore();


	return 0;
}

 

У тебя тоже есть занятные примеры в коде. 😊

Я помню что ты говорил что это ты писал не для себя, что для себя ты пишешь нормально и в паблик не выкладываешь. Однако все равно, смотрится интересно.

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

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

У тебя тоже есть занятные примеры в коде. 😊

Это же не мой репозиторий, это форк, посмотри внимательно ))

image.png

 

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

Ведь так видна сразу вся картина, все перед глазами.

Не видна. Большое количество вложений вызывает ухудшение читаемости и, соответственно, понимание кода. А вот если ты это растаскиваешь по методам c с говорящими именами - это куда лучше. Ты сразу понимаешь, что происходит в твоем метод, а если становятся интересны детали - проваливаешься в меньший метод. 

 

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

Я помню как в Чистом коде читал про эту вложенность и как она вредна. И помню, как я не мог понять как без нее то. Если такая настоит необходимость, что нужно проверить кучу условий.

Надо посидеть, подумать, прикинуть архитектурно как это разрулить. Можно глянуть с точки зрения паттернов.

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

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

Это же не мой репозиторий, это форк, посмотри внимательно ))

image.png

Понятно теперь.

 

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

1. Опять местами отсутствуют операторские скобки. Да, там, где в условии одно действие, они не являются необходимыми, но для упрощения читаемости кода их всё равно рекомендуется указывать

Попробовал сейчас поставить скобки, - выглядит так себе.

Без скобок

Спойлер
VOID WindowPropsTitleBar::DrawTitleBar()
{
	DrawTitleBarBackground();
	if (isIconMustBeShown)
		DrawIcon();
	if (isTitleTextMustBeShown)
		DrawTitleText();
	if (hasMinimizeBtn)
		DrawMinimizeButton();
	if (hasMaximizeBtn)
		DrawMaximizeButton();
	DrawCloseButton();
	BitBlt(windowOwner.CompatibleDC.origDC, titleBarRect.left,
		titleBarRect.top,
		titleBarRect.right - titleBarRect.left,
		titleBarRect.bottom - titleBarRect.top,
		windowOwner.CompatibleDC.compDC,
		titleBarRect.left,
		titleBarRect.top, SRCCOPY);
}

 

 

Со скобками

Спойлер
VOID WindowPropsTitleBar::DrawTitleBar()
{
	DrawTitleBarBackground();
	if (isIconMustBeShown)
	{
		DrawIcon();
	}
	if (isTitleTextMustBeShown)
	{
		DrawTitleText();
	}
	if (hasMinimizeBtn)
	{
		DrawMinimizeButton();
	}
	if (hasMaximizeBtn)
	{
		DrawMaximizeButton();
	}
	DrawCloseButton();
	BitBlt(windowOwner.CompatibleDC.origDC, titleBarRect.left,
		titleBarRect.top,
		titleBarRect.right - titleBarRect.left,
		titleBarRect.bottom - titleBarRect.top,
		windowOwner.CompatibleDC.compDC,
		titleBarRect.left,
		titleBarRect.top, SRCCOPY);
}

 

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

 

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

Не видна. Большое количество вложений вызывает ухудшение читаемости и, соответственно, понимание кода. А вот если ты это растаскиваешь по методам c с говорящими именами - это куда лучше. Ты сразу понимаешь, что происходит в твоем метод, а если становятся интересны детали - проваливаешься в меньший метод. 

 

Надо посидеть, подумать, прикинуть архитектурно как это разрулить. Можно глянуть с точки зрения паттернов.

Пока что ничего в голову не приходит. Со временем может.

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

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

Попробовал сейчас поставить скобки, - выглядит так себе.

как по мне - норм. Но наверное чтобы прямо сейчас не переделывать архитектуру и проч., следует просто оставить вызовы функций(условия выноси в сами функции, инвертируй их и т.п.). И точно также в коде выше - сделай хотя бы оболочки для длинных страшных вызовов и выполняй все проверки в них. Доведи свой проект до законченного состояния, после чего перепиши по-человечески.
 

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

Пока что ничего в голову не приходит. Со временем может.

если в голову ничего не пришло, значит мало или плохо думал🙂

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

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

если в голову ничего не пришло, значит мало или плохо думал🙂

Я много думал😊. Ничего более удобного я не смог придумать.

Ну вот например, можно попробовать заменить это

Спойлер
while (std::getline(stringStream, currentString, L'\n'))
{
  ExtTextOutW(CompatibleDC.compDC, 0, 0, ETO_OPAQUE, NULL, currentString.c_str(), currentString.length(), NULL);
  stringsLength += currentString.length();
  if (stringsLength == textLength - 1)
  {
    if (textData->text[textLength - 1] == L'\n')
    {
      dynamicTextYOffset += setting.titleTextLogFont.lfHeight;
      MoveToEx(CompatibleDC.compDC, titleTextRect.left, dynamicTextYOffset, NULL);
    }
  }
  else
  {
    if (textData->text[stringsLength] == L'\n')
    {
      stringsLength++;
      dynamicTextYOffset += setting.titleTextLogFont.lfHeight;
      MoveToEx(CompatibleDC.compDC, titleTextRect.left, dynamicTextYOffset, NULL);
    }
  }
}

 

на вот это

Спойлер
while (std::getline(stringStream, currentString, L'\n'))
{
  ExtTextOutW(CompatibleDC.compDC, 0, 0, ETO_OPAQUE, NULL, currentString.c_str(), currentString.length(), NULL);
  stringsLength += currentString.length();
  if (IsAllTextObserved()) //if (stringsLength == textLength - 1)
  {
    if (IsLastTextCharacterHasNewLineChar()) // if (textData->text[textLength - 1] == L'\n')
    {
      ChangeCurrentPositionDC(); //dynamicTextYOffset += setting.titleTextLogFont.lfHeight;
       							// MoveToEx(CompatibleDC.compDC, titleTextRect.left, dynamicTextYOffset, NULL);
    }
  }
  else
  {
    if (IsCurrentTextPartHasNewLineCharAtTheEnd) //if (textData->text[stringsLength] == L'\n')
    {
      stringsLength++;
      ChangeCurrentPositionDC(); //dynamicTextYOffset += setting.titleTextLogFont.lfHeight;
       							// MoveToEx(CompatibleDC.compDC, titleTextRect.left, dynamicTextYOffset, NULL);
    }
  }
}

 

 

Не знаю, нормально так или нет.

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

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

Скобки для одиночных выражений пожалуй я не буду ставить, так как с ними мне лично тяжелее.

Если у тебя и сам if состоит из одного выражения, и else из одного - тогда да, смысла ставить нет, в других случаях - смысл есть, чтобы было единообразие кода. Я не устану повторять - в идеале код должен читаться, как книга. Из-за этого мне синтаксис Rust не очень нравится - его местами тяжело читать из-за его макроконструкций.

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

Не знаю, нормально так или нет.

Для начала, надо понять, какую проблему ты решаешь. Сейчас ты поменял одну конструкцию на другую, стало чуть более читаемо, но суть не изменилась. Суть же в том, что у тебя несколько вложенных условных конструкций, что и ведет к снижению читаемости кода.

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

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

Для начала, надо понять, какую проблему ты решаешь. Сейчас ты поменял одну конструкцию на другую, стало чуть более читаемо, но суть не изменилась. Суть же в том, что у тебя несколько вложенных условных конструкций, что и ведет к снижению читаемости кода.

Подумаю еще над этим.

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

Подскажите, как лучше вызывать методы?

Так, то есть сперва проверять активна ли кнопка или нет, а потом уже вызывать ее функцию рисования

Спойлер
VOID WindowPropsTitleBar::DrawTitleBar()
{
	DrawTitleBarBackground();
	if (isIconMustBeShown)
		DrawIcon();
	if (isTitleTextMustBeShown)
		DrawTitleText();
	if (hasMinimizeBtn)
		DrawMinimizeButton();
	if (hasMaximizeBtn)
		DrawMaximizeButton();
	DrawCloseButton();
	BitBlt(windowOwner.CompatibleDC.origDC, titleBarRect.left,
		titleBarRect.top,
		titleBarRect.right - titleBarRect.left,
		titleBarRect.bottom - titleBarRect.top,
		windowOwner.CompatibleDC.compDC,
		titleBarRect.left,
		titleBarRect.top, SRCCOPY);
}

 

 

или так, то есть проверку активности кнопки проводить в самом методе?

Спойлер
VOID WindowPropsTitleBar::DrawTitleBar()
{
	DrawTitleBarBackground();
	DrawIcon();
	DrawTitleText();
	DrawMinimizeButton();
	DrawMaximizeButton();
	DrawCloseButton();
	BitBlt(windowOwner.CompatibleDC.origDC, titleBarRect.left,
		titleBarRect.top,
		titleBarRect.right - titleBarRect.left,
		titleBarRect.bottom - titleBarRect.top,
		windowOwner.CompatibleDC.compDC,
		titleBarRect.left,
		titleBarRect.top, SRCCOPY);
}

 

 

Не могу решить как лучше. Особенно вот в таком случае

Спойлер
VOID WindowPropsTitleBar::OnOwnerWmLButtonDown(LPARAM& lParam)
{
  POINT cursorPosRelative{ 0 };
  cursorPosRelative.x = GET_X_LPARAM(lParam);
  cursorPosRelative.y = GET_Y_LPARAM(lParam);
  if (sysMenu.IsLButtonDownOnSysMenuClickRect(cursorPosRelative))
    sysMenu.ShowSysMenu(TRUE, cursorPosRelative);
  if (cursorPosRelative.y < titleBarRect.bottom && IsCursorHoverTitleBarNotUsedArea(cursorPosRelative))
    DragWindowByTitleBarNotUsedArea();
}

 

 

Если поменяю на это, проведя проверку в самих методах

Спойлер
VOID WindowPropsTitleBar::OnOwnerWmLButtonDown(LPARAM& lParam)
{
  POINT cursorPosRelative{ 0 };
  cursorPosRelative.x = GET_X_LPARAM(lParam);
  cursorPosRelative.y = GET_Y_LPARAM(lParam);
  sysMenu.ShowSysMenu(TRUE, cursorPosRelative);
  DragWindowByTitleBarNotUsedArea();
}

 

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

А если будет не два метода, а десять.

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

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

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

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

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

То есть лучше делать вот так? С предварительной проверкой перед вызовом?

Спойлер
VOID WindowPropsTitleBar::DrawTitleBar()
{
	DrawTitleBarBackground();
	if (isIconMustBeShown)
		DrawIcon();
	if (isTitleTextMustBeShown)
		DrawTitleText();
	if (hasMinimizeBtn)
		DrawMinimizeButton();
	if (hasMaximizeBtn)
		DrawMaximizeButton();
	DrawCloseButton();
	BitBlt(windowOwner.CompatibleDC.origDC, titleBarRect.left,
		titleBarRect.top,
		titleBarRect.right - titleBarRect.left,
		titleBarRect.bottom - titleBarRect.top,
		windowOwner.CompatibleDC.compDC,
		titleBarRect.left,
		titleBarRect.top, SRCCOPY);
}

 

И по идее это еще лучше тем что нет лишних затрат на вызовы, ведь проверить переменную быстрее чем вызвать метод

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

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

То есть лучше делать вот так? С предварительной проверкой перед вызовом?

ага. Или задачу измени🙂Чего ты на мелочах так стопоришься?

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

7 часов назад, youneuoy сказал:

Чего ты на мелочах так стопоришься?

Пытаюсь научиться писать код правильно и красиво.

 

7 часов назад, youneuoy сказал:

ага.

 

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

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

Я немного не понял тогда, ведь было написано

Спойлер
В 25.07.2022 в 10:20, Xipho сказал:

Пробегусь чисто поверхностно:

 

image.png

2, 3, 4 - вынести в отдельный метод, завести сущность, характеризующую рисование для этого метода, и сразу три условия схлопнется.

 

Как схлопнется три условия, если лучше проверять условия не в методах рисования, и перед их вызовом. У меня здесь именно так и есть, проверка условий перед вызовом методов рисования.

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

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

Как схлопнется три условия, если лучше проверять условия не в методах рисования, и перед их вызовом

И снова у меня толком нет времени, чтобы тебе обстоятельно ответить, а давая контекст кусками, я создаю у тебя в голове впечатление противоречий. Это плохо. Давай так. Попробуй внимательно почитать про принципы SOLID. Если тебе что-то в них не будет понятно, пиши, постараюсь объяснить. На конкретных примерах кода в разных участках программы это делать сложно. Попутно - прочитать/перечитать книгу по паттернам от "банды четырех", внимательно вникая, как авторы рассуждают по ходу книги. Поинт на заметку - почему в винде каждый контрол является окном, туда же - в упомянутой книге почему в редакторе везде  и всюду во главе всего идет класс Glyph.

 

Да, по поводу "как схлопнется три условия". Смотри, я увидел в твоем коде, что во всех этих трех условиях код практически одинаковый. Следовательно, я бы его схлопнул примерно как-то так (псевдокодом)

void checkAndDrawTooltipItem(ToolTipItem &item) {
     if (item != NULL) {
          dynamicTextYOffset = item.rect.top;
          ...
          DrawToolTipTexts(item.data)
     }
}

Соответственно, я бы создал абстрактный класс ToolTipItem, и в нем бы свел все свойства, общие для всех компонентов тултипа, а отличающиеся части вывел бы в классы-наследники. В моем понимании (не заглядывая дальше в твои реализации), это бы сильно облегчило кодинг (почему я и намекаю на класс Glyph в книге "банды четырех") и расширение функционала. То, как ты сейчас пишешь код - у тебя получается помесь ООП и процедурного стиля программирования. Этим ты сам себя запутываешь. Если хочешь, можно на выходных найти время, состыковаться в дискорде голосом, чтобы проговорить основные моменты.

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

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

Соответственно, я бы создал абстрактный класс ToolTipItem, и в нем бы свел все свойства, общие для всех компонентов тултипа, а отличающиеся части вывел бы в классы-наследники. В моем понимании (не заглядывая дальше в твои реализации), это бы сильно облегчило кодинг (почему я и намекаю на класс Glyph в книге "банды четырех") и расширение функционала.

Я кстати такое сделал для TitleBar класса, после этого твоего совета

В 25.07.2022 в 10:20, Xipho сказал:

2, 3, 4 - вынести в отдельный метод, завести сущность, характеризующую рисование для этого метода, и сразу три условия схлопнется.

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

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

Для ToolTip планирую сделать такое же.

 

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

Попробуй внимательно почитать про принципы SOLID.

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

 То, как ты сейчас пишешь код - у тебя получается помесь ООП и процедурного стиля программирования.

Процедурный стиль видимо остался от опыта на LUA в CE. Я заметил что я пишу на нем потому что он проще для понимания. Но он не проще при последующих расширениях, как я заметил за последние два дня. Буду перестраиваться на ООП мышление.

 

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

 Если хочешь, можно на выходных найти время, состыковаться в дискорде голосом, чтобы проговорить основные моменты.

Да, можно. Но сейчас у меня ничего не готово. У меня получается два шага вперед, и один назад. Потому так долго.

Текущие глобальные ошибки мы обговорили, над ними я сейчас буду работать.

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

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

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

Правильный подход, но, сразу оговорюсь, что такое полезно не всегда и везде. Есть ряд случаев, где лучше применить композицию вместо наследования.

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

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

Да, по поводу "как схлопнется три условия". Смотри, я увидел в твоем коде, что во всех этих трех условиях код практически одинаковый. Следовательно, я бы его схлопнул примерно как-то так (псевдокодом)

void checkAndDrawTooltipItem(ToolTipItem &item) {
     if (item != NULL) {
          dynamicTextYOffset = item.rect.top;
          ...
          DrawToolTipTexts(item.data)
     }
}

Но при таком подходе не придется ли все методы класса называть "check" и далее имя операции? Все методы которые должны предварительно проверить состояние объекта.

Двойное название как-то не так смотрится.

 

Я бы остановился на таком варианте, если он допустим. Он мне вполне нравится.

Спойлер
VOID WindowPropsTitleBar::DrawTitleBar()
{
  DrawTitleBarBackground();
  if (icon.isIconEnabled)
    icon.DrawIcon(windowOwner.CompatibleDC.compDC);
  if (text.isTextEnabled)
    text.DrawTitleText(windowOwner.CompatibleDC.compDC);
  if (buttonMinimize.isButtonStyleEnabled)
    buttonMinimize.DrawButton(windowOwner.CompatibleDC.compDC);
  if (buttonMaximize.isButtonStyleEnabled)
    buttonMaximize.DrawButton(windowOwner.CompatibleDC.compDC);
  buttonClose.DrawButton(windowOwner.CompatibleDC.compDC);
  BitBlt(windowOwner.CompatibleDC.origDC, titleBarRect.left, titleBarRect.top, titleBarRect.right - titleBarRect.left,
         titleBarRect.bottom - titleBarRect.top,	windowOwner.CompatibleDC.compDC, titleBarRect.left,	titleBarRect.top, SRCCOPY);
}

 

 

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

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

Я бы остановился на таком варианте, если он допустим. Он мне вполне нравится

Он, разумеется, допустим. Но, как по мне, компонент должен "рисовать себя сам". И тогда у тебя получится очень простая иерархия вызовов. Кто-то (система) у родительского окна вызывает метод Draw, родительское окно у всех своих дочерних компонентов вызывает этот же метод, и так по цепочке до самого нижнего компонента. А тут у тебя получается то, о чем я говорил - размазывание ответственности. По сути, родительскому компоненту незачем знать детали отрисовки дочерних компонентов. Каждый отвечает сам за себя. Снова делаю отсыл к классу Glyph из книги "банды четырех". Там прямо очень четко это объяснено.

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

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

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

У меня сейчас именно такая схема о которой ты говоришь. Класс главного окна имеет своим полем объект класса TitleBar.  В оконной процедуре главное окно вызывает методы TitleBar. В определенных видах сообщений.

Например, сообщение WM_PAINT, при нем главное окно вызывает этот метод DrawTitleBar() для TitleBar, проверив предварительно активен ли TitleBar.

Спойлер
VOID WindowPropsTitleBar::DrawTitleBar()
{
  DrawTitleBarBackground();
  if (icon.isIconEnabled)
    icon.DrawIcon(windowOwner.CompatibleDC.compDC);
  if (text.isTextEnabled)
    text.DrawTitleText(windowOwner.CompatibleDC.compDC);
  if (buttonMinimize.isButtonStyleEnabled)
    buttonMinimize.DrawButton(windowOwner.CompatibleDC.compDC);
  if (buttonMaximize.isButtonStyleEnabled)
    buttonMaximize.DrawButton(windowOwner.CompatibleDC.compDC);
  buttonClose.DrawButton(windowOwner.CompatibleDC.compDC);
  BitBlt(windowOwner.CompatibleDC.origDC, titleBarRect.left, titleBarRect.top, titleBarRect.right - titleBarRect.left,
         titleBarRect.bottom - titleBarRect.top,	windowOwner.CompatibleDC.compDC, titleBarRect.left,	titleBarRect.top, SRCCOPY);
}

 

А TitleBar класс имеет своими полями объекты классов icon, text, и buttonMinimize, buttonMaximize, buttonClose.

TitleBar рисует только свой background color. А все остальное рисование производится самими элементами.

Весь вопрос в предварительной проверке активности элемента в родительском методе, перед его вызовом.

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

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

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

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