PyQt is executing my function multiple times after signal - python

I'm building a pyqt Qwidget with multiple QcomboBox in which the user can add items at will. But I also want to give the user the option to delete any of the items by clicking a QPushbutton.
Because I can't know in advance which comboBox and item the user will want to delete, I created an event filter to find which object is being activated, and which item is being selected.
The code below works as expected, but the function printevent is being called an increasingly number of times, which will eventually turn my program very slow.
I tried to define #QtCore.pyqtSlot() to no avail.
What did I do wrong?
My code (for this example, I'm only showing one of the ComboBoxes):
from PyQt4 import QtCore, QtGui
import sys, os, json
class Ui_MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(Ui_MainWindow,self).__init__(parent=parent)
### MAIN WINDOW ####
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(1016, 790)
MainWindow.move(200,30)
self.centralwidget = QtGui.QWidget()
self.gridLayout_2 = QtGui.QGridLayout()
self.gridLayout_2.setVerticalSpacing(0)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.comboBox_nationality = QtGui.QComboBox(self.centralwidget)
self.comboBox_nationality.setEditable(True)
self.gridLayout_2.addWidget(self.comboBox_nationality, 1, 0, 1, 1)
self.comboBox_nationality.installEventFilter(self)
def eventFilter(self,obj, event):
if event.type() == QtCore.QEvent.FocusIn:
objt=str(obj.objectName())
exec('self.' + objt + '.activated[str].connect(self.printevent)')
return super(Ui_MainWindow,self).eventFilter(obj,event)
#QtCore.pyqtSlot(QtCore.QString) # Doesn't change anything
#QtCore.pyqtSlot() # TypeError: printevent() takes exactly 2 arguments (1 given)
def printevent(self,item):
print item
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ex = Ui_MainWindow()
ex.__init__(MainWindow)
MainWindow.show()
sys.exit(app.exec_())

Related

QTabWidget not letting me use custom class

Edits: Phrasing and included a minimum reproducible example
I have a question about how object names work in PyQt5 / how OOP works in PyQt5:
Background
I'm working on a project which includes a QTabWidget. This QTabWidget adds a new tab each time a '+' tab is clicked:
Before click:
After click:
When I add the new tab as just a blank widget using insertNewRegionTab(), everything works properly; however, when I instead use a custom class that is a derived class of QtWidgets.QWidget (customTab), the program crashes. I created this class so that each tab would have the same formatting / internal relationships between objects, and that I could just add as many as I needed with the '+' button.
I think the crashing may be due to name conflicts, e.g. a label called "label1" being created in two tabs simultaneously.
Question
The problem is that I don't see how name conflicts could be an issue if each customTab is it's own object. I'm wondering if any one knows why I can't implement this customTab class in the desired way.
Proposed Solution
My thoughts on a solution is to just define class counters and then increment every time a new tab is added so they all have unique names, but this is a bit of work so I wanted to make sure that this is actually what the problem is before I implement it.
Code
N.B. - The two original tabs using this code will not have names. The one on the left, 'Test 1', has a combo box in it and is in the customTab class. The one on the right, '+', adds a new tab, but a normal QWidget instead of customTab object
from PyQt5 import QtCore, QtGui, QtWidgets
class topLevelWindow(QtWidgets.QMainWindow):
def __init__(self):
# Function that is used to add tabs
def insertNewRegionTab(self):
if self.mainTabWidgetCount == 1 + self.mainTabWidget.currentIndex():
# Attempted method 1:
tab = QtWidgets.QWidget() # Want this to be a customTab not a QWidget
self.mainTabWidget.insertTab(self.mainTabWidget.currentIndex(),
tab, "Tab " + str(self.mainTabWidgetCount))
# Attempted method 2:
# self.tabInstaces.append(customTab(self.mainTabWidget))
self.mainTabWidgetCount += 1
super().__init__()
# Initialization of main window
self.setObjectName("MainWindow")
self.resize(500, 300)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_5 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_5.setObjectName("gridLayout_5")
# Create the main tab widget and set properties
self.mainTabWidget = QtWidgets.QTabWidget(self.centralwidget)
self.mainTabWidget.setObjectName("mainTabWidget")
self.mainTabWidget.setCurrentIndex(0)
self.gridLayout_5.addWidget(self.mainTabWidget, 1, 1, 1, 1)
self.setCentralWidget(self.centralwidget)
# Insert a tab (of the custom, pre-formatted tab class) into this tab widget
self.tabInstances = [customTab(self.mainTabWidget)]
self.mainTabWidgetCount = 2
# Add the tab which creates other tabs to the tan widget
self.addRegionTab = QtWidgets.QWidget()
self.addRegionTab.setObjectName("addRegionTab")
self.mainTabWidget.addTab(self.addRegionTab, "")
# Add functionality: When '+' tab is selected, add a tab
self.mainTabWidget.currentChanged.connect(lambda: insertNewRegionTab(self))
# Show window
self.show()
class customTab(QtWidgets.QWidget):
def __init__(self, parent=None):
# Initializes the object itself and renames it. Add vertical layout to it
super(customTab, self).__init__()
self.setObjectName("tabInstances")
self.verticalLayout = QtWidgets.QVBoxLayout(self)
self.verticalLayout.setObjectName("verticalLayout")
self.comboBox1 = QtWidgets.QComboBox(self)
self.comboBox1.setObjectName("comboBox1")
self.comboBox1.addItem("")
self.comboBox1.addItem("")
# Add self to parent
parent.addTab(self, "")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = topLevelWindow()
sys.exit(app.exec_())
I do not understand the logic of OP since it asks about objectName() that has nothing to do with the problem, the objectName is only a name that allows us to identify elements and nothing else.
In my answer I show how to create the button that when pressing must add tabs:
from PyQt5 import QtCore, QtGui, QtWidgets
class TabWidget(QtWidgets.QTabWidget):
plusClicked = QtCore.pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.tabBar().installEventFilter(self)
self.add_button = QtWidgets.QToolButton(self, text="+")
self.add_button.clicked.connect(self.plusClicked)
def eventFilter(self, obj, event):
if obj is self.tabBar() and event.type() == QtCore.QEvent.Resize:
r = self.tabBar().geometry()
h = r.height()
self.add_button.setFixedSize((h - 1) * QtCore.QSize(1, 1))
self.add_button.move(r.right(), 0)
return super().eventFilter(obj, event)
class topLevelWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
# Initialization of main window
self.setObjectName("MainWindow")
self.resize(500, 300)
self.centralwidget = QtWidgets.QWidget(self)
self.setCentralWidget(self.centralwidget)
lay = QtWidgets.QGridLayout(self.centralwidget)
# Create the main tab widget and set properties
self.mainTabWidget = TabWidget()
self.mainTabWidget.setObjectName("mainTabWidget")
self.mainTabWidget.setCurrentIndex(0)
lay.addWidget(self.mainTabWidget, 1, 1, 1, 1)
self.mainTabWidget.addTab(CustomWidget(), "Tab1")
self.mainTabWidget.plusClicked.connect(self.add_clicked)
def add_clicked(self):
index = self.mainTabWidget.count() + 1
self.mainTabWidget.addTab(CustomWidget(), "Tab {}".format(index))
class CustomWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
# Initializes the object itself and renames it. Add vertical layout to it
super(CustomWidget, self).__init__(parent)
self.comboBox1 = QtWidgets.QComboBox()
self.comboBox1.addItems(list("abcdef"))
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.comboBox1)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = topLevelWindow()
w.show()
sys.exit(app.exec_())

