Руководство программиста
Глава 2. Создание моделей блоков
§2.12. Реакция блоков на действия пользователя
Рассматриваются различные способы организации взаимодействия блоков схемы с пользователем: реакция на мышь и клавиатуру, добавление новых пунктов в контекстное меню блока и системное меню RDS. Отдельно описываются реакции на действия пользователя при редактировании схемы: добавление блоков, их удаление, изменение их размеров и т.п.
§2.12.1. Реакция на мышь
Рассматриваются возможные реакции модели блока на действия пользователя мышью. Приводится пример блока, позволяющего щелчками по верхней и нижней части его изображения увеличивать и уменьшать значение его выхода. Затем в модель блока добавляется реакция на щелчки по активным элементам векторной картинки.
Основной способ организовать взаимодействия схемы с пользователем – это разрешить некоторым блокам схемы (рукояткам, кнопкам, полям ввода и т.п.) реагировать на нажатие и отпускание кнопок мыши и перемещение ее курсора. В режиме редактирования мышь используется для перетаскивания блоков, рисования связей и прочих действий по изменению схемы, поэтому RDS позволяет моделям блоков реагировать на мышь только в режимах моделирования и расчета. Для того, чтобы модель блока вызывалась в этих случаях, должны одновременно выполниться три условия:
- курсор мыши должен находиться в пределах изображения блока (точнее, его описывающего прямоугольника);
- блок должен находиться на слое, для которого разрешено редактирование;
- в параметрах блока должна быть разрешена реакция на мышь.
Из первого условия есть исключение: при необходимости, установив специальный флаг в структуре данных блока RDS_BLOCKDATA, модель может захватить мышь, чтобы получать информацию о перемещениях курсора даже после того, как он покинет пределы изображения блока (подобный пример будет рассмотрен в §2.12.2). Отключение редактирования слоя не только запрещает пользователю перемещать блоки и менять их параметры в режиме редактирования, но и блокирует реакции их моделей на мышь в режимах моделирования и расчета. Это позволяет, например, сделать какую-либо группу полей ввода отладочной, и защитить их от случайного изменения. Разрешение или запрещение реакции на мышь в параметрах блока позволяет, например, использовать блоки с одной и той же моделью и как индикаторы, и как поля ввода.
В примере блока, создающего панель в окне подсистемы, уже встречалась реакция модели на двойной щелчок мышью по изображению блока, но там она играла только вспомогательную роль, поэтому ей было уделено мало внимания. Теперь более подробно рассмотрим обработку нажатия кнопки мыши на простом примере: создадим блок, который будет увеличивать значение своего целого выхода «v» на единицу при каждом щелчке левой кнопкой мыши по верхней части его изображения, и уменьшать его на единицу при каждом щелчке по нижней части.
Блоку нужен единственный целый выход, поэтому структура переменных блока будет выглядеть следующим образом:
| Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|---|---|
| 0 | Start | Сигнал | 1 | Вход | ✓ | 0 |
| 1 | Ready | Сигнал | 1 | Выход | 0 | |
| 2 | v | int | 4 | Выход | 0 |
Модель блока будет достаточно простой:
// Увеличение/уменьшение значения по щелчку extern "C" __declspec(dllexport) int RDSCALL PlusMinus(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { // Макроопределения для статических переменных #define pStart ((char *)(BlockData->VarTreeData)) #define Start (*((char *)(pStart))) #define Ready (*((char *)(pStart+RDS_VSZ_S))) #define v (*((RDSINT32 *)(pStart+2*RDS_VSZ_S))) // Вспомогательная — указатель на структуру события мыши RDS_PMOUSEDATA mouse; switch(CallMode) { // Проверка типа статических переменных case RDS_BFM_VARCHECK: return strcmp((char*)ExtParam,"{SSI}")? RDS_BFR_BADVARSMSG:RDS_BFR_DONE; // Реакция на нажатие кнопки мыши case RDS_BFM_MOUSEDOWN: // Приведение ExtParam к нужному типу mouse=(RDS_PMOUSEDATA)ExtParam; if(mouse->Button==RDS_MLEFTBUTTON) { // Нажата левая кнопка if(mouse->y<mouse->Top+mouse->Height/2) v++; // В верхней половине блока - увеличиваем else v--; // В нижней половине блока — уменьшаем // Взводим сигнал готовности Ready=1; } break; } return RDS_BFR_DONE; // Отмена макроопределений #undef v #undef Ready #undef Start #undef pStart } //=========================================
Кроме стандартной проверки типа переменных,блок реагирует еще только на одно событие: нажатие какой-либо кнопки мыши RDS_BFM_MOUSEDOWN. При вызове модели в этом режиме в параметре ExtParam передается указатель на структуру RDS_MOUSEDATA, описывающую произошедшее событие:
typedef struct { int x,y; // Координаты курсора мыши на рабочем поле int BlockX,BlockY; // Координаты точки привязки блока с учетом // связи с переменными int Left,Top; // Верхний левый угол зоны блока int Width,Height; // Ширина и высота зоны блока int IntZoom; // Масштаб окна подсистемы в процентах DWORD Button; // Кнопка мыши (константа RDS_M*) DWORD Shift; // Флаги клавиш (RDS_M*, RDS_K*) double DoubleZoom; // Масштаб в долях единицы int MouseEvent; // Причина вызова (константа RDS_BFM_MOUSE*) } RDS_MOUSEDATA; typedef RDS_MOUSEDATA *RDS_PMOUSEDATA;
Для упрощения доступа к полям этой структуры указатель на нее, приведенный к нужному типу, записывается во вспомогательную переменную mouse.
Сейчас нас в этой структуре будут интересовать координаты курсора (x,y), координаты и размеры описывающего прямоугольника блока в текущем масштабе (Left, Top, Width и Height), а также нажатая кнопка (Button). Увеличивать и уменьшать выход блока нужно только при щелчках левой кнопки, поэтому, прежде всего, поле mouse->Button сравнивается с константой RDS_MLEFTBUTTON, обозначающей левую кнопку мыши. Далее модель сравнивает вертикальную координату мыши mouse->y с вертикальной координатой центра описывающего прямоугольника блока mouse->Top+mouse->Height/2 (координата верхней границы плюс половина высоты). Если координата курсора меньше этого значения, значит, щелчок пришелся на верхнюю часть блока, и значение выхода нужно увеличить. В противном случае значение уменьшается – щелчок пришелся на нижнюю часть. После этого сигналу готовности блока Ready присваивается единица, чтобы в ближайшем такте расчета сработали связи, присоединенные к выходу блока v.
Для проверки работы созданной модели необходимо разрешить реакцию на мышь в параметрах блока, к которому она подключена (рис. 73), и присоединить к его выходу числовой индикатор.
Рис. 73. Включение реакции на мышь в параметрах блока
В качестве изображения блока можно выбрать прямоугольник с двумя текстовыми строчками «+1» и «–1», расположенными друг под другом. В режиме расчета при щелчке по верхней части блока значение индикатора, соединенного с выходом блока, будет увеличиваться, а при щелчке по верхней – уменьшаться.
Модель этого блока получилась достаточно простой – мы обходимся без класса личной области данных, и все действия выполняем непосредственно в функции модели, внутри макроопределений для статических переменных. Здесь, однако, следует внимательно следить за возможными конфликтами имен макросов переменных и используемых в функции имен переменных и полей структур. Представим себе, что выход данного блока мы назвали бы не v, а y. Это простое, на первый взгляд, переименование приведет к тому, что компиляция функции станет невозможной. Дело в том, что у используемой в модели структуры RDS_MOUSEDATA есть поле с точно таким же именем – «y». При обработке текста программы препроцессором языка C имя «y» будет воспринято как макроопределение и раскрыто, в результате чего выражение «mouse->y» будет преобразовано в «mouse->(*((int*)(pStart+2)))», которое, естественно, не будет понято компилятором. Ошибки, выданные компилятором при разборе такого выражения, могут показаться программисту, особенно начинающему, довольно странными, тем более, что в тексте компилируемой программы написано, на первый взгляд, правильное выражение mouse->y, а результат работы препроцессора обычно в явном виде нигде не отображается. Для того, чтобы избежать подобных проблем, в более-менее сложных моделях блоков имеет смысл выносить все действия за пределы главной функции модели с ее макроопределениями, оформляя эти действия как обычные функции или функции-члены класса личной области данных.
Рис. 74. Ввод идентификатора
элемента картинки
Созданный нами блок можно использовать в качестве простейшего органа управления для увеличения и уменьшения какого-либо параметра схемы. При этом его работа не будет зависеть от его внешнего вида: как бы он ни выглядел, щелчок по его верхней части будет увеличивать параметр, щелчок по нижней – уменьшать. Можно, например, написать в прямоугольнике с текстом не «+1» и «–1», а «увеличить» и «уменьшить», или задать блоку векторную картинку со стрелками вверх и вниз. Однако, модель блока написана так, что эти стрелки или надписи обязательно должны располагаться друг под другом, одна – в верхней половине прямоугольника блока, другая – в нижней. Чтобы разместить их, например, слева и справа, придется переделывать модель.
Чтобы сделать блок более гибким, можно проверять, в какой конкретный элемент векторной картинки попал курсор мыши (если, конечно у блока есть картинка). RDS позволяет присваивать каждому элементу картинки какое-либо целое число, и модель блока, вызвав одну из специальных сервисных функций, может получить число, соответствующее элементу по заданным координатам или под курсором мыши. Если присвоить элементам картинки, символизирующим увеличение параметра (например, стрелке вверх) одно число, а символизирующим уменьшение (стрелке вниз) – другое, модель сможет правильно реагировать на щелчки по этим элементам, независимо от того, как они расположены.
Будем присваивать частям картинки, щелчок по которым должен привести к увеличению и уменьшению переменной «v», числа-идентификаторы 1 и −1 соответственно. Все остальные элементы картинки будут иметь идентификатор 0 (он присваивается по умолчанию). При отсутствии у блока векторной картинки модель будет вести себя, как и раньше: щелчок по верхней части изображения будет увеличивать «v», щелчок по нижней – уменьшать. Поместим на картинку блока (рис. 74) вытянутый по горизонтали белый прямоугольник (идентификатор 0), в левой части которого будет находиться зеленый квадрат (идентификатор 1) с наложенным на него треугольником, направленным вверх (идентификатор 1), а в правой – красный квадрат (идентификатор −1) с направленным вниз треугольником (идентификатор −1). Идентификаторы квадрата и наложенного на него треугольника должны совпадать, чтобы оба элемента воспринимались моделью блока как единое целое.
Теперь внесем изменения в модель блока. В реакцию на событие RDS_BFM_MOUSEDOWN нужно добавить проверку наличия у блока векторной картинки, и, если она есть, считывание идентификатора ее элемента, на который пришелся щелчок левой кнопкой (изменения выделены цветом):
// Реакция на нажатие кнопки мыши case RDS_BFM_MOUSEDOWN: // Приведение ExtParam к нужному типу mouse=(RDS_PMOUSEDATA)ExtParam; if(mouse->Button==RDS_MLEFTBUTTON) { // Нажата левая кнопка // Проверяем, есть ли у блока картинка (получаем описание блока) RDS_BLOCKDESCRIPTION descr; descr.servSize=sizeof(descr); rdsGetBlockDescription(BlockData->Block,&descr); if(descr.Flags & RDS_BDF_HASPICTURE) { // Картинка есть – определяем идентификатор // элемента под курсором int id=rdsGetMouseObjectId(mouse); switch(id) { case 1: v++; break; case -1: v--; break; } } else if(mouse->y<mouse->Top+mouse->Height/2) v++; // В верхней половине блока - увеличиваем else v--; // В нижней половине блока — уменьшаем // Взводим сигнал готовности Ready=1; } break;
Убедившись, что нажата левая кнопка мыши, модель запрашивает у RDS описание блока, который она обслуживает. Для этого используется уже знакомая нам по примерам структура RDS_BLOCKDESCRIPTION – ранее мы использовали ее для получения текста комментария блока. В служебное поле servSize этой структуры записывается ее размер (так RDS контролирует правильность переданного параметра), после чего вызывается сервисная функция rdsGetBlockDescription, которая записывает описание блока, идентификатор которого передан в первом параметре функции, в структуру, указатель на которую передается во втором. Нас интересует битовый флаг RDS_BDF_HASPICTURE поля Flags: если он взведен, у блока есть векторная картинка. Целая константа RDS_BDF_HASPICTURE, описанная в «RdsDef.h», имеет единственный единичный бит в позиции интересующего нас флага. Выполняя операцию побитового «ИЛИ» над полем Flags и этой константой, мы получим нулевой результат, если флаг сброшен, и не нулевой в противном случае. Таким образом, оператор
if(descr.Flags & RDS_BDF_HASPICTURE)
выполнится только в том случае, если для блока задана векторная картинка. В противном случае будет выполняться старая часть модели (после else), определяющая попадание курсора в верхнюю/нижнюю половину изображения.
Если картинка у блока есть, модель вызывает сервисную функцию rdsGetMouseObjectId, передавая ей указатель на структуру описания события мыши RDS_MOUSEDATA. Функция берет из этой структуры необходимые ей параметры и возвращает идентификатор элемента картинки блока, находящегося под курсором мыши, который присваивается вспомогательной переменной id. В данном случае id может принимать всего три значения: 1 (курсор мыши попал в зеленый квадрат или лежащий на нем треугольник), −1 ( курсор попал в красный квадрат или его треугольник) и 0 (курсор не попал в указанные элементы). Если id равен 1 или −1, выход блока соответственно увеличивается или уменьшается на единицу.
Теперь щелчок по зеленому квадрату будет увеличивать выход блока, по красному – уменьшать. Причем, если в редакторе картинки поменять эти квадраты местами, расположить их друг под другом, или вообще в произвольных местах картинки, работа блока не нарушится – модель опознает не какие-то жестко заданные области картинки, а идентификаторы ее элементов, где бы они не находились.
Можно пойти еще дальше, и переделать модель следующим образом:
if(descr.Flags & RDS_BDF_HASPICTURE) { // Картинка есть – определяем идентификатор // элемента под курсором int id=rdsGetMouseObjectId(mouse); v+=id; }
Теперь полученное значение идентификатора элемента не анализируется оператором switch, а сразу прибавляется к выходу блока v. При щелчках на элементах с идентификаторами 1 и −1 поведение блока не изменится: выход будет увеличиваться или уменьшаться на единицу. При этом такая модель позволяет добавить в картинку новые элементы для увеличения или уменьшения выхода на произвольное число. Например, если добавить в картинку еще один квадрат с идентификатором 2, щелчок по нему приведет к увеличению v на 2. Блок с новой моделью может иметь любую картинку с любым расположением и количеством активных областей, нажатие на которые будет изменять его выход на число, равное идентификатору области.
Таким образом, из векторной картинки любого блока можно сделать «пульт» с кнопками, нажатие на которые будет отслеживаться моделью, причем внешний вид этого «пульта» ограничен только фантазией разработчика.