Tkinter, Windows message pump, Move/Resize
"Правильно" с точки зрения WIN32 с этим борются через оконные процедуры (нечто, вызываемое из внутренностей DispatchMessage) или другие колбэки, работающие оттуда же.
К слову говоря, разверну мысль, почему мне это не нравится (и почему это костыль — с чем ты спорил в том треде): перехват виндовой процедуры позволяет что-то делать, когда приходит мессага. А если мессаги не приходит, то ничего делать нельзя. С этим, наверное, можно бороться запустив виндовый таймер с каким-то интервалом, который будет присылать WM_TIMER, или ловя WM_ENTERIDLE, засыпая, и посылая себе какую-нибудь особую мессагу, но всё это неэлегантно и костыли. То есть понятно, конечно, что получаемого таким образом разрешения достаточно для большинства задач, а в остальных задачах по любому нужен как минимум отдельный тред с realtime priority, но всё равно неаккуратненько!
и почему это костыль — с чем ты спорил в том тредеЕщё раз скажу, с чем я спорил в том треде и почему это не костыль. Все циклы обработки сообщений перехватить нельзя, потому что кто угодно может добавить в систему модальный диалог или другую сущность, для которой по каким-то причинам нужен свой цикл.
А использовать очередь GUI сообщений для задач, не связанных с GUI - это уже костыль. Следить за сообщениями можно и с помощью SetWindowsHookEx, но это правильно только в тех задачах, которые этого действительно требуют.
Все циклы обработки сообщений перехватить нельзя, потому что кто угодно может добавить в систему модальный диалог или другую сущность, для которой по каким-то причинам нужен свой цикл.Поясни, пожалуйста, почему это вообще релевантно. Модальность диалога есть совокупность его признаков, заботящая Window Manager и никого более: активация парента автоматически перекидывает фокус на диалог, минимизация диалога автоматически минимизирует парента, у диалога нет собственной кнопки в таскбаре. Какое это имеет отношение к message pump? Особенно что даже когда активирован диалог, WM_TIMER (как и WM_PAINT например) всё равно исправно приходит по адресу — паренту?
Насколько я понимаю, единственный смысл подобной архитектуры состоит в том, что есть один кусочек информации, о котором Window Manager почему-то предпочитает не знать — что диалог по-прежнему активен, или что перетаскивание окна активно, или что ресайз активен. Почему так?
А использовать очередь GUI сообщений для задач, не связанных с GUI - это уже костыль.Конечно, я как раз про это: архитектура не оставляет мне другого выхода. Я не могу сделать свой собственный main loop, который бы вызывал DoEvents (Точнее, GetMessage, DispatchMessage которые бы вытаскивали из очереди сообщений сообщения и отправляли по адресу. Максимум, что я могу — добавить hook to the global application message pump.
То есть смотри, я вообще правильно понимаю суть? Window Manager о всех этих внутренних пертурбациях не знает вообще, он тупо постит мессаги в очередь, из которой их может забрать приложение при помощи GetMessage. Ну и менеджит что ему скажут. И вызывает особые хуки перед возвращением GetMessage, реализуя application-wide message pump как бы.
Далее, теперь у самого приложения есть message loop, который существует только в сознании приложения конечно же. Точнее даже в сознании его главного окна. Оно достаёт мессаги, смотрит на них и отправляет адресатам. Иногда, когда оно решает показать диалог, оно вызывает новый мессадж луп, который отправляет все мессаги WndProc диалога (или сам с ними разбирается особым образом? Как правильно? который, в свою очередь, переадресует всякие WM_PAINT куда нужно, а всякие другие ловит и не переадресует, реализуя таким образом как минимум часть того, чем должен был бы заниматься Window Manager. И всё вроде бы работает, причём понятие модального диалога оказывается реализовано на стороне приложения (но при помощи дефолтной wndproc, которую предоставила ОС!).
Если всё так, то я понимаю, почему им понравилась такая архитектура, что не мешает мне видеть что она кривая, всё равно приложения крайне редко выходят за рамки концепций предоставляемых ОС, когда им этого бы хотелось они бы всё равно это могли бы делать, а подобного бреда когда для изменения функциональности мне нужно подпатчить WndProc вообще всех использующихся окон (включая дефолтные MessageBox!) там бы не было.
Поясни, пожалуйста, почему это вообще релевантно. Модальность диалога есть совокупность его признаков, заботящая Window Manager и никого более: активация парента автоматически перекидывает фокус на диалог, минимизация диалога автоматически минимизирует парента, у диалога нет собственной кнопки в таскбаре. Какое это имеет отношение к message pump? Особенно что даже когда активирован диалог, WM_TIMER (как и WM_PAINT например) всё равно исправно приходит по адресу — паренту?Так уж вышло, что модальным считается диалог, функции отображения которого не выходят до тех пор, пока он не будет закрыт. А Windows написана на Си, так что иного выхода, кроме добавления отдельной очереди сообщений, просто нет. Сообщения типа WM_TIMER, WM_PAINT и прочие приходят из недр DispatchMessage, которая вызывается в любой очереди сообщений: модального диалога, DCOM и прочего, прочего, прочего.
Насколько я понимаю, единственный смысл подобной архитектуры состоит в том, что есть один кусочек информации, о котором Window Manager почему-то предпочитает не знать — что диалог по-прежнему активен, или что перетаскивание окна активно, или что ресайз активен.Менеджер это всё прекрасно знает, просто вряд ли GUI программистов обрадовали бы модальные диалоги с обратными вызовами.
Конечно, я как раз про это: архитектура не оставляет мне другого выхода. Я не могу сделать свой собственный main loop, который бы вызывал DoEvents (Точнее, GetMessage, DispatchMessage которые бы вытаскивали из очереди сообщений сообщения и отправляли по адресу. Максимум, что я могу — добавить hook to the global application message pump.
Ты можешь сделать сколько угодно очередей GUI сообщений в разных потоках.
Далее, теперь у самого приложения есть message loop, который существует только в сознании приложения конечно же. Точнее даже в сознании его главного окна. Оно достаёт мессаги, смотрит на них и отправляет адресатам.
В MSDN описано, как это всё работает на самом деле. Message Loop существует явно не в сознании какого-то окна, он может существовать и без окон. (В WINAPI он используется для огромного количества задач, кроме GUI, и меня это изрядно бесит.) Но все GUI, включая даже Swing на Java, работают схожим образом с понятием очереди сообщений. Модальные диалоги с обратными вызовами делают в JavaScript/DOM, в силу особенностей этой песочницы. Там всё действительно реализуют вручную.
Иногда, когда оно решает показать диалог, оно вызывает новый мессадж луп, который отправляет все мессаги WndProc диалога (или сам с ними разбирается особым образом? Как правильно? который, в свою очередь, переадресует всякие WM_PAINT куда нужно, а всякие другие ловит и не переадресует, реализуя таким образом как минимум часть того, чем должен был бы заниматься Window Manager. И всё вроде бы работает, причём понятие модального диалога оказывается реализовано на стороне приложения (но при помощи дефолтной wndproc, которую предоставила ОС!).
Нет, когда программист решает показать модальный диалог, он его просто показывает. Всё остальное за него делает WINAPI, но не при помощи дефолтной WndProc.
Так уж вышло, что модальным считается диалог, функции отображения которого не выходят до тех пор, пока он не будет закрыт. А Windows написана на Си, так что иного выхода, кроме добавления отдельной очереди сообщений, просто нет.А. Я наконец понял, спасибо. Проблема в том, что в воображаемой функции
while (running) { GetMessage(&msg); DispatchMessage(&msg); }DispatchMessage вызывает какой-нибудь хэндлер, который вызывает что-нибудь ещё, которое вызывает ShowDialog, который не возвращается пока пользователь не нажмёт Ок, и следовательно управление не возвращается в тот мессадж луп, и следовательно нужен ещё один. Хм, очевидно же, что-то я затупил!
Оставить комментарий
bleyman
Обнаружил забавный эффект: когда например начинаешь двигать или ресайзить Tkinter-овское окошко, root.update равно как и tkinter.dooneevent(tkinter.DONT_WAIT) не возвращаются, пока двиганье не прекратится.У меня есть определённые воспоминания насчёт того, что Tkinter тут ни при чём в общем-то, а это свойство виндового message pump: после получения WM_MOVE/WM_RESIZE контрол то ли по дефолту уходит в особый отдельный message loop, то ли обязан это делать (потому что иначе resize/move тут же прекратится).
Я правильно помню? И если да, то как с этим правильно бороться?
Один вариант я уже нашёл: message pump на самом деле продолжает обрабатывать все мессаги, поэтому ткинтеровский таймер (root.after(ms, callback продолжает работать, из коллбэка можно замечательно апдейтить содержимое контролов етс. Мне не очень нравится то, что при этом я вынужден всегда использовать его мессадж луп, плюс я ещё не проверял, насколько часто он обновляется и как вообще устроен его таймер — там вроде busy loop, который можно настраивать _tkinter.setbusywaitinterval, но от них всего можно ожидать же!