Текущий раздел:

Описания RDS

Описание пользователя

Глава 3. Использование стандартных модулей автокомпиляции

§3.6. Принципы создания автокомпилируемых моделей блоков

§3.6.11. Реакция блока на мышь

# Описание пользователя

## Глава 3. Использование стандартных модулей автокомпиляции

### §3.6. Принципы создания автокомпилируемых моделей блоков

#### §3.6.11. Реакция блока на мышь

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

Введение в модель блока реакции на нажатие и отпускание кнопок мыши и перемещение ее курсора – основной способ создания интерактивных блоков: кнопок, рукояток и т.п. Даже блоки, выполняющие ввод с клавиатуры (например, стандартное поле ввода), как правило, активируются щелчком на их изображении, то есть реагируют на нажатие левой кнопки мыши. Следует помнить, что модель блока может реагировать на мышь только в режимах моделирования и расчета – если разработчику необходимо, чтобы блок взаимодействовал с пользователем еще и в режиме редактирования, необходимо либо вводить какие-либо поля и флаги в окно настройки (см. §3.6.6), либо дополнять контекстное меню блока (см. §3.6.12), либо программно создавать собственные окна и панели (см. §1.8, §2.7.5 и §2.10.4 руководства программиста).

Рис. 451. Реакции на мышь в списке событий

Для реакции на мышь в RDS предусмотрены события нажатия кнопки, отпускания кнопки, двойного щелчка и перемещения курсора. В редакторе модели все они находятся в группе «мышь и клавиатура» (рис. 451). Для того, чтобы в ответ на соответствующее действия пользователя вызвалась одна из этих реакций, должны одновременно выполниться следующие условия:

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

Если эти условия выполнены, при нажатии или отпускании кнопки или при перемещении курсора мыши вызовется реакция модели, в которую через параметр MouseData будет передан указатель на структуру RDS_MOUSEDATA, описывающую произошедшее событие. Ниже кратко перечислены поля этой структуры и их назначение:

x, y(int)
  Координаты курсора мыши на рабочем поле окна подсистемы на момент возникновения события (уже с учетом текущего масштаба подсистемы).
BlockX, BlockY(int)
  Координаты точки привязки блока на рабочем поле с учетом масштаба и возможной связи положения этого блока с переменными. Для блоков с векторной картинкой точка привязки – это положение начала координат этой картинки, для всех остальных – левый верхний угол прямоугольной области.
Left, Top, Width, Height(int)
  Координаты левого верхнего угла (Left, Top) прямоугольной области, занимаемой блоком, ее ширина (Width) и высота (Height) в текущем масштабе с учетом возможной связи положения блока с его переменными.
IntZoom(int)
  Текущий масштаб окна родительской подсистемы блока в процентах (используется крайне редко).
Button(DWORD)
  Кнопка мыши, нажатие или отпускание которой вызвало событие: RDS_MLEFTBUTTON – левая кнопка, RDS_MRIGHTBUTTON – правая кнопка, RDS_MMIDDLEBUTTON – средняя кнопка.
Shift(DWORD)
  Набор битовых флагов, описывающие нажатые специальные клавиши клавиатуры и кнопки мыши в момент возникновения события: RDS_MLEFTBUTTON, RDS_MRIGHTBUTTON, RDS_MMIDDLEBUTTON – нажатые кнопки мыши (см. выше), RDS_KSHIFT – нажата клавиша Shift, RDS_KALT – нажата клавиша Alt, RDS_KCTRL – нажата клавиша Ctrl.
DoubleZoom(double)
  Текущий масштаб окна родительской подсистемы блока в долях единицы: 1 – 100%, 0.5 – 50%, 2 – 200% и т.п.
MouseEvent(int)
  Константа, указывающая на произошедшее событие (нажатие, отпускание, перемещение курсора и т.п.) Поскольку в автокомпилируемых моделях для каждого события создается независимая функция, в них это поле структуры используется крайне редко. Подробнее о его возможных значениях можно прочесть в А.2.6.10 приложений.

