PyQt lineEdit on textEdited signal value lost after function call - python

#^^^^class stuff here for setting UI^^^^
# connecting combobox to slot/function get_baffle_number
self.baffle_number_combobox.currentIndexChanged.connect(get_baffle_number)
# connecting PushButton action "clicked" to function on_click
self.pushButton.clicked.connect(on_click)
#connecting lineEdit to slot/function get_baffle_cost
self.baffle_cost_lineEdit.textEdited.connect(get_baffle_cost)
#pyqtSlot()
def get_baffle_cost(text):
baffle_cost = text
return baffle_cost
def get_baffle_number(text):
#add 1 to the index returned by comboBox to get the number desired by user
baffle_number = text + 1
return baffle_number
def calc_baffle_cost():
test_total = (get_baffle_cost() * get_baffle_number())
return test_total
#pyqtSlot()
def on_click(self):
baffle_cost = calc_baffle_cost()
print(baffle_cost)
After I connect that lineEdit to the function via the pyqtSlot(), it seems to get the value, but immediately dumps it if I attempt to use baffle_cost from another function. I watch it in PyCharm during debugging and it holds it just as long as the lineEdit has focus it seems. Pressing the pushButton is right when it loses its value.
I cannot use the returned baffle_cost anywhere from get_baffle_cost.
Am I missing something? The furthest I've got is that attempting to simply print calc_baffle_cost() and a hexadecimal is printed. I am assuming that is a memory location, but can't be sure. (new to Python)
Sorry if this isn't enough information. I am simply attempting to take baffle_cost from a lineEdit and multiplying that by a value taken from a comboBox.

It seems my problem was two-fold, or more.
I was not correctly scoping my functions and not utilizing the right namespaces. (Sorry if this terminology is incorrect. I am new to Python and PyQt.)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
# sizing of widgets
def retranslateUi(self, MainWindow):
# UI stuff here QlineEdits, etc.
self.baffle_cost_lineEdit.editingFinished.connect(self.get_baffle_cost)
def get_baffle_cost(self):
baffle_cost = self.baffle_cost_lineEdit.text()
return baffle_cost
This needed to be in the same scope(indentation) as my retranslateUi() function in my Ui_MainWindow Class like above.
I think if I had better structured my project this wouldn't have been an issue. I have definitely learned my lesson about having it all in one script. (program.py)
The other issue(s) I encountered is that the PyQt function text() called from a QlineEdit returns a QString and not a str. I simply needed to cast it to a string.
I was losing the value because the function get_baffle_cost wasn't properly scoped with the baffle_cost_lineEdit

Related

QPushButton shortcut does not work properly

In this simple pyqt5 app, I have a QPushButton and I define a shortcut for it. I want to change its text every time it is triggered. Problem is that the shortcut works only for the first time. How can I fix it?
from PyQt5.QtWidgets import QPushButton, QMainWindow, QApplication
import sys
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.btn = QPushButton('&Connect', self)
self.btn.setShortcut('Ctrl+C')
self.btn.pressed.connect(self.btn_func)
def btn_func(self):
if self.btn.text() == '&Connect':
self.btn.setText('Dis&connect')
else:
self.btn.setText('&Connect')
def main():
app = QApplication(sys.argv)
win = window()
win.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
As explained in the text property documentation [emphasis mine]:
If the text contains an ampersand character ('&'), a shortcut is automatically created for it. [...] Any previous shortcut will be overwritten or cleared if no shortcut is defined by the text
I know that the above might seem confusing, as it seems that the shortcut is overwritten or cleared only if no shortcut is defined by the text, consider it like this:
Any previous shortcut will be overwritten, or it is cleared if no shortcut is defined by the text
The solution is to always reset the shortcut after setting the new text:
def btn_func(self):
if self.btn.text() == '&Connect':
self.btn.setText('Dis&connect')
else:
self.btn.setText('&Connect')
self.btn.setShortcut('Ctrl+C')
Note that using the button's text for comparison is considered bad practice, for three reasons:
the text of a button could (should) be localized;
you could easily forget to correctly update all the & texts, making the function behave in the wrong way;
some QStyles override existing mnemonics and change them by themselves, which also causes the text to change without any warning;
The most preferred way to achieve what you want would be to use an internal flag for the current state, and also a QAction with its own shortcut.
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.btn = QPushButton('&Connect', self)
self.btn.clicked.connect(self.btn_func)
self.connectAction = QAction('Toggle connection', self)
self.connectAction.setShortcut('Ctrl+c')
self.connectAction.triggered.connect(self.btn.animateClick)
self.addAction(self.connectAction)
self.connected = False
def btn_func(self):
self.connected = not self.connected
if self.connected:
self.btn.setText('Dis&connect')
else:
self.btn.setText('&Connect')
Also note that:
you should not use the pressed() signal, as it's standard convention to consider a button clicked when the user presses and releases the mouse button while inside the button area (so that the pressed action could be "undone" by moving the mouse away if the mouse button was pressed by mistake on the button); use the clicked() signal instead;
I changed the class name using a capitalized Window, as classes should always use capitalized names and lower cased names should only be used for functions and variables;
I used the animateClick slot to show that the button was clicked (as a visual feedback is always preferable), but you can directly connect to the function: self.connectAction.triggered.connect(self.btn_func);

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.

