Closed theodox closed 7 years ago
Options I can think of:
Nested
which can change the MayaEvents to Events when a layout context manager closes. That would make it safer - but you'd still have to remember to hook events after layout time... which is the style I use most often but it may not seem naturalcmds.layoutDialog
replacement which hides the issue: maybe for the duration we replace MayaEvent with Event?MayaEvent.fire()
that knows when a layoutDialog is open and works synchronously....So I'm not running into any problems with the following code:
from maya import cmds
from mGui import gui, forms, lists
from mGui.bindings import bind, BindingContext, BindableObject
from mGui.observable import ObservableCollection
from mGui.events import Event
@object.__new__
class testDialog(object):
def _layout(self):
self._menu_items = ObservableCollection(*cmds.ls(type='transform'))
with forms.LayoutDialogForm() as self.base:
with BindingContext() as bc:
with forms.FooterForm() as form:
with forms.FillForm() as main:
self._menu = gui.OptionMenu()
self._menu.label = 'Items: '
self._menu < bind() < self._menu_items
with forms.HorizontalStretchForm() as footer:
okay = gui.Button()
okay.label = 'Okay'
okay.command += self._okay
update = gui.Button()
update.label = 'Duplicate'
update.command += self._duplicate
cancel = gui.Button()
cancel.command += self._cancel
cancel.label = 'Cancel'
def _duplicate(self, *args, **kwargs):
cmds.duplicate(self._menu.value)
self.base.update_bindings()
def _cancel(self, *args, **kwargs):
cmds.layoutDialog(dismiss='dismiss')
def _okay(self, *args, **kwargs):
cmds.layoutDialog(dismiss='Okay')
def __call__(self):
return cmds.layoutDialog(uiScript=self._layout, title='Test Dialog Events')
td = testDialog()
print(td)
Each of the buttons is working, and the duplicate command is firing properly while the dialog is active.
Does the duplicate work more than once? If you press it a few times with the dialog opne?
Yeah, and when I was duplicating some cameras it was even spawning the little on screen boxes talking about messing with the FOV. So it seemed to only be blocking user interaction, and not actually stalling the idle thread. This is in 2016 btw, might be different in older versions.
Also updated my example to use the Observable collections, and update the bindings after a duplicate. So those events are also working properly.
I'll have to dig in a bit deeper with @mouthlessbobcat ; he and I have bumped into this in 2016 on different occasions
The only time I've really bumped into something similar is when an uncaught exception gets raised. So at the very least the default exception handler is getting blocked. Which certainly makes debugging the dialog a pain.
I wonder if another solution might be checking which thread we're attempting to fire the event from, and if its the main thread to avoid the executeDeferred
call.
So, we tried a cut and paste of your example, and it still showed the incorrect behavior for us. What version are you on?
More mystery:
import inspect
import maya.cmds as cmds
import maya.utils
def ll(*x):
def close(*_):
cmds.layoutDialog(dismiss = "close")
def ok (*_):
cmds.layoutDialog(dismiss = "OK")
def do(*_):
def anon():
print "DONE"
maya.utils.executeDeferred(anon)
c = cmds.columnLayout()
d = cmds.button("do", c=do)
b1 = cmds.button("cancel", c = close)
b2 = cmds.button("ok", c = ok)
print cmds.layoutDialog(ui=ll)
This... works. so maybe MayaEvent is not exactly the culprit...
Maya 2016 sp6(no extensions) and Maya 2017, are the primary versions I've been working with.
So when it does go haywire, the dialog is still responsive, we just don't see anything useful until after it closes correct? We're not completely locking up Maya?
Also, if a print(threading.current_thread())
statement is added to MayaEvent._fire
does that trigger immediately? Does it return MainThread? Because that was the result I was getting when testing my example from above.
Now I'm extra confused.
import inspect
import maya.cmds as cmds
import maya.utils
def ll(*x):
def close(*_):
cmds.layoutDialog(dismiss = "close")
def ok (*_):
cmds.layoutDialog(dismiss = "OK")
def do(*_):
def anon():
cmds.polySphere()
print "DONE"
maya.utils.executeDeferred(anon)
c = cmds.columnLayout()
d = cmds.button("do", c=do)
b1 = cmds.button("cancel", c = close)
b2 = cmds.button("ok", c = ok)
print cmds.layoutDialog(ui=ll)
Won't create any of the spheres until the dialog closes. (Maya 2017) And just tested again, and it worked properly... Maya is weird.
That's more in line with the behavior we're seeing. It's along the lines of:
Ah, so maybe this is in part a testing artifact. The prints in my example didn't wait for the closure of the dialog -- but scene activities do?
On Thu, Jan 5, 2017 at 8:21 AM, Mouthlessbobcat notifications@github.com wrote:
That's more in line with the behavior we're seeing. It's along the lines of:
- Show LayoutDialog.
- Click buttons that fire events from their command.
- Nothing happens.
- Close dialog.
- All the events have been queued up and fire now.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/theodox/mGui/issues/56#issuecomment-270685658, or mute the thread https://github.com/notifications/unsubscribe-auth/AD3mGOgjDD4881P5RLOpmllXZIBgrWCCks5rPRiUgaJpZM4LUhQ8 .
Doesn't seem to be consistent though, I tried testing again with the scene activity, and it triggers properly.
Came up with a potential idea, instead of relying on cmds.layoutDialog
we could get at the underling QWidget
on a Window
and just enable Modality directly.
No idea if this actually sidesteps the issue or now, given how random this seems to be, it could just be a dice roll that I haven't run into it again.
But either way, it feels nicer to just define a ModalWindow
in the same manner as our other windows, and not have to jump through extra hoops with the LayoutDialogForm
, etc...
I wonder if this is a part of the problem? http://around-the-corner.typepad.com/adn/2016/07/the-idle-queue-isnt-100-a-queue.html
Also, there seem to be some weird edge cases on the Qt side when using exec
to block execution with a dialog, which I think is how cmds.layoutDialog
actually works.
http://blog.qt.io/blog/2010/02/23/unpredictable-exec/
So, my fun ModalWindow
patch won't actually fix this problem.
I think option 2 from the above list might be the better starting point. But I'd like to keep the ability to hook callbacks while inside the layout's context manager.
1 - Add a flag to Nested
during LayoutDialogForm.__enter__
.
2 - During Layout.add_current
check to see if that flag is active, if active, add an _is_modal
flag to each control.
3 - When hooking a callback, check the control for the _is_modal
flag, and use that to decide between MayaEvent
vs Event
4 - Remove flag from Nested
during LayoutDialogForm.__exit__
At this point it shouldn't matter where we hook any callbacks, as each control will already know whether it was defined inside a modal dialog or not.
that's in line with stuff that eric and I have discussed, I think it's the least complex of all the solutions we've considered .
@bob-white that link from ADSK is almost certainly why my print test was a false indicator...
Fixed
By default mGui widgets are created with
MayaEvent()
objects as their event delegates. Ordinarily that's nice because it minimizes damage from UI reacting across threads...but it also means that the events for a widget created using
cmds.layoutDialog
don't fire until after the dialog closes, since we never hit the idle state while the dialog is open.The workaround it to replace the
MayaEvent()
with anEvent()
-- that's synchronous so it doesn't get hung up. Here's an example:where the
login_button
is post-edited to switch types. That's an annoying piece of trivia for users to remembr