I'm displaying some information from a sql server in a qtableview using a sqlmodel.
I have set up a custom delegate to deal with the editing of the data.
I would like to display my dates in a specific format, when they table is first loaded the dates are displayed as such:
20011-04-30
But when I edit the date and click off the cell to accept the date is then displayed like:
30/04/2011
Which is how its stored in the database and how I would like it to be displayed to start with, I have no idea why it changes format once its been edited.
I'm guessing I have to reimplement the paint method for that column I have done something similar with a progress bar but I have no idea how to do it for text.
Here is my delegate as it stands, note is has some editors set up for other columns but my main question only relates to how to display the date correctly.
import sys
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtSql import *
PROJECTID, PROJECTTITLE, CLIENTID, DEADLINE, PROGRESS, ROOT = range(6)
class projectsDelegate(QSqlRelationalDelegate):
def __ini__(self, parent = None):
super(projectsDelegate, self).__init__(parent)
def createEditor(self, parent, option, index):
if index.column() == DEADLINE:
editor = QDateEdit(parent)
#editor.setDisplayFormat("yyyy/MM/dd")
editor.setMinimumDate(QDate.currentDate())
editor.setCalendarPopup(True)
return editor
elif index.column() == PROGRESS:
editor = QSpinBox(parent)
editor.setRange(0, 100)
editor.setSingleStep(5)
editor.setSuffix("%")
return editor
elif index.column() == ROOT:
editor = QFileDialog(parent)
editor.setFileMode(QFileDialog.Directory)
editor.setOptions(QFileDialog.ShowDirsOnly)
editor.setFixedSize(400, 400)
editor.setWindowTitle("Select A Root Folder For The Project")
return editor
else:
return QSqlRelationalDelegate.createEditor(self, parent, option, index)
def setEditorData(self, editor, index):
if index.column() == DEADLINE:
text = index.model().data(index, Qt.DisplayRole).toDate()
editor.setDate(text)
elif index.column() == PROGRESS:
prog = index.model().data(index, Qt.DisplayRole).toInt()[0]
editor.setValue(prog)
elif index.column() == ROOT:
root = index.model().data(index, Qt.DisplayRole).toString()
editor.setDirectory(os.path.dirname(str(root)))
screen = QDesktopWidget().screenGeometry()
size = editor.geometry()
editor.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2)
else:
QSqlRelationalDelegate.setEditorData(self, editor, index)
def setModelData(self, editor, model, index):
if index.column() == DEADLINE:
model.setData(index, QVariant(editor.date()))
elif index.column() == PROGRESS:
model.setData(index, QVariant(editor.value()))
elif index.column() == ROOT:
model.setData(index, QVariant(editor.directory().absolutePath()))
else:
QSqlRelationalDelegate.setModelData(self, editor, model, index)
def paint(self, painter, option, index):
if index.column() == PROGRESS:
bar = QStyleOptionProgressBarV2()
bar.rect = option.rect
bar.minimum = 0
bar.maximum = 100
bar.textVisible = True
percent = index.model().data(index, Qt.DisplayRole).toInt()[0]
bar.progress = percent
bar.text = QString("%d%%" % percent)
QApplication.style().drawControl(QStyle.CE_ProgressBar, bar, painter)
else:
QSqlRelationalDelegate.paint(self, painter, option, index)
def sizeHint(self, options, index):
if index.column() == PROGRESS:
return QSize(150, 30)
elif index.column() == ROOT:
return QSize(400, 800)
else:
return QSqlRelationalDelegate.sizeHint(self, options, index)
Thanks, Tom.
Why is editor.setDisplayFormat("yyyy/MM/dd") commented out? Shouldn't that take care of the formatting?
Related
I'm developping an application that allows to read and edit json files on pyqt5; the
aplication lets the user make changes to the data, by default it lets the user edit by
hand the fields, however I prefer them to select the information from the a set of options to avoid wrong editions.
To achieve this I am creating an multiple delegates, as an exaple in the code below Delegate_1 and delgate_2 that inherit from QStyledItemDelegate and rewrites the createEditor method. Searching in internet I found three methods from the class QTreeView that can be used to apply the delegates to different situations: setItemDelegateForColumn, setItemDelegateForRow and setItemDelegate, the fist two are works for full columns or rows and the third works for the whole tree, however my intention is to use the delegate_1 for cell with the index (0, 1), and the delegate_2 for the index (1, 1).
is there a method of QTreeView or a way to achieve that ?
import sys
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
class Delegate_1(QStyledItemDelegate):
def createEditor(self, parent, option, index):
combo = QComboBox()
combo.addItem('BINARY')
combo.addItem('ASCII')
return combo
def setModelData(self, editor, model, index):
txt = editor.currentText()
model.setData(index, txt)
class Delegate_2(QStyledItemDelegate):
def createEditor(self, parent, option, index):
combo = QComboBox()
combo.addItem('True')
combo.addItem('False')
return combo
def setModelData(self, editor, model, index):
txt = editor.currentText()
model.setData(index, txt)
if __name__ == '__main__':
app = QApplication(sys.argv)
model = QStandardItemModel(2, 2)
it = QStandardItem('File_mode')
model.setItem(0, 0, it)
it = QStandardItem('ASCII') # apply delegate_1 to the cell
model.setItem(0, 1, it)
it = QStandardItem('Opened')
model.setItem(1, 0, it)
it = QStandardItem('True') # apply delegate_2 to the cell
model.setItem(1, 1, it)
t = QTreeView() # <- it's json data
t.setModel(model)
t.setItemDelegateForColumn(1, Delegate_1()) # <- column 1
#t.setItemDelegate(Delegate()) # <- all cells
t.show()
sys.exit(app.exec_())
Thanks for your help
In the createEditor method, you can call the index.row() and index.column() methods, and depending on the row and column values, create the necessary editor.
def createEditor(self, parent, option, index):
if index.row() == 0 and index.column() == 1:
combo = QComboBox()
combo.addItem('BINARY')
combo.addItem('ASCII')
return combo
elif index.row() == 1 and index.column() == 1:
combo = QComboBox()
combo.addItem('True')
combo.addItem('False')
return combo
I want to make some data hidden in QTableWidget until a specific cell is clicked. Right now I only manage to display "*" instead of actual value. I think I could connect somehow with action when the specific cell is clicked and replaced the value of the clicked cell. I also know that there is a function setData() that can be invoked on QTableWidgetItem and give me wanted behavior. But I cannot find any useful example for Python implementation of qt. What is the best solution to this problem?
def setTableValues(self):
self.table.setRowCount(len(self.tableList))
x = 0
for acc in self.tableList:
y = 0
for val in acc.getValuesAsList():
if isinstance(val,Cipher):
val = "*"*len(val.getDecrypted())
item = QTableWidgetItem(val)
self.table.setItem(x,y,item)
y += 1
x += 1
self.table.resizeRowsToContents()
self.table.resizeColumnsToContents()
You can associate a flag with a role that indicates the visibility of the text and then use a delegate to hide the text. That flag will change when the items are pressed:
from PyQt5 import QtCore, QtGui, QtWidgets
VisibilityRole = QtCore.Qt.UserRole + 1000
class VisibilityDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
if not index.data(VisibilityRole):
option.text = "*" * len(option.text)
class TableWidget(QtWidgets.QTableWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
delegate = VisibilityDelegate(self)
self.setItemDelegate(delegate)
self.visibility_index = QtCore.QModelIndex()
self.pressed.connect(self.on_pressed)
#QtCore.pyqtSlot(QtCore.QModelIndex)
def on_pressed(self, index):
if self.visibility_index.isValid():
self.model().setData(self.visibility_index, False, VisibilityRole)
self.visibility_index = index
self.model().setData(self.visibility_index, True, VisibilityRole)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = TableWidget(10, 4)
for i in range(w.rowCount()):
for j in range(w.columnCount()):
it = QtWidgets.QTableWidgetItem("{}-{}".format(i, j))
w.setItem(i, j, it)
w.show()
w.resize(640, 480)
sys.exit(app.exec_())
this is the code I use to fill a table drawn in QT Designer.
Designed to be universal for any table, it works fine, but...
When I try to show a datasat containing 18 columns and ~12000 rows, it just freezes for 30 seconds or more.
So, what I am doing wrong and is there way to speed up, keeping the code still suitable for any table?
That's my code:
...blablabla...
self.connect(self, SIGNAL("set"), self.real_set)
...blablabla...
def set_table(self, table, data):
self.emit(SIGNAL('set'), table, data)
def real_set(self, table, data):
"""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Assuming data is list of dict and table is a QTableWidget.
Get first key and get len of contents
"""
for key in data:
rows = len(data[key])
table.setRowCount(rows)
break
"""
Forbid resizing(speeds up)
"""
table.horizontalHeader().setResizeMode(QHeaderView.Fixed)
table.verticalHeader().setResizeMode(QHeaderView.Fixed)
table.horizontalHeader().setStretchLastSection(False)
table.verticalHeader().setStretchLastSection(False)
"""
Set number of columns too
"""
table.setColumnCount(len(data))
table.setHorizontalHeaderLabels(sorted(data.keys()))
"""
Now fill data
"""
for n, key in enumerate(sorted(data.keys())):
for m, item in enumerate(data[key]):
newitem = QTableWidgetItem(item)
table.setItem(m, n, newitem)
Here a test script which compares a few ways of populating a table.
The custom model is much faster, because it does not have to create all the items up front - but note that it is a very basic implementation, so does not implement sorting, editing, etc. (See Model/View Programming for more details).
from random import shuffle
from PyQt4 import QtCore, QtGui
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None):
super(TableModel, self).__init__(parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data)
def columnCount(self, parent=None):
return len(self._data[0]) if self.rowCount() else 0
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = index.row()
if 0 <= row < self.rowCount():
column = index.column()
if 0 <= column < self.columnCount():
return self._data[row][column]
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.table = QtGui.QTableView(self)
self.tablewidget = QtGui.QTableWidget(self)
self.tablewidget.setSortingEnabled(True)
self.button1 = QtGui.QPushButton('Custom Model', self)
self.button1.clicked.connect(
lambda: self.populateTable('custom'))
self.button2 = QtGui.QPushButton('StandardItem Model', self)
self.button2.clicked.connect(
lambda: self.populateTable('standard'))
self.button3 = QtGui.QPushButton('TableWidget', self)
self.button3.clicked.connect(
lambda: self.populateTable('widget'))
self.spinbox = QtGui.QSpinBox(self)
self.spinbox.setRange(15000, 1000000)
self.spinbox.setSingleStep(10000)
layout = QtGui.QGridLayout(self)
layout.addWidget(self.table, 0, 0, 1, 4)
layout.addWidget(self.tablewidget, 1, 0, 1, 4)
layout.addWidget(self.button1, 2, 0)
layout.addWidget(self.button2, 2, 1)
layout.addWidget(self.button3, 2, 2)
layout.addWidget(self.spinbox, 2, 3)
self._data = []
def populateTable(self, mode):
if mode == 'widget':
self.tablewidget.clear()
self.tablewidget.setRowCount(self.spinbox.value())
self.tablewidget.setColumnCount(20)
else:
model = self.table.model()
if model is not None:
self.table.setModel(None)
model.deleteLater()
if len(self._data) != self.spinbox.value():
del self._data[:]
rows = list(range(self.spinbox.value()))
shuffle(rows)
for row in rows:
items = []
for column in range(20):
items.append('(%d, %d)' % (row, column))
self._data.append(items)
timer = QtCore.QElapsedTimer()
timer.start()
if mode == 'widget':
self.tablewidget.setSortingEnabled(False)
for row, items in enumerate(self._data):
for column, text in enumerate(items):
item = QtGui.QTableWidgetItem(text)
self.tablewidget.setItem(row, column, item)
self.tablewidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
else:
self.table.setSortingEnabled(False)
if mode == 'custom':
model = TableModel(self._data, self.table)
elif mode == 'standard':
model = QtGui.QStandardItemModel(self.table)
for row in self._data:
items = []
for column in row:
items.append(QtGui.QStandardItem(column))
model.appendRow(items)
self.table.setModel(model)
self.table.setSortingEnabled(True)
self.table.sortByColumn(0, QtCore.Qt.AscendingOrder)
print('%s: %.3g seconds' % (mode, timer.elapsed() / 1000))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 1200, 800)
window.show()
sys.exit(app.exec_())
In GUI applications one comes across a situation where there is a need
to display a lot of items in a tabular or list format (for example
displaying large number of rows in a table). One way to increase the
GUI responsiveness is to load a few items when the screen is displayed
and defer loading of rest of the items based on user action. Qt
provides a solution to address this requirement of loading the data on
demand.
You can find the implementation of this technique called pagination in this link
I have a table, with 4 columns.
Two of this 4 columns are about features. One is Feature, another is subfeature.
in each column, there are comboboxes for all cells.
I can open txt in these cells.
I want to: when I choose cinema for feature, I want to see only name of films in subfeature comboboxes and no every subfeature that I have in my "data"... and when I choose Food in feature, I want to see only types of food in my subfeature comboboxes...
.. I dont know how to do it... there is a way to do it?
Here there is my def to put combobox in table and open the text file into these comboboxes:
def createEd(self, parent, option, index):
if index.column() == POLARITY:
combobox = QComboBox(parent)
combobox.addItems(sorted(index.model().TPolarities))
combobox.setEditable(True)
arquivo = codecs.open("ln2.txt",encoding='utf-8',mode="r")
conTWordsdo = arquivo.readlines()
lista =[]
for i in conTWordsdo:
lista.append(i.replace("\n",""))
combobox.addItems(sorted(lista))
return combobox
elif index.column() == FEATURE:
combobox = QComboBox(parent)
combobox.addItems(sorted(index.model().TFeatures))
combobox.setEditable(True)
arquivo = codecs.open("ln1.txt",encoding='utf-8',mode="r")
conTWordsdo = arquivo.readlines()
lista = []
for i in conTWordsdo:
lista.append(i.replace("\n",""))
combobox.addItems(sorted(lista))
return combobox
elif index.column() == SUBFEATURE:
combobox = QComboBox(parent)
combobox.addItems(sorted(index.model().TSubFeatures))
combobox.setEditable(True)
arquivo = codecs.open("ln3.txt",encoding='utf-8',mode="r")
conTWordsdo = arquivo.readlines()
lista = []
for i in conTWordsdo:
lista.append(i.replace("\n",""))
combobox.addItems(sorted(lista))
return combobox
elif index.column() == SENTENCE:
editor = QLineEdit(parent)
self.connect(editor, SIGNAL("returnPressed()"), self.commitAndCloseEditor)
return editor
else:
return QItemDelegate.createEditor(self, parent, option, index)
You'll be using the currentIndexChanged signal, something like this:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtGui, QtCore
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.items = dict(zip(
[ "Parent {0}".format(x)
for x in range(3)
],
[
[ "Child {0} - {1}".format(x, y)
for y in range(3)
]
for x in range(3)
]
))
self.comboBoxChild = QtGui.QComboBox(self)
self.comboBoxParent = QtGui.QComboBox(self)
self.comboBoxParent.addItems(self.items.keys())
self.comboBoxParent.currentIndexChanged[str].connect(self.on_comboBoxParent_currentIndexChanged)
self.comboBoxParent.setCurrentIndex(1)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.comboBoxParent)
self.layoutVertical.addWidget(self.comboBoxChild)
#QtCore.pyqtSlot(str)
def on_comboBoxParent_currentIndexChanged(self, index):
items = self.items[str(index)]
self.comboBoxChild.clear()
self.comboBoxChild.addItems(items)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
main.resize(222, 111)
sys.exit(app.exec_())
I can add a single tooltip to all headers using
tableview = QTableView()
tableview.horizontalHeader().setToolTip("headers")
but can I add different tooltips to each header, i.e. I need to access the QWidgets that contains the headers, e.g. (not working):
tableview.horizontalHeader().Item[0].setToolTip("header 0")
I'm pretty new to this stuff too, but I think you'll need to subclass QTableView and reimplement the headerData function. Here is a working example. Hopefully you can extract what you need from it:
from PyQt4 import QtGui, QtCore
import sys
class PaletteListModel(QtCore.QAbstractListModel):
def __init__(self, colors = [], parent = None):
QtCore.QAbstractListModel.__init__(self,parent)
self.__colors = colors
# required method for Model class
def rowCount(self, parent):
return len(self.__colors)
# optional method for Model class
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return QtCore.QString("Palette")
else:
return QtCore.QString("Color %1").arg(section)
if role == QtCore.Qt.ToolTipRole:
if orientation == QtCore.Qt.Horizontal:
return QtCore.QString("Horizontal Header %s Tooltip" % str(section))
else:
return QtCore.QString("Vertical Header %s Tooltip" % str(section))
# required method for Model class
def data(self, index, role):
# index contains a QIndexClass object. The object has the following
# methods: row(), column(), parent()
row = index.row()
value = self.__colors[row]
# keep the existing value in the edit box
if role == QtCore.Qt.EditRole:
return self.__colors[row].name()
# add a tooltip
if role == QtCore.Qt.ToolTipRole:
return "Hex code: " + value.name()
if role == QtCore.Qt.DecorationRole:
pixmap = QtGui.QPixmap(26,26)
pixmap.fill(value)
icon = QtGui.QIcon(pixmap)
return icon
if role == QtCore.Qt.DisplayRole:
return value.name()
def setData(self, index, value, role = QtCore.Qt.EditRole):
row = index.row()
if role == QtCore.Qt.EditRole:
color = QtGui.QColor(value)
if color.isValid():
self.__colors[row] = color
self.dataChanged.emit(index, index)
return True
return False
# implment flags() method
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
app.setStyle("plastique")
data = QtCore.QStringList()
data << "one" << "two" << "three" << "four" << "five"
tableView = QtGui.QTableView()
tableView.show()
red = QtGui.QColor(255,0,0)
green = QtGui.QColor(0,255,0)
blue = QtGui.QColor(0,0,255)
model = PaletteListModel([red, green, blue])
tableView.setModel(model)
sys.exit(app.exec_())
Here's what worked for me:
headerView = self._table.horizontalHeader()
for i in range(headerView.count()):
key = headerView.model().headerData(i, QtCore.Qt.Horizontal)
toolTip = myDictOfToolTips.get(key, None)
self._table.horizontalHeaderItem(i).setToolTip(toolTip)
QTableWidget (which inherits QTableView) has a method horizontalHeaderItem(int) which can be used to get the header items, so you maybe could swich to use that instead of QTableView?
If you use QTableView, you can set tooltip by QStandardItemModel:
QStandardItemModel myModel;
myModel.horizontalHeaderItem(1)->setToolTip("");