Выражение благодарности автору оригинальной статьи
Прежде чем перейти к анализу, хочу выразить искреннюю благодарность автору оригинальной статьи.
Его работа — редкий пример глубокого погружения в низкоуровневую отрисовку VCL, а реализация двойной буферизации с кэшированием фона действительно решает насущную проблему мерцания.
Именно такие публикации двигают малочисленное, но увлечённое сообщество C++ Builder вперёд.
Однако, как это часто бывает с техническими решениями, хорошая идея может быть реализована неоптимально с точки зрения архитектуры. И сегодня я хочу обсудить именно этот аспект — не для того, чтобы «опровергнуть», а чтобы предложить более совместимый и устойчивый подход.
Код с Delphi был переписан на С++
/*
Метод PaintWindow — это точка входа для отрисовки контрола в ответ на WM_PAINT. Именно здесь Windows предоставляет HDC, в который нужно рисовать. Стандартная реализация VCL не использует двойную буферизацию, что приводит к мерцанию. Ниже — реализация, которая решает эту проблему, но…
*/
void __fastcall TEsWinControl::PaintWindow(HDC DC)
{
HDC TempDC = nullptr;
TRect UpdateRect;
HDC BufferDC = nullptr;
HBITMAP BufferBitMap = nullptr;
HRGN Region = nullptr;
TPoint SaveViewport;
bool BufferedThis = !BufferedChildren || ComponentState.Contains(csDesigning);
if (::GetClipBox(DC, &UpdateRect) == ERROR)
UpdateRect = ClientRect;
try
{
if (BufferedThis)
{
if (!DoubleBuffered)
{
BufferDC = ::CreateCompatibleDC(DC);
if (BufferDC)
{
if (FIsCachedBuffer || FIsFullSizeBuffer)
{
if (!CacheBitmap)
{
BufferBitMap = ::CreateCompatibleBitmap(DC, ClientWidth, ClientHeight);
if (FIsCachedBuffer)
CacheBitmap = BufferBitMap;
}
else
BufferBitMap = CacheBitmap;
Region = ::CreateRectRgnIndirect(&UpdateRect);
::SelectClipRgn(BufferDC, Region);
}
else
{
BufferBitMap = ::CreateCompatibleBitmap(DC,
RectWidth(UpdateRect), RectHeight(UpdateRect));
}
::SelectObject(BufferDC, BufferBitMap);
if (!(FIsCachedBuffer || FIsFullSizeBuffer))
{
::GetViewportOrgEx(BufferDC, &SaveViewport);
::SetViewportOrgEx(BufferDC,
-UpdateRect.Left + SaveViewport.x,
-UpdateRect.Top + SaveViewport.y, nullptr);
}
}
else
BufferDC = DC;
}
else
BufferDC = DC;
}
else
BufferDC = DC;
// Background drawing
if (!ControlStyle.Contains(csOpaque))
{
if (ParentBackground)
{
if (FIsCachedBackground)
{
if (!CacheBackground)
{
TempDC = ::CreateCompatibleDC(DC);
CacheBackground = ::CreateCompatibleBitmap(DC, ClientWidth, ClientHeight);
::SelectObject(TempDC, CacheBackground);
DrawBackground(TempDC);
::DeleteDC(TempDC);
}
TempDC = ::CreateCompatibleDC(BufferDC);
::SelectObject(TempDC, CacheBackground);
::BitBlt(BufferDC, UpdateRect.Left, UpdateRect.Top,
RectWidth(UpdateRect), RectHeight(UpdateRect),
TempDC, UpdateRect.Left, UpdateRect.Top, SRCCOPY);
::DeleteDC(TempDC);
}
else
DrawBackground(BufferDC);
}
}
else
{
if (!DoubleBuffered || DC)
{
TRect rc = ClientRect;
TColor FillColor = StyleServices()->GetSystemColor(Color);
::SetDCBrushColor(BufferDC, ColorToRGB(FillColor));
::FillRect(BufferDC, &rc, (HBRUSH)::GetStockObject(DC_BRUSH));
}
}
FCanvas->Lock();
try
{
Canvas->Handle = BufferDC;
static_cast<TControlCanvas*>(Canvas)->UpdateTextFlags();
/***********************************************************/
if (OnPainting)
OnPainting(this, Canvas, ClientRect);
Paint();
if (OnPaint)
OnPaint(this, Canvas, ClientRect);
/***********************************************************/
}
__finally
{
Canvas->Handle = nullptr;
FCanvas->Unlock();
}
}
__finally
{
if (BufferedThis)
{
try
{
if (!DoubleBuffered)
{
if (!(FIsCachedBuffer || FIsFullSizeBuffer))
{
::SetViewportOrgEx(BufferDC, SaveViewport.x, SaveViewport.y, nullptr);
::BitBlt(DC, UpdateRect.Left, UpdateRect.Top,
RectWidth(UpdateRect), RectHeight(UpdateRect),
BufferDC, 0, 0, SRCCOPY);
}
else
{
::BitBlt(DC, UpdateRect.Left, UpdateRect.Top,
RectWidth(UpdateRect), RectHeight(UpdateRect),
BufferDC, UpdateRect.Left, UpdateRect.Top, SRCCOPY);
}
}
}
__finally
{
if (BufferDC != DC)
::DeleteDC(BufferDC);
if (Region != 0)
::DeleteObject(Region);
if (!FIsCachedBuffer && BufferBitMap != 0)
::DeleteObject(BufferBitMap);
}
}
}
}Недавно в одной статье на Хабре был предложен кастомный TEsWinControl со следующим событием:
void __fastcall OnPaint(TObject* Sender, TCanvas* Canvas, const TRect& Rect);
На первый взгляд — удобно: всё сразу передано. Но на деле это нарушает контракт VCL.
Ошибка №1: «Удобный» OnPaint ломает совместимость
Вспомните: у TForm, TPanel, TButton — везде один и тот же тип:
__property TNotifyEvent OnPaint; // т.е. void __fastcall(TObject* Sender)
Если вы меняете сигнатуру, ваш компонент:
Нельзя использовать в шаблонах, где ожидается наследование от
TWinControl.Требует уникального кода обработки, который не работает с другими контролами.
Ломает условную компиляцию: заменить
TWinControlнаTEsWinControlчерез#defineтеперь невозможно — придётся править множество мест, где используется данная сигнатура метода
Ошибка №2: Canvas уже есть — зачем его передавать?
При написание события возникает мысль «Почему бы не передать Canvas, чтобы пользователю было проще». Но это избыточно.
Во-первых, Canvas доступен напрямую:
void __fastcall MyPaint(TObject* Sender)
{
Canvas->Rectangle(0, 0, 100, 100);
}
Во-вторых, при двойной буферизации Canvas->Handle временно переназначается на буфер. Пользователь не должен знать об этом — он просто рисует, а компонент сам заботится о том, куда попадут пиксели.
Передача Canvas в параметре:
Нарушает инкапсуляцию,
Создаёт иллюзию «особого» канваса,
Вынуждает пользователя думать о внутренней реализации.
Canvas должен оставаться свойством. А магия отрисовки в PaintWindow
Ошибка №3: Rect вводит в заблуждение
В коде из статьи Rect всегда равен ClientRect:
OnPaint(this, Canvas, ClientRect);
Но зачем тогда его передавать? Это создаёт ложное впечатление, что:
Нужно рисовать только в
Rect,Это область повреждения (как
ps.rcPaint),Поведение отличается от стандартного
OnPaint.
На самом деле, VCL всегда ожидает полной перерисовки в OnPaint. Частичность обрабатывается на уровне оконной системы (WM_PAINT + GetClipBox), но никогда не должна просачиваться в пользовательский код.
Пример правильного использования
class TEsPaint : public
#ifdef USE_ES_WINCONTROL
TWinControl
#else
TEsWinControl
#endif // USE_ES_WINCONTROL
{
// Создаем событие с правильной сигнатурой
void __fastcall FormPaintCanvas(TObject* Sender);
void draw_shapes(TCanvas* Canvas) { /* код */ };
void draw_shapes(TCanvas* Canvas, const TPoint& AStartPoint, const TPoint& AEndPoint, const int& ATypeFigure) { /* код */ };
TPoint FStartPoint = const_value::InvalidPoint;
TPoint FEndPoint = const_value::InvalidPoint;
int FTypeFigure = const_value::InvalidInt;
bool FDrawing = false;
public: // События
__fastcall TEsPaint(TComponent* Owner);
__fastcall virtual ~TEsPaint();
__property OnPaint;
};
__fastcall TEsPaint::TEsPaint(TComponent* Owner) : TEsWinControl(Owner) {
OnPaint = FormPaintCanvas;
};
void __fastcall TEsPaint::FormPaintCanvas(TObject* Sender) {
// Используем собственный Canvas
// 1. Рисуем все сохранённые фигуры
draw_shapes(Canvas);
// 2. Если идёт рисование — рисуем
if (FDrawing)
{
draw_shape(Canvas, FStartPoint, FEndPoint, FTypeFigure);
draw_lines(Canvas, FStartPoint, FEndPoint, FTypeFigure);
}
};
Сохраняется максимальная гибкость с VCL-архитектурой
Сравнение с официальными компонентами VCL
Рассмотрим компонент TCustomPanel . Его метод TCustomPanel::Paint() — переопределяет отрисовку, но OnPaint остаётся стандартным по сигнатуре. У TGraphicControl рисуется напрямую в Canvas но не передаёт его в событие. А TDBGrid это вообще сложнейший компонент, но его OnDrawColumnCell это расширение, а не замена OnPaint. Они не ломают контракт. Они расширяют функционал, добавляя новые события, если нужно, но не меняют старые.
Кастомный контрол — не повод выдумывать свой API.
Настоящая сложность — не в том, чтобы наворотить кучу параметров, а в том, чтобы спрятать всю сложность внутри, а снаружи оставить всё так же просто, как в стандартном TWinControl.
Если приходится писать отдельный обработчик, который больше ни с чем не работает, если приходится помнить, что «тут Canvas передаётся, а тут — нет», или если замена TWinControl на ваш класс ломает половину формы — вы не упростили ему жизнь. Вы просто переложили свою головную боль на него.
А суть хорошего компонента как раз в другом: пусть он делает всё сам, а пользователь рисует в Canvas, как привык, и даже не догадывается, что под капотом — двойная буферизация, кэширование фона и прочая магия.
Комментарии (33)