Параметр MouseData, указывающий на структуру описания события – не единственный параметр функций реакции на нажатие и отпускание кнопок мыши и перемещение курсора. Помимо него, в эти функции передается ссылка на целую переменную Result, изменяя которую можно повлиять на действия, которые RDS выполнит после того, как реакция сработает. В эту переменную можно записать одну из трех стандартных констант RDS:

- RDS_BFR_DONE – блок среагировал на мышь, никаких дальнейших действий не требуется;
- RDS_BFR_NOTPROCESSED – блок отказался реагировать на мышь, необходимо вызвать реакцию блока, находящегося под ним, если такой есть, или реакцию всей подсистемы;
- RDS_BFR_SHOWMENU (только при нажатии правой кнопки) – блок среагировал на мышь, но, несмотря на это, RDS необходимо вывести обычное контекстное меню блока, появляющееся по правой кнопке.

По умолчанию в переменной Result записана константа RDS_BFR_DONE, поэтому, если модель не предпримет никаких специальных действий, блок будет перехватывать все щелчки, пришедшиеся на его изображение. Чтобы блок, которому разрешена реакция на мышь, мог становиться «прозрачным» для щелчков и пропускать их к блокам, изображения которых он перекрывает, модель этого блока должна записать в Result константу RDS_BFR_NOTPROCESSED. В реакции на нажатие правой кнопки модель может также записать в Result константу RDS_BFR_SHOWMENU, информируя RDS о том, что, хотя нажатие обработано, необходимо все равно вывести контекстное меню блока. Это необходимо делать, если блок не обрабатывает щелчки правой кнопкой, потому что в противном случае модель перехватит нажатие любой кнопки и заблокирует меню (пример использования RDS_BFR_SHOWMENU будет приведен далее).

Теперь перейдем к примерам моделей блоков, реагирующих на мышь. Сначала рассмотрим самый простой пример: создадим блок, который по щелчку левой кнопки мыши будет увеличивать свой целый выход на единицу, а по щелчку правой – уменьшать его, тоже на единицу. Создадим в схеме новый блок, запускающийся по сигналу, и зададим ему следующую структуру статических переменных:

Имя | Тип | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | ---
Start | Сигнал | Вход | ✓ | 0
Ready | Сигнал | Выход |  | 0
y | int | Выход |  | 0

В модели этого блока будет единственная реакция – реакция на нажатие кнопки мыши, в которой мы будем увеличивать или уменьшать переменную «y» в зависимости от нажатой кнопки. Добавим в модель эту реакцию: на вкладке «события» левой панели окна редактора раскроем раздел «мышь и клавиатура» и дважды щелкнем на подразделе «нажатие кнопки мыши (RDS_BFM_MOUSEDOWN)». В правой части окна появится новая пустая вкладка «нажатие кнопки мыши», в которой необходимо ввести следующий текст:

      switch(MouseData->Button) // Какая кнопка нажата
        { case RDS_MLEFTBUTTON: y++; break;  // Левая
          case RDS_MRIGHTBUTTON: y--; break; // Правая
        }
      // Взводим сигнал готовности для передачи выхода по связям
      Ready=1;

Здесь мы, в зависимости от того, какая именно кнопка мыши нажата, либо увеличиваем, либо уменьшаем y. Идентификатор кнопки мы считываем из поля Button структуры RDS_MOUSEDATA, переданной через указатель MouseData. После изменения y мы принудительно присваиваем сигналу готовности Ready единицу: у нашего блока нет реакции на такт расчета, в котором готовность взводится автоматически, поэтому, если мы хотим, чтобы изменившееся значение y было передано по связям на входы других блоков после выполнения реакции на нажатие кнопки мыши, необходимо взвести сигнал готовности вручную.

