OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Пятница, 26 Апрель, 2024 09:08

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 14 ] 
Автор Сообщение
СообщениеДобавлено: Четверг, 23 Ноябрь, 2006 11:27 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
Всем привет!

Как я уже писал в теме "BlackBox Framework/ Активные компоненты", я работаю над собственной подсистемой Mate. У меня есть компонент MateViews.View, отображающий последнее полученное значение какого-либо параметра системы отопления. Например, "температура воздуха на первом этаже". В общем, число с плавающей точкой. Другой вариант - битовая маска вида "00101001" (8 бит). Поскольку для всех целей использую один и тот же компонент, хотел сделать так, чтобы его размер по ширине автоматически устанавливался в зависимости от отображаемого текста. Для этого нашел процедуру Models.Context.SetSize. Возникло 2 проблемы:

1. При каждом вызове SetSize документ-контейнер помечается как "грязный" (dirty), то есть, требующий сохранения. Но мои компоненты автоматически выставляют свой размер, так что на самом деле его сохранять не требуется. Попытки сделать изменение "чистым" не приводят к желаемому результату:

Код:
PROCEDURE (v: View) HandleModelMsg- (VAR msg: Models.Message);
  VAR w, h: INTEGER;
BEGIN
  WITH msg: MateModels.UpdateMsg DO
    GetAutoSize(v, w, h);
    Models.BeginModification(Models.clean, v.context.ThisModel());
    v.context.SetSize(w, h);
    Models.EndModification(Models.clean, v.context.ThisModel());
  ELSE
  END;
  Views.Update(v, Views.keepFrames)
END HandleModelMsg;

При этом если вместо Models.clean подставить Models.notUndoable или Models.invisible, то изменение размера становится невозможно отменить либо оно становится невидимым соответственно. Значит, эти модификаторы влияют на характеристику операции. Но Models.clean не работает! Это что, глюк или так и было задумано? Почему так может быть?

2. Я не совсем понял, как правильно делать поправку на рамку при вычислении автоматического размера для компонента. Чтобы узнать размер текста, нужно запросить текущий шрифт контекста, потом померить строку с помощью Fonts.Font.GetBounds и Fonts.Font.StringWidth. Чтобы нарисовать рамку толщиной в один пиксель, нужно использовать Frames.Frame.dot. А как заранее посчитать размер компонента вместе с рамкой, когда Frames.Frame еще нет? Хотелось бы иметь решение, которое бы работало для любого Frame, в том числе при печати на лазерном принтере. Наверное, нужно отвязаться от ширины "пикселя", но альтернативы в голову не приходят... 1/12 высота буквы? Что взять за ориентир, буду признателен за любой совет.

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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Четверг, 23 Ноябрь, 2006 12:01 
Модератор
Аватара пользователя

