How to get access to handler id from Gtk.Builder.connect_signals - python

I have a Glade file with some buttons and I use Gtk.Builder.connect_signals() to connect methods (on_button_toggled) with the corresponding signals (toggled).
(It is acutally quickly which does that for me, but I can see and change that code, so that is only a detail).
What I want to do now, is stop a signal from being processed, e.g. though a call to object.handler_block(handler_id) or object.disconnect(handler_id). So my question is: how can I get the handler_ids for connections created via Gtk.Builder.connect_signals()?
Normally you would get the handler_id from a call to one of:
handler_id = object.connect(name, cb, cb_args)
handler_id = object.connect_after(name, cb, cb_args)
handler_id = object.connect_object(name, cb, slot_object, cb_args)
handler_id = object.connect_object_after(name, cb, slot_object, cb_args)
but the Gtk.Builder version does not return the ids.

Sadly, I don't believe that there's any way to get at the signal handler IDs that were connected by Gtk.Builder. If you really want the handlers, you have to manually connect your signals, storing any handler_ids you care about.
An alternative approach is to decide that you don't actually need the handlers themselves, but can block/unblock/etc. based on the connected callable, using GObject.handler_block_by_func and similar.
The final option is to try to actually find the handler after the fact, using as many details as you can. In C, use g_signal_handler_find; this isn't bound for pygtk2, but presumably will work using pygobject3. The downside here is that there's no guarantee that you'll find what you actually connected.

This is probably too late for you but I have a solution to get the name of the widget you clicked that is set up with...
self.builder.connect_signals(self)
From this source, the author uses gtk.Buildable.get_name() to get the name of the widget/object. In my scenario, depending on what button was clicked a different action occurred. I wanted to attach the same event handler to all of the buttons of a certain type, because they largely do the same thing. Here's the code.
import gtk
class GUI:
def __init__(self):
self.builder = gtk.Builder()
self.builder.add_from_file('myGUI.glade')
self.builder.connect_signals(self)
self.window = self.builder.get_object("window1")
self.window.show()
def on_button_click(self, object, data=None):
print(gtk.Buildable.get_name(object))
Pretty cool, eh? I think so, at least.

With PyGObject, the best way is the signal_handler_find approach I think. Use this function, which uses two functions from GObject
def get_handler_id(obj, signal_name):
signal_id, detail = GObject.signal_parse_name(signal_name, obj, True)
return GObject.signal_handler_find(obj, GObject.SignalMatchType.ID, signal_id, detail, None, None, None)
Usage example:
get_handler_id(button, "clicked")
will get you the handler_id for a handler connected to the clicked signal of button. This can then be used for handler_block.

Related

Python3.8 with PySide2 Class to Class Usage

My GUI essentially wraps various backend PowerShell scripts that perform some automated functions. Kind of beside the point, but alright, here's where I'm stuck at.
I've got my interface designed in Qt Designer, outputted to a .ui file, converted to a .py file via PySide2-UIC, and a mainwindow class that is a subclass of the main window class I created in Qt Designer. All is well. No issues with any of that.
I'm now on to a part in my programming that I'm capturing form data from QWidgets (which is working) to a list. I've got a completely custom written class that is meant to handle taking that user input, setting other variables like filenames or paths to certain configuration files that are needed, and executing a subprocess PowerShell command with all of that information. Where I'm stuck at is trying to determine what the right place is to instantiate this custom object, inside my MainWindow class, outside my MainWindow class? But if so, where? Here's some simplified code to help explain my dilemma.
Interface Sequence
App start
MainWindow appears
User browses to form with input controls
User enters info like (IP address, username, password)
User clicks button that is connected to a method in the class
Method recurses through the child widgets on the page and captures info into a dictionary via finding qLabels and qLineEdit (buddies)
Questions:
How do I call the next method (only once even though the capturing of data is recursive)? I'm thinking about just connecting the signal to a second method that handles taking the captured data and sending/setting it into the custom class object. However, when I instantiate my custom object inside of the MainWindow class and I try to reference the object by self.customObject.sendUsesrInput(self.userInputVariable), PyCharm doesn't think self is defined inside this particular method. It doesn't properly highlight the word "self" like in the rest of the class definition, and it suggests that I need to import self.
Update
I was able to clear the errors around "import self" in PyCharm. It had something to do with improper spaces vs. tabs, even though I only ever use the tab key to do indentation. Might need to go and check my inpection settings closer. The other questions still stand though. Where is the best place to call methods on my custom class to "form a command", and "run a command", should that be executed by the mainWindow class, or should I set a flag on the customObject class that then triggers those other actions? Or more generally, should an object be in charge of executing it's own functions/methods, something tells me not usually, but I can't be sure. Also, if there are any books on the matter, I'd be happy to do my own research. I'm currently reading "Rapid GUI Programming" but not sure if this topic is covered in the later chapters just yet.
So I guess my question is, where do I handle the customObject class, in the mainWindow class, or in some other place? If so, where?
I apologize if this question is NOT clear. I promise to update as necessary to help work through this.
Here's come simplified code examples:
class customClass(object): # this is actually in a separate file but for argv sake
def __init__(self):
self.userInput = ""
self.file1 = ""
self.file2 = ""
self.otherstuff...
def setUserInput(self, uinput):
self.userInput = uinput
def dostuffwithdata(self):
# method to execute subprocess command
class MainWindow( QMainWindow ):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.customObject = customClass.customCmdClass()
self.cmddata = dict()
self.ui.toolButton.clicked.connect(self.getformdata)
def getformdata(self):
# recurses through QWidgets and grabs QLabels and QLineEdit.Text() and updates dict()
for w in widgets:
if w is qlabel:
k = w.text()
v = w.buddy().text()
self.cmddata.update({k: v})
""" all the above works fine. what doesn't work is this part"""
# at this point I want to send the collected data to the customObject for processing
def senddatatocustomObject(self):
self.customObject.setUserInput(self.cmddata) """but this says that 'self' isn't defined.
I know it has to be because of the object in an object, or something I'm doing wrong here.
**Update**: figured this out. PyCharm was freaking out about some sort of
perceived indentation error despite there not appearing to actually be one.
Was able to correct this. """
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
In an effort to close this out, I offer this answer to my previously posted question about where to put the "logic" and flow in my code.
Given that this is a graphical application without any back-end services, it makes the most sense to put most of the user-interaction logic and flow within the MainWindow object because that is essentially the control center of my program. When a user clicks or enters something, it is going to happen on the MainWindow, when a state changes, it happens (mostly) on the MainWindow or is directly tied to the MainWindow in some way. Therefore, it makes sense to include the majority of my method calls, user-input-flow logic, and other code, in the MainWindow class/object of my program.
My other classes and objects are there to capture state and to perform actions on different sets of data, but in most cases, these auxiliary classes/objects will/should be controlled by the MainWindow of my application.
This is certainly not the only way to write this application or others, but I believe this at least answers my previously posted question(s).

