Многие ветераны программирования в BlackBox Component Builder знакомы с целым комплексом проблем, вызываемых самим наличием процедуры Ports.Rider.Input, не говоря уже о последствиях активного использования ее разработчиками при написании фреймворка. Предлагаю обсудить здесь достоинства и недостатки данного проектного решения, а заодно и предложить альтернативы.
Пробему вызывают многочисленные циклы следующего вида:
Код:
REPEAT f.Input(x, y, m, isDown) UNTIL ~isDown;
Во время работы подобного цикла загрузка процессора вырастает до 100%, не работают фоновые задачи типа Services.Action и не производится обработка оконной очереди сообщений Windows.
Зачем нужны такие циклы, какую задачу они решают? Дело в том, что подобный обработчик вынужден использовать всякий View, который хочет дождаться отпускания кнопки мыши пользователем. А такая задача возникает повсеместно. От банального нажатия кнопкоподобного элемента интерфейса (ведь если отпустить кнопку мыши за пределами элемента, нажатие не должно иметь эффекта) до выделения блока текста. Последний пример наглядно демонстрирует, что мы имеем дело с режимом в интерфейсе. Как известно, режимов должно быть как можно меньше и всякий режим должен быть известен пользователю. В данном случае пользователь знает о режиме ровно настолько, насколько он знает, что удерживает кнопку мыши в нажатом состоянии. Обычному пользователю этого достаточно, так что проблема не в самом наличии режима, а в его реализации.
Возможны ли альтернативные реализации данного режима, столь необходимого в нашей повседневной работе? Послушаем мнение специалистов...
Штирлиц писал(а):
Обычно обработку реализуют так (схема):
WM_LBUTTONDOWN:
SetCapture; (подготовка к обработке)
WM_MOUSEMOVE:
(обработка, например выделяем текст)
WM_MOUSEUP:
ReleaseCapture;(конец обработки)
Для этого у нас почти все есть
Controllers.TrackMsg - возникает когда нажали мышь
Controllers.PollCursorMsg - возникает при движении мыши
ввести еще сообщение типа(по крайней мере в документации я не нашел чего-то подобного)
Controllers.MouseUpMsg возникает когда мышь отпущена
И тогда не надо будет писать
REPEAT f.Input(x, y, m, isDown) UNTIL ~isDown;
Если такой способ обработки в приложении остается на совести программиста, то из ядра Blackbox такие вещи надо выкинуть однозначно (если это возможно). Необходимо критически пересмотреть обработку сообщений в ядре Blackbox. Тогда и процессор не будет загружаться на 100% и другие задачи замирать не будут, только от того что нажата мышь.
Ввести все-таки функцию (например, включить ее в модуль System\Windows) типа PROCEDURE ProcessMessages: BOOLEAN,
которую программист мог-бы вызывать на свое усмотрение (все зависит от задачи которую он решает).
переносимость не пострадает, потому как на других платформах ProcessMessages может быть пустышкой.
В предложенном решении для маркировки входа и выхода из режима используются стандартные процедуры SetCapture и ReleaseCapture. Именно так реализован "захват мыши" в Windows API. Все сообщения о перемещении "захваченной" мыши и изменении состояния ее кнопок приходят в оконную процедуру того элемента, который был передан в качестве параметра процедуре SetCapture (если, конечно, пользователь не переключится в другой процесс!).
К сожалению или к счастью, но аналогичного механизма не было заложено при разработке фреймворка BlackBox. Windows API нам может помочь только лишь привязать сообщения мыши к одному из окон BlackBox, но не к конкретному View внутри окна. За доставку сообщений до конкретного View отвечают процедуры с именем ForwardCtrlMsg (модули Views, Windows, HostWindows). Эти процедуры отправляют сообщения типа Views.CtrlMessage тому View, который в настоящий момент находится в фокусе ввода. View получает и обрабатывает эти сообщения в методе HandleCtrlMsg. Сообщения об изменении состояния кнопок мыши приходят в виде типа Controllers.TrackMsg, а об изменении положения курсора - в виде Controllers.PollCursorMsg. Нет нужды в особом сообщении типа MouseUpMsg, поскольку объект, если его интересует нажатость некоторой кнопки, может запомнить факт нажатия в обработчике Controllers.TrackMsg и дожидаться отпускания (т.е. пропадания соответствующего флага из набора Controllers.TrackMsg.modifiers) в этом же обработчике.
Таким образом, все необходимые сообщения у нас есть, нет только уверенности, что они придут к нам в полном объеме. Предполагаю, что проблемы появятся в двух случаях:
1) если нажать кнопку мыши в сфокусированном View, а затем увести курсор мыши за пределы содержащего его окна;
2) если вывести курсор мыши за пределы View c так называемым "горячим фокусом".
Оба эти варианта лучше проверить экспериментально, создав соответствующие тестовые View без цикла опроса Input.
В первом случае проблема будет вызвана отсутствием захвата мыши окном на уровне Windows API (с помощью SetCapture). Во втором случае проблема будет вызвана тем, что View с "горячим фокусом" не оповещается об уводе курсора за его пределы, а фокус приобретается только на время обработки сообщения о нажатии кнопки мыши. Оба эти случая, повторяю, требуют проверки и являются на настоящий момент моими домыслами. Естественно, возможны и другие "подводные камни", о которых я пока не догадываюсь.
Что касается преимуществ циклов с Rider.Input, то самые очевидные из них таковы:
1. отсутствие дополнительной сущности "режим захвата мыши" в устройстве фреймворка;
2. простота реализации кода без разбивки на три части: "вход в режим", "работа в режиме" и "выход из режима";
3. гарантия целостности данных и объектов, связанная с отсутствием разрывов в обработке. Например, можно быть уверенным, что в процессе выделения блока текста этот самый блок не будет модифицирован или удален вклинившейся фоновой задачей Services.Action или каким-то иным образом в процессе обработки оконной очереди сообщений Windows.
Как видите, и преимущества, и недостатки достаточно сильные, поэтому лично я держу для себя под большим вопросом необходимость доработки фреймворка (особенно при моем неполном знакомстве с ним). Хотя, опять же, вместо локального режима "выделение текста" в конкретном объекте вводить глобальный режим блокировки всей системы - это... не для всякого интерфейса годится. Вот некоторые жалуются, что кубик не вертится, а другие возражают, что кнопку долго не продержишь.
Что скажете, господа?