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

Описания RDS

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

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

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

§3.6.13. Вызов функций блоков

§3.6.13.2. Вызов функции у всех блоков подсистемы

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

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

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

#### §3.6.13. Вызов функций блоков

##### §3.6.13.2. Вызов функции у всех блоков подсистемы

Рассматривается способ вызова заданной функции у всех блоков какой-либо подсистемы и, при необходимости, у блоков всех подсистем, вложенных в нее.

В §3.6.11 мы создали модель блока, который по щелчкам мыши на элементах своей картинки увеличивал, уменьшал или сбрасывал в ноль значение своего выхода. Сделаем так, чтобы одним щелчком мыши можно было увеличить, уменьшить или сбросить выходы всех таких блоков в подсистеме. Проще всего сделать это при помощи вызова функций блоков: мы добавим в подсистему новый управляющий блок, который, при щелчках мыши на нем, будет вызывать у всех блоков в одной с ним подсистеме функции для увеличения, уменьшения или сброса выхода. В наш старый блок мы добавим реакции на вызов таких функций, в которых выход блока будет изменяться согласно вызванной функции. Модели всех остальных блоков в подсистеме, включая, например, стандартные индикаторы, не будут иметь реакций на вызов наших функций, и, поэтому, будут просто игнорировать их. Это стандартная практика в RDS: если модель блока не рассчитана на выполнение какой-либо функции, она немедленно завершается, не выполняя никаких действий. Таким образом, вызов у блока функции, которую он не поддерживает, не приводит к каким-либо неприятным последствиям.

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

Сначала нужно придумать уникальные имена для наших функций. Создаваемая модель является частью описания пользователя, поэтому включим в имена функций текст «UserManual» («описание пользователя»). Функции эти будут выполняться блоками, которые в §3.6.11 иллюстрировали щелчки мыши на картинке, поэтому добавим в имена функций текст «PictureClick» («щелчок на картинке»). И, наконец, функции будут отвечать за увеличение, уменьшение и сброс выхода блока, поэтому их имена будут содержать «Inc», «Dec» и «Reset» (сокращения от «увеличить», «уменьшить» и «сбросить»). Перечислим все эти слова через точки, и получим имена для наших новых функций:

Имя функции | Параметр | Назначение
--- | --- | ---
UserManual.PictureClick.Inc | нет | увеличение выхода
UserManual.PictureClick.Dec | нет | уменьшение выхода
UserManual.PictureClick.Reset | нет | сброс выхода

Мы уже решили, что в первом варианте моделей у функций параметров не будет.

Рис. 465. Картинка
управляющего блока

Начнем с создания управляющего блока, который будет вызывать функции у всех блоков своей подсистемы. Создадим в схеме новый блок с автокомпилируемой моделью и зададим для него векторную картинку, похожую на картинку нашего старого блока. Чтобы не путать эти блоки, заменим в картинке простой белый прямоугольник на прямоугольник со скругленными углами, а квадраты заменим кругами (рис. 465, редактор векторной картинки блока и работа с ним описывается в §2.10). Идентификаторы кругов сделаем такими же, какие были у квадратов: красному (левому) кругу дадим идентификатор 1, зеленому (правому) – 2, а белому (центральному) – 3. Чтобы модель блока отзывалась на щелчки мыши, разрешим в окне параметров блока реакцию на мышь.

Теперь нужно добавить в модель три придуманных нами функции блока. Откроем редактор модели и выберем на его левой панели вкладку «функции» – она пока пуста. Добавим новую функцию: нажмем в нижней части панели кнопку «+» и заполним открывшееся окно описанием функции «UserManual.PictureClick.Inc» (рис. 466).

Рис. 466. Добавление функции «UserManual.PictureClick.Inc» в модель блока

