Accessing QComboBox index values - python

I have a comboxbox which contains 2 items - Method01, Method02
How can I tell my code to execute Method01Func when Method01 is selected, likewise for Method02 too?
self.connect(self.exportCombo, SIGNAL('currentIndexChanged(int)'), self.Method01Func)
I tried to code it in something similar when accessing in a list - [0],[1]... but I was bumped with errors

One way to do it is to make use of the userData parameter when calling addItem(), and pass in a reference to the function you want that item to call.
Here's a simple example:
import sys
from PyQt4 import QtCore, QtGui
def Method01Func():
print 'method 1'
def Method02Func():
print 'method 2'
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
widget = QtGui.QWidget()
self.combo = QtGui.QComboBox()
self.combo.addItem('Method 1', Method01Func)
self.combo.addItem('Method 2', Method02Func)
self.combo.currentIndexChanged.connect(self.execute_method)
layout = QtGui.QVBoxLayout(widget)
layout.addWidget(self.combo)
self.setCentralWidget(widget)
#QtCore.pyqtSlot(int)
def execute_method(self, index):
method = self.combo.itemData(index).toPyObject()
method()
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

alternatively You can send the current items text with the signal:
self.exportCombo.currentIndexChanged[str].connect(self.execute_method)
and check it in the slot:
def execute_method(self, text):
(self.Method01Func() if text == 'Method01' else self.Method02Func())

Related

Python PyQt6 ListWidget [duplicate]

I'm stuck using myItem.hide() method every time I need to remove Item from QListWidget list. Hiding an item instead of deleting/removing makes things unnecessary complex. I would appreciate if you show me how to delete Item from ListWidget permanently.
from PyQt4 import QtGui, QtCore
class MyApp(object):
def __init__(self):
super(MyApp, self).__init__()
self.mainWidget = QtGui.QWidget()
self.mainLayout = QtGui.QVBoxLayout()
self.mainWidget.setLayout(self.mainLayout)
self.hLayout = QtGui.QHBoxLayout()
self.mainLayout.insertLayout(0, self.hLayout)
self.listA=QtGui.QListWidget()
for i in range(3):
self.listA.addItem('Item '+str(i))
self.hLayout.addWidget(self.listA)
self.buttonGroupbox = QtGui.QGroupBox()
self.buttonlayout = QtGui.QVBoxLayout()
self.buttonGroupbox.setLayout(self.buttonlayout)
okButton = QtGui.QPushButton('Remove Selected')
okButton.clicked.connect(self.removeSel)
self.buttonlayout.addWidget(okButton)
self.mainLayout.addWidget(self.buttonGroupbox)
self.mainWidget.show()
sys.exit(app.exec_())
def removeSel(self):
listItems=self.listA.selectedItems()
if not listItems: return
for item in listItems:
print type(item), dir(item)
I don't know why but removeItemWidget don't work as expected. You have to use take item instead:
def removeSel(self):
listItems=self.listA.selectedItems()
if not listItems: return
for item in listItems:
self.listA.takeItem(self.listA.row(item))
Posting here an example showing how to implement same approach but now applied to QTreeWidget which a bit more involved than QListWidget.
from PyQt4 import QtGui, QtCore
class MyApp(object):
def __init__(self):
super(MyApp, self).__init__()
self.mainWidget = QtGui.QWidget()
self.mainLayout = QtGui.QVBoxLayout()
self.mainWidget.setLayout(self.mainLayout)
self.hLayout = QtGui.QHBoxLayout()
self.mainLayout.insertLayout(0, self.hLayout)
self.listA=QtGui.QTreeWidget()
self.listA.setColumnCount(3)
self.listA.setHeaderLabels(['Checkbox','Name','Data'])
for i in range(3):
item=QtGui.QTreeWidgetItem()
item.setCheckState(0,QtCore.Qt.Checked)
item.setText(1, 'Item '+str(i))
item.setData(2, QtCore.Qt.UserRole, id(item) )
item.setText(2, str(id(item) ) )
self.listA.addTopLevelItem(item)
self.hLayout.addWidget(self.listA)
self.buttonGroupbox = QtGui.QGroupBox()
self.buttonlayout = QtGui.QVBoxLayout()
self.buttonGroupbox.setLayout(self.buttonlayout)
okButton = QtGui.QPushButton('Remove Selected')
okButton.clicked.connect(self.removeSel)
self.buttonlayout.addWidget(okButton)
getDataButton = QtGui.QPushButton('Get Items Data')
getDataButton.clicked.connect(self.getItemsData)
self.buttonlayout.addWidget(getDataButton)
self.mainLayout.addWidget(self.buttonGroupbox)
self.mainWidget.show()
sys.exit(app.exec_())
def removeSel(self):
listItems=self.listA.selectedItems()
if not listItems: return
for item in listItems:
itemIndex=self.listA.indexOfTopLevelItem(item)
self.listA.takeTopLevelItem(itemIndex)
print '\n\t Number of items remaining', self.listA.topLevelItemCount()
def getItemsData(self):
for i in range(self.listA.topLevelItemCount()):
item=self.listA.topLevelItem(i)
itmData=item.data(2, QtCore.Qt.UserRole)
itemId=itmData.toPyObject()
print '\n\t Item Id Stored as Item Data:', itemId, 'Item Checkbox State:', item.checkState(0)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
MyApp()
A ListWidget is a list of ListWidgetItems. A ListWidgetItems can be assigned a custom widget to override the default, so removeItemWidget() only removes the custom widget. Hence the need for takeItem, which pops the item from the list and returns it (similar to how a python list works)

