maya close window signal - python

I have created a ui from scratch using the commands within Maya documentation.
The following function that I have wrote applies in two scenerios:
When the user has clicked onto another button - Import, in which it will do as what it was written in the code then close it off with the following function (see readFile function)
When user has clicked onto the button where it close the UI without running anything.
In my script, to cater the above two scenarios, I wrote as the following where closeWindow is catered to Scenario1 and cancelWindow is catered to Scenario2
def ui(self):
...
cancelButton = cmds.button( label='Cancel', command=self.cancelWindow, width=150, height=35)
def closeWindow(self, *args):
cmds.deleteUI(self.window, window=True)
def cancelWindow(self, *args):
cmds.delete(camSel[0])
cmds.deleteUI(self.window, window=True)
def readFile(self, *args):
...
self.closeWindow()
As such, is it possible to create some sort of signal like those in PyQt (clicked(), returnPressed() etc) by combining the above 2 (automated + manual), seeing that the deleteUI command usage is the same?

Default Maya UI provides only callbacks, not signals. You can create a sort of 'pseudo signal' by calling an event handler object instead of a function. In that scenario the button only knows 'I fired the button event' and the handler can call as many functions as needed.
class Handler(object):
def __init__(self):
self.handlers = []
def add_handler (self, func):
self.handlers.append(func)
def __call__(self, *args, **kwargs):
for eachfunc in handler:
eachfunc(*args, **kwargs)
hndl = Handler()
hndl.add_handler(function1) # do some ui work...
hndl.add_handler(function2) # do some scene work...
hndl.add_handler(function3) # do something over network, etc....
b = cmds.button('twoFunctions', c = Hndl)
In a large complex UI this is a nice way to keep minor things like button higlights and focus changes separated out from important stuff like changing the scene. In your application it's almost certainly overkill. You've only sharing 1 line between close and cancel, that's not too bad :)
Heres' more background on on pseudo-events in maya gui.
You can also use Maya's QT directly to get at the close event... Again, seems like overkill. More here

Related

Why is QWidget being destroyed? (PyQt)

