Python retrieve QComboBox from QTablewidget - python

I am now a month into Python and half a month into QDesigner/Qt.
Within QDesigner, I have a QTableWidget, which I promote to my own InputTable(QTableWidget) class.
class InputTable(QTableWidget):
def __init__(self, parent=None):
self.parent = parent
super(QTableWidget, self).__init__(parent)
...
I inserted some QComboboxes, which allow the user to select the units of the values in the table. This is at the initialization within the InputTable class.
pressureunits = [ "barg","bara", "psig", "psia"]
pressurecombobox = QComboBox()
for i in range(len(pressureunits)):
pressurecombobox.addItem(pressureunits[i])
self.setCellWidget(2, 0, pressurecombobox)
So far, so good.
Now I want to convert all the inputdata to a parallel table (master table) with values in SI units for further analysis.
units = self.tableInputs.cellWidget(2,0)
if units.currentIndex()==0: # this means the combobox is at the first entry
mastertable[0,2] = float(self.tableInputs.item(2,1).text())
This fails, because units is of type QWidget, which has no attribute currentIndex. On some forums I read that the QComboBox is a child of the QWidget and I have played with the children() functions, but helas no solution here. Some people recommend switching to QTableView structure. I spend my day searching how to do this, nothing found yet. Any smart ideas?
Update: After reducing all complexity from my question, I was able to find the solution below.
from PyQt5.QtWidgets import QComboBox, QTableWidget, QTableWidgetItem
Mytable = QTableWidget(3,3)
Mytable.setItem(0, 0, QTableWidgetItem("1"))
print("Now retrieve the data :", Mytable.item(0, 0).text())
pressureunits = [ "barg","bara", "psig", "psia"]
pressurecombobox = QComboBox()
for i in range(len(pressureunits)):
pressurecombobox.addItem(pressureunits[i])
Mytable.setCellWidget(2, 0, pressurecombobox)
print("Now retrieve the active (first) entry :", Mytable.cellWidget(2,0).currentText())

Related

Trying to retrieve QcomboBox selection from with QtableWidget

I have a function that inserts multiple QcomboBoxes in the first row of QtableWidget and then updates an excel file starting in row 2. This is at the end of my function:
for i in range(df.shape[1]):
combo = comboCompanies(self)
self.tableWidget.setCellWidget(0, i, combo)
What I would like to do is know when one of the indexes is changed on a combobox but they currently have the same name so I need to figure out how to uniquely identity them. I found the following but it isnt in Python
QComboBox* myComboBox = new QComboBox(); // making a new dropdown box
myComboBox->setObjectName(QString::number(i)); // pass column number as object name
connect(myComboBox, SIGNAL(currentIndexChanged(QString)),
SLOT(onComboChanged(QString)));
ui->tableWidget->setCellWidget(0,i,myComboBox); // put box into table
How do I code in Python?
You can identify Your Combobox eihter by setting it's objectname or just by passing it's column as a parameter to the callback.
currentIndexChanged signal only gives You new changed index, but not the Combobox itself. That's why we have to use inline lambda function.
Here is the code with both options used:
import sys
from PyQt5.QtWidgets import QApplication, QTableWidget, QComboBox
def index_changed_id_by_column(column: int, selected_index: int) -> None:
"""combobox in column changed selected_index"""
print(f"Combobox in column: {column}, changed to index:{selected_index}")
def index_changed_id_by_combobox(combobox: QComboBox, selected_index: int) -> None:
"""combobox changed selected_index"""
print(f"Combobox: {combobox.objectName()}, changed to index:{selected_index}")
if __name__ == "__main__":
app = QApplication(sys.argv)
# Create some Table
table = QTableWidget()
table.setRowCount(2)
table.setColumnCount(4)
# Solution 1, using column index to identify Combobox
for i in range(table.columnCount()):
combobox = QComboBox()
combobox.addItems(["Option 1", "Option 2", "Option 3"])
'''
This connects currentIndexChanged signal with lambda function (selected_index, column),
calling index_changed_id_by_column slot
'''
combobox.currentIndexChanged.connect(
lambda selected_index, column=i: index_changed_id_by_column(column, selected_index))
table.setCellWidget(0, i, combobox)
# Solution 2, using objectName to identify Combobox
for i in range(table.columnCount()):
combobox = QComboBox()
combobox.setObjectName(f"combobox_row1_coll{i}")
combobox.addItems(["Option 1", "Option 2", "Option 3"])
'''
This connects currentIndexChanged signal with lambda function (selected_index, combobox),
calling index_changed_id_by_combobox slot
'''
combobox.currentIndexChanged.connect(
lambda selected_index, combobox=combobox: index_changed_id_by_combobox(combobox, selected_index))
table.setCellWidget(1, i, combobox)
table.show()
app.exec()
In first row we have Comboboxes calling index_changed_id_by_column callback.
In second row Comboboxes calling index_changed_id_by_combobox callback.
Pick the one that You need for Your project.