Если запустить расчет прямо сейчас, можно будет увидеть, что при щелчках на изображении блока его выход не изменяется. Дело в том, что, несмотря на то, что мы ввели в модель реакцию на нажатие кнопки мыши, мы не разрешили самому блоку реагировать на мышь, поэтому наша реакция не вызывается. Поскольку наш блок пока существует в единственном числе, проще всего разрешить реакцию на мышь в окне его параметров (пункт «параметры» в его контекстном меню): на вкладке «DLL» этого окна следует включить флажок «блок реагирует на мышь» (рис. 452).

Рис. 452. Разрешение реакции на мышь в окне параметров блока

Если бы наша модель была подключена к нескольким блокам, разрешить реакцию на мышь проще было бы через окно групповой установки, в котором на вкладке «DLL» нужно было бы включить флажок «реакция на мышь» и выбрать в выпадающем списке справа от него вариант «нажатие, отпускание и перемещение с нажатыми кнопками» (рис. 453).

Рис. 453. Разрешение реакции на мышь в окне групповой установки

Рис. 454. Тестирование блока,
реагирующего на левую и
правую кнопки мыши

Для тестирования созданной модели подключим к выходу нашего блока индикатор (рис. 454). Если запустить расчет, щелчки левой кнопки мыши на изображении блока будут увеличивать число на индикаторе, а щелчки правой – уменьшать его.

Созданный нами блок очень примитивен – выполняемые им действия жестко привязаны к кнопкам мыши. Если блок выполняет несколько действий, чаще всего их привязывают к различным частям изображения блока: например, на блоке может быть нарисовано несколько кнопок, и модель может по-разному реагировать на их нажатие. Чтобы определить, в какой именно части изображения блока находится курсор мыши в момент нажатия кнопки, модель может сравнивать координаты курсора из полей x и y структуры структуры RDS_MOUSEDATA, передаваемой ей через указатель указатель MouseData, с заранее вычисленными координатами элементов изображения (координаты всей прямоугольной области блока можно считать из полей Left, Top, Width и Height той же структуры). Если блок изображается векторной картинкой, можно поступить проще: присвоить различным элементам картинки уникальные целые идентификаторы, а в момент нажатия кнопки мыши запрашивать у RDS идентификатор элемента картинки под курсором. Так можно достаточно легко создавать на изображении блока области, чувствительные к нажатию.

Создадим новый блок, который также будет увеличивать и уменьшать свой целый выход «y», но уже в зависимости от того, на какой части картинки пользователь щелкнул мышью. Кроме уменьшения и увеличения предусмотрим также обнуление выхода. Создадим в схеме новый блок, запускающийся по сигналу, и зададим ему точно такую же, как и у предыдущего блока, структуру статических переменных:

Имя | Тип | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | ---
Start | Сигнал | Вход | ✓ | 0
Ready | Сигнал | Выход |  | 0
y | int | Выход |  | 0

Теперь временно закроем редактор модели, сохранив при этом сделанные в структуре переменных изменения, откроем окно параметров блока и вызовем редактор векторной картинки. Добавим в картинку три квадрата: красный – слева, белый – в центре и зеленый – справа. Красному квадрату дадим идентификатор 1, зеленому – 2, а белому – 3 (рис. 455).

Рис. 455. Идентификатор одного из элементов в редакторе картинки

Разрешим новому блоку реакцию на мышь и введем в модель реакцию на нажатие кнопки мыши, в которой мы будем выяснять, на какой из квадратов нажал пользователь. Снова откроем редактор модели, на вкладке «события» левой панели окна редактора раскроем раздел «мышь и клавиатура» (см. рис. 451), дважды щелкнем на подразделе «нажатие кнопки мыши», и на появившейся справа одноименной вкладке введем следующий текст:

      if(MouseData->Button==RDS_MLEFTBUTTON) // Нажата левая
        { switch(rdsGetMouseObjectId(MouseData)) // Идентификатор
            { case 1: y--; break; // Красный квадрат
              case 2: y++; break; // Зеленый квадрат
              case 3: y=0; break; // Белый квадрат
            }
          // Взводим сигнал готовности для передачи выхода по связям
          Ready=1;
        }
      else // Нажата не левая
        Result=RDS_BFR_SHOWMENU; // Разрешаем контекстное меню

