Илья Ермаков писал(а):
Александр, не могу сказать, почему не срабатывает clean, возможно, в текстовых моделях это недоработано... Но ведь можно пойти обходным путем - до изменения размера запоминать "грязность", а затем принудительно ее возвращать в прежнее состояние...
Я обнаружил, почему не работает clean. Дело в том, что для оповещения Model->View об изменении данных я использовал свой тип сообщения, унаследованный от Models.UpdateMsg, как это рекомендуется в документации. Это была моя первая ошибка. Именно на сообщение типа Models.UpdateMsg стандартный сиквенсер модуля Windows реагировал установкой признака "загрязненности" документа. А я думал, что дело в изменении размера. Просто не мог протестировать раздельно эти два действия.
Но на этом проблемы мои не закончились. Когда я унаследовал мой UpdateMsg от Models.Message, clean заработал и документ перестал отмечаться как измененный. Но операция по изменению размера встроенного View по-прежнему была "отменяемой". Это значит, что если вы редактируете текстовый документ, в котором находятся активные объекты, то ваша редактура "разбавляется" их сообщениями, как будто это вы сами по ходу правки меняли их размеры. Потом вы хотите отменить последнее написанное слово, а вместо этого начинается установка прежних, уже неправильных, размеров разным компонентам. Постепенно пришло понимание, что никакая комбинация из clean, invisible и notUndoable не дает подходящего решения. Мне нужно было выполнить операцию, которая бы отсутствовала в пользовательском буфере отмены. Мне нужно было вообще выбросить эту операцию из буфера Undo/Redo. Ведь компонент все равно установит себе нужный размер при прорисовке его на экране, значит не нужно этот размер вообще помнить, и тем более следить за его изменениями.
Тут я с удивлением обнаружил неопубликованную константу "notRecorded = 3" в модуле Windows (там реализуется StdSequencer). Это оказалось именно тем, что мне нужно! Недостающий атрибут в цепочке clean - invisible - notUndoable. И вся функциональность уже реализована. Только почему-то спрятана, не задокументирована и использована "подпольно": в модуле Documents я нашел вызов BeginModification(3, ...), то есть, очередной случай "магического числа" вместо нормально объявленной константы.
Что касается того, когда и как следует устанавливать автоматический размер компонента, то на данный момент я остановился на следующем варианта, который считаю оптимальным. Здесь SetSize вызывается в отложенной манере из наследника Services.Action, но откладывается это не на неопределенный срок, а на самый короткий, так что и некрасивой заторможенности интерфейса нет, и со всеми стандартными контейнерами работает, и BlackBox насиловать не пришлось. Вот код:
Код:
TYPE
View* = POINTER TO RECORD (Views.View)
model: MateModels.Model;
setSizeAction: SetSizeAction (* Здесь хранится ссылка на последний запущенный SetSizeAction. Если было запущено несколько подряд, то сработает только последний. *)
END;
(** SetSizeAction используется для отложенной установки размера view. **)
SetSizeAction = POINTER TO RECORD (Services.Action)
view: View;
width, height: INTEGER
END;
PROCEDURE GetAutoSize (v: View; OUT w, h: INTEGER);
BEGIN
... вычисление размера
END GetAutoSize;
(* SetSizeAction *)
PROCEDURE (a: SetSizeAction) Do;
VAR m: Models.Model;
BEGIN
IF a = a.view.setSizeAction THEN
a.view.setSizeAction := NIL;
m := a.view.context.ThisModel();
Models.BeginModification(Models.notRecorded, m);
a.view.context.SetSize(a.width, a.height);
Models.EndModification(Models.notRecorded, m)
END
END Do;
PROCEDURE SetSizeLater (v: View; width, height: INTEGER);
VAR action: SetSizeAction;
BEGIN
Services.RemoveAction(v.setSizeAction);
NEW(action);
action.view := v;
action.width := width;
action.height := height;
Services.DoLater(action, Services.now);
v.setSizeAction := action
END SetSizeLater;
(* View *)
PROCEDURE CheckAutoSize (v: View; currWidth, currHeight: INTEGER);
VAR w, h: INTEGER;
BEGIN
GetAutoSize(v, w, h);
IF (w # currWidth) OR (h # currHeight) THEN
SetSizeLater(v, w, h)
END
END CheckAutoSize;
PROCEDURE AutoResize (v: View);
VAR w, h: INTEGER;
BEGIN
v.context.GetSize(w, h);
CheckAutoSize(v, w, h)
END AutoResize;
PROCEDURE (v: View) Restore* (f: Views.Frame; l, t, r, b: INTEGER);
VAR w, h: INTEGER;
BEGIN
v.context.GetSize(w, h);
CheckAutoSize(v, w, h);
......... код прорисовки
END Restore;
PROCEDURE (v: View) HandleModelMsg- (VAR msg: Models.Message);
BEGIN
WITH msg: MateModels.UpdateMsg DO
AutoResize(v)
END;
Views.Update(v, Views.keepFrames)
END HandleModelMsg;
В результате компонент всегда имеет правильный размер. Если оповещение приходит тогда, когда он видим на экране, то срабатывает HandleModelMsg. Если сообщение приходит, когда он спрятан за прокруткой, то HandleModelMsg не срабатывает, зато Restore обновляет размер при первой необходимости. А если компонент уже удален из контейнера, то, соответственно, никогда.
В принципе, внутри HandleModelMsg можно было бы сразу вызывать context.SetSize, не откладывая, но я решил не умножать количество мест, где это делается, без необходимости. К тому же Services.Action дает дополнительную защиту от trap'ов.