PyQt: Adding user input into list

I am trying to make a list box where a user can enter more items to the list. I have a list box set up with an add button. The add button opens up a user input box where it will direct the user to enter a value. However, I am have issue with passing on the value of user input to the add onto the list. Any suggestions would help. Below is my code:
List box
from input_box import *
class list_form(QtGui.QWidget):
def __init__(self,list_of_items,open_text,parent= None):
super(list_form, self).__init__()
global output_path
output_path = output_path_i
grid = QtGui.QGridLayout()
grid.setSpacing(10)
self.widget = QtGui.QWidget()
self.layout = QtGui.QGridLayout(self.widget)
open_message = QtGui.QLabel(open_text)
grid.addWidget(open_message,0,0,2,4)
self.lst = QtGui.QListWidget()
grid.addWidget(self.lst,3, 0,1,4)
for i in list_of_items:
self.lst.addItem(str(i))
self.setLayout(grid)
add = QtGui.QPushButton('Add')
grid.addWidget(add,50,0)
add.clicked.connect(self.add_button)
def add_button(self):
self.input_box = input_box()
self.input_box.setWindowTitle("Window 2")
self.input_box.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = list_form(list(xrange(100)),"List of Values")
window.setWindowTitle('Window 1')
window.show()
sip.setdestroyonexit(False)
sys.exit(app.exec_())
Input box
import sys
from PyQt4 import QtCore, QtGui
import sip
class input_box(QtGui.QWidget):
def __init__(self,parent= None):
super(input_box, self).__init__()
grid = QtGui.QGridLayout()
grid.setSpacing(10)
self.widget = QtGui.QWidget()
self.layout = QtGui.QGridLayout(self.widget)
open_message = QtGui.QLabel("Enter Value:")
grid.addWidget(open_message,0,0,2,3)
self.txt = QtGui.QLineEdit()
grid.addWidget(self.txt,2, 0,1,2)
self.setLayout(grid)
save = QtGui.QPushButton('Save')
grid.addWidget(save,50,0)
a = save.clicked.connect(self.save)
save.clicked.connect(self.close)
cancel = QtGui.QPushButton('Cancel')
grid.addWidget(cancel,50,1)
cancel.clicked.connect(self.close)
def save(self):
value = self.txt.text()
return value
If you want to get data from a window you should use a QDialog instead of a QWidget, connect the clicked signal of save and cancel to the accept and reject slot, respectively:
input_box.py
from PyQt4 import QtCore, QtGui
class Input_Box(QtGui.QDialog):
def __init__(self,parent= None):
super(Input_Box, self).__init__(parent)
open_message = QtGui.QLabel("Enter Value:")
self.txt = QtGui.QLineEdit()
save = QtGui.QPushButton('Save', clicked=self.accept)
cancel = QtGui.QPushButton('Cancel', clicked=self.reject)
grid = QtGui.QGridLayout(self)
grid.setSpacing(10)
grid.addWidget(open_message, 0, 0)
grid.addWidget(self.txt, 1, 0, 1, 2)
grid.addWidget(save, 2, 0)
grid.addWidget(cancel, 2, 1)
self.setFixedSize(self.sizeHint())
def save(self):
value = self.txt.text()
return value
Then you use the exec_() method that returns a code if it is called accept or reject, and according to that the data must be obtained and added. On the other hand, do not use global variables because they are a headache when debugging.
from PyQt4 import QtCore, QtGui
from input_box import Input_Box
class list_form(QtGui.QWidget):
def __init__(self,list_of_items,open_text,parent= None):
super(list_form, self).__init__()
open_message = QtGui.QLabel(open_text)
self.lst = QtGui.QListWidget()
self.lst.addItems([str(i) for i in list_of_items])
add = QtGui.QPushButton('Add', clicked=self.add_button)
grid = QtGui.QGridLayout(self)
grid.setSpacing(10)
grid.addWidget(open_message)
grid.addWidget(self.lst)
grid.addWidget(add)
#QtCore.pyqtSlot()
def add_button(self):
input_box = Input_Box()
input_box.setWindowTitle("Window 2")
if input_box.exec_() == QtGui.QDialog.Accepted:
val = input_box.save()
it = QtGui.QListWidgetItem(val)
self.lst.addItem(it)
self.lst.scrollToItem(it)
if __name__ == "__main__":
import sys
import sip
app = QtGui.QApplication(sys.argv)
window = list_form(list(range(100)),"List of Values")
window.setWindowTitle('Window 1')
window.show()
sip.setdestroyonexit(False)
sys.exit(app.exec_())
The advantage of using QDialog is the decoupling between the classes, for example there is no need to pass a QListWidget as the other answer suggests making it possible that you can use the same dialog for other purposes.
You need to arrange for the action taken when the save button is pressed to pass information back to the list widget. There's more than one way to do it, but just returning the data won't get it done.
Here's an example of the sort of thing that will work - though other approaches are possible.
Change the input_box constructor so it keeps a reference to the list widget which it expects:
class input_box(QtGui.QWidget):
def __init__(self, list_widget, parent= None):
super(input_box, self).__init__()
self.list_widget = list_widget
...
Change the call to the constructor to provide that information:
def add_button(self):
self.input_box = input_box(self.lst)
self.input_box.setWindowTitle("Window 2")
self.input_box.show()
And then use that information in the save method to add to the list widget:
def save(self):
value = self.txt.text()
self.list_widget.addItem(value)
Bingo!
An alternative approach could be to arrange for the input_box to emit a signal with the new value in it, and connect that to a slot on the list_form, or on the list_widget. Or in the input_box you could navigate via its parent to the list_widget. But I think mine is simple and straightforward.