Error1024
17.01.2026 02:49Аргументированная критика - это безусловно хорошо, но как автор "оригинала", не соглашусь по всем пунктам:
Ошибка №1: «Удобный» OnPaint ломает совместимость
Вспомните: у
TForm,TPanel,TButton— везде один и тот же тип:__property TNotifyEvent OnPaint; // т.е. void __fastcall(TObject* Sender)Если вы меняете сигнатуру, ваш компонент:
Нельзя использовать в шаблонах, где ожидается наследование от
TWinControl.Требует уникального кода обработки, который не работает с другими контролами.
Ломает условную компиляцию: заменить
TWinControlнаTEsWinControlчерез#defineтеперь невозможно — придётся править множество мест, где используется данная сигнатура метода
Не ясно о чем вообще речь, у TWinControl и TButton - нет свойства OnPaint, следовательно - никакой контракт TWinControl не был нарушен.
То, что у TCustomControl тоже есть свойство OnPaint, которое отличается по сигнатуре - не более чем совпадение. В VCL куча мест, где одно и тоже свойство имеет разный тип в "дальних" ветках TControl.
TEsCustomControl это не наследник TCustomControl,
TEsCustomControl - это наследник TWinControl, и не должен соблюдать контракты TCustomControl.
Про #define - а зачем это вообще? Все, что я обеспечиваю - это работоспособность FreeEsVclComponents в C++Builder, игры с #define - это уже не ко мне.
Ошибка №2: Canvas уже есть — зачем его передавать?
При написание события возникает мысль «Почему бы не передать
Canvas, чтобы пользователю было проще». Но это избыточно.Во-первых,
Canvasдоступен напрямую:Нет, у TEsCustomControl свойство Canvas не доступно напрямую. Свойство Canvas объявлено в секции protected, исключительно для удобства написания наследников данного компонента. Из кода "снаружи" Canvas не доступен.
Canvas передается в событие OnPaint, и это сделано специально, чтобы у пользователя не возникало "соблазна" рисовать вне события OnPaint, что не приводит ни к чему хорошему.
То, что в TCustomControl свойство Canvas доступно вне событий отрисовки - ошибка дизайна. Причем ошибка дизайна WinApi, которая "воссоздана" в VCL.
Ошибка №3: Rect вводит в заблуждение
В коде из статьи
Rectвсегда равенClientRectПараметр Rect был добавлен для удобства, дабы из Sender не надо было вытягивать ClientRect для "заливки" цветом и т.д.. Кому не надо - могут не использовать.
Сравнение с официальными компонентами VCL
Рассмотрим компонент
TCustomPanel. Его методTCustomPanel::Paint()— переопределяет отрисовку, ноOnPaintостаётся стандартным по сигнатуре. УTGraphicControlрисуется напрямую вCanvasно не передаёт его в событие. АTDBGridэто вообще сложнейший компонент, но егоOnDrawColumnCellэто расширение, а не заменаOnPaint. Они не ломают контракт. Они расширяют функционал, добавляя новые события, если нужно, но не меняют старые.TCustomPanel - наследник TCustomControl и обязан соблюдать его контракт.
TEsCustomControl - не наследник TCustomControl и не должен соблюдать его контракт.
Если бы я унаследовал TEsCustomControl от компонента с событием OnPaint и поменял бы сигнатуру события, то да, я нарушил бы контракт OnPaint. Но я создал наследника от TWinControl, у которого нет никакого OnPaint, контракт которого я должен был бы соблюдать.
Совпали имена событий в разных "ветках"? Да. Бывает. Это нормально.
Если приходится писать отдельный обработчик, который больше ни с чем не работает, если приходится помнить, что «тут Canvas передаётся, а тут — нет», или если замена
TWinControlна ваш класс ломает половину формы — вы не упростили ему жизнь. Вы просто переложили свою головную боль на него.Да, при использовании TEsCustomControl приходиться задуматься, когда видишь другую сигнатуру OnPaint - это фича. Приходит понимание, почему рисовать на Canvas компонента можно только в OnPaint. И почему Canvas доступный "всегда" - это "багофича".
А суть хорошего компонента как раз в другом: пусть он делает всё сам, а пользователь рисует в
Canvas, как привык, и даже не догадывается, что под капотом — двойная буферизация, кэширование фона и прочая магия.Если из TEsCustomControl вытащить Canvas, и начать рисовать вне события OnPaint "как привык", что часто встречается в древнем коде, то будут какие угодно глюки.
Я сделал компонент с API запрещающим рисовать вне специально отведенного места. Это стандартный подход для GUI библиотек.
---
В конце концов, это всего лишь мой взгляд на "правильный" API OnPaint, если он не нравиться, то исходники открыты, можно сделать как удобно, пока соблюдается лицензия.
Смысл оригинальной статьи - дать набор идей и реализацию, которую каждый может доработать под себя. Вы доработали, нашли применение? - Отлично, поделитесь кейсом использования "в продакшене", как автору, это мне интереснее, чем абстрактная "красота" API.

