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

Описания RDS

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

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

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

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

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

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

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

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

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

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

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

Поскольку эта функция нужна нам для примера в руководстве программиста, реагировать на нее будут только блоки управления полями ввода (мы назвали функцию модели этих блоков EditControlFrame), и предназначена она для установки режима работы блока, мы отразим в ее названии три этих момента: назовем ее «ProgrammersGuide.EditCtrlFrame.Set». Параметры этой функции мы оформим как структуру из двух полей: первое будет содержать размер этой структуры для проверки правильности передачи параметров (см. §2.13.1), во втором будет находиться целое число, указывающее на действие, которое должен выполнить блок: 0 – закрыться, 1 – открыться, 2 – переключиться. Таким образом, для работы с функцией нам потребуются следующие описания:

      //=========================================
      // Функция управления состоянием блока
      //=========================================
      // Имя функции
      #define PROGGUIDEEDITCTRLFUNC_SET \
                "ProgrammersGuide.EditCtrlFrame.Set"
      // Структура параметров функции
      typedef struct
      { DWORD servSize; // Размер этой структуры для проверки
        int Command;    // Команда блоку (0, 1 или 2)
      } TProgGuideEditCtrlSetParams;
      //=========================================

Для строки имени функции мы вводим константу PROGGUIDEEDITCTRLFUNC_SET. Использование этой константы позволит избежать ошибок, если мы когда-либо захотим ввести поддержку этой функции в другие модели блоков. Набирая тексте каждой модели имя функции вручную или копируя его через буфер обмена, можно случайно пропустить в нем один символ или как-то еще исказить его, что приведет к трудно выявляемым ошибкам: RDS просто зарегистрирует еще одну функцию, в результате чего в схеме будет одновременно зарегистрировано две независимые функции – одна с правильным именем, а другая – с искаженным, и их целые идентификаторы, естественно, будут различаться. Если же мы будем использовать вместо строки имени функции введенную для нее константу, и случайно исказим имя этой константы, такая ошибка будет обнаружена на этапе компиляции. Вообще, если создаваемую функцию предполагается в будущем использовать и в других моделях, целесообразно вынести необходимые для нее описания в файл заголовка, чтобы другие программисты могли просто включить его в свои тексты моделей. Например, имена и описания всех функций блоков из стандартной библиотеки «Common.dll» находятся в файле «CommonBl.h», поэтому в предыдущих примерах можно было, включив этот файл, вместо строки имени функции «Common.ControlValueChanged» использовать константу COMBL_F_CONTROLCHANGED_NAME.

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