Сначала мы выясняем, левая ли кнопка нажата, сравнивая поле Button структуры описания события с константой RDS_MLEFTBUTTON. Если это так, мы запрашиваем у RDS идентификатор элемента картинки под курсором мыши при помощи функции rdsGetMouseObjectId, в которую передается указатель MouseData на структуру описания события RDS_MOUSEDATA. Функция вернет идентификатор, присвоенный нами элементу под курсором в редакторе векторной картинки, и, в зависимости от его значения, мы либо увеличим значение y, либо уменьшим его, либо присвоим ему ноль. Затем, как и в прошлом примере, мы вручную взводим сигнал готовности блока Ready, чтобы новое значение y передалось по связям. Если курсор будет находиться в пределах изображения блока, но не над одним из тех элементов, которым мы присвоили идентификаторы, rdsGetMouseObjectId вернет нулевое значение, и мы не выполним в реакции никаких действий над y.

Если же была нажата не левая кнопка, мы присваиваем переменной Result значение RDS_BFR_SHOWMENU, чтобы не блокировать вывод контекстного меню по правой кнопке мыши. Если мы не сделаем этого, RDS будет считать, что щелчок правой кнопкой мыши перехвачен моделью блока, и меню выведено не будет.

Рис. 456. Тестирование блока,
запрашивающего идентификатор
элемента картинки под курсором

Для тестирования созданной модели, как и в прошлом примере, подключим к выходу блока индикатор (рис. 456). Если запустить расчет, щелчки левой кнопки мыши на левом квадрате картинки будут увеличивать число на индикаторе, на правом – уменьшать его, а на центральном – сбрасывать его в ноль.

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

Чтобы избежать описанных проблем, обычно при создании различных рукояток используют так называемый «захват мыши». Реагируя на нажатие кнопки мыши, модель включает этот захват, после чего, независимо от того, где окажется курсор, реакция на его перемещение будет вызываться только у модели захватившего блока. При отпускании кнопки мыши модель выключает захват, и реакция на перемещение, как обычно, снова начинает вызываться только у блоков под курсором.

Рассмотрим пример модели блока, захватывающего мышь и реагирующего на перемещение курсора. Создадим блок, изображающий двухкоординатную рукоятку (рис. 457) – этот же пример рассматривается в §2.12.2 руководства программиста.

Рис. 457. Предполагаемый внешний вид блока-рукоятки и его параметры

Блок будет представлять собой прямоугольник с перекрестием по центру, внутри которого пользователь сможет двигать мышью закрашенный круг, нажав на этом круге левую кнопку и перемещая его в желаемое положение. Выходами блока будут вещественные переменные «x» и «y», соответствующие положению центра круга внутри прямоугольника. Перемещение центра круга по горизонтали от левой границы прямоугольника до правой меняет «x» от −1 до 1, перемещение круга по вертикали от нижней границы до верхней меняет «y» от −1 до 1. Совпадение центра круга с перекрестием соответствует нулям в обеих переменных «x» и «y». Рисовать круг в прямоугольнике мы будем программно, причем нужно будет следить за тем, чтобы при приближении круга к границе его часть, выходящая за прямоугольник, не рисовалась. Цвета блока и размер круга мы сделаем настроечными параметрами блока: диаметр круга в точках экрана будет храниться в целом параметре «CircleD», цвет рамки и перекрестия – в параметре «Border», цвет фона прямоугольника – в параметре «Fill», цвет круга – в параметре «Circle» (все цвета будут иметь стандартный для Windows тип COLORREF). Для лучшей визуальной обратной связи сделаем так, чтобы в процессе перетаскивания круг изображался другим цветом – будем хранить его в параметре «CircleMoving».