1QDenisQ Автор
17.01.2026 02:49TCustomPanel - наследник TCustomControl и обязан соблюдать его контракт.
TEsCustomControl - не наследник TCustomControl и не должен соблюдать его контракт.
Если бы я унаследовал TEsCustomControl от компонента с событием OnPaint и поменял бы сигнатуру события, то да, я нарушил бы контракт OnPaint. Но я создал наследника от TWinControl, у которого нет никакого OnPaint, контракт которого я должен был бы соблюдать.
Совпали имена событий в разных "ветках"? Да. Бывает. Это нормально.
Ваш TEsWinControl является аналогом TCustomControl. Значит он должен соблюдать соглашение API.
Если из TEsCustomControl вытащить Canvas, и начать рисовать вне события OnPaint "как привык", что часто встречается в древнем коде, то будут какие угодно глюки.
Тогда необходимо расширять методы, менять название, чтобы не было внутренних противоречий со сложившей практикой написания сигнатур для обработчиков.
Смысл оригинальной статьи - дать набор идей и реализацию, которую каждый может доработать под себя. Вы доработали, нашли применение? - Отлично, поделитесь кейсом использования "в продакшене", как автору, это мне интереснее, чем абстрактная "красота" API.
Я бы рад выложить, но пока продукт ещё сырой и не готов выложить на публику