How to get the Maya/Qt GUI component that is calling a function?

I am trying to figure out a way to somehow "get" the GUI component that is invoking a function. This way I can further consolidate my code into reusable parts for components that do similar tasks. I need a way to do this in Maya's GUI commands as well as Qt's. I guess what I'm looking for is a general python trick like the "init", "file", "main", etc. If there isn't a general python way to do it, any Maya/Qt specific tricks are welcome as well.
Here is some arbitrary pseudo code to better explain what I'm looking for:
field1 = floatSlider(changeCommand=myFunction)
field2 = colorSlider(changeCommand=myFunction)
def myFunction(*args):
get the component that called this function
if the component is a floatSlider
get component's value
do the rest of the stuff
elif the component is a colorSlider
get component's color
do the rest of the stuff
Expanding from Gombat's comment, here's an example of how to get a generic function to work with a slider and a spinBox control:
from PySide import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
# Create a slider
self.floatSlider = QtGui.QSlider()
self.floatSlider.setObjectName('floatSlider')
self.floatSlider.valueChanged.connect(self.myFunction)
# Create a spinbox
self.colorSpinBox = QtGui.QSpinBox()
self.colorSpinBox.setObjectName('colorSlider')
self.colorSpinBox.valueChanged.connect(self.myFunction)
# Create widget's layout
mainLayout = QtGui.QHBoxLayout()
mainLayout.addWidget(self.floatSlider)
mainLayout.addWidget(self.colorSpinBox)
self.setLayout(mainLayout)
# Resize widget and show it
self.resize(300, 300)
self.show()
def myFunction(self):
# Getting current control calling this function with self.sender()
# Print out the control's internal name, its type, and its value
print "{0}: type {1}, value {2}".format( self.sender().objectName(), type( self.sender() ), self.sender().value() )
win = Window()
I don't know what control you would want colorSlider (I don't think PySide has the same slider as the one in Maya, you may have to customize it or use a QColorDialog). But this should give you a rough idea of how to go about it.

How to close a QDialog

I've been trying to close a QDialog window that is branching off of my main window. The following have not worked for me so far:
self.close()
QDialog.close()
I tried other commands such as exit and exec_() with no luck.
The most common error I get is
[className] object has no attribute 'close'
# Creating our window
class Ui_MainWindow(object):
# Sets up GUI
def setupUi(self, MainWindow):
[GUI CODE]
# Sets text for parts of GUI
def retranslateUi(self, MainWindow):
[MORE GUI CODE]
# Function handling screencap on click and metadata for filenames
def cap_on_Click(arg1,arg2):
popup = QDialog()
popup_ui = Ui_Dialog()
popup_ui.setupUi(popup)
popup.show()
sys.exit(popup.exec_())
The above is my main window
class Ui_Dialog(object):
def setupUi(self, Dialog):
[GUI CODE]
def retranslateUi(self, Dialog):
[MORE GUI CODE]
def button_click(self, arg1):
self.close()
The second block is the dialog window code. How do I close this dialog window?
First of all, sorry for the links related to C++, but Python has the same concept.
You can try using the reject or accept or done function to close the dialog box. By doing this, you're setting the return value appropriately (Rejected, Accepted or the one you specify as the argument).
All in all, you should try calling YourDialog.done(n) to close the dialog and return n and YourDialog.accept() or YourDialog.reject() when you want it accepted/rejected.
Since a QDialog is a QWidget, and a QWidget has the close() method, I don't understand how it can not work. You should never invoke popup.exec_() though, since it will happily require a lot of your code to be reentrant without you realizing it. It is unnecessary - you already have the application event loop running and on the call stack when cap_on_Click is executing.
After popup.show(), the dialog will be visible and usable until it gets accepted or rejected by the user. Hopefully your design for the dialog connects the button box's accepted() and rejected() signals to the dialog's accept() and reject() slots. That is the default behavior of a QDialog template provided with Qt Creator and perhaps Qt Designer as well, but you should check it by going into the signal/slot editor mode while viewing the Ui file.
I guess the problem is that Ui_Dialog does not inherit QDialog, so reject(), accept() and done() is not defined. I think
class Ui_Dialog(object):
should be changed to
class Ui_Dialog(QDialog):
But I can not test it because minimal working example is not provided.
I know it's over 5months but I choose to drop this comment here, it might be of help to others tomorrow.
To be able to close or cancel an opened dialog box, using only self.close would close the entire program.
use this example:
self.dlg = QDialog(self)
self.dlg.setWindowTitle("Admin")
self.dlg.setGeometry(100,100,300,200)
btnBox = QDialogButtonBox()
btnBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
btnBox.rejected.connect(self.close1)
def close1():
self.dlg.close()

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.

Categories

Resources