В верхней панели окна включим флажок «произвольная функция»: мы добавляем функцию, которую придумали сами, она не относится к стандартным. В поле «имя функции в RDS» введем «UserManual.PictureClick.Inc» – это имя нашей функции. После того, как мы выйдем из этого поля, поля «объект для функции в программе» и «имя функции реакции в классе блока» заполнятся автоматически – они станут похожими на введенное имя функции, и при этом такими, чтобы удовлетворять правилам синтаксиса языка C. В поле «объект для функции» появится «rdsfuncUserManual_PictureClick_Inc», в поле «имя функции реакции» – «rdsfuncUserManual_PictureClick_IncEvent». Чтобы вызывать эту функцию из модели блока, нам придется обращаться к созданному для функции объекту по его имени, и длинное имя, предлагаемое для объекта по умолчанию, использовать не очень удобно. Поскольку это имя будет использоваться только внутри нашей модели, мы можем изменить его как угодно – главное, чтобы оно не совпало с именем какой-либо другой переменной в программе. Исправим его на «rdsfuncUMPC_Inc», так будет гораздо короче. При этом имя функции реакции тоже изменится (оно преобразуется в «rdsfuncUMPC_IncEvent»), но нам это не важно: с именем функции реакции мы напрямую не работаем, модуль автокомпиляции самостоятельно добавляет ее вызов там, где необходимо.

Параметра у нашей функции нет, поэтому поле «тип параметра функции (указатель)» мы оставляем пустым. В нижней части окна на панели «дополнительные действия» мы оставляем включенный по умолчанию флажок «никаких дополнительных действий» – мы не собираемся регистрировать наш блок в RDS в качестве выделенного исполнителя этой функции или искать такого исполнителя (см. §3.6.13.4). Нажмем кнопку «OK», чтобы функция добавилась в редактор модели – она появится в списке на вкладке «функции».

Точно так же добавим в модель функции «UserManual.PictureClick.Dec» и «UserManual.PictureClick.Reset», изменяя предлагаемые по умолчанию имена их объектов на «rdsfuncUMPC_Dec» и «rdsfuncUMPC_Reset» соответственно. Если все сделано правильно, список на вкладке «функции» примет вид, изображенный на рис. 467: в колонке «имя C++» будут содержаться имена объектов, созданных для функций, в колонке «имя в RDS» – имена функций, колонка «параметр» будет пустой. Все иконки в левой колонке будут пустыми белыми листами, поскольку в нашем блоке нет реакций на вызов этих функций. Их и не будет: управляющий блок, который мы создаем, будет только вызывать эти функции у других блоков.

Рис. 467. Вкладка «функции» редактора модели после добавления функций
(размеры панели и колонок увеличены для наглядности)

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

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

      if(MouseData->Button==RDS_MLEFTBUTTON) // Нажата левая
        { switch(rdsGetMouseObjectId(MouseData)) // Идентификатор
            { case 1: // Красный круг
                rdsfuncUMPC_Dec.Broadcast(rdsbcppBlockData->Parent,0);
                break;
              case 2: // Зеленый круг
                rdsfuncUMPC_Inc.Broadcast(rdsbcppBlockData->Parent,0);
                break;
              case 3: // Белый круг
                rdsfuncUMPC_Reset.Broadcast(rdsbcppBlockData->Parent,0);
                break;
            }
        }
      else // Нажата не левая
        Result=RDS_BFR_SHOWMENU; // Разрешаем контекстное меню

Если нажата левая кнопка мыши, поле Button структуры описания события, указатель MouseData на которую передается в функцию реакции, будет содержать константу RDS_MLEFTBUTTON. В этом случае мы запрашиваем у RDS идентификатор элемента картинки под курсором мыши при помощи функции rdsGetMouseObjectId, которая вернет идентификатор, присвоенный нами элементу в редакторе векторной картинки. В зависимости от его значения, мы вызовем функцию, соответствующую объектам rdsfuncUMPC_Dec, rdsfuncUMPC_Inc или rdsfuncUMPC_Reset, для всех блоков родительской подсистемы нашего блока.

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

      имя_объекта_функции.Broadcast(
        идентификатор_подсистемы,
        битовые_флаги_вызова,
        параметр_функции);	// Отсутствует для функций без параметра

В качестве идентификатора подсистемы мы во всех трех случаях передаем идентификатор родительской подсистемы нашего блока. Мы берем его из поля Parent структуры данных блока, доступной в модели по указателю rdsbcppBlockData. Битовые флаги управляют вызовом функции у блоков. Мы передаем вместо них ноль – это значит, что функция будет вызвана у всех блоков указанной подсистемы независимо от их действий. Указание флага RDS_BCALL_SUBSYSTEMS привело бы к тому, что функция была бы вызвана и у всех блоков всех подсистем, вложенных в указанную, а указание флага RDS_BCALL_ALLOWSTOP позволило бы какому-нибудь из блоков прекратить все дальнейшие вызовы. Нам это не нужно, поэтому флагов мы не указываем. Параметра у наших функций нет, третий параметр во всех трех вызовах Broadcast отсутствует.