Создадим в схеме новый блок, запускающийся по сигналу – нам не нужно, чтобы он работал каждый такт – и зададим ему следующую структуру статических переменных:

Имя | Тип | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | ---
Start | Сигнал | Вход | ✓ | 0
Ready | Сигнал | Выход |  | 0
x | double | Выход |  | 0
y | double | Выход |  | 0

В редакторе модели на вкладке «настройки» добавим следующие настроечные параметры, одновременно создавая для них поля ввода (см. §3.6.6):

Имя | Тип | По умолчанию | Заголовок поля ввода | Тип поля ввода
--- | --- | --- | --- | ---
Border | COLORREF | 0 | Рамка | Выбор цвета
Fill | COLORREF | 0xffffff | Заливка | Выбор цвета
Circle | COLORREF | 0xff0000 | Круг | Выбор цвета
CircleMoving | COLORREF | 0xff | Круг в движении | Выбор цвета
CircleD | int | 20 | Диаметр круга | Ввод (+/−), шаг 1, минимум 0, максимум 100

В качестве заголовка окна настройки введем «рукоятка». В результате вкладка «настройки» окна редактора и окно настройки блока (его можно увидеть, нажав кнопку «тест» под списком полей ввода) будут выглядеть так, как на рис. 458.

Рис. 458. Настроечные параметры и тест окна настройки рукоятки

Теперь добавим в модель функцию рисования. На левой панели окна редактора следует выбрать вкладку «события», раскрыть на ней раздел «внешний вид блока» и дважды щелкнуть на его подразделе «рисование блока (RDS_BFM_DRAW)» (см. рис. 416). При этом значок подраздела станет желтым, а в правой части окна появится новая пустая вкладка «рисование». На этой вкладке необходимо ввести следующий текст:

      // Вспомогательные переменные
      int hx,hy,cx,cy; RECT r;
      int hR=CircleD*DrawData->DoubleZoom/2; // Радиус круга-рукоятки
      // Мышь захвачена блоком?
      BOOL captured=rdsbcppBlockData->Flags & RDS_MOUSECAPTURE;

      // Если размер блока - нулевой, рисовать негде
      if(DrawData->Height==0 || DrawData->Width==0)
        return;

      // Рисование поля блока
      rdsXGSetPenStyle(0,PS_SOLID,1,Border,R2_COPYPEN);
      rdsXGSetBrushStyle(0,RDS_GFS_SOLID,Fill);
      rdsXGRectangle(DrawData->Left,DrawData->Top,
                     DrawData->Left+DrawData->Width,
                     DrawData->Top+DrawData->Height);

      // Вычисление центра прямоугольника блока
      cx=DrawData->Left+DrawData->Width/2;
      cy=DrawData->Top+DrawData->Height/2;

      // Вычисление координат центра круга-рукоятки
      hx=cx+x*DrawData->Width/2;
      hy=cy-y*DrawData->Height/2;

      // Установка области отсечения
      r.left=DrawData->Left+1;
      r.top=DrawData->Top+1;
      r.right=DrawData->Left+DrawData->Width-1;
      r.bottom=DrawData->Top+DrawData->Height-1;
      rdsXGSetClipRect(&r);

      // Рисование перекрестия
      rdsXGMoveTo(cx,DrawData->Top);
      rdsXGLineTo(cx,DrawData->Top+DrawData->Height);
      rdsXGMoveTo(DrawData->Left,cy);
      rdsXGLineTo(DrawData->Left+DrawData->Width,cy);

      // Рисование круга (цвет зависит от признака захвата мыши)
      rdsXGSetPenStyle(RDS_GFSTYLE,PS_NULL,0,0,0);
      rdsXGSetBrushStyle(0,RDS_GFS_SOLID,
                         captured?CircleMoving:Circle);
      rdsXGEllipse(hx-hR,hy-hR,hx+hR+1,hy+hR+1);

      // Отмена отсечения
      rdsXGSetClipRect(NULL);