"Python has stopped working" error when instantiating subclass of QStyledItemDelegate twice

I have the following code to align text of QTableWidget's column.
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, QStyledItemDelegate
class AlignRightDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super(AlignRightDelegate, self).initStyleOption(option, index)
option.displayAlignment = Qt.AlignRight
class Table(QTableWidget):
def __init__(self, data, alignColumns = 1, *args):
QTableWidget.__init__(self, *args)
self.setRowCount(len(data))
self.setColumnCount(3)
self.setData(data)
self.resizeColumnsToContents()
for colIndex in range(1, 1 + alignColumns):
print("Align Column [", colIndex, "]", sep="")
self.setItemDelegateForColumn(colIndex, AlignRightDelegate())
def setData(self, data):
horizontalHeaders = []
for m, rowContent in enumerate(data):
for n, cellContent in enumerate(data[m]):
if (m == 0):
horizontalHeaders.append(cellContent)
else:
self.setItem(m - 1, n, QTableWidgetItem(cellContent))
self.setHorizontalHeaderLabels(horizontalHeaders)
if __name__ == "__main__":
data = [["col1", "col2", "col3"],
[ "1", "1", "a"],
[ "-1", "2", "b"],
[ "0", "3", "c"]]
print("python QTableWidgetAlignRight.py[ <AlignColumnCount=1]")
app = QApplication(sys.argv)
table = Table(data, int(sys.argv[1])) if (len(sys.argv) > 1) else Table(data)
table.show()
sys.exit(app.exec_())
If I execute the above code with python QTableWidgetAlignRight.py 1 it sort of works as it should as shown below with col2 aligned to the right but apparently to the top also:
However when I execute the same code with python QTableWidgetAlignRight.py 2 where I try to align 2 columns to the right, I ran into Python has stopped working error. The following screenshot is actually on my Japanese Windows OS (Win 10 Pro 20H2 (OS Build 19402.1165))
However I searched the net for the same error message in English and I found a screenshot albeit not due to my code above (burrowed from this page: Python has stopped working).
So what is the correct way of aligning 2 columns of my QTableWidget (without an error and without aligning to the top vertically)? Also is this a bug in PyQt5?
For your information, I am using the following: Python 3.7.6, conda 4.8.2, pyqt 5.9.2 py37h6538335_2. For column alignment, I looked at this for reference: How to align all items in a column to center in QTableWidget
Due to the way python and PyQt keep references to objects, only one unparented and unreferenced delegate can theoretically be set for each view.
But, while it is possible to set one delegate like this, it should not be done: delegates should always have a persistent reference or valid parent since the view does not take ownership of the delegate (see the docs for all setItemDelegate* functions).
Also, considering that you are using the same delegate for multiple columns, there's no point in creating more than one.
self.alignDelegate = AlignRightDelegate(self)
for colIndex in range(1, 1 + alignColumns):
print("Align Column [", colIndex, "]", sep="")
self.setItemDelegateForColumn(colIndex, self.alignDelegate)
Note that you could either use the parent argument (self) or create an instance attribute:
self.alignDelegate = AlignRightDelegate()
# or
alignDelegate = AlignRightDelegate(self)
Using both, though, is not an issue, and specifying the parent is a good choice anyway when dealing with Qt objects.
In any case, at least one of the methods above should always be used.
You get a wrong alignment because you only set the horizontal alignment: use option.displayAlignment = Qt.AlignRight | Qt.AlignVCenter.