Если была нажата не левая кнопка мыши (то есть поле MouseData->Button не равно RDS_MLEFTBUTTON), мы присваиваем переменной Result значение RDS_BFR_SHOWMENU, чтобы не блокировать вывод контекстного меню по правой кнопке мыши.

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

Откроем редактор модели управляемого блока и добавим в него три наших функции – точно так же, как мы делали для управляющего. Крайне важно не ошибиться в именах функций, они должны в точности совпадать с именами, которые мы использовали для управляющего блока. Если они будут отличаться хотя бы регистром символов, с точки зрения RDS это будут разные функции, и управляемый блок не будет реагировать на вызов, сделанный управляющим. Объекты, создаваемые для функций в управляемом блоке, можно не переименовывать: хотя у них и будут длинные, неудобные в использовании имена, в модели управляемого блока мы не будем обращаться к этим объектам. Здесь мы только реагируем на вызов функций, а не вызываем их – следовательно, имя объекта, через который вызывается функция, для нас не важно.

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

Рис. 468. События реакций на вызовы функций

Сначала любым из этих способов добавим реакцию на вызов функции блока «UserManual.PictureClick.Inc» – реагируя на нее, блок должен увеличить свой выход на единицу. На открывшейся пустой вкладке с названием этой функции нужно ввести следующий текст:

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

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

Реакция на вызов функции «UserManual.PictureClick.Dec», в которой блок должен уменьшать выход, будет аналогичной:

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

Реакция на функцию сброса выхода «UserManual.PictureClick.Reset» тоже будет похожей:

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

Рис. 469. Управляющий (справа) и
управляемые (слева) блоки

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

В этом примере для увеличения, уменьшения и сброса выхода управляемого блока мы использовали три разных функции. Можно было обойтись единственной функцией, сделав команду блоку (уменьшить, увеличить или сбросить выход) параметром этой функции. По аналогии с ранее созданными, дадим этой новой функции имя «UserManual.PictureClick.Cmd» и придумаем для нее структуру параметров. Чтобы вызываемая модель могла проверить правильность переданных ей параметров, оформим параметры функции в виде структуры, первым полем которой будет размер этой структуры. Поскольку эта структура нам будет нужна и в модели управляющего, и в модели управляемого блоков, запишем ее в файл «pictureclick.h», который разместим в одной папке с файлами моделей этих блоков – в этом случае в обеих моделях нам нужно будет просто добавить в глобальные описания команду «#include» для этого файла. В «pictureclick.h» нужно ввести следующее описание:

      struct TPictureClickFuncParam
      { DWORD servSize; // Размер структуры
        int Command;    // Команда блоку
      };

Первое поле структуры (servSize) будет содержать ее собственный размер, а в целое поле Command мы будем записывать команду управляемому блоку. Для упрощения модели сделаем так, чтобы команды совпадали с идентификаторами элементов векторной картинки, щелчки по которым должны изменять выходы блоков (эти идентификаторы мы сделали одинаковыми и в управляющем, и в управляемом блоках): 1 – уменьшить выход, 2 – увеличить, 3 – сбросить. Таким образом, в управляющем блоке в качестве посылаемой через функцию команды можно будет использовать идентификатор элемента картинки, на котором щелкнул пользователь.

Как и в прошлый раз, начнем с модели управляющего блока. Откроем ее редактор, сотрем три добавленных ранее функции (см. §3.5.5) и добавим новую с именем «UserManual.PictureClick.Cmd» (рис. 470).

Рис. 470. Добавление функции «UserManual.PictureClick.Cmd» в модель блока

Длинное имя объекта для функции в программе, предлагаемое по умолчанию, заменим на «rdsfuncUMPC_Cmd» (с коротким именем удобнее работать), а поле «тип параметра» на этот раз не будем оставлять пустым: введем в него «TPictureClickFuncParam*», то есть «указатель на TPictureClickFuncParam». После нажатия «OK» в списке функций блока будет единственная функция – «UserManual.PictureClick.Cmd» (рис. 471).