Why does my QTableWidgetItems's rowHeight not get properly set on refresh?

I'm creating a table widget that I want to auto-refresh at certain intervals. The trouble I'm having is that refreshing the contents of the table is resetting their rowHeight property and ignoring the call to setRowHeight().
For example, I have a repeater class here running on a separate thread:
class RepeatedTimer(QtCore.QThread):
def __init__(self, obj):
super(RepeatedTimer, self).__init__(obj)
self.obj = obj
self.stop = False
def run(self):
while not self.stop:
time.sleep(2)
self.obj.refresh()
and it's being used in my QTableWidget like this:
from PySide import QtCore, QtGui
import sys, time
class TestTable(QtGui.QTableWidget):
def __init__(self, parent=None):
super(TestTable, self).__init__(parent)
self.setColumnCount(1)
self.thread = RepeatedTimer(self) # Create the auto-refresher thread
self.thread.start() # Start the thread
self.refresh()
def refresh(self):
print "Clearing table"
while self.rowCount():
self.removeRow(0)
for each in xrange(3):
self.insertRow(each)
text = str(time.time())
item = QtGui.QTableWidgetItem(text)
self.setItem(each, 0, item)
for row in xrange(self.rowCount()):
self.setRowHeight(row, 100) # This part is not behaving as expected
print 'Row %d height: %d' % (row, self.rowHeight(row))
def closeEvent(self, event):
print 'Stopping thread...'
self.thread.stop = True
self.thread.exit()
app = QtGui.QApplication(sys.argv)
test = TestTable()
test.show()
sys.exit(app.exec_())
If you run this, you'll see that each time the table refreshes, it clears all the contents, adds new items in each row, and sets all the row heights to 100. Except that last part. It is correctly looping through the rows because it prints each time. But for some reason it stops setting the row heights after the first loop.
Any ideas why this is happening?
It is not necessary to create a thread to update the QTableWidget, you could use a QTimer, on the other hand remove QTableWidgetItem and set them again is expensive so I recommend you update them.
import sys
import time
from PySide import QtCore, QtGui
class TestTable(QtGui.QTableWidget):
def __init__(self, parent=None):
super(TestTable, self).__init__(parent)
self.setRowCount(3)
self.setColumnCount(1)
for row in range(self.rowCount()):
for col in range(self.columnCount()):
self.setItem(row, col, QtGui.QTableWidgetItem(str(time.time())))
for each in range(self.rowCount()):
self.setRowHeight(each, 100)
self.setColumnWidth(each, 250)
timer_refresh = QtCore.QTimer(self)
timer_refresh.timeout.connect(self.refresh)
timer_refresh.start(2000)
def refresh(self):
for row in range(self.rowCount()):
for col in range(self.columnCount()):
self.item(row, col).setText(str(time.time()))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
test = TestTable()
test.show()
sys.exit(app.exec_())
Sorry, but you do not need a separate thread for this task.
Use the QTimer class provides repetitive and single-shot timers.
Here is an example, sorry for me PyQt5
import sys
import time
#from PySide import QtCore, QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TestTable(QTableWidget):
def __init__(self, parent=None):
super(TestTable, self).__init__(parent)
self.setWindowTitle("QTableWidget setRowHeight ")
self.resize(530, 330);
self.setRowCount(3)
self.setColumnCount(1)
self.setHorizontalHeaderLabels(['time.time())',])
for each in range(3):
self.setRowHeight(each, 100)
self.setColumnWidth(each, 250)
self.my_qtimer = QTimer(self)
self.my_qtimer.timeout.connect(self.timerTick)
self.my_qtimer.start(1000) # msec
def timerTick(self):
for each in range(3):
item = QTableWidgetItem(str(time.time()))
self.setItem(0, each, item)
app = QApplication(sys.argv)
test = TestTable()
test.show()
sys.exit(app.exec_())