Inserting row in QTableView

I have a PostgreSQL parent table called 'rooms' and a related table called 'configurations'. I have a form which would allow the user to add edit and add new configurations to individual rooms in a QTableView. In the configuration table, room is a foreign key to the rooms table. The form looks like this:
As far as editing the configurations, there seems to be no issue. The problem arises when I add a new record in the configurations model. If I insert a row and do not set the room cell, the user can edit any of the fields and it will save to the database. Although, when finished editing, the row clears and ! shows up in the row header. If I reload the form, the record edited correctly.
What I want to do, however, is to set the room number for the user and keep the room number field in the configuration view hidden from the user. When I add a row and set the room number cell's value, I cannot edit the row. Any ideas?
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtSql import *
from rooms import *
from config import *
class Setup_rooms(QDialog):
def __init__(self):
QDialog.__init__(self)
self.ui = Ui_rooms_setup()
self.ui.setupUi(self)
self.rooms_model = QSqlRelationalTableModel(self)
self.rooms_model.setTable('rooms')
self.rooms_model.setRelation(4, QSqlRelation('room_types1', 'room_type', 'room_type'))
self.rooms_model.setSort(int(self.rooms_model.fieldIndex("room")), Qt.AscendingOrder)
self.rooms_model.select()
self.rooms_mapper = QDataWidgetMapper(self)
self.rooms_mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)
self.rooms_mapper.setModel(self.rooms_model)
self.rooms_mapper.setItemDelegate(QSqlRelationalDelegate(self))
self.rooms_mapper.addMapping(self.ui.room_num, self.rooms_model.fieldIndex("room"))
self.rooms_mapper.addMapping(self.ui.room_description, self.rooms_model.fieldIndex("description"))
self.rooms_mapper.addMapping(self.ui.deep_clean_date, self.rooms_model.fieldIndex("deepcleandate"))
self.rooms_mapper.addMapping(self.ui.deep_clean_cycle, self.rooms_model.fieldIndex("deepcleancycle"))
room_types_model = self.rooms_model.relationModel(4)
self.ui.room_type.setModel(room_types_model)
self.ui.room_type.setModelColumn(room_types_model.fieldIndex('room_type'))
self.rooms_mapper.addMapping(self.ui.room_type, self.rooms_model.fieldIndex('room_type'))
self.rooms_mapper.toFirst()
self.ui.current_index.setText(str(self.rooms_mapper.currentIndex()))
self.config_model = QSqlRelationalTableModel(self)
self.config_model.setTable('room_configurations')
self.config_model.setRelation(self.config_model.fieldIndex("configuration"), QSqlRelation('configurations', 'configuration', 'configuration'))
self.room_model = self.config_model.relationModel(self.config_model.fieldIndex('room'))
self.config_view = self.ui.configurations
self.config_view.setModel(self.config_model)
self.config_view.horizontalHeader().setStretchLastSection(True)
self.config_model.select()
self.config_model.setHeaderData(self.config_model.fieldIndex("sort_order"), Qt.Horizontal, "sort order")
self.config_view.setItemDelegate(View_room_config(self))
self.config_view.setItemDelegate(QSqlRelationalDelegate(self.config_view))
self.config_view.resizeColumnsToContents()
room = self.rooms_model.record(self.rooms_mapper.currentIndex()).value('room')
self.config_model.setFilter("room = '{0}'".format(room))
#self.config_view.setColumnHidden(0, True)
#self.config_view.setColumnHidden(2, True)
self.ui.add_config.clicked.connect(self.add_config)
def add_config(self):
row=int(self.config_model.rowCount())
self.config_model.insertRow(row)
self.index = QModelIndex(self.config_model.index(row, 2))
self.config_model.setData(self.index, self.ui.room_num.text(), Qt.EditRole)
class View_room_config(QSqlRelationalDelegate):
def __init__(self, parent=None):
QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column() == 3:
combo = super().createEditor(parent, option, index)
if __name__=="__main__":
app=QApplication(sys.argv)
db = QSqlDatabase.addDatabase("QPSQL")
db.setHostName(host)
db.setDatabaseName(database)
db.setUserName(user)
db.setPassword(password)
if (db.open()==False):
QMessageBox.critical(None, "Database Error", db.lastError().text())
myapp = Setup_rooms()
myapp.show()
sys.exit(app.exec_())
I kind of sorted my problem. I restored a back up of the database into the development area. I am not aware of any underlying tables which would have caused an issue but my theory is that a database error was occurring. I am now testing for errors on self.config_model using self.config_model.lastError().text() but I haven't found any.
Nonetheless, without changing my code the program is kind of working although the user interface when editing is rather clunky. I will now look into that.