Чтобы круг, который перетаскивает пользователь, увеличивался и уменьшался вместе с масштабом подсистемы, мы вычисляем радиус рисуемого круга hR как половину произведения настроечного параметра CircleD и масштабного множителя подсистемы DrawData->DoubleZoom. На время перетаскивания круга мы будем захватывать мышь, поэтому признаком движения круга можно считать наличие захвата мыши. Для захвата мыши блок взводит в поле Flags структуры данных блока RDS_BLOCKDATA битовый флаг RDS_MOUSECAPTURE. Структура данных блока доступна во всех реакциях модели через указатель rdsbcppBlockData. Таким образом, если флаг взведен, то есть если выражение rdsbcppBlockData->Flags & RDS_MOUSECAPTURE не равно нулю, то блок в данный момент захватил мышь. В процедуре рисования это будет означать, что круг нужно рисовать цветом CircleMoving вместо Circle. Полученный таким образом признак захвата записывается во временную логическую переменную captured.

Далее размеры блока сравниваются с нулем (на блоке нулевой ширины или высоты бессмысленно что-либо рисовать), устанавливается цвет рамки Border и цвет заливки Fill и рисуется прямоугольник размером во весь блок (программное рисование подробно рассматривается в §3.6.5). Затем вычисляется центр изображения блока (cx,cy) и координаты центра круга (hx,hy), соответствующие текущим значениям вещественных выходов x и y.

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

Теперь можно нарисовать линии перекрестия с центром в (cx,cy) и круг радиуса hR с центром в (hx,hy). Цвет круга зависит от вспомогательной переменной captured: если мышь захвачена блоком (то есть круг в данный момент перетаскивается пользователем), он будет нарисован цветом CircleMoving, в противном случае – цветом Circle. Нарисовав все, что нужно, мы отменяем ранее установленное ограничение рисования, вызвав функцию rdsXGSetClipRect с параметром NULL.

Программное рисование блока написано – теперь нужно заняться реализацией перетаскивания круга. Перетаскивание будет устроено следующим образом:

- в момент нажатия левой кнопки мыши на изображении круга модель будет захватывать мышь и запоминать текущие координаты центра нарисованного круга и координаты курсора мыши на момент нажатия;
- при каждом перемещении курсора модель будет вычислять новые координаты центра круга (для этого нам будут нужны запомненные при нажатии кнопки координаты), а по ним – новые вещественные значения x и y;
- при отпускании кнопки модель снимет захват мыши.

Начнем с того, что добавим в класс блока поля, в которых мы будем запоминать координаты курсора и координаты центра круга на момент начала перетаскивания. На левой панели окна редактора выберем вкладку «события», раскроем на ней раздел «описания» и дважды щелкнем на его подразделе «описания внутри класса блока» (см. рис. 446). В правой части окна появится новая пустая вкладка «описания в классе», на которой нужно ввести следующий текст:

      // Центр круга (рукоятки) до начала перетаскивания
      int OldCircleX,OldCircleY;
      // Координаты курсора на момент начала перетаскивания
      int OldMouseX,OldMouseY;

Перед перетаскиванием круга в полях OldCircleX и OldCircleY мы будем сохранять координаты его центра, а в полях OldMouseX и OldMouseY – координаты курсора мыши. Начиная перетаскивание, пользователь, вероятнее всего, нажмет не точно в центр круга, поэтому нам нужно запомнить две пары координат. В процессе перетаскивания положения центра круга относительно курсора мы будем сохранять постоянным.

