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

Описания RDS

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

Глава 1. Устройство RDS

§1.8. Открытие окон в модели блока

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

## Глава 1. Устройство RDS

### §1.8. Открытие окон в модели блока

Описываются особенности открытия модальных (блокирующих доступ к остальным окнам приложения до своего закрытия) и немодальных (позволяющих переключаться в другие окна) окон из программы модели блока и связанные с этим проблемы, на которые следует обратить внимание. Различные примеры работы с окнами в моделях блоков приведены в §2.7.

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

Для корректного взаимодействия создаваемых моделью окон с RDS необходимо соблюдать некоторые правила. Если модель открывает немодальное окно, процедура обработки сообщений этого окна будет вызываться в произвольные моменты времени, тогда, когда это необходимо Windows. В режиме расчета к главному потоку RDS, занимающемуся обслуживанием окон и интерфейса, добавляется поток расчета, по очереди вызывающий модели простых блоков и передающий данные по связям между блоками. При этом необходимо синхронизировать работу процедуры окна, которая, вероятнее всего, будет работать в главном потоке, с потоком расчета. Если оба потока одновременно обратятся к каким-либо данным блока, могут возникнуть серьезные ошибки. При обращении к данным изнутри функции модели таких проблем не возникает, поскольку RDS самостоятельно синхронизирует вызовы моделей блоков в обоих потоках. Однако, процедура окна может быть вызвана Windows в любой момент, без синхронизации, за которой следит RDS. Поэтому, чтобы избежать коллизий, в процедуре окна (и связанных с ней функциях, если они есть) необходимо перед любым обращением к данным блока и вызовом сервисных функций RDS вызывать функцию rdsLockBlockData, которая заблокирует данные блока и запретит другому потоку доступ к ним. После выполнения всех необходимых действий с данными блока необходимо вызвать функцию rdsUnlockBlockData, которая разблокирует данные и снова разрешит к ним доступ. Если второй поток попытается обратиться к данным блока после вызова rdsLockBlockData, он будет остановлен до тех пор, пока не будет вызвана rdsUnlockBlockData, поэтому желательно разблокировать данные блока как можно быстрее. Крайне необходимо следить за тем, чтобы за каждым вызовом функции rdsLockBlockData обязательно следовал вызов rdsUnlockBlockData, иначе один из потоков может зависнуть. Если компилятор поддерживает конструкции типа try...finally, целесообразно использовать их для слежения за парностью этих вызовов, например (для Borland C++):

      rdsLockBlockData(); // Блокировка данных
      try
        { // .....
          // Действия с данными блока или вызов сервисных функций
          // .....
        }
      __finally
        { rdsUnlockBlockData(); // Снятие блокировки
        }

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

Для удобства пользователя модель блока может зарегистрировать созданные ей немодальные окна в RDS при помощи сервисной функции rdsRegisterWindow, которая добавит название созданного окна в меню «Окна» и кнопку для вызова этого окна на панель окон в главном окне RDS. При этом следует информировать RDS об активации зарегистрированного окна функцией rdsRegWinActivateNotify.

Обычно открытие окон связывают с выбором каких-либо пунктов меню, созданных блоком, или с нажатием кнопок мыши или клавиш. Все эти действия выполняются в главном потоке RDS. Открывать окна из потока расчета (при вызове модели с параметром RDS_BFM_MODEL) не рекомендуется. Следует также помнить, что при удалении блока модель обязана уничтожить все открытые этим блоком окна.

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

Если логика работы блока требует открытия модального окна в режиме расчета, и это окно не может быть заменено немодальным, можно воспользоваться механизмом вызова функции без блокировки данных. Для этого нужно создать функцию специального вида, которая будет открывать модальное окно, после чего передать указатель на эту функцию сервисной функции RDS rdsUnlockAndCall. Эта функция снимет блокировку данных, после чего вызовет указанную функцию пользователя, по завершении которой блокировка будет восстановлена. Поскольку в этом случае на время открытия модального окна блокировка данных снимается, в процедуре окна, несмотря на его модальность, необходимо вызывать rdsLockBlockData перед обращением к данным блока и rdsUnlockBlockData после него. Пример использования функции приведен в §2.7.6.

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

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