Error1024
17.01.2026 02:49Ваш TEsWinControl является аналогом TCustomControl. Значит он должен соблюдать соглашение API.
Вы точно не запутались с использованием нейросетей? У меня нет никакого TEsWinControl, у меня есть TEsCustomControl. TEsWinControl - это ваше изобретение, соблюдайте что хотите.
Мой TEsCustomControl является аналогом TCustomControl по концепции «кастомный компонент». Но TEsCustomControl не является наследником TCustomControl, и не должен соблюдать интерфейс TCustomControl, это ваша хотелка, не более.
Вы когда-нибудь слышали про концепцию ООП? Про наследование классов и т.д.? Вам должно быть известно, что наследник класса должен быть полностью работоспособным в коде ожидающим объект класса-предка. Но вот только вы требуете, чтобы TEsCustomControl соблюдал интерфейс «левого» TCustomControl, который не является предком TEsCustomControl. Предок TEsCustomControl это TWinControl, его «контракт» соблюден на 100%.
Иерархия классов, наследование, полиморфизм, вам знакомы эти слова? Вы точно профессионал и архитектор? Такое ощущение что вы базы ООП не понимаете.
Тогда необходимо расширять методы, менять название, чтобы не было внутренних противоречий со сложившей практикой написания сигнатур для обработчиков.
Что именно должен расширять TEsCustomControl? У его предка TWinControl нет события OnPaint. Я же в TEsCustomControl создал новое событие OnPaint, и создал его таким, каким захотел. И нет, ничего не сломал, в соответствии с базовыми принципами ООП, разрешающими добавлять в наследниках что угодно, пока оно не ломает интерфейс предка.

