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

Описания RDS

Руководство программиста

Глава 2. Создание моделей блоков

§2.12. Реакция блоков на действия пользователя

§2.12.2. Захват мыши, реакция на перемещение курсора

# Руководство программиста

## Глава 2. Создание моделей блоков

### §2.12. Реакция блоков на действия пользователя

#### §2.12.2. Захват мыши, реакция на перемещение курсора

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

Для создания полноценного пользовательского интерфейса обычно мало реагировать только на щелчки мыши на изображении блока. Достаточно часто нужно отслеживать и перемещения курсора – например, для виртуальных рукояток, которые пользователь может двигать, меняя какие-либо значения, или для выделения области графика, которую нужно рассмотреть подробнее. Чаще всего в таких случаях перемещения курсора производятся при нажатой кнопке мыши: нажатие кнопки отмечает начало операции (перемещения рукоятки, выделения области и т.п.), а отпускание – ее конец. Необходимость закладывать в модель блока реакцию на перемещение курсора мыши без нажатия кнопок встречается довольно редко. Такие реакции могут замедлять систему в режиме расчета, поскольку каждый проход курсора мыши над изображением блока будет порождать большое количество вызовов модели этого блока. При этом, поскольку все реакции на действия пользователя производятся в главном потоке RDS, поток расчета будет каждый раз останавливаться и ждать завершения реакции модели. Кроме того, если перемещение мыши над блоком должно как-либо отражаться на его внешнем виде, каждое перемещение курсора над блоком будет приводить к необходимости обновления окна подсистемы, что также может приводить к существенному замедлению. По этой причине при включении в параметрах блока реакции на мышь, модель будет реагировать на перемещения курсора по изображению блока только при нажатой кнопке мыши. Для того, чтобы модель всегда реагировала на перемещения курсора, в параметрах блока нужно включить дополнительный флаг (см. рис. 73).

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

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

Рис. 75. Двухкоординатная рукоятка

В качестве примера рассмотрим блок, имитирующий двухкоординатную рукоятку. Внутри прямоугольника, разделенного на четыре части вертикальными линиями, будет изображаться круг синего цвета (рис. 75). Пользователь может «перетаскивать» этот круг мышью, меняя значения выходов блока «x» и «y». При перемещении круга по горизонтали от левой границы прямоугольника до правой, выход «x» будет изменяться от −1 до 1. При перемещении круга по вертикали от нижней границы до верхней, выход «y» точно так же будет изменяться от −1 до 1. Таким образом, положение круга в центре блока, на пересечении линий, соответствует нулевым значениям обоих выходов. Для лучшей визуальной обратной связи сделаем так, чтобы в процессе перетаскивания круг менял цвет на красный. Чтобы не делать для блока сложную векторную картинку, будем рисовать его внешний вид программно.

Для упрощения примера мы не будем включать в модель функцию настройки параметров блока (цветов прямоугольника и круга, размера круга), но, тем не менее, сделаем их полями класса личной области данных блока. При необходимости, функции настройки, загрузки и сохранения этих параметров можно будет написать позже. Большую часть реакций модели на мышь мы вынесем в функции-члены класса, чтобы макроопределение для выхода блока y, которое будет использоваться в функции модели, не конфликтовало с одноименным полем структуры RDS_MOUSEDATA (эта проблема была подробно описана в §2.12.1).

Блок будет иметь следующую структуру переменных:

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

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

      //====== Класс личной области данных ======
      class TSimpleJoystick
      { private:
          // Центр круга (рукоятки) до начала перетаскивания
          int OldHandleX,OldHandleY;
          // Координаты курсора на момент начала перетаскивания
          int OldMouseX,OldMouseY;
        public:
          // Настроечные параметры блока
          COLORREF BorderColor;       // Цвет рамки блока
          COLORREF FieldColor;        // Цвет прямоугольника
          COLORREF HandleColor;       // Цвет круга в покое
          COLORREF MovingHandleColor; // Цвет круга при таскании
          int HandleSize;             // Диаметр круга

          // Реакция на нажатие кнопки мыши
          int MouseDown(RDS_PMOUSEDATA mouse,double x,double y,DWORD *pFlags);
          // Реакция на перемещение курсора мыши
          void MouseMove(RDS_PMOUSEDATA mouse,double *px,double *py);
          // Рисование изображения блока
          void Draw(RDS_PDRAWDATA draw,double x,double y,BOOL moving);

          // Конструктор класса
          TSimpleJoystick(void)
            { BorderColor=0;          // Черная рамка
              FieldColor=0xffffff;    // Белое поле
              HandleColor=0xff0000;   // Синий круг
              MovingHandleColor=0xff; // Красный при таскании
              HandleSize=20;          // Диаметр круга
            };
      };
      //=========================================