pyqt : Display All values present in QListWidget as selected to user

I have searched a lot but I am unable to find a solution for doing this.
I have a QListWidget which will populate a list of values. I need to implement a "select all" as default behaviour in the list.
I have already used MultiSelection and that works well, but the need is to avoid clicking on each item, in case the user wants to do the update on all items listed.
Can someone help me understand how to do this?
Use QtWidgets.QAbstractItemView.ExtendedSelection
When the user selects an item in the usual way, the selection is cleared and the new item selected. However, if the user presses the Ctrl key when clicking on an item, the clicked item gets toggled and all other items are left untouched. If the user presses the Shift key while clicking on an item, all items between the current item and the clicked item are selected or unselected, depending on the state of the clicked item. Multiple items can be selected by dragging the mouse over them.
import sys
from PyQt5 import QtWidgets
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QListWidget()
for i in range(12):
w.addItem('Item {}'.format(i))
w.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
#w.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
w.show()
sys.exit(app.exec_())
From what I understand you want to implement a function that selects all items, the solution is to iterate using the setSelected() method of QListWidget as shown below:
import sys
from PyQt4 import QtCore, QtGui
class ListWidget(QtGui.QListWidget):
def __init__(self, parent=None):
super(ListWidget, self).__init__(parent)
self.setSelectionMode(QtGui.QListWidget.MultiSelection)
#QtCore.pyqtSlot()
def selectAll(self):
for i in range(self.count()):
it = self.item(i)
if it is not None:
it.setSelected(True)
#QtCore.pyqtSlot()
def clearSelection(self):
for i in range(self.count()):
it = self.item(i)
if it is not None:
it.setSelected(False)
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
lay = QtGui.QVBoxLayout(self)
button_sel = QtGui.QPushButton("Select All")
button_unsel = QtGui.QPushButton("Clear Selection")
self.list_widget = ListWidget()
for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
it = QtGui.QListWidgetItem(letter)
self.list_widget.addItem(it)
button_sel.clicked.connect(self.list_widget.selectAll)
button_unsel.clicked.connect(self.list_widget.clearSelection)
lay.addWidget(button_sel)
lay.addWidget(button_unsel)
lay.addWidget(self.list_widget)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