Теперь, как в предыдущих примерах, добавим в модель реакцию на нажатие кнопки мыши. На вкладке «события» раскроем раздел «мышь и клавиатура» (см. рис. 451), дважды щелкнем на подразделе «нажатие кнопки мыши» и введем на открывшейся вкладке следующий текст:

      // Вспомогательные переменные
      int hx,hy,cx,cy,hR;
      hR=CircleD*MouseData->DoubleZoom/2; // Радиус круга

      // Если размер - нулевой, реакция не имеет смысла
      if(MouseData->Height==0 || MouseData->Width==0)
        return;
      // Если нажата не левая кнопка, перетаскивать не надо
      // Разрешаем в этом случае вызов контекстного меню блока
      if(MouseData->Button!=RDS_MLEFTBUTTON)
        { Result=RDS_BFR_SHOWMENU;
          return;
        }

      // Координаты цента блока
      cx=MouseData->Left+MouseData->Width/2;
      cy=MouseData->Top+MouseData->Height/2;
      // Координаты центра круга-рукоятки
      hx=cx+x*MouseData->Width/2;
      hy=cy-y*MouseData->Height/2;

      // Проверка попадания курсора в круг
      if(abs(MouseData->x-hx)<=hR && abs(MouseData->y-hy)<=hR)
        { // Курсор попал в круг
          // Запоминаем координаты центра круга на момент
          // начала перетаскивания
          OldCircleX=hx;
          OldCircleY=hy;
          // Координаты курсора на начало перетаскивания
          OldMouseX=MouseData->x;
          OldMouseY=MouseData->y;
          // Взводим флаг захвата мыши
          rdsbcppBlockData->Flags|=RDS_MOUSECAPTURE;
        }

Здесь, как и в функции рисования, мы сначала вычисляем радиус круга (он нам нужен для проверки попадания курсора мыши в круг) и прерываем выполнение реакции, если один из размеров блока – нулевой. Затем, как в предыдущем примере, мы выясняем, левая ли клавиша мыши нажата. Если нажата не левая клавиша, мы записываем в переменную Result константу RDS_BFR_SHOWMENU, чтобы разрешить RDS вывод контекстного меню, и завершаем реакцию. В противном случае, опять, как в функции рисования, вычисляются координаты центра изображения блока (cx,cy) и координаты центра круга (hx,hy), соответствующие текущим значениям x и y.

Далее мы проверяем попадание курсора в круг – точнее, в прямоугольник, внутрь которого вписан этот круг. Если по обеим координатам расстояние между курсором (поля x и y структуры MouseData) и текущим центром круга (hx,hy) не больше радиуса круга hR, значит, курсор попал внутрь круга. При этом мы запоминаем координаты курсора в полях OldMouseX и OldMouseY, которые мы уже добавили в класс блока, а координаты центра круга – в полях OldCircleX и OldCircleY. Затем мы взводим флаг захвата мыши RDS_MOUSECAPTURE в поле Flags структуры данных блока rdsbcppBlockData при помощи оператора присваивания с битовым «ИЛИ»:

      rdsbcppBlockData->Flags|=RDS_MOUSECAPTURE;

Вся основная работа будет выполняться в реакции на перемещение курсора мыши, но сначала добавим в модель реакцию на отпускание кнопки – в ней нам нужно только снять захват мыши. На вкладке «события» в разделе «мышь и клавиатура» (см. рис. 451) дважды щелкнем на подразделе «отпускание кнопки мыши» и введем на открывшейся одноименной вкладке единственную строчку:

      RDS_SETFLAG(rdsbcppBlockData->Flags,RDS_MOUSECAPTURE,FALSE);

Здесь мы используем стандартный макрос RDS RDS_SETFLAG для очистки флага RDS_MOUSECAPTURE в поле Flags структуры данных блока. Можно было бы, не пользуясь макросом, записать

      rdsbcppBlockData->Flags=
        (DWORD)(rdsbcppBlockData->Flags & (~RDS_MOUSECAPTURE));

Однако, запись с макросом несколько компактнее.

