Пётр упомянул корневой фрейм типа Views.RootFrame, который возвращается отображением Documents.Document. Интересно, что структура типа корневого фрейма отличается от обычного фрейма Views.Frame только тем, что содержит регион обновления (и ещё какие-то флаги, которые, похоже, нигде не используются). И алгоритмы модуля Views жёстко контролируют применение типа RootFrame.
А ещё интересная штука - модуль HostBitmaps содержит, можно сказать, канонический паттерн применения корневого фрейма:
Код:
PROCEDURE Paint (dc: WinApi.HDC; v: Views.View; w, h, unit: INTEGER);
VAR d: RootView; c: RootContext; p: HostPorts.Port; f: Views.RootFrame; g: Views.Frame;
BEGIN
NEW(p);
p.Init(unit, Ports.screen);
p.SetSize(w, h);
p.SetDC(dc, 0);
NEW(c);
c.w := w * p.unit;
c.h := h * p.unit;
NEW(d);
d.view := Views.CopyOf(v, Views.shallow);
Stores.Join(d, d.view);
d.InitContext(c);
d.view.InitContext(c);
Stores.InitDomain(d);
d.GetNewFrame(g); f := g(Views.RootFrame); f.ConnectTo(p);
Views.SetRoot(f, d, FALSE, {});
Views.AdaptRoot(f);
Views.RestoreRoot(f, 0, 0, c.w, c.h);
END Paint;
Тут прямо всё как на тарелочке: порт, контекст, домен, корневой фрейм и перестройка дерева фреймов. Пройдусь поэтапно.
1) Мы хотим, чтобы наше отображение не участвовало в системном дереве фреймов, а отрисовалось непосредственно на устройстве (в нашем случае это винапишный контекст отображения DC, а эталонный хост ББ как раз умеет на нём рисовать).
2) Отображение рисует средствами Views.Frame, потомка Ports.Frame - который, как известно, занимается преобразованием из пространства документа в пространство отображения (маппер, да-да) и кроме этого, использует бегунки Ports.Rider для операций рисования.
3) Бегунки Ports.Rider опираются на носитель пиксельных данных Ports.Port. Следовательно, первым делом необходимо завести объект порта (для прикладного софта важно задать размер порта и единицы измерения, для платформенного софта можно ещё и контекст отображения указать):
Код:
NEW(p);
p.Init(unit, Ports.screen);
p.SetSize(w, h);
p.SetDC(dc, 0);
4) Концепция отображения подразумевает, что его поведение зависит от модели, в которую встроено это отображение. Связь между моделью и отображением реализует объект контекста. Поэтому создаём контекст и ему тоже надо задать размеры.
Код:
NEW(c);
c.w := w * p.unit;
c.h := h * p.unit;
5) Вот здесь как раз такое место, которое я ещё не очень понимаю. Особый тип отображения, RootView, является контейнером для нашего отображения. Оно отличается от стандартных тем, что на запрос GetNewFrame возвращает фрейм корневого типа Views.RootFrame. Остальное всё стандартно - инициализация контейнера, контекста, домена.
Код:
d.view := Views.CopyOf(v, Views.shallow);
Stores.Join(d, d.view);
d.InitContext(c);
d.view.InitContext(c);
Stores.InitDomain(d);
И я не понимаю, почему бы нашему отображению не возвращать корневой фрейм и таким образом, стать самому в корне, без прокладки RootView? Ведь маппером может быть любой фрейм, подключённый к носителю:
Код:
d.GetNewFrame(g); f := g(Views.RootFrame); f.ConnectTo(p);
6) В финале у нас инициализация корневого фрейма и сброс счётчиков регионов, "адаптация" (обрезка) фреймов-потомков по габаритам корня, перерисовка дерева фреймов.
Код:
Views.SetRoot(f, d, FALSE, {});
Views.AdaptRoot(f);
Views.RestoreRoot(f, 0, 0, c.w, c.h);