Руководство программиста
Глава 2. Создание моделей блоков
§2.7. Настройка параметров блока
§2.7.6. Открытие модальных окон в режиме расчета
Рассматриваются особенности открытия модальных окон в режиме расчета. Блок из предыдущего примера модифицируется так, чтобы модальное окно в режиме расчета не вызывало проблем. Описывается сервисная функция RDS rdsUnlockAndCall.
Изменим пример из §2.7.5 так, чтобы в режимах моделирования и расчета можно было по щелчку мыши изменить цвет блока (значение переменной «Color»), но при сбросе расчета значение переменной «Color» возвращалось бы к исходному, заданному в функции настройки. Может показаться, что для этого достаточно добавить в модель блока реакцию на нажатие кнопки мыши, в которой выполнить ту же последовательность действий, что и в функции настройки, кроме установки значения переменной «Color» по умолчанию. Попробуем сделать так и посмотрим, к каким проблемам это приведет.
Добавим рядом с оператором case для события RDS_BFM_SETUP еще один case для нажатия кнопки мыши (RDS_BFM_MOUSEDOWN) – в обоих случаях модель будет открывать одно и то же модальное окно. Эта часть модели блока будет выглядеть следующим образом (изменения выделены цветом):
// Функция настройки или нажатие кнопки мыши case RDS_BFM_SETUP: case RDS_BFM_MOUSEDOWN: // Установка параметров структуры для работы с диалогом memset(&cc,0,sizeof(cc)); cc.lStructSize=sizeof(cc); cc.hwndOwner=rdsGetAppWindowHandle(); cc.lpCustColors=CustomColors; cc.rgbResult=Color; cc.Flags=CC_RGBINIT; // Уведомление RDS об открытии модального окна rdsBlockModalWinOpen(NULL); // Вызов диалога ok=ChooseColor(&cc); // Уведомление RDS о закрытии модального окна rdsBlockModalWinClose(NULL); if(ok) // Пользователь выбрал цвет { // Запись цвета в Color Color=cc.rgbResult; if(CallMode==RDS_BFM_SETUP) // Функция настройки { // Установка этого значения Color по умолчанию // (2 – порядковый номер переменной Color в блоке) rdsSetBlockVarDefValueByCur(BlockData->Block,2); // Возвращаемое значение должно сигнализировать // о наличии изменений в схеме result=RDS_BFR_MODIFIED; } else // Нажатие кнопки мыши // Нужно перерисовать окно подсистемы rdsRefreshBlockWindows(BlockData->Parent,FALSE); } break; // …
Если пользователь закрыл окно кнопкой «» (значение ok истинно), выбранный цвет переписывается в переменную Color независимо от того, произошло это в функции настройки в режиме редактирования или в реакции на нажатие кнопки мыши в режимах моделирования и расчета. Однако, дальнейшие действия будут зависеть от режима, в котором находится RDS.
Если окно было открыто в функции настройки блока (параметр CallMode равен RDS_BFM_SETUP), необходимо выполнить в точности те же действия, что и в предыдущем варианте этого примера: установить значение Color по умолчанию и вернуть в RDS константу RDS_BFR_MODIFIED. Если же окно было открыто внутри реакции блока на нажатие кнопки мыши (параметр CallMode равен RDS_BFM_MOUSEDOWN), ничего этого делать не нужно, но необходимо перерисовать окно подсистемы, чтобы изменившееся значение переменной Color немедленно отразилось на изображении блока (при условии, что для блока задана векторная картинка, как было указано в §2.7.5). Если этого не сделать, значение переменной Color изменится, но изображение блока в окне подсистемы сохранит свой прежний цвет до тех пор, пока Windows не потребуется обновить это окно. Для перерисовки окна используется сервисная функция rdsRefreshBlockWindows:
void RDSCALL rdsRefreshBlockWindows( RDS_BHANDLE Block, // Блок или подсистема BOOL Recursive); // Перерисовывать окна вложенных
Первый параметр функции указывает блок или подсистему, чьи окна должны быть перерисованы. Изображение блока находится в окне его родительской подсистемы, поэтому в качестве первого параметра передается поле Parent (идентификатор родительской подсистемы) структуры данных блока BlockData. Второй параметр функции указывает на необходимость обновить окна всех блоков и подсистем, вложенных в указанную – в данном случае этого не нужно, поэтому этот параметр равен FALSE.
Следует отметить, что обычно в реакции на нажатие кнопок мыши не требуется вызывать функцию для обновления окна подсистемы, блок которой получил сообщение о нажатии – RDS делает это автоматически. Однако, в данном случае этого недостаточно: после нажатия кнопки мыши RDS обновит окно тогда, когда снова начнет работать цикл обработки сообщений приложения (принципы обработки сообщений Windows подробно описаны в литературе по Windows API), то есть сразу после открытия модального окна диалога. Но переменная Color изменяется только после закрытия диалога, когда автоматическое обновление окна подсистемы уже выполнено. Поэтому необходимо перерисовать окно еще раз, уже с новым значением переменной Color.
Для того, чтобы модель блока начала получать информацию о нажатии кнопок мыши, необходимо включить в окне параметров блока флаг «» (см. рис. 7). После этого можно перейти в режим моделирования и щелкнуть на изображении блока какой-нибудь кнопкой мыши. Должно открыться такое же окно диалога выбора цвета, как и при вызове функции настройки (рис. 53), и, если выбрать в нем какой-нибудь цвет, картинка блока должна изменить свой цвет на выбранный.
Рис. 54. Пока модальное окно открыто,
расчет работать не будет
В режиме моделирования все работает, как и планировалось, однако в режиме расчета начнутся проблемы. Поместим в систему блок-планировщик динамического расчета и подключим числовой индикатор к его выходу «Time» – по изменению числа на индикаторе мы сможем проверить, работает ли расчет (рис. 54). Выход «Time» планировщика не отображается в меню при создании связи, поэтому придется сначала выбрать в меню пункт «», а потом уже выбрать выход «Time» в полном списке переменных. Запустим расчет – число на индикаторе начнет увеличиваться, значит, расчет идет. Если теперь щелкнуть какой-нибудь кнопкой мыши на изображении блока выбора цвета, откроется окно диалога, но число на индикаторе перестанет изменяться – открытие модального окна вызвало остановку расчета. Если закрыть окно, расчет продолжится, но пока окно открыто, он будет стоять.
Все дело в том, что в режиме расчета обычно работает два потока команд. В главном потоке приложения обслуживается пользовательский интерфейс (включая реакции блоков на нажатие кнопок мыши), а в потоке расчета циклически вызываются модели простых блоков и производится передача данных по связям. Чтобы эти два потока не пытались одновременно обратиться к одним и тем же данным, перед вызовом функции модели в любом из потоков данные блоков блокируются. Другой поток, попытавшись получить доступ к этим данным, будет остановлен до тех пор, пока функция модели в первом потоке не завершится и данные не будут разблокированы. Тогда второй поток сможет сам заблокировать данные, и его выполнение продолжится. При щелчке на блоке выбора цвета включается блокировка данных и модель блока вызывается в главном потоке с параметром RDS_BFM_MOUSEDOWN. Поскольку внутри реакции на это событие открывается модальное окно, функция модели завершится только после закрытия этого окна, а, значит, и данные останутся заблокированными, пока окно не будет закрыто. Когда поток расчета, выполняя очередной такт, попытается получить доступ к данным блоков, он будет остановлен, пока данные не будут разблокированы, то есть до закрытия модального окна в главном потоке. Когда окно будет закрыто, функция модели в главном потоке завершится, данные будут разблокированы, и поток расчета сможет продолжить выполнение.
Такая непреднамеренная остановка расчета может привести к неприятным, а главное – неожиданным для пользователя последствиям, особенно если идет моделирование какого-нибудь процесса с синхронизацией с реальным временем. Чтобы расчет не останавливался, необходимо снимать блокировку данных на время открытия модального окна. Окно диалога выбора цвета работает только с локальной переменной cc и не обращается ни к каким данным блока, поэтому перед открытием окна можно без опасений снять блокировку, а после его закрытия – восстановить ее, поскольку цвет необходимо записать в переменную блока Color, и обращаться к ней без блокировки нельзя.
Для временного снятия блокировки данных с последующим ее восстановлением служит сервисная функция rdsUnlockAndCall:
BOOL RDSCALL rdsUnlockAndCall( RDS_IpV Callback, // Функция обратного вызова LPVOID Arg, // Аргумент функции Callback int *pResult); // Результат функции Callback
Эта функция снимает блокировку данных, после чего вызывает функцию пользователя, указатель на которую передан в параметре Callback. Функция пользователя должна иметь тип int RDSCALL func(LPVOID), в качестве ее единственного параметра ей передается параметр Arg, указанный при вызове rdsUnlockAndCall. После завершения пользовательской функции блокировка данных восстанавливается, и возвращенное функцией целое значение записывается по адресу pResult, если в этом параметре не было передано значение NULL. Таким образом, rdsUnlockAndCall позволяет вызвать произвольную функцию пользователя, сняв блокировку данных на время ее выполнения.
Изменим модель блока выбора цвета таким образом, чтобы она не приводила к остановке расчета. Для этого необходимо вынести вызов ChooseColor в отдельную функцию, имеющую совместимый с rdsUnlockAndCall формат:
int RDSCALL ModalWindowTest2Callback(LPVOID data) { return ChooseColor((CHOOSECOLOR*)data); } //=========================================
Внутри этой функции указатель общего вида data, переданный как параметр, приводится к типу «указатель на структуру CHOOSECOLOR» и подставляется в вызов ChooseColor. Функция обратного вызова имеет тип int, а возвращает она результат выполнения ChooseColor, то есть BOOL – в языке C это допустимо. Функция вернет нулевое значение, если ChooseColor вернула FALSE, и ненулевое, если TRUE. В файлах заголовков Windows API тип BOOL определен как int, а константы TRUE и FALSE как 0 и 1, так что на самом деле возвращаемые функциями типы в точности совпадают. Но даже если бы они и не совпадали, такое приведение типов не вызвало бы проблем – в языке C истиной считается любое ненулевое значение.
Теперь необходимо внести изменения в функцию модели блока (выделены цветом):
// … // Уведомление RDS об открытии модального окна rdsBlockModalWinOpen(NULL); // Вызов диалога if(rdsCalcProcessIsRunning()) // Идет расчет { // Вызов со снятием блокировки int ret; rdsUnlockAndCall(ModalWindowTest2Callback,&cc,&ret); ok=ret; } else // Режим редактирования или моделирования ok=ChooseColor(&cc); // Уведомление RDS о закрытии модального окна rdsBlockModalWinClose(NULL); if(ok) // Пользователь выбрал цвет { // Запись цвета в Color Color=cc.rgbResult; // …
Вызывать диалог со снятием блокировки имеет смысл только в режиме расчета, поэтому сначала можно определить режим RDS. Для этого используется функция rdsCalcProcessIsRunning, возвращающая TRUE, если в данный момент RDS находится в режиме расчета. В этом случае вызывается rdsUnlockAndCall, в которую в качестве указателя на функцию обратного вызова передается ModalWindowTest2Callback, в качестве аргумента функции – указатель на структуру cc, а результат функции обратного вызова будет записан в целую переменную переменную ret. Таким образом, вызов
rdsUnlockAndCall(ModalWindowTest2Callback,&cc,&ret);
эквивалентен вызову
ret=ModalWindowTest2Callback(&cc);
со снятием блокировки перед вызовом и восстановлением ее после него.
После закрытия диалога выбора цвета блокировка данных восстановится и rdsUnlockAndCall завершится. Теперь можно присвоить результат возврата функции обратного вызова (ret) переменной ok, которая будет анализироваться далее в модели. На самом деле, поскольку в Windows API типы BOOL и int полностью эквивалентны, можно было бы написать
rdsUnlockAndCall(ModalWindowTest2Callback,&cc,&ok);
и обойтись без вспомогательной переменной ret – здесь она введена для большей ясности примера.
Если же функция rdsCalcProcessIsRunning вернет FALSE, это будет означать, что RDS находится в режимах редактирования или моделирования. В этом случае диалог можно открыть обычным образом, при помощи непосредственного вызова функции ChooseColor.
Теперь, если щелкнуть на блоке выбора цвета в режиме расчета, можно увидеть, что число на индикаторе, подсоединенном к блоку-планировщику (см. рис. 54), продолжает увеличиваться, несмотря на открытое модальное окно диалога, то есть поток расчета продолжает выполняться. Это не мешает работе самого блока – если выбрать в диалоге какой-нибудь цвет, изображение блока окрасится в него, как и планировалось.
Все вышеизложенное справедливо только в том случае, если RDS работает в стандартном режиме, то есть с двумя потоками. Если в настройках RDS на вкладке «» будет установлен флаг «», открытие модального окна в любом случае будет останавливать расчет – главный поток будет занят окном и не сможет выполнять расчет до его закрытия. Снятие блокировки на время открытия окна никак не может помочь потоку расчета, потому что потока расчета просто не существует. К счастью, режим с единственным потоком используется крайне редко (в основном, для увеличения скорости расчета на медленных машинах), и в окне настроек содержится предупреждение о проблемах, возникающих при его включении, так что можно считать, что пользователь предупрежден, и знает, что делает, включая этот режим.