Теперь займемся реакцией на перемещение курсора мыши. На вкладке «события» в разделе «мышь и клавиатура» дважды щелкнем на подразделе «перемещение мыши» и на открывшейся одноименной вкладке введем следующий текст:

      // Вспомогательные переменные
      int hx,hy,cx,cy;

      // Если размер - нулевой, реакция не имеет смысла
      if(MouseData->Height==0 || MouseData->Width==0)
        { x=y=0.0;
          return;
        }
      if(!(rdsbcppBlockData->Flags & RDS_MOUSECAPTURE))
        { // Мышь не захвачена - ничего не нужно делать
          return;
        }

      // Новые координаты центра рукоятки
      hx=OldCircleX+(MouseData->x-OldMouseX);
      hy=OldCircleY+(MouseData->y-OldMouseY);

      // Координаты центра блока
      cx=MouseData->Left+MouseData->Width/2;
      cy=MouseData->Top+MouseData->Height/2;

      // По новым координатам центра рукоятки вычисляем соответствующие
      // им вещественные значения выходов, ограничивая их
      // диапазоном [-1...1]
      x=2.0*(hx-cx)/MouseData->Width;
      if(x>1.0) x=1.0;
      else if(x<-1.0) x=-1.0;
      y=-2.0*(hy-cy)/MouseData->Height;
      if(y>1.0) y=1.0;
      else if(y<-1.0) y=-1.0;

      // Взводим сигнал готовности для передачи выхода по связям
      Ready=1;

Мы снова проверяем размеры блока и завершаем реакцию, обнуляя выходы, если его ширина или высота равны нулю. Затем мы проверяем флаг захвата мыши: если он не взведен, значит, была нажата не левая кнопка, или при нажатии кнопки курсор был за пределами круга – в общем, перетаскивание не было начато. В этом случае мы немедленно завершаем реакцию.

Если мышь захвачена, то есть перетаскивание круга выполняется, мы вычисляем новое положение центра круга с учетом перемещения курсора: курсор с момента нажатия кнопки мыши переместился из (OldMouseX,OldMouseY) в (MouseData->x,MouseData->y), значит, центр круга сместился на ту же величину от (OldCircleX,OldCircleY). Зная новое положение центра круга внутри прямоугольника блока, мы можем вычислить новые значения вещественных выходов x и y. Эти значения мы ограничиваем диапазоном [−1…1], то есть границами блока. Вычислив x и y, мы, как и в прошлых примерах, взводим сигнал готовности блока Ready, чтобы значения выходов передались по связям.

Модель блока-рукоятки полностью написана. Теперь, чтобы он смог работать, необходимо правильно настроить его параметры:

- чтобы включить программное рисование, необходимо в окне параметров блока на вкладке «внешний вид» включить флажок «внешний вид – определяется функцией DLL» (см. рис. 415);
- чтобы пользователь мог задать размеры блока, растягивая мышью маркеры его выделения, на этой же вкладке следует включить флажок «разрешить масштабирование»;
- чтобы модель блока вызывалась при нажатии и отпускании кнопок мыши и перемещении курсора, на вкладке «DLL» окна параметров следует включить флажок «блок реагирует на мышь» (см. рис. 452).

Рис. 459. Тестирование рукоятки

Теперь наш блок полностью готов к работе. Сделаем его достаточно большим, чтобы было удобно перетаскивать круг внутри него, и подключим к его выходам «x» и «y» числовые индикаторы (рис. 459). Если настроечные параметры блока не изменены, то его прямоугольник будет белым с черной рамкой и перекрестием, а круг будет синим. Запустив расчет и перетаскивая круг внутри прямоугольника (круг при этом будет становиться красным), на индикаторах можно будет наблюдать изменение значений выходов: когда центр круга будет находиться в центре блока, выходы будут близки к нулю, у границ выходы будут близки к ±1. При выведении курсора за пределы прямоугольника блока центр круга останется на его границе (при этом выходящий за прямоугольник сегмент не будет нарисован), а при возврате курсора обратно в прямоугольник круг снова будет следовать за ним. При желании, можно войти в окно настроек блока через контекстное меню и изменить его цвета и диаметр круга.