В закрытой секции класса описаны четыре целых поля, они понадобятся нам при реализации перетаскивания круга-рукоятки по прямоугольнику блока. В момент нажатия левой кнопки мыши в пределах круга мы запомним координаты центра круга в переменных OldHandleX и OldHandleY, а координаты курсора – в OldMouseX и OldMouseY. После этого при перемещении курсора относительно (OldMouseX,OldMouseY) мы будем перемещать центр круга на то же расстояние относительно (OldHandleX,OldHandleY), таким образом, круг будет двигаться за курсором, не смещаясь относительно него.

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

Теперь рассмотрим функции-члены класса, и начнем с функции рисования Draw, которая определяет внешний вид нашего блока. Кроме указателя на структуру параметров рисования RDS_DRAWDATA в эту функцию передаются текущие значения переменных блока x и y (по ним вычисляются координаты центра рисуемого круга) и дополнительный логический параметр moving, указывающий на то, идет ли в данный момент перетаскивание круга (в этом случае он должен быть нарисован красным вместо синего). Значение этого параметра будет вычисляться в основной функции модели, которую мы рассмотрим позже. Функция Draw имеет следующий вид:

      // Рисование изображения блока
      void TSimpleJoystick::Draw(RDS_PDRAWDATA draw,double x,double y,BOOL moving)
      { int hx,hy,cx,cy;
        RECT r;
        int hR=HandleSize*draw->DoubleZoom/2; // Радиус круга-рукоятки

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

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

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

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

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

        // Линии перекрестия
        rdsXGMoveTo(cx,draw->Top);
        rdsXGLineTo(cx,draw->Top+draw->Height);
        rdsXGMoveTo(draw->Left,cy);
        rdsXGLineTo(draw->Left+draw->Width,cy);

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

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

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

Перед тем, как нарисовать круг внутри прямоугольника блока, необходимо отсечь возможность рисования за пределами этого прямоугольника. Дело в том, что при x или y, равных ±1, центр круга будет находиться точно на границе прямоугольника блока, и, если не принять меры, половина круга окажется за пределами изображения блока. Чтобы этого не случилось, вызывается уже знакомая нам по блоку-графику функция отсечения по прямоугольнику rdsXGSetClipRect. Ей передаются координаты с отступом на одну точку внутрь прямоугольника блока, чтобы круг, который будет нарисован, не задел рамку прямоугольника.

Далее рисуются линии перекрестия в центре прямоугольника (cx,cy) – их нужно нарисовать до круга-рукоятки, чтобы они его не перекрывали. Затем, в зависимости от значения параметра moving, устанавливается нужный цвет заливки (при moving==TRUE – MovingHandleColor, то есть красный, при FALSE – HandleColor, то есть синий), и функцией rdsXGEllipse рисуется круг заданного в параметрах блока радиуса с центром в (hx,hy). После этого отсечение отключается и функция на этом завершается.

Теперь напишем функцию реакции на нажатие кнопки мыши MouseDown. Эта функция проверит, левая ли кнопка мыши нажата, попал ли курсор мыши в круг рукоятки (для этого в параметрах функции передаются вещественные значения обоих выходов блока, от которых зависит положение круга), и, если оба условия выполнены, подготовит вспомогательные переменные к перетаскиванию круга и установит в флагах блока, указатель на которые передан в параметре pFlags, флаг захвата мыши. Функция MouseDown будет возвращать целое значение, которое функция модели без изменений использует в качестве своего результата – таким образом мы сообщим RDS, обработано ли нажатие кнопки мыши.

      // Реакция на нажатие кнопки мыши
      int TSimpleJoystick::MouseDown(RDS_PMOUSEDATA mouse,
                  double x,double y,
                  DWORD *pFlags)
      { int hx,hy,cx,cy,
            hR=HandleSize*mouse->DoubleZoom/2; // Радиус круга

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

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

        // Проверка попадания курсора в круг
        if(abs(mouse->x-hx)<=hR && abs(mouse->y-hy)<=hR)
          { // Курсор попал в круг
            // Запоминаем координаты центра круга на момент
            // начала перетаскивания
            OldHandleX=hx;
            OldHandleY=hy;
            // Координаты курсора на начало перетаскивания
            OldMouseX=mouse->x;
            OldMouseY=mouse->y;
            // Взводим флаг захвата мыши
            *pFlags|=RDS_MOUSECAPTURE;
          }
        // Курсор не попал в рукоятку - захватывать мышь
        // и подготавливать перетаскивание не нужно
        return RDS_BFR_DONE;
      }
      //=========================================

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

Если же была нажата левая кнопка мыши, точно так же, как и в функции Draw, вычисляются координаты центра блока (cx,cy), и, через них, координаты центра рукоятки (hx,hy). Попадание курсора мыши в круг рукоятки проверяется по близости его координат к центру круга – расстояние по обеим координатам не должно быть больше радиуса круга. Если это условие выполняется, можно начинать перетаскивание: текущие координаты центра круга сохраняются в полях класса (OldHandleX,OldHandleY), а координаты курсора – в (OldMouseX,OldMouseY). Эти значения будут использоваться в процессе перетаскивания (в реакции на перемещение курсора) как начальные условия. После сохранения начальных значений координат функция захватывает мышь, взводя битовый флаг RDS_MOUSECAPTURE в поле Flags структуры данных блока BlockData (указатель на это поле должен быть передан в функцию MouseDown из вызвавшей ее функции модели в параметре pFlags). Для взведения флага используется побитовая операция «ИЛИ» (в данном случае использован оператор присваивания «*pFlags|=RDS_MOUSECAPTURE», который эквивалентен записи «*pFlags=*pFlags | RDS_MOUSECAPTURE»).

Последняя функция класса – MouseMove – реагирует на перемещение курсора. Внутри нее по изменившемуся положению курсора вычисляются новые координаты центра перетаскиваемого круга, а по ним – новые значения выходов блока x и y. Поскольку функция не имеет непосредственного доступа к статическим выходам блока, в ее параметрах передаются указатели на эти выходы px и py, а она записывает по этим указателям результат своей работы. Мы не будем делать в функции проверку, производится в данный момент перетаскивание рукоятки или нет – возложим эту обязанность на вызывающую функцию. Пока будем считать, что, раз MouseMove вызвана, значит, в данный момент выполняется перетаскивание.

      // Реакция на перемещение курсора мыши
      void TSimpleJoystick::MouseMove(RDS_PMOUSEDATA mouse,
                                      double *px,double *py)
      { int hx,hy,cx,cy;

        // Если размер - нулевой, реакция не имеет смысла
        if(mouse->Height==0 || mouse->Width==0)
          { *px=*py=0.0;
            return;
          }

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

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

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

Как и в двух других функциях, в этой сначала проверяется, не нулевая ли длина или ширина блока (в этом случае выходам блока принудительно присваиваются нули и функция завершается). Если с размерами блока все в порядке, вычисляются новые, с учетом перемещения курсора, координаты центра круга-рукоятки. Зная координаты рукоятки и курсора мыши до начала перетаскивания, вычислить новые координаты рукоятки не сложно. По горизонтали с момента начала перетаскивания курсор переместился на (mouse->x-OldMouseX) точек, рукоятка перемещается вместе с курсором, значит, для вычисления ее новой горизонтальной координаты нужно к старому ее значению OldHandleX добавить перемещение курсора мыши. Вертикальное перемещение вычисляется аналогично.

По новым координатам рукоятки можно вычислить новые значения выходов блока, разделив для каждой из двух координат расстояние от центра круга рукоятки до центра блока на половину размера блока (половину ширины или высоты, в зависимости от вычисляемой координаты). При этом вычисленные значения необходимо ограничить диапазоном [–1…1]. Поскольку блок у нас будет захватывать мышь, его модель будет получать от RDS сообщения о перемещении курсора и, реагируя на них, вызывать функцию MouseMove, даже при выходе курсора за пределы прямоугольника блока. Вместе с координатами курсора координаты центра рукоятки тоже выйдут за прямоугольник блока, что, без ограничений на диапазон выходов, может привести к весьма неприятной ситуации: если перетащить круг рукоятки за пределы блока и отпустить там, в него уже невозможно будет попасть мышью, чтобы перетащить обратно. Действительно, по окончании перетаскивания модель снимет захват мыши, поэтому реакция на нажатие кнопки будет вызываться только при нахождении курсора в пределах прямоугольника блока. А круг рукоятки находится за его пределами (к тому же еще и не изображается, поскольку функция Draw устанавливает отсечение рисования вне прямоугольника). Значит, в этом случае проверка попадания курсора в рукоятку в функции MouseDown никогда не выполнится, и новое перетаскивание никогда не начнется. Если же ограничивать выходы блока диапазоном [–1…1], центр рукоятки не сможет покинуть прямоугольник блока, как бы далеко от него ни переместил курсор пользователь.

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

      // Двухкоординатная рукоятка
      extern "C" __declspec(dllexport)
        int RDSCALL SimpleJoystick(int CallMode,
                RDS_PBLOCKDATA BlockData,
                LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define x (*((double *)(pStart+2*RDS_VSZ_S)))
      #define y (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
        // Вспомогательная переменная - указатель на личную область,
        //приведенный к правильному типу
        TSimpleJoystick *data=(TSimpleJoystick*)(BlockData->BlockData);
        switch(CallMode)
          { // Инициализация
           case RDS_BFM_INIT:
              BlockData->BlockData=new TSimpleJoystick();
              break;
            // Очистка
            case RDS_BFM_CLEANUP:
              delete data;
              break;
            // Проверка допустимости типов переменных
            case RDS_BFM_VARCHECK:
              return strcmp((char*)ExtParam,"{SSDD}")?
                RDS_BFR_BADVARSMSG:RDS_BFR_DONE;
            // Нажатие кнопки мыши
            case RDS_BFM_MOUSEDOWN:
              return data->MouseDown((RDS_PMOUSEDATA)ExtParam,x,y,
                &(BlockData->Flags));
            // Отпускание кнопки мыши
            case RDS_BFM_MOUSEUP:
              // Снятие захвата мыши
              RDS_SETFLAG(BlockData->Flags,RDS_MOUSECAPTURE,FALSE);
              break;
            // Перемещение курсора мыши
            case RDS_BFM_MOUSEMOVE:
              // Проверка: включен ли захват мыши
              if(BlockData->Flags & RDS_MOUSECAPTURE)	// Включен
                { // Вызываем функцию реакции
                  data->MouseMove((RDS_PMOUSEDATA)ExtParam,&x,&y);
                  Ready=1; // Взводим сигнал готовности
                }
              break;
            // Рисование
            case RDS_BFM_DRAW:
              data->Draw((RDS_PDRAWDATA)ExtParam,x,y,
              BlockData->Flags & RDS_MOUSECAPTURE);
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef y
      #undef x
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

Поскольку в этой модели предусмотрена личная область данных блока, при вызовах в режимах RDS_BFM_INIT и RDS_BFM_CLEANUP эта область, как обычно, создается и уничтожается. Вызов проверки типов статических переменных RDS_BFM_VARCHECK тоже не отличается от рассмотренных уже много раз. Мы не будем в очередной раз подробно описывать эти реакции, вместо этого сосредоточимся на реакциях модели на нажатие кнопок и перемещение курсора мыши.

При нажатии какой-либо кнопки мыши модель вызывается в режиме RDS_BFM_MOUSEDOWN. При этом она немедленно вызывает функцию-член личной области данных блока MouseDown, передавая ей приведенный к правильному типу указатель на структуру описания произошедшего события, текущие значения выходов блока x и y, а также указатель на поле флагов структуры BlockData для взведения в этом поле, при необходимости, флага захвата мыши.

При отпускании кнопки (реакция RDS_BFM_MOUSEUP) модель снимает захват мыши, сбрасывая флаг RDS_MOUSECAPTURE. Для сброса флага используется макрос для сброса и установки битовых флагов RDS_SETFLAG, описанный в «RdsDef.h» следующим образом:

      #define RDS_SETFLAG(storage,mask,value) \
        ((storage) = (value)? \
        ((storage) | (mask)): \
        ((DWORD)((storage) & (~(mask)))))

Параметр storage соответствует переменной типа DWORD, в которой устанавливается или сбрасывается флаг, параметр mask – битовой маске флага, а вместо value подставляется TRUE для установки флага или FALSE для сброса. На самом деле, отключение захвата мыши можно было бы записать и так:

      BlockData->Flags=BlockData->Flags & (~(RDS_MOUSECAPTURE));

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

При перемещении курсора мыши модель вызывается в режиме RDS_BFM_MOUSEMOVE. Если флаг RDS_MOUSECAPTURE взведен (побитовое «И» поля Flags структуры BlockData c RDS_MOUSECAPTURE дает ненулевой результат), значит, мышь в данный момент захвачена (то есть идет перетаскивание рукоятки), и вызывается функция MouseMove. В нее, как и в MouseDown, передается указатель на структуру RDS_MOUSEDATA, содержащую координаты курсора мыши, текущий размер блока и т.п. Кроме того, ей передаются указатели на выходы блока x и y, чтобы функция могла изменить их значения. После вызова MouseMove выходу готовности блока присваивается единица, чтобы измененные значения выходов блока передались по связям.

Последняя реакция в модели блока – рисование его изображения (RDS_BFM_DRAW). В ней вызывается уже описанная функция Draw, в которую передается указатель на структуру параметров рисования, текущие значения выходов блока (чтобы функция нарисовала рукоятку в нужном месте) и признак захвата мыши, от которого зависит цвет рукоятки (при перетаскивании она меняет цвет на красный).

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