QTableWidget Integer

I am trying to insert and display integers in my QTableWidget. They don't display. It works if I convert everything to strings, but then I can't sort columns numerically--only lexically (1, 10, 100, etc.). This is using PyQt.
I've tried some of the suggested solutions, using QTableWidgetItem.setData(someRole,intValue), bu then nothing at all displays. I've tried, Qt.UserRole, DisplayRole and Edit Role. (I don't understand why these Roles are needed to display integers, but have just followed the examples). My specific code is:
item = QTableWidgetItem()
item.setData = (Qt.DisplayRole,intValue)
myTable.setItem(row, column, item)
The following code works, but for strings only:
item = QTableWidgetItem(str(intValue))
myTable.setItem(row, column, item)
Also, the suggestions for reading the data back, only show the object location, not the actual data. Example, using Eric as an interpreter shell:
item.data(Qt.DisplayRole)
Response: PyQt4.QtCore.QVariant object at 0x1f01fa60
or this:
item.data(Qt.EditRole).data()
Response: sip.voidptr object at 0x1e904a80
Any insight is appreciated.
You were on the right track. Your code doesn't work because you're not calling the QTableWidgetItem's setData() function but trying to assign it a value. You have
item.setData = (Qt.DisplayRole,intValue)
instead of
item.setData(Qt.DisplayRole,intValue)
Also, when reading the data back it's not just the location that's shown but the data itself as a QVariant. You should find that item.data(Qt.DisplayRole).toString() will return your data back as a string by converting the QVariant (via its .toString() method).
Here's a quick working example just to demonstrate:
import sys
from PyQt4.QtGui import QApplication, QWidget, QTableWidget, QTableWidgetItem, QVBoxLayout
from PyQt4.QtCore import Qt
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.widget_layout = QVBoxLayout()
self.table_widget = QTableWidget(101, 1)
self.table_widget.setSortingEnabled(True)
self.widget_layout.addWidget(self.table_widget)
self.setLayout(self.widget_layout)
for num in xrange(101):
item = QTableWidgetItem()
item.setData(Qt.EditRole, num)
self.table_widget.setItem(num, 0, item)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())

How to sort items in Qt QListview using Qt.UserRole