Нам также потребуется глобальная переменная для хранения идентификатора этой функции, полученного при регистрации:

      // Идентификатор функции PROGGUIDEEDITCTRLFUNC_SET
      int EditCtrlFuncSet=0;

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

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

      extern "C" __declspec(dllexport)
        int RDSCALL EditControlFrame(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_ext (*((double *)(pStart+2*RDS_VSZ_S)))
      #define x_int (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
      #define out (*((double *)(pStart+2*RDS_VSZ_S+2*RDS_VSZ_D)))
      #define bypass (*((char *)(pStart+2*RDS_VSZ_S+3*RDS_VSZ_D)))
        const int fr=20; // Толщина рамки
        // Вспомогательные переменные
        RDS_PMOUSEDATA mouse;
        RDS_PDRAWDATA draw;
        int frz,x1,y1,x2,y2,xi1,yi1,xi2,yi2;

        RDS_PFUNCTIONCALLDATA func; // Описание вызванной функции
        RDS_PMENUFUNCDATA menu;     // Описание выбранного пункта меню
        TProgGuideEditCtrlSetParams callparams; // Параметры функции
        switch(CallMode)
        // …

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

            // Инициализация блока
            case RDS_BFM_INIT:
              if(!EditCtrlFuncSet)
                EditCtrlFuncSet=
                  rdsRegisterFunction(PROGGUIDEEDITCTRLFUNC_SET);
              break;

Как и в предыдущих примерах, мы регистрируем функцию только в том случае, если глобальная переменная EditCtrlFuncSet сохранила свое нулевое значение по умолчанию, то есть если функции еще не присвоен идентификатор.

В контекстное меню мы добавляем три новых пункта:

            // Открытие контекстного меню блока
            case RDS_BFM_CONTEXTPOPUP:
              // Добавление временных пунктов меню
              // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
              //   в противном случае необходимо использовать версии функций
              //   с суффиксом "W" и символьные константы с префиксом "L"
              rdsAdditionalContextMenuItemEx(bypass?"Открыть":"Закрыть",
                                             0,1,2);
              rdsAdditionalContextMenuItemEx(NULL,RDS_MENU_DIVIDER,0,0);
              rdsAdditionalContextMenuItemEx("Открыть все",0,2,1);
              rdsAdditionalContextMenuItemEx("Закрыть все",0,2,0);
              rdsAdditionalContextMenuItemEx("Переключить все",0,2,2);
              break;

Первый из четырех добавленных вызовов rdsAdditionalContextMenuItemEx, с флагом RDS_MENU_DIVIDER и NULL вместо названия, добавляет в меню горизонтальный разделитель, чтобы визуально отделить старый пункт меню, меняющий режим этого блока, от новых, управляющих всеми блоками подсистемы. Три следующих вызова добавляют новые пункты меню, задавая для всех них номер функции меню 2. В качестве данных меню (второго целого числа, связанного с пунктом) используется номер команды, которую, как мы решили, мы будем давать блоку: 0 для закрытия, 1 для открытия и 2 для переключения.

Реакцию на выбор пункта меню необходимо изменить следующим образом:

            // Выбор пункта меню пользователем
            case RDS_BFM_MENUFUNCTION:
              menu=(RDS_PMENUFUNCDATA)ExtParam; // Данные пункта меню
              switch(menu->Function)
                { case 1: // Открыть/закрыть
                    bypass=!bypass;         // Переключить режим
                    out=bypass?x_ext:x_int; // Подать на выход
                    Ready=1;                // Взвести флаг готовности

                    break;
                  case 2: // Один из новых пунктов
                    // Заполняем структуру параметров функции
                    callparams.Command=menu->MenuData;
                    callparams.servSize=sizeof(callparams);
                    // Вызываем у всех блоков родительской подсистемы
                    rdsBroadcastFunctionCallsEx(BlockData->Parent,
                                                EditCtrlFuncSet,
                                                &callparams,
                                                0);
                    break;
                }
              break;

Раньше выбранный пункт меню не анализировался: он был единственным, и проверять, какой именно пункт выбран пользователем, не имело смысла. Теперь у нас четыре дополнительных пункта меню – старый, как и раньше, имеет номер функции, равный единице, три новых – равный двум. Переданный в параметре ExtParam указатель на структуру данных выбранного пункта меню приводится к правильному типу и записывается во вспомогательную переменную menu, после чего оператором switch проверяется номер функции этого пункта menu->Function. Для номера 1 (старый пункт «Открыть/Закрыть») выполняются те же действия, что и в старой модели: состояние блока переключается, значение нужного входа подается на выход, взводится сигнал готовности. Для номера 2 (три новых пункта) данные меню menu->Data записываются в поле Command структуры параметров функции callparams – при добавлении пунктов в контекстное меню мы специально сделали так, чтобы данные пункта меню соответствовали команде, которую нужно передать блокам при его выборе. В поле servSize структуры параметров записывается ее размер, после чего вызывается сервисная функция RDS rdsBroadcastFunctionCallsEx, которая вызовет функцию, идентификатор которой хранится в глобальной переменной EditCtrlFuncSet, у всех блоков родительской для данного блока подсистемы (BlockData->Parent), передав им в качестве параметра функции указатель на структуру callparams. Нам не нужно вызывать функцию у блоков вложенных подсистем или останавливать дальнейшие вызовы по желанию одного из вызванных блоков, поэтому мы не указываем в вызове никаких флагов (последний параметр функции rdsBroadcastFunctionCallsEx равен нулю).

Осталось только добавить в switch реакцию на вызов функции, и модель готова:

            // Вызов функции блока
            case RDS_BFM_FUNCTIONCALL:
              // Приведение ExtParam к правильному типу
              func=(RDS_PFUNCTIONCALLDATA)ExtParam;
              // Проверяем, какая функция вызвана
              if(func->Function==EditCtrlFuncSet)
                { // Вызвана нужная нам функция
                  TProgGuideEditCtrlSetParams *params;
                  // Приводим указатель на параметры к нужному типу
                  // и проверяем размер переданной структуры
                  params=(TProgGuideEditCtrlSetParams*)(func->Data);
                  if(params->servSize<
              sizeof(TProgGuideEditCtrlSetParams))
                    break; // Размер недостаточен
                  // В зависимости от Command меняем режим блока
                  switch(params->Command)
                    { case 0: bypass=1; break;       // Закрыть
                      case 1: bypass=0; break;       // Открыть
                      case 2: bypass=!bypass; break; // Переключить
                      default: return RDS_BFR_DONE;  // Ошибка
                    }
                  // Взводим сигнал готовности и передаем нужный вход на выход
                  Ready=1;
                  out=bypass?x_ext:x_int;
                }
              break;

В этой реакции прежде всего проверяется, совпадает ли идентификатор вызванной функции с EditCtrlFuncSet. Если совпадает, переданный указатель на параметры функции приводится к типу «указатель на TProgGuideEditCtrlSetParams», после чего значение поля servSize переданной структуры параметров сравнивается с размером типа TProgGuideEditCtrlSetParams. Если размер переданной структуры больше ожидаемого значения или равен ему, значит, блок может выполнять функцию – параметры, вероятнее всего, переданы верно. В зависимости от поля Command переданной структуры параметров переменной состояния блока bypass присваивается нужное значение, взводится сигнал готовности блока и значение выбранного согласно состоянию блока входа передается на его выход.

Рассмотрим работу измененной нами модели подробнее. Допустим, в какой-либо подсистеме есть несколько блоков с моделью EditControlFrame, управляющих какими-то полями ввода. Представим себе, что пользователь щелкнул на одном из них правой кнопкой мыши, и выбрал в контекстном меню пункт «Закрыть все». Модель этого блока будет вызвана в режиме RDS_BFM_MENUFUNCTION, при этом в ExtParam ей будет передан указатель на структуру с данными выбранного пункта, в которой поле Function будет равно 2, а MenuData – 0. Реагируя на этот вызов, модель запишет в поле Command структуры callparams значение 0, взятое из MenuData, и запустит вызов функции с идентификатором EditCtrlFuncSet у всех блоков подсистемы – все они будут по очереди вызываться в режиме RDS_BFM_FUNCTIONCALL. Блоки с моделью EditControlFrame, включая и тот блок, контекстное меню которого открыл пользователь, среагируют на вызов функции, присвоив переменной bypass значение 1 (поскольку Command==0) и «закрыв» тем самым поле ввода. Модели же, в которых не предусмотрена реакция на эту функцию, не опознают переданный им идентификатор, и завершатся, ничего не выполнив. Таким образом выбор пункта меню «Закрыть все» в одном из блоков приведет к закрытию всех таких же блоков подсистемы.

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