To send two arguments to a function with the click of a button

I have used QT Designer to have two QLineEdit's to take input from the user. After the user enters the values , when the Enter button is clicked I need the buttons to pass the values to the disk_angles function.
How to pass two strings to a function via signals with the press of a button?
Here is my code
class Maindialog(QMainWindow,diskgui.Ui_MainWindow):
pass_arguments = SIGNAL((str,),(str,))
def __init__(self,parent = None):
super(Maindialog,self).__init__(parent)
self.setupUi(self)
self.connect(self.Home,SIGNAL("clicked()"),self.home_commands)
self.connect(self.AutoFocus,SIGNAL("clicked()"),self.auto_focus)
self.Enter.clicked.connect(self.entervalues)
self.connect(self,SIGNAL("pass arguments"),self.Criterion_disk_angles)
def entervalues(self):
if self.RotationEdit.text() != "" and self.TiltEdit.text() != "":
self.RotationEdit = str(self.RotationEdit.text())
self.TiltEdit = str(self.TiltEdit.text())
self.pass_arguments.emit(self.RotationEdit,self.TiltEdit)
def disk_angles(self,rotation_angle, tilt_angle):
I have tried to pass tuples as input to the signal
pass_arguments = SIGNAL((str,),(str,))
but I get the error
pass_arguments = SIGNAL((str,),(str,))
TypeError: SIGNAL() takes exactly one argument (2 given)
In PyQt5 it is recommended to use the new style, In addition you are sending 2 tuples of place of one, here I show an example of a correct implementation.
import sys
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QApplication, QPushButton
class Widget(QObject):
sig = pyqtSignal((str, str))
def __init__(self, parent=None):
super(Widget, self).__init__(parent=parent)
self.sig.connect(self.printer)
def click(self):
self.sig.emit("hello", "bye")
def printer(self, text1, text2):
print(text1, text2)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QPushButton()
w1 = Widget()
w.clicked.connect(w1.click)
w.show()
sys.exit(app.exec_())