I'm having some problem sorting the items in my QListView using values in a field I specified.
Basically what I'm trying to do is this:
Detect faces in a collection of photos and display them in a QListView
Cluster the faces (images)
Update the view by placing items in the list (which are face images) belonging to the same cluster in together. Concretely, if item 1, 3, 5 are in one cluster and items 2, 4, 6 are in another, then items 1, 3, 5 should be displayed (in whatever permutations) before any of items 2, 4, 6 are displayed or vice versa.
The way I went about doing this is to set one of the UserRole field for each QStandardItem in my list to the cluster label and then try to get the QStandardModel to sort according to this UserRole. This would then display items in the same cluster (i.e. with the same cluster label in the UserRole) next to each other.
I'm able to set the UserRole successfully for the items but calling the sort function on the QStandardModel did not sort the items even though when I set the sort role to be the default DisplayRole (i.e. sort according to the text label of each face) it worked as intended.
Can anyone tell me what is wrong with my code or offer an alternative method? I've googled sorting list and I found the following link on QSortFilterProxyModel but as I'm quite new to Qt, I'm not able to adapt it to my situation.
Thanks in advance to any replies.
Here is the relevant code:
import os
from PySide.QtGui import QListView, QStandardItemModel, QStandardItem, QIcon
from PySide.QtCore import Qt
class FacesView(QListView):
"""
View to display detected faces for user to see and label.
"""
UNCLUSTERED_LABEL = -1
CLUSTER_ROLE = Qt.UserRole + 1
def __init__(self, *args):
super(FacesView, self).__init__(*args)
self._dataModel = QStandardItemModel()
self.setModel(self._dataModel)
# Layout items in batches instead of waiting for all items to be
# loaded before user is allowed to interact with them.
self.setLayoutMode(QListView.Batched)
def updateFaceClusters(self, labels):
"""Update the cluster label for each face.
#param labels: [1 x N] array where each element is an integer
for the cluster the face belongs to."""
assert(len(labels) == self._dataModel.rowCount())
# Put the cluster label each item/face belong to in the
# CLUSTER_ROLE field.
for i in xrange(self._dataModel.rowCount()):
index = self._dataModel.index(i, 0)
self._dataModel.setData(index, labels[i], self.CLUSTER_ROLE)
# Use cluster label as sort role
self._dataModel.setSortRole(self.CLUSTER_ROLE)
# This does NOT seem to sort the items even though it works fine
# when sort role is the default Qt.DisplayRole.
self._dataModel.sort(0)
print("Finished updating face clusters")
def itemsInList(self):
"""Returns the label for a face and the path to its image.
#return: (label, path)"""
items = []
for i in xrange(self._dataModel.rowCount()):
label = self._dataModel.index(i, 0).data(Qt.DisplayRole)
imagePath = self._dataModel.index(i, 0).data(Qt.UserRole)
clusterLabel = self._dataModel.index(i, 0).data(self.CLUSTER_ROLE)
items.append((imagePath, label, clusterLabel))
return items
def addItem(self, label, imagePath):
"""Add an item to list view
#param label: The label associated with the item.
#param imagePath: Path to image for the icon."""
if os.path.exists(imagePath):
icon = QIcon(imagePath)
else:
icon = QIcon(':/res/Unknown-person.gif')
item = QStandardItem(icon, label)
item.setEditable(True)
# Add image path to the UserRole field.
item.setData(imagePath, Qt.UserRole)
# Add cluster label to image. CLUSTER_ROLE is where I intend
# to put the item's cluster label.
item.setData(self.UNCLUSTERED_LABEL, self.CLUSTER_ROLE)
# Prevent an item from dropping into another item.
item.setDropEnabled(False)
# Add item to list indirectly by adding it to the model.
self._dataModel.appendRow(item)
def clear(self):
self._dataModel.clear()
There's nothing wrong with the code you posted. So there must be something wrong with how you are using it. How are you generating the cluster labels?
Here's a test script using your FacesView class that sorts as you intended:
from random import randint
from PySide.QtGui import QWidget, QPushButton, QVBoxLayout, QApplication
from facesview import FacesView
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.list = FacesView(self)
self.button = QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.list)
layout.addWidget(self.button)
def handleButton(self):
labels = []
self.list.model().setRowCount(0)
for row in range(10):
labels.append(randint(0, 3))
text = 'Item(%d) - Cluster(%d)' % (row, labels[-1])
self.list.addItem(text, 'icon.png')
self.list.updateFaceClusters(labels)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Categories