1QDenisQ Автор
17.01.2026 02:49Вы точно не запутались с использованием нейросетей? У меня нет никакого TEsWinControl, у меня есть TEsCustomControl. TEsWinControl - это ваше изобретение, соблюдайте что хотите.
Причем тут использование нейросетей? Я изменил изначальное TEsCustomControl на TEsWinControl короткое данное имя лучше ассоциируется.
Мой TEsCustomControl является аналогом TCustomControl по концепции «кастомный компонент». Но TEsCustomControl не является наследником TCustomControl, и не должен соблюдать интерфейс TCustomControl, это ваша хотелка, не более.
Вы когда-нибудь слышали про концепцию ООП? Про наследование классов и т.д.? Вам должно быть известно, что наследник класса должен быть полностью работоспособным в коде ожидающим объект класса-предка. Но вот только вы требуете, чтобы TEsCustomControl соблюдал интерфейс «левого» TCustomControl, который не является предком TEsCustomControl. Предок TEsCustomControl это TWinControl, его «контракт» соблюден на 100%.
Иерархия классов, наследование, полиморфизм, вам знакомы эти слова? Вы точно профессионал и архитектор? Такое ощущение что вы базы ООП не понимаете.
Как связаны по вашему концепции наличие соглашение API и знание ООП? Я разве отрицал какие-то парадигмы и говорил что они неправильные? Не выдумывайте себе
Что именно должен расширять TEsCustomControl? У его предка TWinControl нет события OnPaint. Я же в TEsCustomControl создал новое событие OnPaint, и создал его таким, каким захотел. И нет, ничего не сломал, в соответствии с базовыми принципами ООП, разрешающими добавлять в наследниках что угодно, пока оно не ломает интерфейс предка.
Я вам говорю, о том, что расширение/изменение сигнатуры у обработчиков, с точки зрения дизайна VCL, не ломает общую логическую цепочку. Если у класса есть метод OnPaint, то этот метод всегда имеет одну сигнатуру, в противном случае, меняется название. Об этом статья. Вы зациклились на одном слове "API" и всякую фигню выдумываете