So I have the main window. When I click a button in the main window, a new widget is created (in a new window):
self.doorButton.clicked.connect(self.open_door)
def open_door(self):
self.doorwin = QtWidgets.QWidget()
self.doorui = doorwinActions(self.doors)
self.doorui.setupUi(self.doorwin)
self.doorwin.show()
The new QWidget or doorwin has only one widget - tableWidget
I use the object self.doors to populate the table. Now since I have a worker thread (QThread) updating the said object (self.doors), I use QTimer to repopulate the table every 1 second.
class doorwinActions(Ui_doorWin):
def __init__(self,doors):
self.doors = doors
# update setupUi
def setupUi(self, Widget):
super().setupUi(Widget)
Widget.move(self.left, self.top) # set location for window
Widget.setWindowTitle(self.title) # change title
self.timer = QTimer()
self.timer.timeout.connect(lambda:self.popTable())
self.timer.start(1000)
def popTable(self):
mutex.lock()
entries = len(self.doors)
self.tableWidget.setRowCount(entries)
for i in range(entries):
self.tableWidget.setItem(i,0,QtWidgets.QTableWidgetItem(str(self.doors[i][0])))
self.tableWidget.setItem(i,1,QtWidgets.QTableWidgetItem(self.doors[i][1].get_id()))
self.tableWidget.setItem(i,2,QtWidgets.QTableWidgetItem(self.doors[i][1].get_name()))
self.tableWidget.setItem(i,3,QtWidgets.QTableWidgetItem(str(self.doors[i][2])))
mutex.unlock()
When I run the program, it runs smoothly. It opens a new window. If the self.doors object is updated while the window is open, the GUI reflects the change.
BUT, the problem occurs if I reopen the window. If I close the window and then click on the button again, the program crashes with the error:
RuntimeError: wrapped C/C++ object of type QTableWidget has been deleted
From what I understand about my code, when I close the window, the whole widget window (and the table) is deleted. And when I click on the doorButton, new Qwidget/table are created. So, the question is, why would it delete something it just created?
What (sort of) works? - If I move the setup of the door window to the main window's setup, it works. So the open_door function would just be:
def open_door(self):
self.doorwin.show()
The rest would be in the main window setup. But the problem is, then even when I close the window, the QTimer is still going in the background, just eating up processing power.
So, either,
How do I stop the event when the window is closed OR
How do I stop the tableWidget from being deleted?
Your main problem is garbage collection.
When you do this:
def open_door(self):
self.doorwin = QtWidgets.QWidget()
self.doorui = doorwinActions(self.doors)
self.doorui.setupUi(self.doorwin)
self.doorwin.show()
You are creating a new QWidget. It has absolutely no other reference but self.doorwin. This means that if you call open_door again, you will be overwriting self.doorwin, and since the previous widget was not referenced anywhere else, it will get deleted along with all its contents.
Now, QTimers are tricky. You created a QTimer in doorwinActions and QTimers can be persistent even if they have no parent: they keep going on until they're stopped or deleted, and they can only be deleted explicitly or when their parent is deleted (with the exception of timers created with the static QTimer.singleShot() function).
Finally, you must remember that PyQt (like PySide) is a binding. It creates "connections" with the objects created in Qt (let's call them "C++ objects"), and through those bindings we can access those objects, their functions and so on, through python references.
But, and this is of foremost importance, both objects can have a different lifespan:
the python reference can be deleted, and the Qt object can still exist;
the Qt object can be destroyed, yet we still have the python object that referenced (past tense) it;
This is exactly what happens in your case: the self.doorui object is overwritten, but it has an object (the QTimer, self.timer) that is still alive, so the Python garbage collector will not delete it, and the timer is still able to call popTable. But, at that point, the widget (self.doorwin) and its contents have been destroyed on the "C++ side", which causes your crash: while self.tableWidget still exists as a python reference, the actual widget has been destroyed along with its parent widget, and calling its functions causes a fatal error as the binding cannot find the actual object.
Now, how to solve that?
There are various options, and it depends on what you need to do with that window. But, before that, there is something much more important.
You have been manually editing a file generated by pyuic, but those files are not intended for that. They are to be considered like "resource" files, used only for their purpose (the setupUi method), and never, EVER be manually edited. Doing that is considered bad practice and is conceptually wrong for many reasons - and their header clearly warns about that. To read more about the commonly accepted approaches for those files, read the official guidelines about using Designer.
One of those reasons is exactly related to the garbage collection issue explained above.
Note that subclassing the pyuic form class alone is also discouraged (and pointless if you want to extend the widget behavior); the most common, accepted and suggested practice is to create a class that inherits both from the Qt widget class and the UI class. The following code assumes that you have recreated the file with pyuic and named ui_doorwin.py:
# ...
from ui_doorwin import Ui_DoorWin
# ...
class DoorWin(QtWidgets.QWidget, Ui_DoorWin):
def __init__(self, doors):
super().__init__()
self.doors = doors
self.setupUi(self)
self.timer = QTimer(self) # <-- IMPORTANT! Note the "self" argument
self.timer.timeout.connect(self.popTable)
self.timer.start(1000)
def popTable(self):
# ...
With the above code you can be sure that whenever the widget gets deleted for any reason, the timer will be destroyed along with it, so the function will not be called trying to access objects that don't exist anymore.
If you need to keep using an existing instance of the window, the solution is pretty simple: create a None instance (or class) attribute and check if it already exists before creating a new one:
class SomeParent(QtWidgets.QWidget):
doorwin = None
# ...
def open_door(self):
if not self.doorwin:
self.doorwin = DoorWin()
self.doorwin.show()
The above code will not stop the table from updating, which is something you might not want, so you might choose to start and stop the timer depending on when the window is actually shown:
class DoorWin(QtWidgets.QWidget, Ui_DoorWin):
def __init__(self, doors):
super().__init__()
self.doors = doors
self.setupUi(self)
self.timer = QTimer(self)
self.timer.timeout.connect(self.popTable)
def showEvent(self, event):
if not event.spontaneous():
self.timer.start()
def hideEvent(self, event):
if not event.spontaneous():
self.timer.stop()
The event.spontaneous() check above is to prevent stopping the timer if the show/hide event is caused by system calls, like minimizing the window or changing desktop. It's up to you to decide if you want to keep the timer going on and process all data, even if the window is not shown.
Then, if you want to completely destroy the window when it's closed and when a new one is opened, do the following:
class DoorWin(QtWidgets.QWidget, Ui_DoorWin):
def __init__(self, doors):
# ... (as above)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
and then ensure that the widget exists (note that if it's closed by the user the reference still exists):
class SomeParent(QtWidgets.QWidget):
doorwin = None
# ...
def open_door(self):
if self.doorwin:
try:
self.doorwin.close()
except RuntimeError:
pass
self.doorwin = DoorWin()
self.doorwin.show()
I found the solution. My two proposed solutions are the same. If I stop the QTimer on window closing, it no longer gives me the error.
self.exitButton.clicked.connect(self.close_win)
def close_win(self):
self.timer.stop()
self.Widget.close()

grabbing x,y coordinates of a mouse click/release from Tkinter GUI to do some calculations (not only to print out)

I try to grab the coordinates of the mouse clicked/released points to do other calculations with them, using "bind" on a Tkinter GUI. I know that the purpose of binding is not for the program to wait for a mouse click where exactly they're defined. But, I need the coordinates (x,y) of the clicked/released points to do the further calculations, and the program already reaches to these calculations before letting me to click on the GUI. All the answers to similar questions out there mostly output x and y using "print" (which is the simplest solution). I couldn't find any way to be able to click, grab the coordinates, and then use them further in the code. Here's part of my code. Any thoughts?
class imageLabel(Tk.Label):
def __init__(self, parent):
self.parent = parent
Tk.Label.__init__(self)
self.label = Tk.Label(self,image=[], height=600,width=2000)
self.pack()
def select_ROI (self):
self.bound = []
self.bind("<ButtonPress-1>", self.on_click)
self.bind("<ButtonRelease-1>", self.on_click)
def on_click(self, event):
self.bound.append(event.x)
self.bound.append(event.y)
if len(self.bound)==4:
self.unbind('<ButtonPress-1>')
self.unbind('<ButtonRelease-1>')
I have other functions in this class, and that's why I initialized it so. Now, if I use it in the main program as:
...
imageDisp = imageLabel(UsrInp)
imageDisp.select_ROI()
print "boundaries: ", imageDisp.bound
alpha = (imageDisp.bound[0]+ imageDisp.bound[2])/2
...
I get this error (before I get a chance to click on GUI):
boundaries: []
...
alpha = (imageDisp.bound[0]+ imageDisp.bound[2])/2
IndexError: list index out of range
It appears that your intent is for your program to pause, and wait for the user to click before continuing. That is, you want the flow to be this:
imageDisp = imageLabel(UsrInp)
imageDisp.select_ROI()
(wait for the user to click)
alpha = (imageDisp.bound[0]+ imageDisp.bound[2])/2
That is not normally how GUIs work. Usually you set events and then respond to events, rather than explicitly waiting for a specific event.
However, what you want to do is possible. You just need to cause select_ROI to not return until the clicks happen. The simplest way is to have your program wait on a variable, then make sure that your event bindings causes that variable to be set.
First, create a flag in imageLabel.__init__ that can be used later:
class imageLabel(Tk.Label):
def __init__(self, parent):
...
self.var = Tk.BooleanVar()
...
Next, in select_ROI, wait for that variable to be set before returning:
def select_ROI (self):
...
self.wait_variable(self.var)
return
Finally, make sure that your bindings set this flag:
def on_click(self, event):
...
if len(self.bound)==4:
...
self.var.set(True)
That will cause select_ROI to wait for the user to click and release before returning.
Be aware that this can lead to a very confusing user experience if you don't give the user sufficient feedback on what they are supposed to do, and you don't take steps to prevent them from doing anything else before they do the clicks.

How can I unbind every single binding from a Tkinter root window

So I have an app in Tkinter that has a lot of buttons in the first screen and when you press one you pass into a new "Window" (basically destroying all widgets and drawing the ones that are needed for the 'window'). There is a standard function that uses some commands to destroy every child on the root. I would like to add some code that can unbind all of the bindings that are made in the root. Bindings that are on specific widgets get destroyed but those that are bind on the root stay there and cause error.
Here's the code for destroying the widgets.
#staticmethod
def clear():
for widget in guihandle.root.winfo_children():
widget.destroy()
#staticmethod
def set(db,table):
guihandle.clear()
curW = Window(db,table)
guihandle.current_Window = curW
curW.initialize()
guihandle.windows.push(curW)
(Yes, I make the base GUI from a sqlite3 database :P)
There is nothing in Tkinter to do what you want. Your app will need to keep track of the bindings it wants to remove.
That being said, depending on just how complex your real problem is, there may be other solutions. For example, instead of binding to the root window, bind to a custom binding tag (also called a bind tag or bindtag). You will then need to add that tag to every widget to enable the bindings, and remove the tag from any existing widgets to effectively disable the bindings.
I know I am VERY late but, if you go through your code and replace every widget.bind with the function below, which was taken and modified from here
def bindWidget(widget: Widget, event, all:bool=False, func=None):
# https://stackoverflow.com/a/226141/19581763
'''Set or retrieve the binding for an event on a widget'''
try:
_ = widget.__dict__['bindings']
except KeyError:
has_binding_key = False
else:
has_binding_key = True
if not has_binding_key:
setattr(widget, 'bindings', dict())
if func:
if not all:
widget.bind(event, func)
else:
widget.bind_all(event, func)
widget.bindings[event] = func
else:
return(widget.bindings.setdefault(event, None))
Than the function will keep track of every binding for you by setting a attribute called bindings which has both, the event and the function inside of it.
For example:
label.bind('<Button-1>', label.destroy)
Will become:
bindWidget(label, '<Button-1>', func=label.destroy)
After doing that, you can write a simple function which deletes all of the bindings and widgets:
def clear(self): # Self will be your Tk() instance
"""Clears everything on the window, including bindings"""
for child in self.winfo_children():
child.destroy()
if not hasattr(self, 'bindings'):
self.bindings = {}
for event, func in self.bindings.items():
self.unbind_all(event)
self.bindings = {}
There are only 2 caveats with this approach
Time
You will have to go through your code to replace every widget.bind and if you have lots of bindings, than it will take a lot of time
Readability
bindWidget(label, '<Button-1>', func=label.destroy) is less readable and less clear than label.bind('<Button-1>', label.destroy)
Edit 8/11/2022
Alternatively, you can destroy the entire window and recreate it like this:
window.destroy()
window = Tk()
However, I am not sure if creating a whole Tcl interpreter is good.

How to use QSignalMapper with QActions created dynamically?

I'd like to create a dynamic menu which enumerates all QDockWidget from my QMainWindow and allows to show/hide the QDockWidgets, so far I got this code:
class PluginActionsViewDocks():
def __init__(self, main_window):
self.main_window = main_window
mapper = QSignalMapper(self.main_window)
self.actions = []
for dock in main_window.findChildren(QtWidgets.QDockWidget):
action = create_action(
main_window, dock.windowTitle(),
slot=mapper.map,
tooltip='Show {0} dock'.format(dock.windowTitle())
)
mapper.setMapping(action, dock)
self.actions.append(action)
mapper.mapped.connect(self.toggle_dock_widget)
help_menu = main_window.menuBar().addMenu('&View')
setattr(help_menu, "no_toolbar_policy", True)
add_actions(help_menu, tuple(self.actions))
def toggle_dock_widget(self, dock_widget):
print("toggle_dock_widget")
The menu is populated with all QDockWidget windowTitles but when i press each of them the slot toggle_dock_widget is not called. create_action is a helper which creates the QAction and connect the triggered signal to slot.
The thing is, I don't really understand quite well how QSignalMapper works but my intuition tells me it's the right choice for this particular problem.
What could I be missing here?
There's aleady a built-in dock-widget menu. Just right-click any dock title-bar, or any tool-bar or menu-bar. See: QMainWindow::createPopupMenu.
PS:
The reason why your QSignalMapper code doesn't work is probably because you are connecting to the wrong overload of the mapped signal. Try this instead:
mapper.mapped[QtWidgets.QWidget].connect(self.toggle_dock_widget)

Switching scenes with pyglet

Can anybody recommend how to switch between scenes in pyglet.
I.e.
menu > game
game > menu
menu > help
ect
The only way that i can think to do it off the top of my head is by using different windows, which i am quite sure would be the complete wrong way to do it. Or by overloading all of the window's event functions.
Sorry if i haven't made myself clear but any help would be appreciated
The cocos2d.org framework is built on pyglet and includes scene management.
Here is a rough schematic of a class structure that might work for you:
class Game(object):
"This class contains the Scene which is the current scene the user is look ing at."
def __init__(self):
self.current_level = 0
self.current_screen = MainMenu(self)
def load(self):
"Load progress from disk"
pass
def save(self):
"Save progress to disk"
pass
def clearCurrentScreen(self):
self.current_screen.clear()
self.window.remove_handlers()
def startCurrentScreen(self):
self.window.set_handler("on_key_press", self.current_screen.on_key_press)
# etc
self.current_screen.start()
def gotoNextLevel(self):
"called from within LevelPlayer when the player beats the level"
self.clearCurrentScreen()
self.current_level += 1
self.current_screen = LevelPlayer(self, game, self.current_level)
self.startCurrentScreen()
def startPlaying(self):
"called by the main menu when the user selects an option"
self.clearCurrentScreen()
self.current_screen = LevelPlayer(self, game, self.current_level)
self.startCurrentScreen()
def execute(self):
self.window = pyglet.window.Window()
self.startCurrentScene()
pyglet.app.run()
class Screen(object):
def __init__(self):
pass
def start():
pass
def clear():
"delete all graphical objects on screen, batches, groups, etc. Clear all state in pyglet."
pass
def on_key_press(self, key, etc):
pass
def on_draw(self):
pass
# etc
class LevelPlayer(Screen):
"This class contains all your game logic. This is the class that enables the user to play through a level."
def __init__(self, game, level_to_play):
pass
# be sure to implement methods from Screen
class MainMenu(Screen):
"This class presents the title screen and options for new game or continue."
def __init__(self, game):
self.game = game
def handleNewGame(self):
self.game.startPlaying()
def handleContinue(self):
self.game.load()
self.game.startPlaying()
# be sure to implement methods from Screen
game = Game()
game.execute()
So you have a Game class who owns the window and that manages which Screen is displayed to the user. Here I use "Screen" to mean what the user is interacting with, for example a MainMenu, or a LevelPlayer. The key here is the clear() method of Screen, which you should implement to delete all the sprites, media, groups, and batches that you were displaying. You also have to remove the window handlers on clear and set them on start.
You can see this solution in action here: https://github.com/superjoe30/lemming/tree/master/lemming
I'm not very experienced, but for what it's worth, the method I use is as follows. It does not recognise explicit 'states' or 'scenes' as such, but rather it relies on the adding (and removal) of discrete items into my game world. Each such item may have its own key handlers, and knows how and when to create other such items.
A specific example:
GameItem is a subclass for all items that can be put into the world. It is a simple collection of attributes, with no behaviour. It is subclassed by items in the game world like Bush, Player, etc, and also by HUD items like ScoreDisplay and MainMenu.
World is just a collection of GameItems.
My 'update' function iterates through all items in the world, calling their update method if they have one.
My 'draw' function similarly iterates through all items in the world, drawing each of them. (many of them might be drawn en-masse by simply calling something like pyglet's Batch.draw)
Right at application start-up, one of the first items I add to the world is a MainMenu object. It has an on_key_down method.
World.add looks at the item being added. If an item has an on_key_down method, then it adds this handler to the pyglet key handlers stack. (Similarly, it undoes this in World.remove) (Actually, on reflection, I guess world.add raises an event when it adds an item. If the application's keyboard handler module is interested, then on recieving one of these events it then does the adding of the item's key hander, but this is kinda incidental to the question)
The MainMenu.on_key_handler method looks at the pressed key, and if it's the key to start the game, then it makes a series of calls:
# Do everything needed to start the game
# For dramatic pacing, any of these might be scheduled to be
# called in a second or so, using pyglet.clock.schedule_once
world.add(player)
camera.to_follow(player)
world.add(scoredisplay)
# Finally, remove the main menu from the world
# This will stop showing it on screen
# and it will remove its keyboard event handler
world.remove_item(self)
This is just a simple example, but hopefully you can see how, instead of starting the game, the mainmenu could instead display secondary menus by adding them to the world, or any such thing.
Once in the game, you can change 'scenes' by simply calling 'world.remove' on all items that you no longer want visible, and calling 'world.add' on all the items in the new scene.
An example of a game that uses this technique is previous pyweek entry SinisterDucks:
http://code.google.com/p/brokenspell/
(although, to be fair, it does not feature distinct 'scenes' during gameplay. It merely usesthis technique to manage menus, game over screen, etc)
The solution i ended up using is by giving the main window a stack and having it pass all events out to items on the top of the stack.
i.e.
class GameWindow(pyglet.window.Window):
def __init__(self,*args, **kwargs):
pyglet.window.Window.__init__(self, *args, **kwargs)
self.states = [PlayLevel(self)] ## Starting State
def on_draw(self):
if hasattr(self.states[-1],"on_draw"):
self.states[-1].on_draw()
def on_mouse_press(self,*args):
if hasattr(self.states[-1],"on_mouse_press"):
self.states[-1].on_mouse_press(*args)
ect. For all of the other events that i use
i am currently in the process of writing up some functions to go in GameWindow that will manage pushing and popping scenes from the stack

Categories

Resources