Qt canonical way of retrieving values from Wizard / Dialog on accepted / finished signal

I'm using PyQt, but I guess the same questions also applies to Qt C++.
Assume that I have a main window with a button that opens a wizard that collects data and that data needs to be used in the main window after the wizard has closed. standard procedure.
So there are multiple ways to do this. either I can pass a reference to the main window to the Wizard and it does all the work using the main window reference, but I'd say that breaks modularity. I can also wire up a callback to the wizard accepted rejected or finished signal, but in that callback, I don't have a reference to the wizard itself, so I cannot get to the data in the wizards fields. Unless I store a reference to the wizard as instance variable in order to access it again from the callback.
Another option is (even though I haven't fully understood it yet) to get a reference to the emitter of the signal (i.e. the wizard) in the callback using https://doc.qt.io/qt-5/qobject.html#sender. But that seems not recommended.
So whats the canonical way?
Premise: this is a bit of an opinion based question, as there is not one and only "good" way to do that. I just wanted to comment (opinion based answer/questions are discouraged here in SO), but the limited formatting isn't very helpful.
"Passing a reference" doesn't necessarily break modularity.
Instead, that's exactly what QDialog usually are initialized: the parent is the "calling" window, which is also how a QDialog can be "modal" to the parent or the whole application (meaning that no interaction outside the dialog is allowed as long as it is active).
AFAIK, I don't know if this is actually considered canonical, but the following is the most commonly suggested approach.
The idea is that you have a child object (a QDialog, usually) which might or might not be initialized everytime you need it, that's up to you; the important part is that you need a reference to it at least for the time required to update its result, which can even happen within the scope of a single method/slot.
from PyQt5 import QtWidgets
class MyWizard(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.checkBox = QtWidgets.QCheckBox('check')
layout.addWidget(self.checkBox)
self.input = QtWidgets.QLineEdit()
layout.addWidget(self.input)
buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel)
layout.addWidget(buttonBox)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
def setData(self, **data):
self.checkBox.setChecked(data.get('check', False))
self.input.setText(data.get('text', ''))
def getData(self):
return {'check': self.checkBox.isChecked(), 'text': self.input.text()}
def exec_(self, **data):
self.setData(**data)
return super().exec_()
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)
layout = QtWidgets.QHBoxLayout()
centralWidget.setLayout(layout)
self.showWizBtn = QtWidgets.QPushButton('Show wizard')
layout.addWidget(self.showWizBtn)
self.showWizBtn.clicked.connect(self.getDataFromWizard)
self.data = {}
def getDataFromWizard(self):
wiz = MyWizard(self)
if wiz.exec_(**self.data):
self.data.update(wiz.getData())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Another possibility is to create a persistent child dialog (but keep in mind that if the data can be changed by the parent, you'll have to find a way to update it, at least when executed); the concept here is that you can exec the dialog whenever you need, and you have the accepted signal connected to a slot that can get the data from the dialog. This is not a common use (nor very suggested IMHO) and should be used only for very specific scenarios.
As you already found out, using sender is not suggested: signals are asynchronous, and while in normal conditions the sender is reliable it's better to avoid using it unless absolutely necessary.

maya close window signal

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

wxpython - Binding events in external files

I am trying to bind events from a GUI file to use code from another file (effectively a "front end" and a "back end"). I can get the back end and front end working within the same file, but when I try to move them into separate files, I have issues getting the back end to see parts (labels, buttons, etc.) of the front end.
I. E. I need the back end code to change labels and do math and such, and it would need to affect the GUI.
I have provided a simple version of my program. Everything works with the exception of the error I get when I try to make the back end see the parts of the GUI.
mainfile.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import label_changer
class foopanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
box = wx.BoxSizer()
btn = wx.Button(self,1,"Press")
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
box.Add(btn)
self.lbl = wx.StaticText(self,1,"Foobar")
box.Add(self.lbl)
self.SetSizerAndFit(box)
class main_frame(wx.Frame):
"""Main Frame holding the main panel."""
def __init__(self,*args,**kwargs):
wx.Frame.__init__(self,*args,**kwargs)
sizer = wx.BoxSizer()
self.p = foopanel(self)
sizer.Add(self.p,1)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = main_frame(None,-1,)
app.MainLoop()
label_changer.py
def change_label(self):
self.p.lbl.SetLabel("barfoo")
All I want it to do is change the label of the GUI, but use an external file.
I am doing this mostly to keep my code separate and just as a learning experience.
Thanks in advance!
One solution is to modify change_label to accept an argument that identifies the label to change. For example:
def change_label(event, label):
label.SetLabel("barfoo")
Then, use lambda to create a callback that passes that argument in:
btn.Bind(wx.EVT_BUTTON, label_changer,
lambda event, label=self.p.lbl: label_changer.change_label(event, label))
Make sure you define self.lbl before you do the binding.
For more on passing arguments to callbacks see Passing Arguments to Callbacks on WxPyWiki
A common way to do this is the MVC Pattern and pubsub. See this Example.
This
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
needs to be
btn.Bind(wx.EVT_BUTTON,label_changer.change_label)
and this
def change_label(self):
self.p.lbl.SetLabel("barfoo")
needs to be
def change_label(event):
panel = event.GetEventObject().GetParent()
panel.lbl.SetLabel("barfoo")
To clarify, you need to pass a reference to a function to Bind that is to be called when the event occurs. wx will always pass one argument to these functions - the event. The self that you usually see in the callbacks is a byproduct of them being bound methods. Every bound method (to oversimplify, a function defined in a class) gets implicitly passed a first argument when called that is a reference to a class instance. So since you can't get to this instance the traditional way in an "external" function you have to get to it through the event object.
One more thing, you are not realy separating the gui from the logic this way. This is because the logic (label_changer in this case) needs to know about the gui and to manipulate it directly. There are ways to achieve much stronger separation (st2053 hinted at one of them) but for a relatively small program you don't need to bother if you don't want to right now, simply splitting the code in multiple files and focusing on getting the thing done is fine. You can worry about architecture later.

Tkinter: invoke event in main loop

How do you invoke a tkinter event from a separate object?
I'm looking for something like wxWidgets wx.CallAfter. For example, If I create an object, and pass to it my Tk root instance, and then try to call a method of that root window from my object, my app locks up.
The best I can come up with is to use the the after method and check the status from my separate object, but that seems wasteful.
To answer your specific question of "How do you invoke a TkInter event from a separate object", use the event_generate command. It allows you to inject events into the event queue of the root window. Combined with Tk's powerful virtual event mechanism it becomes a handy message passing mechanism.
For example:
from tkinter import *
def doFoo(*args):
print("Hello, world")
root = Tk()
root.bind("<<Foo>>", doFoo)
# some time later, inject the "<<Foo>>" virtual event at the
# tail of the event queue
root.event_generate("<<Foo>>", when="tail")
Note that the event_generate call will return immediately. It's not clear if that's what you want or not. Generally speaking you don't want an event based program to block waiting for a response to a specific event because it will freeze the GUI.
I'm not sure if this solves your problem though; without seeing your code I'm not sure what your real problem is. I can, for example, access methods of root in the constructor of an object where the root is passed in without the app locking up. This tells me there's something else going on in your code.
Here's an example of successfully accessing methods on a root window from some other object:
from tkinter import *
class myClass:
def __init__(self, root):
print("root background is %s" % root.cget("background"))
root = Tk()
newObj = myClass(root)
Here below just some doc and link to better understand Bryan's answer above.
function description from New Mexico Tech :
w.event_generate(sequence, **kw)
This method causes an event to trigger without any external stimulus. The handling of the event is the same as if it had been triggered by an external stimulus. The sequence argument describes the event to be triggered. You can set values for selected fields in the Event object by providing keyword=value arguments, where the keyword specifies the name of a field in the Event object.
list and description of tcl/tk event attributes here

Categories

Resources