Error1024
17.01.2026 02:49Сейчас вы пишите:
Причем тут использование нейросетей? Я изменил изначальное TEsCustomControl на TEsWinControl короткое данное имя лучше ассоциируется.
Но до этого написали:
Ваш TEsWinControl является аналогом TCustomControl. Значит он должен соблюдать соглашение API.
Противоречие не находите? Сделали некий TEsWinControl, а мне пишите про «недостатки» TEsCustomControl.
Как связаны по вашему концепции наличие соглашение API и знание ООП? Я разве отрицал какие-то парадигмы и говорил что они неправильные? Не выдумывайте себе
Да, вы пишите, что я что-то там «сломал», хотя TEsCustomControl строго соблюдает интерфейс своего предка TWinControl. Вы же ожидаете соответствие интерфейсу TCustomControl, от которого мой класс НЕ наследуется.
Я вам говорю, о том, что расширение/изменение сигнатуры у обработчиков, с точки зрения дизайна VCL, не ломает общую логическую цепочку. Если у класса есть метод OnPaint, то этот метод всегда имеет одну сигнатуру, в противном случае, меняется название. Об этом статья. Вы зациклились на одном слове "API" и всякую фигню выдумываете
Где конкретно, в официальной документации Embarcadero, написано что OnPaint обязан иметь конкретную «стандартную» сигнатуру? Это ваше видение «красоты», не более. У меня оно свое - Canvas - не должен «торчать» в паблике.

Error1024
17.01.2026 02:49И да:
Причем тут использование нейросетей? Я изменил изначальное TEsCustomControl на TEsWinControl короткое данное имя лучше ассоциируется.
У делфового TWinControl нет никаких Canvas и OnPaint, вы нарушили ожидание разработчика о том что у TWinControl нет данных свойств :)
Т.е. в своем WinControl вы симитировали интерфейс CustomControl. Неожиданно знаете ли :)

Error1024
17.01.2026 02:49И еще:
__fastcall TEsPaint::TEsPaint(TComponent* Owner) : TEsWinControl(Owner) {OnPaint = FormPaintCanvas;};Контракт VCL таков, что события OnXXX предназначены для пользователя компонента(программиста который кидает его на форму), не для создания наследников.
Наследники должны перекрывать виртуальные методы.
В случае с TEsCustomControl (у TCustomControl кстати тоже) это метод Paint:
/// <summary>/// Descendantsmustoverride this method for custom rendering/// </summary>procedure Paint; virtual;Таков контракт, вы его нарушили.

1QDenisQ Автор
17.01.2026 02:49Да, я согласен, что в данном случае я некорректно воспользовался методами и действительно надо было использовать метод Paint(), а не обработчиком событий