Pyside crashes when executing function twice

Why does my application crash when i run the function setup_controls() twice.
Am I missing a 'parent/self' somewhere that is critical in the design?
import sys
from PySide import QtGui, QtCore
class QCategoryButton(QtGui.QPushButton):
def __init__(self, Text, treeitem, parent=None):
super(QCategoryButton, self).__init__(Text, parent)
self.treeitem = treeitem
def mousePressEvent(self, event):
mouse_button = event.button()
if mouse_button == QtCore.Qt.LeftButton:
self.treeitem.setExpanded(not self.treeitem.isExpanded())
class Example(QtGui.QWidget):
def __init__(self,):
super(Example, self).__init__()
self.initUI()
def initUI(self):
# formatting
self.resize(300, 300)
self.setWindowTitle("Example")
# widgets
self.ui_treeWidget = QtGui.QTreeWidget()
self.ui_treeWidget.setRootIsDecorated(False)
self.ui_treeWidget.setHeaderHidden(True)
self.ui_treeWidget.setIndentation(0)
self.setup_controls()
# self.setup_controls()
# layout
self.mainLayout = QtGui.QGridLayout(self)
self.mainLayout.addWidget(self.ui_treeWidget)
self.show()
def setup_controls(self):
# Add Category
pCategory = QtGui.QTreeWidgetItem()
self.ui_treeWidget.addTopLevelItem(pCategory)
self.ui_toggler = QCategoryButton('Settings', pCategory)
self.ui_treeWidget.setItemWidget(pCategory, 0, self.ui_toggler)
pFrame = QtGui.QFrame(self.ui_treeWidget)
pLayout = QtGui.QVBoxLayout(pFrame)
self.ui_ctrl = QtGui.QPushButton('Great')
self.ui_ctrlb = QtGui.QPushButton('Cool')
pLayout.addWidget(self.ui_ctrl)
pLayout.addWidget(self.ui_ctrlb)
pContainer = QtGui.QTreeWidgetItem()
pContainer.setDisabled(False)
pCategory.addChild(pContainer)
self.ui_treeWidget.setItemWidget(pContainer, 0, pFrame)
# Main
# ------------------------------------------------------------------------------
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
The setItemWidget method takes ownership of the widget that is passed to it. If you don't keep a reference it, it could get garbage-collected by Python. But of course Qt knows nothing about Python, so when it subsequently tries to access the widget that is no longer there ... boom!
This is the problematic line:
self.ui_toggler = QCategoryButton('Settings', pCategory)
On the second call, the previous widget stored in self.ui_toggler will get deleted, because there is no other reference held for it (on the Python side). So instead you should do this:
ui_toggler = QCategoryButton('Settings', pCategory, self)
self.ui_treeWidget.setItemWidget(pCategory, 0, ui_toggler)

PyQt: QListView in connection with QTextEdit