Пока модальное окно открыто, пользователь не сможет ни загрузить другую схему, ни закрыть RDS, ни удалить какой-либо блок. Модальное окно блокирует доступ к другим окнам, включая главное окно RDS и окна подсистем, поэтому пользователь просто не сможет выполнить соответствующие действия. Может показаться, что выгрузка DLL с моделью блока, открывшего модальное окно, невозможна. Однако, загрузить схему или удалить блок может не только пользователь. Если RDS работает под управлением другого приложения (например, использующего библиотеку «RdsCtrl.dll», подробно описанную в главе 2), это приложение может приказать RDS загрузить другую схему независимо от наличия открытых модальных окон. Открытое модальное окно также не может помешать какой-либо модели блока, вызванной по таймеру или в потоке расчета, загрузить другую схему или удалить блок при помощи соответствующих сервисных функций. Чтобы избежать катастрофических последствий из-за не вовремя выгруженной DLL, модель блока должна информировать RDS об открытии и закрытии модальных окон при помощи сервисных функций rdsBlockModalWinOpen и rdsBlockModalWinClose соответственно (для слежения за парностью вызовов этих функций можно использовать конструкцию try...finally, но в приведенный ниже пример она не включена):

      // Информация об открытии окна блоком Block
      rdsBlockModalWinOpen(Block);
      // ...
      // Открытие модального окна средствами Windows
      // ...
      // Информация о закрытии окна
      rdsBlockModalWinClose(Block);

Обе функции принимают единственный параметр – идентификатор блока, открывающего модальное окно (в примере – Block). Если функция вызывается непосредственно из модели, можно вместо идентификатора блока указать NULL – RDS самостоятельно определит, модель какого блока вызывается в данный момент. Если же модальное окно открывается из немодального (например, при нажатии какой-либо кнопки в окне), идентификатор блока указывать обязательно. Поскольку процедура немодального окна вызывается Windows в произвольные моменты времени без всякой синхронизации с вызовами моделей, RDS в этом случае не сможет определить, к какому именно блоку относится данное модальное окно.

Вызов функции rdsBlockModalWinOpen сообщает RDS об открытии нового модального окна, которое будет считаться открытым до вызова функции rdsBlockModalWinClose. В промежутке между этими двумя вызовами удаление блока, открывшего окно, будет запрещено. Если после вызова rdsBlockModalWinOpen от управляющего приложения или от другой модели блока поступит команда загрузить другую схему, RDS попытается закрыть модальное окно, по очереди передавая в окна верхнего уровня стандартное сообщение Windows WM_CLOSE до тех пор, пока данное окно не закроется (о закрытии окна RDS узнает, получив вызов rdsBlockModalWinClose). Чтобы этот механизм сработал, все модальные окна, открываемые моделями блоков, должны принадлежать главному окну RDS (его дескриптор можно получить при помощи сервисной функции rdsGetAppWindowHandle). Пример открытия модального окна средствами Windows с использованием сервисных функций rdsBlockModalWinOpen и rdsBlockModalWinClose приведен в §2.7.5.

В некоторых случаях логика работы блока требует вывода запроса о необходимости сохранения введенных пользователем данных при закрытии модального окна. Обычно окно запроса содержит три кнопки: «Да», «Нет» и «Отмена», причем при нажатии кнопки «Отмена» модальное окно не закрывается. Написание процедуры модального окна в этом случае требует осторожности, поскольку механизм закрытия окон при помощи сообщения WM_CLOSE, описанный выше, может привести к появлению бесконечного цикла: модальное окно получает сообщение WM_CLOSE и выводит запрос о сохранении данных, окно запроса тоже получает сообщение WM_CLOSE (что равносильно нажатию кнопки «Отмена») и возвращает управление модальному окну, не закрывая его, модальное окно снова получает сообщение «WM_CLOSE» и т.д. Чтобы избежать этого цикла, перед выводом запроса следует вызвать сервисную функцию rdsModalWindowMustClose, которая вернет значение TRUE, если в данный момент идет принудительное закрытие модальных окон. В этом случае модальное окно необходимо закрыть, не выводя запросов пользователю.

Если модальные окна открываются не стандартными средствами Windows, а специальными сервисными функциями RDS (rdsMessageBox, rdsCallColorDialog, rdsFORMShowModalEx и т.п.), в вызове функций rdsBlockModalWinOpen и rdsBlockModalWinClose нет необходимости. Сервисные функции, открывающие модальные окна, самостоятельно информируют RDS об их открытии и закрытии.