In the PyQt creator I made a 9*9 Table looking like this:
In that table I want to make every third line separating the rows and every third line separating columns and the the border lines bold. Is that possible to do in PyQt?
If yes how?
Use a delegate for that:
class Delegate(QStyledItemDelegate):
def paint(self, painter, option, index):
super().paint(painter, option, index)
if ((1+index.row()) % 3 == 0): # Every third row
painter.setPen(QPen(Qt.red, 3))
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
if ((1+index.column()) % 3 == 0): # Every third column
painter.setPen(QPen(Qt.red, 3))
painter.drawLine(option.rect.topRight(), option.rect.bottomRight())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
v = QTableView()
model = QStandardItemModel(9, 9)
v.setModel(model)
v.show()
v.setItemDelegate(Delegate(v))
sys.exit(app.exec_())
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_())
How do I make a pixmap's alignment centered to its column in a QTreeView? I have 2 columns with icons that are aligned to left, but I want one of them centered, so this needs to work on a single column and not force the whole table to one alignment.
I'm using a QTreeView with QAbstractItemModel as its model. On one column I flagged it as QtCore.Qt.DecorationRole and return a pixmap in the model's data() method so that it displays images along that column.
All works well, except that the images all align left, and for the life of me I can't get them centered horizontally.
In the data() method, I tried returning QtCore.Qt.AlignCenter if the role was QtCore.Qt.TextAlignmentRole, but that seems to only effect text (duh!).
Is there another way to achieve this? I'm not interested in taking the route of delegates if possible.
A possible solution is to overwrite the delegate's initStyleOption() method:
from PySide2 import QtCore, QtGui, QtWidgets
class IconCenterDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(IconCenterDelegate, self).initStyleOption(option, index)
option.decorationAlignment = (
QtCore.Qt.AlignHCenter | QtCore.Qt.AlignCenter
)
option.decorationPosition = QtWidgets.QStyleOptionViewItem.Top
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QTreeView()
model = QtGui.QStandardItemModel(w)
w.setModel(model)
delegate = IconCenterDelegate(w)
w.setItemDelegateForColumn(1, delegate)
icons = [
"SP_TitleBarMinButton",
"SP_TitleBarMenuButton",
"SP_TitleBarMaxButton",
"SP_TitleBarCloseButton",
"SP_TitleBarNormalButton",
"SP_TitleBarShadeButton",
"SP_TitleBarUnshadeButton",
"SP_TitleBarContextHelpButton",
"SP_MessageBoxInformation",
"SP_MessageBoxWarning",
"SP_MessageBoxCritical",
"SP_MessageBoxQuestion",
"SP_DesktopIcon",
]
parent = model.invisibleRootItem()
for icon_name in icons:
icon = QtWidgets.QApplication.style().standardIcon(
getattr(QtWidgets.QStyle, icon_name)
)
its = []
for _ in range(3):
it = QtGui.QStandardItem()
it.setIcon(icon)
its.append(it)
parent.appendRow(its)
model.appendRow(it)
w.resize(640, 480)
w.expandAll()
w.show()
sys.exit(app.exec_())
If you want the icons of all the columns to be aligned centrally then you could overwrite the viewOptions() method of the view:
class TreeView(QtWidgets.QTreeView):
def viewOptions(self):
option = super().viewOptions()
option.decorationAlignment = (
QtCore.Qt.AlignHCenter | QtCore.Qt.AlignCenter
)
option.decorationPosition = QtWidgets.QStyleOptionViewItem.Top
return option
I have a QTableView in my GUI in which I want to have some table cells in which I can insert line breaks using something like \n or <br>.
So far I have tried to set an QLabel as the IndexWidget:
l = QLabel(val[2])
self.setRowHeight(i, int(l.height() / 8))
l.setAutoFillBackground(True)
self.setIndexWidget(QAbstractItemModel.createIndex(self.results_model, i, 2), l)
The Problem with this approach is the code is not very clean and cannot just be done in the AbstractTableModel without this code to replace the cell with a widget. The second problem is, that when selecting a row with a widget in it the blue highlighting doesn't apply to the cell. Another problem is that the resizeRowsToContents() method doesn't take the height of this widget in to consideration.
Any Ideas would be very appreciated, thanks!
One way to implement this task is to use an HtmlDelegate, in this case the line break will be given by <br>:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class HTMLDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
opt = QStyleOptionViewItem(option)
self.initStyleOption(opt, index)
painter.save()
doc = QTextDocument()
doc.setHtml(opt.text)
opt.text = "";
style = opt.widget.style() if opt.widget else QApplication.style()
style.drawControl(QStyle.CE_ItemViewItem, opt, painter)
painter.translate(opt.rect.left(), opt.rect.top())
clip = QRectF(0, 0, opt.rect.width(), opt.rect.height())
doc.drawContents(painter, clip)
painter.restore()
def sizeHint(self, option, index ):
opt = QStyleOptionViewItem(option)
self.initStyleOption(opt, index)
doc = QTextDocument()
doc.setHtml(opt.text);
doc.setTextWidth(opt.rect.width())
return QSize(doc.idealWidth(), doc.size().height())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QTableView()
model = QStandardItemModel(4, 6)
delegate = HTMLDelegate()
w.setItemDelegate(delegate)
w.setModel(model)
w.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
w.show()
sys.exit(app.exec_())
what I want to do is to change the color of a QTableWidget item, when I hover with the mouse over the item of my QTableWidget.
Firstly, the table widget needs to have mouse-tracking switched on to get the hover events.
Secondly, we need to find some signals that tell us when the mouse enters and leaves the table cells, so that the background colours can be changed at the right times.
The QTableWidget class has the cellEntered / itemEntered signals, but there is nothing for when the mouse leaves a cell. So, we will need to create some custom signals to do that.
The TableWidget class in the demo script below sets up the necessary cellExited / itemExited signals, and then shows how everything can be hooked up to change the item background when hovering with the mouse:
from PyQt4 import QtGui, QtCore
class TableWidget(QtGui.QTableWidget):
cellExited = QtCore.pyqtSignal(int, int)
itemExited = QtCore.pyqtSignal(QtGui.QTableWidgetItem)
def __init__(self, rows, columns, parent=None):
QtGui.QTableWidget.__init__(self, rows, columns, parent)
self._last_index = QtCore.QPersistentModelIndex()
self.viewport().installEventFilter(self)
def eventFilter(self, widget, event):
if widget is self.viewport():
index = self._last_index
if event.type() == QtCore.QEvent.MouseMove:
index = self.indexAt(event.pos())
elif event.type() == QtCore.QEvent.Leave:
index = QtCore.QModelIndex()
if index != self._last_index:
row = self._last_index.row()
column = self._last_index.column()
item = self.item(row, column)
if item is not None:
self.itemExited.emit(item)
self.cellExited.emit(row, column)
self._last_index = QtCore.QPersistentModelIndex(index)
return QtGui.QTableWidget.eventFilter(self, widget, event)
class Window(QtGui.QWidget):
def __init__(self, rows, columns):
QtGui.QWidget.__init__(self)
self.table = TableWidget(rows, columns, self)
for column in range(columns):
for row in range(rows):
item = QtGui.QTableWidgetItem('Text%d' % row)
self.table.setItem(row, column, item)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
self.table.setMouseTracking(True)
self.table.itemEntered.connect(self.handleItemEntered)
self.table.itemExited.connect(self.handleItemExited)
def handleItemEntered(self, item):
item.setBackground(QtGui.QColor('moccasin'))
def handleItemExited(self, item):
item.setBackground(QtGui.QTableWidgetItem().background())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window(6, 3)
window.setGeometry(500, 300, 350, 250)
window.show()
sys.exit(app.exec_())
You can achieve your goal pretty easily using the proper signals as proved by the following simple code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class TableViewer(QMainWindow):
def __init__(self, parent=None):
super(TableViewer, self).__init__(parent)
self.table = QTableWidget(3, 3)
for row in range (0,3):
for column in range(0,3):
item = QTableWidgetItem("This is cell {} {}".format(row+1, column+1))
self.table.setItem(row, column, item)
self.setCentralWidget(self.table)
self.table.setMouseTracking(True)
self.current_hover = [0, 0]
self.table.cellEntered.connect(self.cellHover)
def cellHover(self, row, column):
item = self.table.item(row, column)
old_item = self.table.item(self.current_hover[0], self.current_hover[1])
if self.current_hover != [row,column]:
old_item.setBackground(QBrush(QColor('white')))
item.setBackground(QBrush(QColor('yellow')))
self.current_hover = [row, column]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
tv = TableViewer()
tv.show()
sys.exit(app.exec_())
You may be interested in other signals too, especially itemEntered. However, if you want total control over the editing and display of items then using delegates (via the QTableWidget.setItemDelegate method) is strongly recommended.
UPDATE:
sorry, I had forgotten the second part of the problem i.e. what happens when the mouse exits a cell. Even then the problem can be solved easily without using events. See the updated code, please.
There are no events based on QTableWidgetItem, but you can do this:
reimplement the mouseMoveEvent() of QTableWidget, you can get the mouse position;
use itemAt() method to get the item under your mouse cursor;
customize your item;
This may simalute what you want.
I know this is old but wanted to update a couple of parts to it as I came across this page looking for a similar solution. This has a couple of parts to it, one is similar to the above but avoids the NoneType error if the cell is empty. Additionally, it will change the color of the highlighted cell, but also update a tooltip for the cell to display the contents of the cell in a tooltip. Nice if you have cells with runoffs 123...
Sure it could be cleaned up a bit, but works for PyQt5. Cheers!
def cellHover(self, row, column):
item = self.My_Table1.item(row, column)
old_item = self.My_Table1.item(self.current_hover[0], self.current_hover[1])
if item is not None:
if self.current_hover != [row,column]:
text = item.text()
if text is not None:
self.My_Table1.setToolTip(text)
item.setBackground(QBrush(QColor('#bbd9f7')))
old_item.setBackground(QBrush(QColor('white')))
self.current_hover = [row, column]