pywin32
pywin32 copied to clipboard
Fix modeless COM browser dialog
A follow-up for #1894.
Fixes [SO]: win32com.client combrowse.main() (Python Object Browser) is not responding Python 3.9.
As I mentioned I am neither a COM expert nor an MFC enthusiast, and this part of PyWin32 is new to me, although I've seen some unorthodox (to me) stuff here :).
The actual fix is the 3rd commit. It's the best compromise I found. After the crash fix, ShowWindow wasn't starting the dialog message loop, so calling RunModalLoop was required. But for some reason when dialog was closed with IDCANCEL (Esc or by clicking the X button), the dialog disappeared, but the loop didn't exit, so Ctrl + Break was required in the console in order to kill the process (this didn't happen for IDOK (Enter) - which worked fine).
That's why EndModalLoop is needed in OnCancel (even if [MS.Docs]: CDialog::OnCancel instructs to call DestroyWindow for modeless dialogs, that doesn't work (probably to PyWin32 extra wrapping layer)).
Overriding RunModalLoop would have been the elegant solution, but dialog.Dialog doesn't have it, so the do_modal wrapper was born (its naming style suggests that it doesn't come from MFC).
Modeless dialog works now (as seen in the image from the answer).
I don't know, maybe in the future, this functionality could be moved up to Dialog, but that's a task far from trivial, which would require extensive testing.
I think the problem in that SO question just came from me making the browser default modal in d10d5594. I was not aware (or forgot) that combrowse.py may be run / called from a shell outside PythonWin as a standalone mini GUI.
Fixed I hope in #1924 - with some auto switching like in tlbrowse.py
.
The low-level RunModalLoop()
in a do_modeless()
seems like a contradiction - and leads to those artificial follow-up problems. DoModal() ist already the thing which does a consistent modal dialog operation including message loop and proper ending of the dialog etc. - even as standalone GUI as it seems. (The MFC virtuals OnOK OnCancel are not supposed to return a value I think - void.)
Modeless operation (without a new message loop) makes only sense inside another (MFC/win32) GUI app - PythonWin - with an outer / main message loop - so e.g. one can continue doing other interaction there while the COM browser is floating as a non-blocking window similar to the object browser.
Thank you for feedbacks. I didn't explicitly stated it in the description, but do_modeless is a (lame) workaround (gainarie). Will check it, but give me some time. I will post another comment when done.
I completed my tests. This is the current situation (build with latest changes):
-
Modal dialog (which is now the default) works
-
Modeless crashes (as expected since no fix was attempted in this area)
Now regarding the commits:
-
Yes, CDialog::OnCancel, CDialog::OnOK return void. But browser.dynamic_browser (which extends Dialog) also have
return super().OnOK()
I thought to make them consistent (especially since in Pythonreturn;
andreturn NULL;
are equivalent), so I think it's fine -
I don't see anything wrong
-
Although it fixes the crash, it should be probably left out as it's incorrect from design perspective. Maybe a proper fix will arise. Ore temporary disable the modeless behavior?
- Modeless crashes (as expected since no fix was attempted in this area)
I guess you are trying to run combrowse.main(modal=False)
somehow from a console script / interactive, like in SO, without main message loop. That would naturally just flash and immediately close and exit again.
Modeless is for running within Pythonwin (or another GUI): from Menu/Tools/COMBrowser usually, or by >>> combrowse.main(modal=False) # alt. mdi=True
from its interactive window - and then does not block the Pythonwin GUI and can be used parallel.
For curiosity I run it from a console script "pseudo-modeless" with minial non-MFC "main message loop" in a further MessageBox - works surprisingly:
#!py -3
import win32api
from win32com.client.combrowse import main
main(modal=False)
win32api.MessageBox(0, "waiting!")
I finally got the modeless purpose. So setting it from a script (like in SO) is not supported. I think it's fine. As a note: changing mdi has no effect.
Yes! the message box trick works!
win32api.MessageBox(0, "waiting!")
Heh - yeah, or a trivial win32gui.PumpWaitingMessages()
loop would work. Sounds like we don't need anything here though.