Зарегистрирован: Понедельник, 14 Ноябрь, 2005 18:39
Сообщения: 9459
Откуда: Россия, Орёл
Александр, не могу сказать, почему не срабатывает clean, возможно, в текстовых моделях это недоработано... Но ведь можно пойти обходным путем - до изменения размера запоминать "грязность", а затем принудительно ее возвращать в прежнее состояние... Правда, стандартная SetDirty позволяет выставлять только в TRUE, ну так можно позаимствовать более низкоуровневый код из ее нутра:
Код:
   PROCEDURE SetDirty* (m: Model);
   (** pre: m # NIL **)
      VAR seq: ANYPTR;
   BEGIN
      ASSERT(m # NIL, 20);
      IF m.Domain() # NIL THEN seq := m.Domain().GetSequencer() ELSE seq := NIL END;
      IF seq # NIL THEN
         WITH seq: Sequencers.Sequencer DO
            seq.SetDirty(TRUE)
         ELSE
         END
      END
   END SetDirty;


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Четверг, 23 Ноябрь, 2006 12:25 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
Илья Ермаков писал(а):
Александр, не могу сказать, почему не срабатывает clean, возможно, в текстовых моделях это недоработано...

На формах тоже не работает, не только в тексте, так что больше похоже на глобальный заговор.
Илья Ермаков писал(а):
Но ведь можно пойти обходным путем - до изменения размера запоминать "грязность", а затем принудительно ее возвращать в прежнее состояние... Правда, стандартная SetDirty позволяет выставлять только в TRUE, ну так можно позаимствовать более низкоуровневый код из ее нутра...

Спасибо за вариант! Вечером попробую... : )


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 23 Ноябрь, 2006 14:32 

Зарегистрирован: Понедельник, 28 Ноябрь, 2005 18:08
Сообщения: 76
Александр Ильин писал(а):
... я работаю над собственной подсистемой Mate. У меня есть компонент MateViews.View, отображающий последнее полученное значение какого-либо параметра системы отопления...

Я тоже делаю систему, аналогичную вашей: связь по Netbios, для одновременности - Services.Action, активные компоненты, как вы их назвали.... Но я не меняю размеры в HandleModelMsg, я делаю это только в Restore:
Код:
        TYPE
           ......
      Context = POINTER TO RECORD (Models.Context)
         w, h: INTEGER;
         view: Views.View;
         base: Views.View
      END;
           ......
   (** Context **)

   PROCEDURE (c: Context) GetSize(OUT w, h: INTEGER);
   BEGIN
      w := c.w;
      h := c.h
   END GetSize;
   
   PROCEDURE (c: Context) SetSize(w, h: INTEGER);
      VAR msg: Properties.SizePref;
   BEGIN
      msg.h := h; msg.w := w;
      msg.fixedH := FALSE; msg.fixedW := FALSE;
      Views.HandlePropMsg(c.view, msg);
      c.w := msg.w; c.h := msg.h
   END SetSize;
.....
        PROCEDURE (v: StdView) Restore (f: Views.Frame; l, t, r, b: INTEGER);
        BEGIN
           ...
      (* Log *)
      v.textView.context.SetSize(rect_width, rect_height);
      Views.InstallFrame(f, v.textView, x, y, 0, TRUE);
           ...
       END Restore;

Хотелось бы, конечно, не создавать собственную вьюшку, состоящую из многих встроенных.. а разместить эти мои 12 встроенных на стандартную форму (чтобы менять их как захочется - и размеры и местоположение), только для этого надо интеракторы делать.. да ещё и лог текстовой, к нему тоже надо... а как это делать - ещё не знаю. Каждый раз переписывать код для изменения положения одной встроенной вьюшки относительно другой - очень не хочется. Зато масштабирование есть, чего на обычной форме не будет.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Четверг, 30 Ноябрь, 2006 01:37 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
Я попытался сделать установку размера в Restore, получил разнообразные trap'ы в процессе прорисовки. К сожалению, воспроизвести на своей машине не могу, поэтому решил спросить у вас. Насколько я понял ваш пример, у вас некий контейнер устанавливает размеры для содержащихся в нем элементов. Это позволяет вам вызывать InstallFrame.

У меня компонент не является контейнером, соответственно, он пытается изменять свой собственный, а не чужой, размер. И делает он это прямо во время прорисовки. При этом, если размер изменился, должен быть создан новый фрейм. Насколько я понял причину ошибки, после изменения размера прежний фрейм (переданный в Restore) может оказаться неправильным.
Вот как сделано у меня сейчас:
Код:
   PROCEDURE (v: View) Restore* (f: Views.Frame; l, t, r, b: INTEGER);
      VAR font: Fonts.Font; w, h, left: INTEGER;
   BEGIN
      (* Попробуем установить желаемый размер *)
      GetAutoSize(v, w, h);
      v.context.SetSize(w, h);
      (* Узнаем реальный размер *)
      v.context.GetSize(w, h);
      font := FontContext(v);
      left := font.StringWidth(' ') DIV 2;
      IF v.model.textValue = NIL THEN
         f.DrawSString(left, h - 2 * f.dot, Ports.black, noData, FontContext(v));
      ELSE
         f.DrawSString(left, h - 2 * f.dot, Ports.black, v.model.textValue, FontContext(v));
      END;
      f.DrawRect(0, 0, w, h, f.dot, Ports.black)
   END Restore;

Можете подсказать, как сделать правильно? Или это невозможно в BlackBox?


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Суббота, 02 Декабрь, 2006 19:47 
Аватара пользователя

Зарегистрирован: Пятница, 25 Ноябрь, 2005 12:02
Сообщения: 8500
Откуда: Троицк, Москва
В таком духе:


PROCEDURE ( v: View ) HandlePropMsg- ( VAR p: Properties.Message ); BEGIN
WITH p: Properties.SizePref DO
p.w := 70 * mm; p.h := 10 * mm
ELSE
END;
END HandlePropMsg;


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Суббота, 02 Декабрь, 2006 21:05 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
info21 писал(а):
В таком духе:...

К сожалению, SizePref не присылается каждый раз перед перерисовкой объекта (по крайней мере, контейнером подсистемы Text), так что этот вариант не работает. Мне нужно, чтобы объект автоматически изменял свой размер при изменении отображаемых данных. Вроде логично было бы изменять размер при получении сообщения от модели (HandleModelMsg). Но сообщения от модели НЕ ПРИХОДЯТ к тем View, которые не видны в данный момент на экране. На практике это означает, что вы прокручиваете документ, чтобы увидеть скрытые параметры, а видите только половину числа до тех пор, пока значение данного параметра не обновится.

На данный момент я вижу только одно место, где можно изменять размер View - это процедура View.Restore. Но здесь получается, что мы поздновато спохватились: пора рисовать, а Frame уже выделен не того размера.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Суббота, 02 Декабрь, 2006 21:17 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
Эврика! Отлично работает вот такой код:
Код:
   PROCEDURE (v: View) HandleViewMsg- (f: Views.Frame; VAR msg: Views.Message);
      VAR w, h: INTEGER;
   BEGIN
      WITH msg: Views.NotifyMsg DO
         GetAutoSize(v, w, h);
         v.context.SetSize(w, h)
      ELSE
      END
   END HandleViewMsg;

Оповещение Views.NotifyMsg приходит в режиме отложенного обновления, несколько раз в секунду. Этого вполне достаточно... Точнее, это, конечно, "удовлетворительно", но никак не "отлично". "Отлично" было бы, если при прокрутке на экране сразу появлялись элементы с правильным размером (как это было при установке размера в Restore или как это было БЫ, если бы скрытые элементы получали сообщения от моделей). А так получается, что правильный размер элемента устанавливается с некоторой задержкой. К тому же, вследствие уникального способа работы с прокруткой в BlackBox, этот момент можно отложить на неопределенный срок, удерживая кнопку мыши.

Другими словами, решение найдено, но пользовательский интерфейс не на высоте. : (


Последний раз редактировалось Александр Ильин Понедельник, 04 Декабрь, 2006 19:15, всего редактировалось 1 раз.

Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Суббота, 02 Декабрь, 2006 21:51 
Модератор
Аватара пользователя

Зарегистрирован: Понедельник, 14 Ноябрь, 2005 18:39
Сообщения: 9459
Откуда: Россия, Орёл
Цитата:
На данный момент я вижу только одно место, где можно изменять размер View - это процедура View.Restore. Но здесь получается, что мы поздновато спохватились: пора рисовать, а Frame уже выделен не того размера.


Может быть, попробовать расширить View.GetNewFrame - и в этот момент изменять размер и давать кадр сразу нужного размера?


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Понедельник, 04 Декабрь, 2006 00:38 
Аватара пользователя

Зарегистрирован: Пятница, 25 Ноябрь, 2005 12:02
Сообщения: 8500
Откуда: Троицк, Москва
Александр Ильин писал(а):
пользовательский интерфейс не на высоте(


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

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

Разумеется, это не значит, что библиотеки ББ идеальны. Из своих упражнений со вьюшками, например, я извлек такую мораль (где-то тут о ней сообчал в ответ на постинги С.Губанова), что хорошо бы иметь регулярный способ рассылки сообщений по Store'ам конкретного домена. Такой способ -- естественная (и, в сущности, совсем небольшая) "регуляризация" механизмов модуля Stores.

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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Понедельник, 04 Декабрь, 2006 19:20 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
info21 писал(а):
Предлагаю все же помнить железное правило, чтобы не впадать в ненужный пафос :) -- а то неискушенный народ пугается: невозможно создать библиотеку на все случаи жизни для сложного класса приложений, тем более такого сложного, как GUI.

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

Полностью согласен.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Вторник, 02 Январь, 2007 04:00 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
info21 писал(а):
Разумеется, это не значит, что библиотеки ББ идеальны. Из своих упражнений со вьюшками, например, я извлек такую мораль (где-то тут о ней сообчал в ответ на постинги С.Губанова), что хорошо бы иметь регулярный способ рассылки сообщений по Store'ам конкретного домена. Такой способ -- естественная (и, в сущности, совсем небольшая) "регуляризация" механизмов модуля Stores.

Я плохо знаком с этой областью, но копаясь сегодня в "недрах", обнаружил, что есть некий механизм Sequencers.Sequencer.Notify. Если я правильно понял, то сиквенсер связан как раз с доменом, ведь он заведует операциями (Undo/Redo). А подписаться на рассылку сообщений может каждый с помощью view.Domain().GetSequencer()(Sequencers.Sequencer).InstallNotifier. Разослать своего наследника Sequencers.Message - аналогично. В общем, в этом направлении можно поисследовать. К сожалению, модуль Sequencers не документирован. За примером перехвата оповещения о закрытии окна можно обратиться к подсистеме Reg на сайте Зинна.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Вторник, 02 Январь, 2007 04:03 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
Илья Ермаков писал(а):
Цитата:
На данный момент я вижу только одно место, где можно изменять размер View - это процедура View.Restore. Но здесь получается, что мы поздновато спохватились: пора рисовать, а Frame уже выделен не того размера.

Может быть, попробовать расширить View.GetNewFrame - и в этот момент изменять размер и давать кадр сразу нужного размера?

К сожалению, GetNewFrame не решает, какого размера кадр ему давать, только создает объект нужного типа. Размер встроенного компонента всегда определяет контейнер, это правило обойти вряд ли удастся.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения:
СообщениеДобавлено: Вторник, 02 Январь, 2007 05:16 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
Илья Ермаков писал(а):
Александр, не могу сказать, почему не срабатывает 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'ов.


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 14 ] 

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 11


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Вся информация, размещаемая участниками на конференции (тексты сообщений, вложения и пр.) © 2005-2024, участники конференции «OberonCore», если специально не оговорено иное.
Администрация не несет ответственности за мнения, стиль и достоверность высказываний участников, равно как и за безопасность материалов, предоставляемых участниками во вложениях.
Без разрешения участников и ссылки на конференцию «OberonCore» любое воспроизведение и/или копирование высказываний полностью и/или по частям запрещено.
Powered by phpBB® Forum Software © phpBB Group
Русская поддержка phpBB