Error1024
17.01.2026 02:49За много денег вы можете получить от меня консультацию по основам создания VCL компонентов, писать в ЛС.
HemulGM
Передача Canvas в событии удобна тем, что не нужно кастить Sender к типу контрола и обращаться к его канве. Тем более, что Sender может даже не быть самим этим контролом. А канвас в VCL у каждого контрола свой.
1QDenisQ Автор
Зачем выполнять лишнее приведение типа (
static_cast)Senderк конкретному контролу, если нужная канва уже передаётся__poperty Canvas = { read = FCanvas }достаточно взять единственно поле которая должна быть единой точкой истины? Кроме того, вызывать событиеOnPaintодного контрола через другой — архитектурно некорректно. Каждый контрол отвечает за собственную отрисовку, и егоCanvasпривязан именно к нему.HemulGM
В обработчике события нет никакого прямого доступа. Это же событие. Оно может быть обратно кем угодно и когда угодно.
1QDenisQ Автор
Нет прямого доступа к чему? Если вы имеете ввиду Canvas, то я прошу вас первым делом ознакомиться с исходниками класса. Уточняйте свой ответ
С точки зрения использование кода - да, но с точки зрения построения архитектуры нет. И как вы это вообще представляете
Вы что, собирается вручную вызывать метод
OnPaintи вручную передавать контролMyControl1->OnPaint(MyControl2);?У ваше контрала должен быть собственный обработчик отрисовки. И он обычно вручную не вызывается. Используется Invalidate(...) или Update()
HemulGM
Вот у вас есть некий контрол с OnPaint событием, которое просто TNotifyEvent. И есть у вас некий контроллер, который будет использовать контролы и задавать обработчики событий контролам формы (вашего представления).
При подключении представления, будет написано
MyControl.OnPaint := FMyControlPaint;,где FMyControlPaint обработчик события отрисовки этого контрола. Внутри этого обработчика нет никакого доступ к контролу, кроме как через Sender и тем более, нет никакого прямого доступа к Canvas.
При этом, обращаться просто к MyControl - нельзя, ведь я могу использовать этот обработчик и для других таких контролов.
Сам контрол, вообще никогда не должен сам использовать свой OnPaint, потому что он генерирует это событие для работы с контролом именно снаружи. Контрол должен использовать напрямую метод Paint, который он должен перекрыть и рисовать что нужно.
HemulGM
Просто добавьте любой контрол на форму и создайте обработчик события отрисовки. Покажите как вы будете обращаться к Canvas напрямую.
1QDenisQ Автор
Сам
TEsWinControlуже содержит свой экземпляр Canvas, он не устанавливается извне. То естьMyControl1,MyControl2,MyControl3,MyControl4содержат свои Canvas. Зачем определять кому чей?Я вам ещё раз говорю, что является членом класса
__property Canvas = { read = FCanvas }. Как он тогда вообще может быть не доступен в принципе, если он передается явно в механизме обработки сообщений?Он доступен для чтения, но не для модификации
HemulGM
Вы действительно не понимаете о чем я говорю? Читайте внимательно мой комментарий ещё раз. Создайте обработчик события отрисовки. И внутри него обратитесь к канвасу контрола в этом обработчике!! Попробуйте и покажите что получилось
1QDenisQ Автор
Вот пример из статьи
Canvasспокойно доступен. В чём проблема?HemulGM
Вы делаете это внутри класса контрола, а не снаружи. События нужны для работы с контролом снаружи!
Обратитесь к контролу снаружи и назначьте ему обработчик события отрисовки.
Чтобы рисовать в контроле изнутри класса контрола вообще нельзя использовать событие отрисовки!
1QDenisQ Автор
Canvasконтрола доступен. Что дальше?HemulGM
А теперь попробуйте назначить этот обработчик сразу нескольким контролам
1QDenisQ Автор
У каждого контрола должен быть свой обработчик а не один контрол на всех. Вы же не носите одни трусы всей семьёй? Так и тут так же.
HemulGM
Зато мы используем одну общую дверь для входа в дом, а не каждый свою
1QDenisQ Автор
Дверью в дом, является конструктор класса и инициализация значений. А не выполнение кода (ношение белья)
HemulGM
Речь о том, что логика может быть разной и один обработчик событий для нескольких контролов - это обычное дело.
Генерировать десяток кнопок с одним обработчиком, который различает контроля, например по тегу, это база
1QDenisQ Автор
Тогда в данном случае необходимо использовать расширенный метод. К примеру
OnPaintExtс передачейCanvasиRect. Если вы говорите о том, что я сказал фигню, то ваши слова полностью позиционируются как отрицание корректности полностью всего фреймворка в целом. Расширяйте Control и добавляйте новый метод. МетодOnPaintявляется ядром отрисовки, и не может быть модифицирован с точки зрения архитектуры VCL.TForm,TButton,TLabel- у них у всех единый метод с одной сигнатурой. И эта сигнатура не меняется с точки зрения имени метода, так как создает согласованность и единообразие. По вашей логике, можно тогда вообще все члены класса передать. А какая разница. Удобно же. Вы себя позиционируете как профессиональный разработчик на Delphi, но профессиональный разработчик, это разработчик который следует не только коду, но и философии языка.HemulGM
Философия языка тут не при чем. Здесь речь о несовершенстве фреймворка VCL. OnPaint - обычное событие, которое является конечным и должно использоваться в конечном коде, на более высоком (пользовательском) уровне. Любые низкоуровневые вещи должны работать через наследование и перекрытие методов.
1QDenisQ Автор
Мне кажется, что мы не понимаем друг друга потому что взгляд лежит в разных областях к тому, как необходимо писать код. Моё понимание лежит в области разработчика VCL, которые поправляет баги, следует стандарту и принципам проектирования API. Вы видите код, как конечный потребитель, для которого каждый метод просто "обычный", который может просто взять и расширить сигнатуру, нарушив принцип наименьшего удивление, когда вы пишет 15 лет OnPaint с одной сигнатурой, а потом узнаете что там еще есть, то нам больше не о чем говорить. Тогда с вашей точки зрения, архитектура полного контрола является неккоректной.