I really like a clean result of self-adjusting to the QTableView's width of the columns using:
self.view.horizontalHeader().setResizeMode(QHeaderView.Stretch)
But unfortunately with this flag used the columns width doesn't stay adjustable any longer (the user is not able to resize the columns width).
I wonder if there is an alternative way to set the columns width to the width of the QTableView and yet keeping the columns width "user-adjustable"?
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Item_A_001','Item_A_002','Item_B_001','Item_B_002']
self.totalColumn=10
def rowCount(self, parent=QModelIndex()):
return len(self.items)
def columnCount(self, parent=QModelIndex()):
return self.totalColumn
def setColumnCount(self, number):
self.totalColumn=number
self.reset()
def data(self, index, role):
if not index.isValid(): return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
row=index.row()
if row<len(self.items):
return QVariant(self.items[row])
else:
return QVariant()
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
tableModel=Model(self)
self.view=QTableView(self)
self.view.setModel(tableModel)
self.view.horizontalHeader().setResizeMode(QHeaderView.Stretch)
hideButton=QPushButton('Hide Column')
hideButton.clicked.connect(self.hideColumn)
unhideButton=QPushButton('Unhide Column')
unhideButton.clicked.connect(self.unhideColumn)
layout = QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(hideButton)
layout.addWidget(unhideButton)
self.setLayout(layout)
def hideColumn(self):
self.view.model().setColumnCount(1)
def unhideColumn(self):
self.view.model().setColumnCount(10)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
so here is a little trick I came up with. I modified the resize event of MyWindow to resize your QTableWidget.
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.tableModel=Model(self) #Set the model as part of your class to access it in the event handler
self.view=QTableView(self)
self.view.setModel(self.tableModel) #Modified here too
self.view.horizontalHeader().setResizeMode(QHeaderView.Interactive) #Mode set to Interactive to allow resizing
hideButton=QPushButton('Hide Column')
hideButton.clicked.connect(self.hideColumn)
unhideButton=QPushButton('Unhide Column')
unhideButton.clicked.connect(self.unhideColumn)
layout = QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(hideButton)
layout.addWidget(unhideButton)
self.setLayout(layout)
def hideColumn(self):
self.view.model().setColumnCount(1)
def unhideColumn(self):
self.view.model().setColumnCount(10)
#Added a reimplementation of the resize event
def resizeEvent(self, event):
tableSize = self.view.width() #Retrieves your QTableView width
sideHeaderWidth = self.view.verticalHeader().width() #Retrieves the left header width
tableSize -= sideHeaderWidth #Perform a substraction to only keep all the columns width
numberOfColumns = self.tableModel.columnCount() #Retrieves the number of columns
for columnNum in range( self.tableModel.columnCount()): #For each column
self.view.setColumnWidth(columnNum, int(tableSize/numberOfColumns) ) #Set the width = tableSize / nbColumns
super(MyWindow, self).resizeEvent(event) #Restores the original behaviour of the resize event
The only downfall is that the scrollbar is flicking. I think this can be solved with some adjustment.
Update:
Get a cleaner look when resizing, no more flicking, disabled the scrollbar.
Modified initialization of the QTableView:
self.view=QTableView(self)
self.view.setModel(self.tableModel)
self.view.horizontalHeader().setResizeMode(QHeaderView.Interactive)
#Added these two lines
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.view.horizontalHeader().setStretchLastSection(True)
Modified resizeEvent:
def resizeEvent(self, event):
super(MyWindow, self).resizeEvent(event)
tableSize = self.view.width()
sideHeaderWidth = self.view.verticalHeader().width()
tableSize -= sideHeaderWidth
numberOfColumns = self.tableModel.columnCount()
remainingWidth = tableSize % numberOfColumns
for columnNum in range( self.tableModel.columnCount()):
if remainingWidth > 0:
self.view.setColumnWidth(columnNum, int(tableSize/numberOfColumns) + 1 )
remainingWidth -= 1
else:
self.view.setColumnWidth(columnNum, int(tableSize/numberOfColumns) )
Related
I am new to Qt. Currently I am trying to learn how to update a table model from a different thread and then how to get an immediate display update for it. I read the documentation and found the dataChanged() and layoutChanged() signals. While dataChanged() works fine, any attempt to emit layoutChanged() fails with:
'QObject::connect: Cannot queue arguments of type 'QList<QPersistentModelIndex>' (Make sure 'QList<QPersistentModelIndex>' is registered using qRegisterMetaType().)
Searching for this particular error didn't give me anything that I could turn into working code. I am not using any QList or QPersistentModelIndex explicitly, but of course that can be implicitly used due to the constructs that I chose.
What am I doing wrong?
class TimedModel(QtCore.QAbstractTableModel):
def __init__(self, table, view):
super(TimedModel, self).__init__()
self.table = table
self.view = view
self.setHeaderData(0, Qt.Horizontal, Qt.AlignLeft, Qt.TextAlignmentRole)
self.rows = 6
self.columns = 4
self.step = 5
self.timer = Thread(
name = "Timer",
target = self.tableTimer,
daemon = True)
self.timer.start()
self.random = Random()
self.updated = set()
#staticmethod
def encode(row, column):
return row << 32 | column
def data(self, index, role):
if role == Qt.DisplayRole or role == Qt.EditRole:
return f'Data-{index.row()}-{index.column()}'
if role == Qt.ForegroundRole:
encoded = TimedModel.encode(index.row(), index.column())
return QBrush(Qt.red if encoded in self.updated else Qt.black)
return None
def rowCount(self, index):
return self.rows
def columnCount(self, index):
return self.columns
def headerData(self, col, orientation, role):
if orientation == Qt.Vertical:
# Vertical
return super().headerData(col, orientation, role)
# Horizontal
if not 0 <= col < self.columns:
return None
if role == Qt.DisplayRole:
return f'Data-{col}'
if role == Qt.TextAlignmentRole:
return int(Qt.AlignLeft | Qt.AlignVCenter)
return super().headerData(col, orientation, role)
def tableTimer(self):
while True:
time.sleep(5.0)
randomRow = self.random.randint(0, self.rows)
randomColumn = self.random.randint(0, self.columns)
encodedRandom = TimedModel.encode(randomRow, randomColumn)
if encodedRandom in self.updated:
self.updated.remove(encodedRandom)
else:
self.updated.add(encodedRandom)
updatedIndex = self.createIndex(randomRow, randomColumn)
self.dataChanged.emit(updatedIndex, updatedIndex)
'''this here does not work:'''
self.layoutAboutToBeChanged.emit()
self.rows += self.step
self.layoutChanged.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.timedTable = QTableView()
self.model = TimedModel(self.timedTable, self)
self.timedTable.setModel(self.model)
headerView = self.timedTable.horizontalHeader()
headerView.setStretchLastSection(True)
self.setCentralWidget(self.timedTable)
self.setGeometry(300, 300, 1000, 600)
self.setWindowTitle('Timed Table')
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.name = "Timed Table Application"
window = MainWindow()
window.show()
app.exec_()
The following code:
self.layoutAboutToBeChanged.emit()
self.rows += self.step
self.layoutChanged.emit()
create new model elements that have QPersistentModelIndex associated that are not thread-safe and that Qt monitors its creation to warn its misuse as in this case since modifying that element is unsafe since it implies modifying the GUI from another thread (Read here for more information).
So you see that message warning that what you are trying to do is unsafe.
Instead dataChanged only emits a signal, does not create any element belonging to Qt, and you have been lucky that the modification of "self.updated" has not generated bottlenecks since you modify a property that belongs to the main thread from a secondary thread without use guards as mutexes.
Qt points out that the GUI and the elements that the GUI uses should only be updated in the GUI thread, and if you want to modify the GUI with information from another thread, then you must send that information, for example, using the signals that are thread- safe:
import random
import sys
import threading
import time
from PySide2 import QtCore, QtGui, QtWidgets
class TimedModel(QtCore.QAbstractTableModel):
random_signal = QtCore.Signal(object)
def __init__(self, table, view):
super(TimedModel, self).__init__()
self.table = table
self.view = view
self.setHeaderData(
0, QtCore.Qt.Horizontal, QtCore.Qt.AlignLeft, QtCore.Qt.TextAlignmentRole
)
self.rows = 6
self.columns = 4
self.step = 5
self.updated = set()
self.random_signal.connect(self.random_slot)
self.timer = threading.Thread(name="Timer", target=self.tableTimer, daemon=True)
self.timer.start()
#staticmethod
def encode(row, column):
return row << 32 | column
def data(self, index, role):
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
return f"Data-{index.row()}-{index.column()}"
if role == QtCore.Qt.ForegroundRole:
encoded = TimedModel.encode(index.row(), index.column())
return QtGui.QBrush(
QtCore.Qt.red if encoded in self.updated else QtCore.Qt.black
)
return None
def rowCount(self, index):
return self.rows
def columnCount(self, index):
return self.columns
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Vertical:
# Vertical
return super().headerData(col, orientation, role)
# Horizontal
if not 0 <= col < self.columns:
return None
if role == QtCore.Qt.DisplayRole:
return f"Data-{col}"
if role == QtCore.Qt.TextAlignmentRole:
return QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
return super().headerData(col, orientation, role)
def tableTimer(self):
while True:
time.sleep(5.0)
randomRow = random.randint(0, self.rows)
randomColumn = random.randint(0, self.columns)
encodedRandom = TimedModel.encode(randomRow, randomColumn)
self.random_signal.emit(encodedRandom)
#QtCore.Slot(object)
def random_slot(self, encodedRandom):
if encodedRandom in self.updated:
self.updated.remove(encodedRandom)
else:
self.updated.add(encodedRandom)
self.layoutAboutToBeChanged.emit()
self.rows += self.step
self.layoutChanged.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.timedTable = QtWidgets.QTableView()
self.model = TimedModel(self.timedTable, self)
self.timedTable.setModel(self.model)
headerView = self.timedTable.horizontalHeader()
headerView.setStretchLastSection(True)
self.setCentralWidget(self.timedTable)
self.setGeometry(300, 300, 1000, 600)
self.setWindowTitle("Timed Table")
self.show()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.name = "Timed Table Application"
window = MainWindow()
window.show()
app.exec_()
I am having troubled using the QHeaderView drag & drop feature. When I subclass a QHeaderView, I am able to accept drops with no issue. However, when I click on the QHeaderView and try to drag from one of the columns, nothing appears to happen.
Below I have re-implemented several drag events to simply print if they were called. However, only the dragEnterEvent is successful. No other event such as startDrag is ever called. My ultimate goal is to have a QTableView where I can drag columns from and to a QListWidget (essentially hiding the column) and the user can then drag the QListWidget item back onto the QTableView if they want the column and its data to be visible again. However, I can’t move forward until I can understand why the QHeaderView is not allowing me to drag. Any help would be greatly appreciated.
class MyHeader(QHeaderView):
def __init__(self, parent=None):
super().__init__(Qt.Horizontal, parent)
self.setDragEnabled(True)
self.setAcceptDrops(True)
def startDrag(self, *args, **kwargs):
print('start drag success')
def dragEnterEvent(self, event):
print('drag enter success')
def dragLeaveEvent(self, event):
print('drag leave success')
def dragMoveEvent(self, event):
print('drag move success')
class Form(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
listWidget = QListWidget()
listWidget.setDragEnabled(True)
listWidget.setAcceptDrops(True)
listWidget.addItem('item #1')
listWidget.addItem('item #2')
tableWidget = QTableWidget()
header = MyHeader()
tableWidget.setHorizontalHeader(header)
tableWidget.setRowCount(5)
tableWidget.setColumnCount(2)
tableWidget.setHorizontalHeaderLabels(["Column 1", "Column 2"])
splitter = QSplitter(Qt.Horizontal)
splitter.addWidget(listWidget)
splitter.addWidget(tableWidget)
layout = QHBoxLayout()
layout.addWidget(splitter)
self.setLayout(layout)
if __name__=='__main__':
import sys
app = QApplication(sys.argv)
form= Form()
form.show()
sys.exit(app.exec_())
The QHeaderView class does not use the drag and drop methods inherited from QAbstractItemView, because it never needs to initiate a drag operation. Drag and drop is only used for rearranging columns, and it is not necessary to use the QDrag mechanism for that.
Given this, it will be necessary to implement custom drag and drop handling (using mousePressEvent, mouseMoveEvent and dropEvent), and also provide functions for encoding and decoding the mime-data format that Qt uses to pass items between views. An event-filter will be needed for the table-widget, so that dropping is still possible when all columns are hidden; and also for the list-widget, to stop it copying items to itself.
The demo script below implements all of that. There are probably some more refinements needed, but it should be enough to get you started:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MyHeader(QHeaderView):
MimeType = 'application/x-qabstractitemmodeldatalist'
columnsChanged = pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(Qt.Horizontal, parent)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self._dragstartpos = None
def encodeMimeData(self, items):
data = QByteArray()
stream = QDataStream(data, QIODevice.WriteOnly)
for column, label in items:
stream.writeInt32(0)
stream.writeInt32(column)
stream.writeInt32(2)
stream.writeInt32(int(Qt.DisplayRole))
stream.writeQVariant(label)
stream.writeInt32(int(Qt.UserRole))
stream.writeQVariant(column)
mimedata = QMimeData()
mimedata.setData(MyHeader.MimeType, data)
return mimedata
def decodeMimeData(self, mimedata):
data = []
stream = QDataStream(mimedata.data(MyHeader.MimeType))
while not stream.atEnd():
row = stream.readInt32()
column = stream.readInt32()
item = {}
for count in range(stream.readInt32()):
key = stream.readInt32()
item[key] = stream.readQVariant()
data.append([item[Qt.UserRole], item[Qt.DisplayRole]])
return data
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._dragstartpos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if (event.buttons() & Qt.LeftButton and
self._dragstartpos is not None and
(event.pos() - self._dragstartpos).manhattanLength() >=
QApplication.startDragDistance()):
column = self.logicalIndexAt(self._dragstartpos)
data = [column, self.model().headerData(column, Qt.Horizontal)]
self._dragstartpos = None
drag = QDrag(self)
drag.setMimeData(self.encodeMimeData([data]))
action = drag.exec(Qt.MoveAction)
if action != Qt.IgnoreAction:
self.setColumnHidden(column, True)
def dropEvent(self, event):
mimedata = event.mimeData()
if mimedata.hasFormat(MyHeader.MimeType):
if event.source() is not self:
for column, label in self.decodeMimeData(mimedata):
self.setColumnHidden(column, False)
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.ignore()
else:
super().dropEvent(event)
def setColumnHidden(self, column, hide=True):
count = self.count()
if 0 <= column < count and hide != self.isSectionHidden(column):
if hide:
self.hideSection(column)
else:
self.showSection(column)
self.columnsChanged.emit(count - self.hiddenSectionCount())
class Form(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.listWidget = QListWidget()
self.listWidget.setAcceptDrops(True)
self.listWidget.setDragEnabled(True)
self.listWidget.viewport().installEventFilter(self)
self.tableWidget = QTableWidget()
header = MyHeader(self)
self.tableWidget.setHorizontalHeader(header)
self.tableWidget.setRowCount(5)
self.tableWidget.setColumnCount(4)
labels = ["Column 1", "Column 2", "Column 3", "Column 4"]
self.tableWidget.setHorizontalHeaderLabels(labels)
for column, label in enumerate(labels):
if column > 1:
item = QListWidgetItem(label)
item.setData(Qt.UserRole, column)
self.listWidget.addItem(item)
header.hideSection(column)
header.columnsChanged.connect(
lambda count: self.tableWidget.setAcceptDrops(not count))
self.tableWidget.viewport().installEventFilter(self)
splitter = QSplitter(Qt.Horizontal)
splitter.addWidget(self.listWidget)
splitter.addWidget(self.tableWidget)
layout = QHBoxLayout()
layout.addWidget(splitter)
self.setLayout(layout)
def eventFilter(self, source, event):
if event.type() == QEvent.Drop:
if source is self.tableWidget.viewport():
self.tableWidget.horizontalHeader().dropEvent(event)
return True
else:
event.setDropAction(Qt.MoveAction)
return super().eventFilter(source, event)
if __name__=='__main__':
app = QApplication(sys.argv)
form = Form()
form.setGeometry(600, 50, 600, 200)
form.show()
sys.exit(app.exec_())
I have a rather big project, written in Python 3.4 and PyQt5 5.5.1. After updating to PyQt5 version 5.5.1 from PyQt5 version 5.4.1 I have strange memory leaks, there are no any leaks on 5.4.1 version. I have custom QAbstractTableModel and custom QTableView in my project with delegates for data types. Here is example code, which has memory leaks in lines 98 and 117 (it can be found using tracemalloc). Memory leak occur when you select cells in table.
Why it can be so?
#!/usr/bin/python3
import tracemalloc
tracemalloc.start()
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
RW = (Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled)
my_array = [['00','01','02'],
['10','11','12'],
['20','21','22']]
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
tablemodel = MyTableModel(my_array, self)
tableview = QTableView()
tableview.setModel(tablemodel)
self.cdelegates = []
for i in range(3):
d = RSpinDelegate(self)
self.cdelegates.append(d)
tableview.setItemDelegateForColumn(i, d)
layout = QVBoxLayout(self)
layout.addWidget(tableview)
self.setLayout(layout)
self.startTimer(5000)
def timerEvent(self, e):
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 ]")
for stat in top_stats[:10]:
print(stat)
class RSpinDelegate(QItemDelegate):
def __init__(self, parent=None, decimals=0, step=1, range_=(0, 1e9), edit=RW, suffix='', colorfill=None, stepFilter=None):
super(RSpinDelegate, self).__init__(parent)
self.decimals = decimals
self.step = step
self.range_ = range_
self.edit = edit
self.suffix = suffix
self.colorfill = colorfill
self.stepFilter = stepFilter
def setDecimals(self, decimals):
self.decimals = decimals
def createEditor(self, parent, option, index):
if self.edit == RW:
if self.decimals:
decimals = self.decimals
dec = int(index.model().data(index, RBaseTableModel.DECIMALS_ROLE))
decimals = dec
d = 10 ** (-decimals)
editor = RDoubleSpinBox(parent)
if self.stepFilter != None:
editor.installEventFilter(self.stepFilter)
editor.setSingleStep(d)
editor.setDecimals(decimals)
editor.setRange(self.range_[0], self.range_[1])
editor.setSuffix(self.suffix)
self._editor = editor
return editor
else:
editor = RSpinBox(parent)
if self.stepFilter != None:
editor.installEventFilter(self.stepFilter)
editor.setSingleStep(self.step)
editor.setRange(self.range_[0], self.range_[1])
editor.setSuffix(self.suffix)
self._editor = editor
return editor
return None
return None
def setEditorData(self, editor, index):
val = index.model().data(index, Qt.EditRole)
try:
editor.setValue(float(val.replace(' ', '')) if self.decimals != 0 else int(val.replace(' ', '')))
except:
editor.setValue(editor.minimum())
def setModelData(self, editor, model, index):
model.setData(index, editor.value(), Qt.EditRole)
def getBrush(self, option):
brush = option.palette.base()
if option.state & QStyle.State_Selected:# memory leak is here!
if option.state & QStyle.State_Active:
brush = option.palette.highlight()
else:
brush = option.palette.light()
return brush
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def paint(self, painter, option, index):
opt = QStyleOptionViewItem(option)
if self.colorfill:
brush = self.colorfill(index.model().data(index, RBaseTableModel.INDEX_ROLE), option)
if not(option.state & QStyle.State_Selected):
painter.fillRect(option.rect, brush)
opt.palette.setBrush(QPalette.Highlight, brush)
else:
brush = self.getBrush(option)
painter.fillRect(option.rect, brush)# memory leak is here!
super(RSpinDelegate, self).paint(painter, opt, index)
# création du modèle
class MyTableModel(QAbstractTableModel):
refreshTable = pyqtSignal()
def __init__(self, datain, parent = None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.arraydata = datain
self.timer = self.startTimer(300)
def timerEvent(self, e):
if self.timer == e.timerId():
self.refreshTable.emit()
else:
super(RBaseTableView, self).timerEvent(e)
def refreshTableSlot(self):
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def rowCount(self, parent):
return len(self.arraydata)
def columnCount(self, parent):
return len(self.arraydata[0])
def data(self, index, role):
if not index.isValid():
return None
elif role != Qt.DisplayRole:
return None
return (self.arraydata[index.row()][index.column()])
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
I am fairly new to PyQt, I'm working on a project that contains a QTableView, with one of its columns displaying system paths. I would like to add a QTreeView so users can click the + or > buttons to expand what is underneath the paths.
Here is my basic implementation:
from PyQt4 import QtGui
from PyQt4 import QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(600,400)
self.setWindowTitle("My Basic Treeview")
self.treeview = QtGui.QTreeView(self)
self.treeview.model = QtGui.QFileSystemModel()
self.treeview.model.setRootPath('/opt')
self.treeview.setModel(self.treeview.model)
self.treeview.setColumnWidth(0, 200)
self.setCentralWidget(self.treeview)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Although, in the above case, I get all folders but I just want the /opt path and its underneath folders.
import operator
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QWidget):
def __init__(self, data_list, header, *args):
QWidget.__init__(self, *args)
# setGeometry(x_pos, y_pos, width, height)
self.setGeometry(300, 200, 570, 450)
self.setWindowTitle("Click on column title to sort")
table_model = MyTableModel(self, data_list, header)
table_view = QTableView()
table_view.setModel(table_model)
# set font
font = QFont("Courier New", 14)
table_view.setFont(font)
# set column width to fit contents (set font first!)
table_view.resizeColumnsToContents()
# enable sorting
table_view.setSortingEnabled(True)
layout = QVBoxLayout(self)
layout.addWidget(table_view)
self.setLayout(layout)
class MyTableModel(QAbstractTableModel):
def __init__(self, parent, mylist, header, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.mylist = mylist
self.header = header
def rowCount(self, parent):
return len(self.mylist)
def columnCount(self, parent):
return len(self.mylist[0])
def data(self, index, role):
if not index.isValid():
return None
elif role != Qt.DisplayRole:
return None
return self.mylist[index.row()][index.column()]
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.header[col]
return None
# the solvent data ...
header = ['Name', ' Email', ' Status', ' Path']
# use numbers for numeric data to sort properly
data_list = [
('option_A', 'zyro#email.com', 'Not Copied', '/Opt'),
('option_B', 'zyro#email.com', 'Not Copied', '/Users'),
]
app = QApplication([])
win = MyWindow(data_list, header)
win.show()
app.exec_()
Visual example :
I think your question can be divided in two parts:
how, in a QTreeView, the /opt path and its children can be shown, but without showing its siblings. In other words, how is it possible to show the root directory in a QTreeView ;
how can a QTreeView be added to a QTableView.
1. How to include the root directory in a QTreeView :
The root of a QTreeView is the directory for which the content is shown in the view. It is set when calling the method setRootIndex. According to a post by wysota on Qt Centre:
You can't display the invisibleRootItem because it is a fake item used only to have an equivalent of empty QModelIndex.
A workaround would be to set the root directory to the parent of /opt and filtering out the siblings of /opt with a subclass of a QSortFilterProxyModel. Note that I've also reimplemented the sizeHint method which will be necessary for the resizing of the rows of the QTableView:
from PyQt4 import QtGui, QtCore
import os
class MyQTreeView(QtGui.QTreeView):
def __init__(self, path, parent=None):
super(MyQTreeView, self).__init__(parent)
ppath = os.path.dirname(path) # parent of path
self.setFrameStyle(0)
#---- File System Model ----
sourceModel = QtGui.QFileSystemModel()
sourceModel.setRootPath(ppath)
#---- Filter Proxy Model ----
proxyModel = MyQSortFilterProxyModel(path)
proxyModel.setSourceModel(sourceModel)
#---- Filter Proxy Model ----
self.setModel(proxyModel)
self.setHeaderHidden(True)
self.setRootIndex(proxyModel.mapFromSource(sourceModel.index(ppath)))
#--- Hide All Header Sections Except First ----
header = self.header()
for sec in range(1, header.count()):
header.setSectionHidden(sec, True)
def sizeHint(self):
baseSize = super(MyQTreeView,self).sizeHint()
#---- get model index of "path" ----
qindx = self.rootIndex().child(0, 0)
if self.isExpanded(qindx): # default baseSize height will be used
pass
else: # shrink baseShize height to the height of the row
baseSize.setHeight(self.rowHeight(qindx))
return baseSize
class MyQSortFilterProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, path, parent=None):
super(MyQSortFilterProxyModel, self).__init__(parent)
self.path = path
def filterAcceptsRow(self, row, parent):
model = self.sourceModel()
path_dta = model.index(self.path).data()
ppath_dta = model.index(os.path.dirname(self.path)).data()
if parent.data() == ppath_dta:
if parent.child(row, 0).data() == path_dta:
return True
else:
return False
else:
return True
2. How to add a *QTreeView* to a *QTableView* :
It is possible to add a QTreeView to a QTableView by using a QItemDelegate. The post by Pavel Strakhov greatly helped me for this, since I had never used QTableView in combination with delegates before answering to this question. I always used QTableWidget instead with the setCellWidget method.
Note that I've setup a signal in the MyDelegate class which call the method resizeRowsToContents in the MyTableView class. This way, the height of the rows resize according the the reimplementation of the sizeHint method of the MyQTreeView class.
class MyTableModel(QtCore.QAbstractTableModel):
def __init__(self, parent, mylist, header, *args):
super(MyTableModel, self).__init__(parent, *args)
self.mylist = mylist
self.header = header
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.mylist)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.mylist[0])
def data(self, index, role):
if not index.isValid():
return None
elif role != QtCore.Qt.DisplayRole:
return None
return self.mylist[index.row()][index.column()]
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.header[col]
return None
class MyDelegate(QtGui.QItemDelegate):
treeViewHeightChanged = QtCore.pyqtSignal(QtGui.QWidget)
def createEditor(self, parent, option, index):
editor = MyQTreeView(index.data(), parent)
editor.collapsed.connect(self.sizeChanged)
editor.expanded.connect(self.sizeChanged)
return editor
def sizeChanged(self):
self.treeViewHeightChanged.emit(self.sender())
class MyTableView(QtGui.QTableView):
def __init__(self, data_list, header, *args):
super(MyTableView, self).__init__(*args)
#---- set up model ----
model = MyTableModel(self, data_list, header)
self.setModel(model)
#---- set up delegate in last column ----
delegate = MyDelegate()
self.setItemDelegateForColumn(3, delegate)
for row in range(model.rowCount()):
self.openPersistentEditor(model.index(row, 3))
#---- set up font and resize calls ----
self.setFont(QtGui.QFont("Courier New", 14))
self.resizeColumnsToContents()
delegate.treeViewHeightChanged.connect(self.resizeRowsToContents)
3. Basic application :
Here is a basic application based on the code you provided in your OP:
if __name__ == '__main__':
header = ['Name', ' Email', ' Status', ' Path']
data_list = [('option_A', 'zyro#email.com', 'Not Copied', '/opt'),
('option_B', 'zyro#email.com', 'Not Copied', '/usr')]
app = QtGui.QApplication([])
win = MyTableView(data_list, header)
win.setGeometry(300, 200, 570, 450)
win.show()
app.exec_()
Which results in:
I have a QTableView with three columns
The second column is about numbers, there are only three types: 1, -1 and 0.
I want to have different colors for this three "types" of numbers (1,-1,0), coloring their rows with different colors. How can i do it?
self.tableView = QTableView(self.tabSentimento)
self.tableView.setGeometry(QRect(550,10,510,700))
self.tableView.setObjectName(_fromUtf8("TabelaSentimento"))
self.tableView.setModel(self.model)
self.tableView.horizontalHeader().setStretchLastSection(True)
obs: I used horizontalheader().setStrechLastSection(True) because I opened an existing csv file (using a button) into my tableview.
You have to define the color in the model, not in the view:
def data(self, index, role):
...
if role == Qt.BackgroundRole:
return QBrush(Qt.yellow)
Edit:
Here's a working example, except for the color part completely stolen from http://www.saltycrane.com/blog/2007/06/pyqt-42-qabstracttablemodelqtableview/
from PyQt4.QtCore import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
my_array = [['00','01','02'],
['10','11','12'],
['20','21','22']]
def main():
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
class MyWindow(QTableView):
def __init__(self, *args):
QTableView.__init__(self, *args)
tablemodel = MyTableModel(my_array, self)
self.setModel(tablemodel)
class MyTableModel(QAbstractTableModel):
def __init__(self, datain, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.arraydata = datain
def rowCount(self, parent):
return len(self.arraydata)
def columnCount(self, parent):
return len(self.arraydata[0])
def data(self, index, role):
if not index.isValid():
return QVariant()
# vvvv this is the magic part
elif role == Qt.BackgroundRole:
if index.row() % 2 == 0:
return QBrush(Qt.yellow)
else:
return QBrush(Qt.red)
# ^^^^ this is the magic part
elif role != Qt.DisplayRole:
return QVariant()
return QVariant(self.arraydata[index.row()][index.column()])
if __name__ == "__main__":
main()