Pyside keypress event for textedit

I have 2 QTextEdit widgets and I need to put whatever is entered in the first to the second on a press of the enter key (Return).... I am not able to implement it please help?
I know I need to use KeyPressEvent but I don't understand how to use it only for the QTextEdit??
self.textEdit = QtGui.QTextEdit(self.widget)
self.textEdit.setMinimumSize(QtCore.QSize(201, 291))
self.textEdit.setMaximumSize(QtCore.QSize(201, 291))
self.textEdit.setObjectName("textEdit")
self.textEdit.setReadOnly(True)
self.verticalLayout.addWidget(self.textEdit)
self.textEdit_2 = QtGui.QTextEdit(self.widget)
self.textEdit_2.setMinimumSize(QtCore.QSize(201, 41))
self.textEdit_2.setMaximumSize(QtCore.QSize(201, 41))
self.textEdit_2.setObjectName("textEdit_2")
self.textEdit_2.setFocusPolicy(Qt.StrongFocus)
self.verticalLayout.addWidget(self.textEdit_2)
Any help is appreciated I am stuck.....
Use the viewportEvent (inherited from QAbstractScrollArea)
self.textEdit.viewportEvent.connect(self.copy_the_text)
def copy_the_text(self, event):
if isinstance(event, QtGui.QKeyEvent): # as viewportEvent gets *all* events
if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
# copy the text from textEdit to textEdit_2
You can use Qt.Key_Enter but I think you probably want Qt.Key_Return
EDIT
If you are using an older version of PySide without new style signals and slots you'll need to use
self.connect(self.textEdit, SIGNAL("viewportEvent(event)"), self.copy_the_text)
Here is a small example that shows QLineEdit and its returnPressed signal. Upon pressing return the text in the QLineEdit will be appended to the QTextEdit:
import sys
from PySide import QtGui
class Window(QtGui.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.output = QtGui.QTextEdit()
self.output.setReadOnly(True)
self.input = QtGui.QLineEdit()
self.input.returnPressed.connect(self.addInput)
self.input.setPlaceholderText('input here')
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.output)
layout.addWidget(self.input)
def addInput(self):
# skip empty text
if self.input.text():
self.output.append(self.input.text())
# clear the QLineEdit for new input
self.input.clear()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())

Categories

Resources