I have a QListView and a QTextEdit on a form and I would like to get them working together, as follows: if a checkbox is checked, the index of the respective item in the QlistView should be displayed in tbe QTextEdit; if the checkbox is unchecked, the value should be deleted from the QTextEdit. The indexes should be displayed cumulatively, delimited by one character (say, a comma), eg., 0,1,3.
Conversely, if a value is typed in the the QTextEdit, the respective checkbox should be automatically checked (or none, in case the value entered does not correspond to any index in the QListView).
I attempted to catch the indices of the selected checboxes by attaching an handler to the clicked event of the QListView, as below:
<del>#QtCore.pyqtSlot(QtCore.QModelIndex)
def onclick(self, index):
editbox.setText(str(index.row()))</del>
but got the error message: "NameError: global name 'self' is not defined".
Any hints? Thanks in advance for any assistance you can provide!
Here is my complete test code:
EDIT: I changed the code below to deal properly with event handlers.
import sys
from PyQt4 import Qt, QtCore, QtGui
class MainWindow(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
model = QtGui.QStandardItemModel()
for n in range(10):
item = QtGui.QStandardItem('Item %s' % n)
item.setCheckState(QtCore.Qt.Unchecked)
item.setCheckable(True)
model.appendRow(item)
listview = QtGui.QListView()
listview.setModel(model)
listview.clicked.connect(self.onclick)
self.editbox = QtGui.QTextEdit()
self.editbox.textChanged.connect(self.onchange)
grid = QtGui.QGridLayout()
grid.setRowStretch(0, 6)
grid.setRowStretch(1, 4)
grid.addWidget(listview)
grid.setSpacing(2)
grid.addWidget(self.editbox)
self.setLayout(grid)
self.setGeometry(300, 150, 350, 300)
self.setWindowTitle("Example")
self.show()
##QtCore.pyqtSlot(QtCore.QModelIndex)
def onclick(self, index):
self.editbox.append(str(index.row()))
def onchange(self):
print "text in edit box changed"
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
sys.exit(app.exec_())
Since you're defining onclick outside of a class definition, there's no self (which refers to an instance of the class) either. Define it as a regular function instead:
#QtCore.pyqtSlot(QtCore.QModelIndex)
def onclick(index):
editbox.setText(str(index.row()))
and connect it to the signal as listview.clicked.connect(onclick).

PySide (or PyQt) signals and slots basics

Consider a simple example like this which links two sliders using signals and slots:
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout()
sone = QSlider(Qt.Horizontal)
vbox.addWidget(sone)
stwo = QSlider(Qt.Horizontal)
vbox.addWidget(stwo)
sone.valueChanged.connect(stwo.setValue)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
sys.exit(app.exec_())
How would you change this so that the second slider moves in the opposite direction as the first? Slider one would be initialized with these values:
sone.setRange(0,99)
sone.setValue(0)
And slider two would be initialized with these values:
stwo.setRange(0,99)
stwo.setValue(99)
And then the value of stwo would be 99 - sone.sliderPosition.
How would you implement the signal and slot to make this work? I would appreciate a working example that builds on the simple example above.
Your example is a bit broken, because you forgot to set the parent of the layout, and also to save the slider widgets as member attributes to be accessed later... But to answer your question, its really as simple as just pointing your connection to your own function:
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout(self)
self.sone = QSlider(Qt.Horizontal)
self.sone.setRange(0,99)
self.sone.setValue(0)
vbox.addWidget(self.sone)
self.stwo = QSlider(Qt.Horizontal)
self.stwo.setRange(0,99)
self.stwo.setValue(99)
vbox.addWidget(self.stwo)
self.sone.valueChanged.connect(self.sliderChanged)
def sliderChanged(self, val):
self.stwo.setValue(self.stwo.maximum() - val)
Note how sliderChanged() has the same signature as the original setValue() slot. Instead of connecting one widget directly to the other, you connect it to a custom method and then transform the value to what you want, and act how you want (setting a custom value on stwo)
You can connect signals to functions that do things. Your code isn't structured to do that easily and required refactoring, so you can do it the easy way:
stwo.setInvertedAppearance(True)
sone.valueChanged.connect(stwo.setValue)
Here's the way I did it. I added this class which reimplements setValue. (I got the idea from http://zetcode.com/tutorials/pyqt4/eventsandsignals/)
class MySlider(QSlider):
def __init__(self):
QSlider.__init__(self, Qt.Horizontal)
def setValue(self, int):
QSlider.setValue(self, 99-int)
Here's the complete code. Is this a good approach?
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class MySlider(QSlider):
def __init__(self):
QSlider.__init__(self, Qt.Horizontal)
def setValue(self, int):
QSlider.setValue(self, 99-int)
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout()
sone = QSlider(Qt.Horizontal)
sone.setRange(0,99)
sone.setValue(0)
vbox.addWidget(sone)
stwo = MySlider()
stwo.setRange(0,99)
stwo.setValue(0)
vbox.addWidget(stwo)
sone.valueChanged.connect(stwo.setValue)
self.setLayout(vbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
sys.exit(app.exec_())

Categories