Руководство программиста
Глава 2. Создание моделей блоков
§2.7. Настройка параметров блока
§2.7.2. Использование объектов-окон RDS
Рассматривается использование вспомогательных объектов RDS, облегчающих создание и открытие модальных окно с полями ввода, описаны сервисные функции для работы с этими объектами. С их помощью в один из ранее описывавшихся блоков добавлен простой пользовательский интерфейс. Во другом примере рассматривается блок-генератор, выдающий на выход синусоиду, косинусоиду или прямоугольные импульсы по выбору пользователя, при этом некоторые поля в его окне настройки, формируемом при помощи вспомогательного объекта RDS, разрешаются или запрещаются в зависимости от значений других полей.
Простейшие диалоговые окна могут также открываться с помощью одного из вспомогательных объектов RDS. Вообще, вспомогательных объектов в RDS довольно много, они позволяют упростить выполнение различных сложных операций (разбор текста, программное задание переменных блока и т.п.). В данном случае мы воспользуемся объектом, создаваемым сервисной функцией rdsFORMCreate. При помощи этой и нескольких других сервисных функций можно создать модальное окно и добавить в него поля ввода для редактирования различных параметров. Подробно эти функции будут описаны ниже, здесь же будет приведен простой пример их использования. Добавим в модель Test1, приведенную в качестве примера реакции на события RDS_BFM_INIT и RDS_BFM_CLEANUP, возможность редактирования параметров, хранящихся в личной области данных. Класс личной области данных TTest1Data и модель блока должны быть изменены следующим образом (изменения выделены цветом):
//====== Класс личной области данных ====== class TTest1Data { public: int IParam; // Целый параметр double DParam; // Вещественный параметр int Setup(void); // Функция настройки параметров TTest1Data(void) // Конструктор класса { IParam=0; DParam=0.0; rdsMessageBoxW(L"Область создана",L"TTest1Data",MB_OK); }; ~TTest1Data() // Деструктор класса { rdsMessageBoxW(L"Область удалена",L"TTest1Data",MB_OK);}; }; //========================================= //============= Модель блока ============== extern "C" __declspec(dllexport) int RDSCALL Test1(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { TTest1Data *data; switch(CallMode) { case RDS_BFM_INIT: // Инициализация BlockData->BlockData=new TTest1Data(); break; case RDS_BFM_CLEANUP: // Очистка data=(TTest1Data*)(BlockData->BlockData); delete data; break; case RDS_BFM_SETUP: // Функция настройки data=(TTest1Data*)(BlockData->BlockData); return data->Setup(); } return RDS_BFR_DONE; } //=========================================
В этом примере часть программы, открывающая модальное окно, является функцией-членом класса TTest1Data. С тем же успехом она могла быть оформлена как обычная функция или вставлена непосредственно внутрь функции модели блока.
В класс личной области добавлена новая функция-член int Setup(void), которая вызывается при реакции на событие RDS_BFM_SETUP и возвращает RDS_BFR_DONE или RDS_BFR_MODIFIED в зависимости от действий пользователя. Тело функции выглядит следующим образом:
// Функция настройки параметров // ВАЖНО: Исходный текст программы должен быть записан в UTF8, // в противном случае необходимо использовать версии функций // с суффиксом "W" и символьные константы с префиксом "L" int TTest1Data::Setup(void) { RDS_HOBJECT window; // Идентификатор вспомогательного объекта BOOL ok; // Пользователь нажал "OK" // Создание окна window=rdsFORMCreate(FALSE,-1,-1,"Ввод параметров"); // Добавление полей ввода rdsFORMAddEdit(window,0,1,RDS_FORMCTRL_EDIT, "Целый параметр:",80); rdsFORMAddEdit(window,0,2,RDS_FORMCTRL_EDIT, "Вещественный параметр:",80); // Занесение исходных значений в поля ввода rdsSetObjectInt(window,1,RDS_FORMVAL_VALUE,IParam); rdsSetObjectDouble(window,2,RDS_FORMVAL_VALUE,DParam); // Открытие окна ok=rdsFORMShowModalEx(window,NULL); if(ok) { // Нажата кнопка OK - запись параметров обратно в блок IParam=rdsGetObjectInt(window,1,RDS_FORMVAL_VALUE); DParam=rdsGetObjectDouble(window,2,RDS_FORMVAL_VALUE); } // Уничтожение окна rdsDeleteObject(window); // Возвращаемое значение return ok?RDS_BFR_MODIFIED:RDS_BFR_DONE; } //=========================================
Сначала при помощи сервисной функции rdsFORMCreate в памяти создается вспомогательный объект-окно. Первый параметр FALSE указывает на то, что у окна нет вкладок, два значения −1 заставляют RDS автоматически вычислить необходимую ширину и высоту окна. Последний параметр определяет текст в заголовке создаваемого окна, в данном случае – «Ввод параметров». Затем, при помощи функции rdsFORMAddEdit, в созданное окно добавляются два поля ввода. Эта функция в разных кодировках принимает следующие параметры:
rdsFORMAddEditA( RDS_HOBJECT Win, // Объект окна int TabId, // Идентификатор вкладки int CtrlId, // Идентификатор поля ввода DWORD Type, // Тип поля ввода RDSCSTR Caption, // Заголовок поля ввода (UTF8) int Width); // Ширина поля ввода rdsFORMAddEditW( RDS_HOBJECT Win, // Объект окна int TabId, // Идентификатор вкладки int CtrlId, // Идентификатор поля ввода DWORD Type, // Тип поля ввода RDSWCSTR Caption, // Заголовок поля ввода (UTF16) int Width); // Ширина поля ввода // Функция-псевдоним rdsFORMAddEdit( RDS_HOBJECT Win, // Объект окна int TabId, // Идентификатор вкладки int CtrlId, // Идентификатор поля ввода DWORD Type, // Тип поля ввода RDSXCSTR Caption, // Заголовок поля ввода (кодировка по умолчанию) int Width); // Ширина поля ввода
В первом параметре передается идентификатор вспомогательного объекта, созданного функцией rdsFORMCreate. Поскольку в созданном окне нет вкладок, вместо идентификатора вкладки передается 0. В качестве идентификатора поля передаются 1 для первого параметра и 2 для второго (это могут быть любые целые числа). Тип поля в обоих случаях равен RDS_FORMCTRL_EDIT (простое поле ввода). Текст, передаваемый в функцию, отображается слева от поля ввода, а ширина задает ширину поля в точках экрана (для обоих параметров задается ширина в 80 точек).
После создания полей ввода в них заносятся исходные значения параметров при помощи функций rdsSetObjectInt и rdsSetObjectDouble. В эти функции передаются: идентификатор объекта окна; идентификатор поля, присвоенный ему при вызове rdsFORMAddEdit (1 для первого поля, 2 для второго); константа RDS_FORMVAL_VALUE, указывающая, что устанавливается значение поля; и, наконец, собственно исходное значение параметра.
После того, как поля ввода созданы и их исходные значения установлены, окно открывается функцией rdsFORMShowModalEx. Эта функция вернет TRUE, если пользователь нажмет в окне кнопку «», и FALSE, если он нажмет кнопку «» или просто закроет окно. При нажатии «» параметрам блока присваиваются новые значения, полученные из полей ввода функциями rdsGetObjectInt и rdsGetObjectDouble (их параметры аналогичны параметрам функций rdsSetObjectInt и rdsSetObjectDouble). Затем вспомогательный объект уничтожается функцией rdsDeleteObject и возвращается значение, соответствующее нажатой пользователем кнопке (RDS_BFR_MODIFIED для кнопки «» и RDS_BFR_DONE для кнопки «»). Следует отметить, что, хотя при изменении значений и нажатии «» функция возвращает константу RDS_BFR_MODIFIED и RDS будет считать схему измененной и предупреждать пользователя при выходе, значения параметров IParam и DParam будут потеряны, даже если пользователь сохранит схему. Эти параметры находятся в личной области данных блока, и за их сохранение и загрузку должна отвечать функция модели. Модель в этом примере не реагирует на события загрузки и сохранения, соответствующие реакции рассмотрены в §2.8.
Рис. 47. Модальное окно для настройки
параметров блока, созданное
вспомогательным объектом RDS
Для того, чтобы пользователь мог вызвать функцию настройки блока, в окне параметров блока с этой моделью необходимо установить флаг «» на вкладке «». Теперь можно проверить работу функции настройки: при выборе соответствующего пункта в контекстном меню блока должно открыться окно с двумя полями ввода (рис. 47).
Функция открытия модального окна rdsFORMShowModalEx возвращает управление модели блока только после того, как пользователь закроет окно. Достаточно часто возникает необходимость реагировать на действия пользователя до закрытия окна, например, разрешать или запрещать ввод данных в какие-либо поля в зависимости от значения других полей. Для этого в функцию rdsFORMShowModalEx можно передать дополнительный параметр – указатель на функцию, которая будет вызываться каждый раз при изменении значений в полях ввода. В этой функции можно разрешать или запрещать отдельные поля ввода окна, изменять их значения и т.п (в предыдущем примере этот параметр имел значение NULL, и никакая дополнительная функция не вызывалась).
Рассмотрим блок, выдающий на вещественный выход «y» синусоиду, косинусоиду или прямоугольные импульсы по выбору пользователя. Значение времени блок будет получать через динамическую переменную «DynTime» типа double, которая создается и изменяется планировщиком динамического расчета (см. также пример в §2.6.2). В функции настройки блока пользователь сможет выбрать тип функции, ее период и, при формировании прямоугольных импульсов, длительность импульса. Для синусоиды и косинусоиды не требуется задание длительности импульса, поэтому, при выборе этих типов функции поле ввода длительности должно быть запрещено.
Блок будет иметь следующую структуру переменных:
| Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|---|---|
| 0 | Start | Сигнал | 1 | Вход | ✓ | 0 |
| 1 | Ready | Сигнал | 1 | Выход | 0 | |
| 2 | y | double | 8 | Выход | 0 |
У этого блока нет входов, участвующих в формировании значения «y», поэтому в его модели не будет реакции на такт расчета – единственная переменная, изменение которой должно приводить к вычислению нового значения выхода, это «DynTime». Все действия по вычислению «y» будут производиться в реакции на изменение этой динамической переменной, поэтому, чтобы блок зря не тратил процессорное время, в его параметрах следует отключить флаг «».
Модель блока вместе с личной областью данных и функцией обратного вызова для запрещения поля ввода будет выглядеть следующим образом:
//====== Класс личной области данных ====== class TTestGenData { public: int Type; // Тип (0-sin,1-cos,2-прямоугольные) double Period; // Период double Impulse; // Длительность импульса RDS_PDYNVARLINK Time; // Связь с динамической // переменной времени int Setup(void); // Функция настройки TTestGenData(void) // Конструктор класса { Type=0; Period=1.0; Impulse=0.5; // Подписка на динамическую переменную времени Time=rdsSubscribeToDynamicVar(RDS_DVPARENT, "DynTime", "D", TRUE); }; ~TTestGenData(void) // Деструктор класса { // Прекращение подписки rdsUnsubscribeFromDynamicVar(Time); }; }; //==== Прототип функции обратного вызова окна настроек ==== void RDSCALL TestGenDataCheckFunc(RDS_HOBJECT); //====== Функция редактирования параметров ====== // ВАЖНО: Исходный текст программы должен быть записан в UTF8, // в противном случае необходимо использовать версии функций // с суффиксом "W" и символьные константы с префиксом "L" int TTestGenData::Setup(void) { RDS_HOBJECT window; // Идентификатор вспомогательного объекта BOOL ok; // Пользователь нажал "OK" // Создание окна window=rdsFORMCreate(FALSE,-1,-1,"Простой генератор"); // Добавление полей ввода rdsFORMAddEdit(window,0,1,RDS_FORMCTRL_COMBOLIST, "Вид:",210); rdsFORMAddEdit(window,0,2,RDS_FORMCTRL_EDIT, "Период:",80); rdsFORMAddEdit(window,0,3,RDS_FORMCTRL_EDIT, "Длительность:",80); // Установка списка вариантов rdsSetObjectStr(window,1,RDS_FORMVAL_LIST, "Синус\nКосинус\nПрямоугольные импульсы"); // Занесение исходных значений в поля ввода rdsSetObjectInt(window,1,RDS_FORMVAL_VALUE,Type); rdsSetObjectDouble(window,2,RDS_FORMVAL_VALUE,Period); rdsSetObjectDouble(window,3,RDS_FORMVAL_VALUE,Impulse); // Открытие окна с указанием функции обратного вызова ok=rdsFORMShowModalEx(window,TestGenDataCheckFunc); if(ok) { // Нажата кнопка OK - запись параметров обратно в блок Type=rdsGetObjectInt(window,1,RDS_FORMVAL_VALUE); Period=rdsGetObjectDouble(window,2,RDS_FORMVAL_VALUE); Impulse=rdsGetObjectDouble(window,3,RDS_FORMVAL_VALUE); } // Уничтожение окна rdsDeleteObject(window); // Возвращаемое значение return ok?RDS_BFR_MODIFIED:RDS_BFR_DONE; } //====== Функция обратного вызова для окна настроек ====== void RDSCALL TestGenDataCheckFunc(RDS_HOBJECT win) { // Считать номер пункта выпадающего списка int type=rdsGetObjectInt(win,1,RDS_FORMVAL_VALUE); // Разрешить ввод длительности, если выбран пункт 2 rdsSetObjectInt(win,3,RDS_FORMVAL_ENABLED,type==2); } //============= Модель блока ============== extern "C" __declspec(dllexport) int RDSCALL TestGen(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { // Макроопределения для статических переменных #define pStart ((char *)(BlockData->VarTreeData)) #define Start (*((char *)(pStart))) #define Ready (*((char *)(pStart+RDS_VSZ_S))) #define y (*((double *)(pStart+2*RDS_VSZ_S))) // Вспомогательная переменная – указатель на личную область // данных блока, приведенный к правильному типу TTestGenData *data; switch(CallMode) { // Инициализация case RDS_BFM_INIT: BlockData->BlockData=new TTestGenData(); break; // Очистка case RDS_BFM_CLEANUP: data=(TTestGenData*)(BlockData->BlockData); delete data; break; // Проверка типа переменных case RDS_BFM_VARCHECK: if(strcmp((char*)ExtParam,"{SSD}")==0) return RDS_BFR_DONE; return RDS_BFR_BADVARSMSG; // Функция настройки case RDS_BFM_SETUP: data=(TTestGenData*)(BlockData->BlockData); return data->Setup(); // Изменение динамической переменной или запуск расчета case RDS_BFM_STARTCALC: case RDS_BFM_DYNVARCHANGE: data=(TTestGenData*)(BlockData->BlockData); if(data->Period==0.0) // Нельзя вычислить частоту return 0; // Проверка наличия переменной “DynTime" if(data->Time!=NULL && data->Time->Data!=NULL) { // Динамическая переменная найдена – чтение значения double t=*((double*)data->Time->Data); switch(data->Type) { case 0: // Синус y=sin(2*M_PI*t/data->Period); break; case 1: // Косинус y=cos(2*M_PI*t/data->Period); break; case 2: // Прямоугольные импульсы t=fmod(t,data->Period); y=(t>data->Impulse)?-1.0:1.0; break; } // Взвести Ready для передачи выхода по связям Ready=1; } break; } return RDS_BFR_DONE; // Отмена макроопределений #undef y #undef Ready #undef Start #undef pStart } //=========================================
Как и в предыдущем примере, личная область данных блока оформлена в виде класса. В нем размещаются три параметра блока: тип формируемой функции Type, период функции Period и длительность прямоугольного импульса Impulse. Также в личной области данных находится указатель на структуру подписки Time для доступа к динамической переменной «DynTime». Подписка на переменную производится в конструкторе класса личной области данных, прекращение подписки – в деструкторе. Функция-член класса Setup отвечает за работу с окном настройки.
За описанием класса личной области данных следует прототип функции обратного вызова TestGenDataCheckFunc, отвечающей за запрет и разрешение поля ввода длительности импульса. Указатель на нее будет передаваться в функцию rdsFORMShowModalEx при открытии модального окна. Тело этой функции будет описано позже. Можно было бы разместить его прямо здесь и обойтись без описания прототипа, но для связности изложения лучше сначала рассмотреть функцию, открывающую окно настройки, а затем – используемую в ней функцию запрета и разрешения поля ввода.
Рис. 48. Окно настройки параметров
генератора (поле длительности
импульса запрещено)
Функция-член класса TTestGenData::Setup отвечает за редактирование параметров блока. Она похожа на аналогичную функцию из прошлого примера. Сначала при помощи функции rdsFORMCreate создается вспомогательный объект-окно. Затем, при помощи функции rdsFORMAddEdit, в него добавляются три поля ввода: выпадающий список (RDS_FORMCTRL_COMBOLIST) для выбора типа формируемой функции и два простых поля ввода (RDS_FORMCTRL_EDIT) для периода и длительности импульса (рис. 48). На этапе создания полей ввода никак не указывается, что возможность задания длительности импульса будет зависеть от выбранного в выпадающем списке типа функции – этим будет заниматься функция обратного вызова TestGenDataCheckFunc. Далее при помощи функции rdsSetObjectStr с параметром RDS_FORMVAL_LIST в поле ввода для выпадающего списка (поле номер 1) передается строка, содержащая список возможных вариантов выбора, разделенных символом перевода строки «\n». Значение этого поля ввода равно номеру выбранного из списка варианта начиная с нуля. Таким образом, варианту «синус» будет соответствовать значение 0, «косинус» – 1, «прямоугольные импульсы» – 2.
Затем в каждое из трех полей ввода записываются текущие значения параметров блока (в поле с выпадающим списком записывается целый номер варианта), после чего окно открывается при помощи функции rdsFORMShowModalEx. Вторым параметром в эту функцию передается указатель на функцию обратного вызова. Если пользователь закроет окно кнопкой «», rdsFORMShowModalEx вернет значение TRUE, и значения полей ввода будут считаны при помощи функций rdsGetObjectInt и rdsGetObjectDouble и записаны в соответствующие параметры блока. Затем объект окна будет уничтожен вызовом rdsDeleteObject, и функция вернет константу RDS_BFR_MODIFIED (если пользователь нажал «») или RDS_BFR_DONE (если пользователь закрыл окно другим способом).
За функцией TTestGenData::Setup следует тело функции обратного вызова TestGenDataCheckFunc, прототип которой был описан ранее. Эта функция должна иметь тип «void RDSCALL» и принимать единственный параметр – идентификатор объекта окна типа RDS_HOBJECT. В самой функции производится всего два действия. Сначала при помощи функции rdsGetObjectInt считывается значение поля ввода с идентификатором 1, то есть номер варианта, выбранного в выпадающем списке. Этот номер присваивается вспомогательной переменной type. Затем в поле ввода номер 3 (длительность импульса) передается флаг разрешения редактирования (вызов rdsSetObjectInt с параметром RDS_FORMVAL_ENABLED), истинный при type==2 (вариант «прямоугольные импульсы»). В результате ввод данных в поле с идентификатором 3 будет разрешен только в том случае, если в выпадающем списке будет выбран последний вариант. Функция TestGenDataCheckFunc будет вызываться при любом изменении данных в полях ввода, в том числе и при выборе нового варианта из списка, в результате чего разрешенность поля ввода длительности импульса будет всегда соответствовать типу формируемой периодической функции. Например, на рис. 48 выбрано формирование синусоиды, и поле длительности запрещено.
В самой функции модели блока TestGen нет ничего необычного, все используемые в ней реакции на события уже рассматривались ранее. При вызове модели в режиме RDS_BFM_INIT создается объект класса С и указатель на него записывается в BlockData->BlockData. В режиме RDS_BFM_CLEANUP созданный объект удаляется. В режиме RDS_BFM_VARCHECK проверяется допустимость структуры статических переменных блока (должно быть два обязательных сигнала и вещественная переменная двойной точности, то есть строка типа должна равняться «{SSD}»). При вызове модели в режиме RDS_BFM_SETUP вызывается функция-член Setup класса личной области данных блока (указатель на объект класса предварительно присваивается вспомогательной переменной data). Наконец, при запуске расчета (RDS_BFM_STARTCALC) и при изменении единственной используемой в блоке динамической переменной (RDS_BFM_DYNVARCHANGE) вычисляется значение выхода блока в зависимости от текущего значения времени, полученного из «DynTime», и типа формируемой функции, хранящегося в поле Type класса личной области данных блока. Для значений Type 0 или 1 вычисляется соответственно синус или косинус произведения времени на частоту, вычисленную по заданному пользователем периоду Period (чтобы синус или косинус имел период T, частота должна быть 2π/T). Для значения 2, соответствующего формированию прямоугольных импульсов, вычисляется остаток от деления времени на период и выходу блока присваивается значение 1, если этот остаток меньше длительности импульса, и −1 в противном случае. Если бы значение y вычислялось в такте моделирования, больше ничего не требовалось бы – при запуске модели в режиме RDS_BFM_MODEL сигнал Ready автоматически взводится, что приводит к передаче выходов блока по связям в конце такта. В этой модели выход вычисляется при запуске расчета и при изменении динамического времени, поэтому функция модели должна самостоятельно присвоить сигналу Ready значение 1. При этом значение выхода блока будет передано по связям в конце ближайшего такта расчета, хотя его модель и не будет вызвана в этом такте.
В рассмотренных ранее моделях, использующих динамические переменные, для вычисления начального значения выхода сигналу Start давалось единичное значение по умолчанию, что приводило к обязательному запуску модели блока в первом такте расчета. Модель этого блока не вызывается в такте расчета, поэтому вычисление начального y производится в реакции на запуск расчета (RDS_BFM_STARTCALC) – при этом просто выполняются те же самые действия, что и при изменении динамической переменной.
Чтобы проверить работу блока с этой моделью, следует установить для него флаг «» на вкладке «» окна параметров, поместить в схему блок-планировщик динамического расчета, который создаст переменную «DynTime» и будет управлять ей, и подключить к выходу блока стандартный график из библиотеки RDS, также получающий значение времени из переменной «DynTime» (рис. 49). При запуске расчета график должен отобразить функцию, тип которой выбран в настройках блока. В окне настроек блока поле ввода длительности импульса должно быть активно, только если в выпадающем списке выбран вариант «прямоугольные импульсы».
Рис. 49. Проверка работы генератора (выбрано формирование синусоиды)
Эта модель имеет тот же дефект, что и предыдущий пример – при сохранении схемы параметры Type, Period и Impulse не сохраняются, хотя RDS и предупреждает пользователя о наличии изменений в схеме. Позже мы исправим этот недостаток, добавив в модель соответствующие реакции.