Рис. 471. Вкладка «функции» редактора модели после добавления функции
с параметром (размеры панели и колонок увеличены для наглядности)

Реакцию управляющего блока на нажатие кнопки мыши мы изменим следующим образом:

      if(MouseData->Button==RDS_MLEFTBUTTON) // Нажата левая
        { TPictureClickFuncParam param; // Структура параметров
          param.servSize=sizeof(param); // Размер
          // Команда – это идентификатор элемента под курсором
          param.Command=rdsGetMouseObjectId(MouseData);
          rdsfuncUMPC_Cmd.Broadcast( // Вызов функции всех блоков
            rdsbcppBlockData->Parent,0,&param);
        }
      else // Нажата не левая
        Result=RDS_BFR_SHOWMENU; // Разрешаем контекстное меню

Если нажата левая кнопка мыши, мы заполняем вспомогательную структуру param созданного нами типа TPictureClickFuncParam (команду включения файла «pictureclick.h», в котором он описан, мы добавим позже) параметрами функции, которую мы будем вызывать. В поле servSize мы записываем размер самой структуры, полученный оператором sizeof, а в поле Command – идентификатор элемента картинки под курсором, как и раньше, полученный при помощи вызова rdsGetMouseObjectId. Затем мы вызываем уже знакомую нам функцию-член Broadcast у объекта функции rdsfuncUMPC_Cmd – это приведет к вызову «UserManual.PictureClick.Cmd» у всех блоков подсистемы. Поскольку у нашей новой функции есть параметр, в третьем параметре Broadcast мы передаем указатель на структуру param.

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

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

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

      #include "pictureclick.h"

Модель управляющего блока готова. Изменим теперь модель управляемого, чтобы она реагировала на новую функцию с параметром. Прежде всего, сотрем в ней три имеющихся на данный момент функции. Поскольку для этих функций введены реакции, редактор модели будет предупреждать об их существовании. Нужно согласиться на удаление функции вместе с текстом реакции – эти реакции в новой модели нам не понадобятся. Затем нужно добавить в список функцию «UserManual.PictureClick.Cmd» точно так же, как мы только что сделали в модели управляющего блока. И, как и в модели управляющего блока, добавить в глобальные описания команду для включения файла «pictureclick.h».

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

      if(Param==NULL || Param->servSize<sizeof(TPictureClickFuncParam))
        return; // Нет параметра или недостаточный размер структуры
      // Параметр в порядке
      switch(Param->Command)
        { case 1: y--; break;
          case 2: y++; break;
          case 3: y=0; break;
        }
      // Взводим сигнал готовности для передачи выхода по связям
      Ready=1;

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

      void rdsbcppBlockClass::rdsfuncUMPC_CmdEvent(
        TPictureClickFuncParam* Param,
        RDS_PFUNCTIONCALLDATA FData,
        int &Result)

У функции реакции, текст которой мы пишем, три параметра. Параметр Param типа TPictureClickFuncParam* – это тот самый параметр функции, который мы описывали при ее создании, и который передается от вызывающего блока в последнем параметре функции-члена Broadcast. Именно к Param мы будем обращаться, чтобы считать команду блока. Параметр FData представляет собой указатель на структуру описания функции, из которой можно узнать, какой именно блок вызвал функцию, сколько блоков уже вызвано и т.п. В нашей модели эта информация нам не потребуется, как и целый параметр Result, передаваемый по ссылке, через который функция может вернуть вызвавшему блоку одно целое число (наша функция ничего не возвращает).

Вернемся теперь к реакции на вызов функции. Сначала мы проверяем допустимость переданного параметра. Если Param равен NULL, значит, при вызове нашей функции не был передан параметр. Если Param не равен NULL, но первое поле структуры по этому указателю меньше размера структуры TPictureClickFuncParam, значит, вызвавшая модель передала указатель на неправильную структуру. В обоих случая мы немедленно завершаем реакцию, поскольку считать команду из параметра функции невозможно. Если же параметр прошел обе проверки, мы читаем из переданной структуры поле Command и, в зависимости от его значения, увеличиваем, уменьшаем или сбрасываем выход блока, после чего принудительно взводим сигнал готовности Ready для передачи выхода по связям.

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