I am new to pyside and probably do not understand quite well the concepts of the GUI libraries. I have a specific question to the following code (python3.3), in which I create a view of a table, and a button. Once the button is pressed, data is added to the date of the widget, and the table should be redrawn. But the table does not update itself with the added contents.
How can I fix the code so the table updates/redraws itself after I press the 'Do_something' button?
For additional suggestions on my code I would be very thankful!
from PySide.QtCore import *
from PySide.QtGui import *
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
# this list represents the database
self.data_list = [
('John', 'Connor', 'terminated'),
('Buzz', 'Lightyea', 'animated')
]
self.header = ['name', 'lastname', 'extra']
# set basic window stuff
self.setGeometry(300, 200, 970, 450)
self.setWindowTitle("Main Stock Overview")
# add the model to the view
self.table_model = MyTableModel(self, self.data_list, self.header)
self.table_view = QTableView()
self.table_view.setModel(self.table_model)
# add the table to the layout
layout = QVBoxLayout(self)
layout.addWidget(self.table_view)
btn1 = QPushButton("Do_something", self)
btn1.clicked.connect(self.do_something)
# add some button to the layout
action_layout = QHBoxLayout(self)
action_layout.addStretch(1)
action_layout.addWidget(btn1)
layout.addLayout(action_layout)
self.setLayout(layout)
def do_something(self):
# update the 'database'
self.data_list.append(('Harry','Potter','wizated'))
print("data has been updated: ", self.data_list)
# required to be redrawn here
index1 = self.table_model.createIndex(0,0)
index2 = self.table_model.createIndex(self.table_model.rowCount(self)+1, self.table_model.columnCount(self))
self.table_model.dataChanged.emit(index1, index2)
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
app = QApplication([])
win = MyWindow()
win.show()
app.exec_()
After reading the docs a bit more it turns out that after adding new data row you need to emit the rowsInserted signal. So, the following is sufficient:
def do_something(self):
self.data_list.append(('Harry','Potter','wizated'))
newRow = len(self.data_list) - 1
self.table_model.rowsInserted.emit(QModelIndex(), newRow, newRow)
Related
I am trying to make everything shown by the current code un-editable.
Previous searches all suggest either modifying the flags() function of the model or using the setEditTriggers of the table. I do both in this code, but neither of them work.
Looking at a widget-by-widget case, I can find readonly modes for LineEdit and others, but not for ComboBox. So I can not even modify the delegate to force the readonly constraint, not that I would necessarily like to do it this way.
EDIT: to clarify, when I say I want the user to not be able to 'edit' I mean that he shouldn't be able to change the state of the widget in any way. E.g. he won't be able to click on a ComboBox (or at least changing the current selected item/index).
from PyQt5 import QtCore, QtWidgets
import sys
class MyWindow(QtWidgets.QWidget):
def __init__(self, *args):
super().__init__(*args)
tableview = TableView()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(tableview)
self.setLayout(layout)
class Delegate(QtWidgets.QStyledItemDelegate):
def __init__(self, model):
super().__init__()
self.model = model
def createEditor(self, parent, option, index):
widget = QtWidgets.QComboBox(parent)
widget.addItems(['', 'Cat', 'Dog'])
return widget
def setModelData(self, widget, model, index):
self.model.setData(index, widget.currentIndex())
class Model(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self.value = 0
def flags(self, index):
return QtCore.Qt.ItemIsEnabled
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid() or role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
return QtCore.QVariant(self.value)
def setData(self, index, value, role=QtCore.Qt.EditRole):
self.value = value
print("data[{}][{}] = {}".format(index.row(), index.column(), value))
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return 1
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
class TableView(QtWidgets.QTableView):
def __init__(self, parent=None):
super().__init__(parent)
self.model = Model(self)
delegate = Delegate(self.model)
self.setItemDelegate(delegate)
self.setModel(self.model)
self.setEditTriggers(QtWidgets.QTableWidget.NoEditTriggers)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.openPersistentEditor(index)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
Explanation:
Some concepts must be clarified:
For Qt to disable editing that a view (QListView, QTableView, QTreeView, etc.) or item of the view implies only that the editor will not open through user events such as clicked, double-clicked, etc.
The user interaction in Qt follows the following path:
The user interacts through the OS with the mouse, keyboard, etc.
the OS notifies Qt of that interaction.
Qt creates QEvents and sends it to the widgets.
The widget analyzes what you should modify regarding the QEvent you receive.
In your case, using openPersistentEditor() shows the widgets, and so the edibility from the Qt point of view is not valid for this case.
Solution:
Considering the above a possible general methodology to make a widget not editable: block some point of the user-widget interaction path. In this case, the simplest thing is to prevent the widget from receiving the QEvents through an event filter.
Considering the above, the solution is:
class DisableEventsManager(QtCore.QObject):
def __init__(self, *, qobject, events=None, apply_childrens=False):
if not isinstance(qobject, QtCore.QObject):
raise TypeError(
f"{qobject} must belong to a class that inherits from QObject"
)
super().__init__(qobject)
self._qobject = qobject
self._events = events or []
self._qobject.installEventFilter(self)
self._children_filter = []
if apply_childrens:
for child in self._qobject.findChildren(QtWidgets.QWidget):
child_filter = DisableEventsManager(
qobject=child, events=events, apply_childrens=apply_childrens
)
self._children_filter.append(child_filter)
#property
def events(self):
return self._events
#events.setter
def events(self, events):
self._events = events
for child_filter in self._children_filter:
child_filter.events = events
def eventFilter(self, obj, event):
if self.events and self._qobject is obj:
if event.type() in self.events:
return True
return super().eventFilter(obj, event)
def createEditor(self, parent, option, index):
combo = QtWidgets.QComboBox(parent)
combo.addItems(["", "Cat", "Dog"])
combo_event_filter = DisableEventsManager(qobject=combo)
combo_event_filter.events = [
QtCore.QEvent.KeyPress,
QtCore.QEvent.FocusIn,
QtCore.QEvent.MouseButtonPress,
QtCore.QEvent.MouseButtonDblClick,
]
return combo
I need auto completion in a table. So far, I could make it work that I get the same list for the entire table.
However, I need a dynamic list for each cell. How can I get update the list when I move to a new position in the cell?
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class mainWindow(QMainWindow):
def __init__(self, parent = None):
super(mainWindow, self).__init__(parent)
self.initUI()
def initUI(self):
self.center_window = centerWindow(parent=self)
self.setCentralWidget(self.center_window)
class centerWindow(QWidget):
def __init__(self, parent=None):
super(centerWindow, self).__init__(parent)
table = QTableWidget()
table.setItemDelegate(TableItemCompleter())
table.setRowCount(5)
table.setColumnCount(1)
vbox = QVBoxLayout(self)
vbox.addWidget(table)
self.setLayout(vbox)
class TableItemCompleter(QStyledItemDelegate):
def __init__(self, parent = None):
super(TableItemCompleter, self).__init__(parent)
def createEditor(self, parent, styleOption, index):
editor = QLineEdit(parent)
completion_ls = ['aaa', 'bbb', 'ccc']
autoComplete = QCompleter(completion_ls)
editor.setCompleter(autoComplete)
return editor
if __name__ == '__main__':
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
else:
print('QApplication instance already exists: %s' % str(app))
ex = mainWindow()
ex.show()
sys.exit(app.exec_())
To make it more clear. Instead of having the completion_ls list in the TableItemCompleter, I'd like to add something like this:
table.setItem(row, column) #add my new auto completion list
QCompleter can be established a model that uses as a source for the autocomplete, we could pass the QModelIndex model that provides the method createEditor(self, parent, option, index) through index.model() but the problem is that you can only take a column and is not what is desired.
Then we must do a conversion of the tablemodel to a listmodel, and then we must filter the repeated elements so that the completer shows unique elements, one way to do them is through proxies, classes that inherit from QAbstractProxyModel, the scheme is as follows:
TableModel ----> ReadTable2ListProxyModel ----> DuplicateFilterProxyModel
En la siguiente sección muestros las clases:
class ReadTable2ListProxyModel(QIdentityProxyModel):
def columnCount(self, parent=QModelIndex()):
return 1
def rowCount(self, parent=QModelIndex()):
return self.sourceModel().rowCount() * self.sourceModel().columnCount()
def mapFromSource(self, sourceIndex):
if sourceIndex.isValid() and sourceIndex.column() == 0\
and sourceIndex.row() < self.rowCount():
r = sourceIndex.row()
c = sourceIndex.column()
row = sourceIndex.model().columnCount() * c + r
return self.index(row, 0)
return QModelIndex()
def mapToSource(self, proxyIndex):
r = proxyIndex.row() / self.sourceModel().columnCount()
c = proxyIndex.row() % self.sourceModel().columnCount()
return self.sourceModel().index(r, c)
def index(self, row, column, parent=QModelIndex()):
return self.createIndex(row, column)
class DuplicateFilterProxyModel(QSortFilterProxyModel):
def setSourceModel(self, model):
model.dataChanged.connect(lambda: self.invalidate())
QSortFilterProxyModel.setSourceModel(self, model)
def filterAcceptsRow(self, row, parent):
value = self.sourceModel().index(row, self.filterKeyColumn())\
.data(self.filterRole())
if value is None:
return False
if row == 0:
return True
for i in reversed(range(0, row)):
val = self.sourceModel().index(i, self.filterKeyColumn())\
.data(self.filterRole())
if val == value:
return False
return True
Then the conversion is established in the delegate:
class TableItemCompleter(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
completer = QCompleter(parent)
proxy1 = ReadTable2ListProxyModel(parent)
proxy2 = DuplicateFilterProxyModel(parent)
proxy1.setSourceModel(index.model())
proxy2.setSourceModel(proxy1)
completer.setModel(proxy2)
editor.setCompleter(completer)
return editor
In the following link you will find an example, and in the following image the operation is illustrated, in the first widget the table is observed, in the second the conversion to list and in the third the list eliminating duplicate elements.
If you want each item to have a list what can be done is to store the list in each item through a role that is not in use as Qt.UserRole through the setData() method, and in the delegate through the method data() of the QModelIndex:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import random
class TableItemCompleter(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
completion_ls = index.data(Qt.UserRole) # get list
completer = QCompleter(completion_ls, parent)
editor.setCompleter(completer)
return editor
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
lay = QHBoxLayout(self)
tv = QTableWidget(3, 4, self)
lay.addWidget(tv)
l = ["AA", "AB", "AC", "AD", "BA", "BB", "BC"]
for i in range(tv.rowCount()):
for j in range(tv.columnCount()):
it = QTableWidgetItem(f"{i},{j}")
tv.setItem(i, j, it)
it.setData(Qt.UserRole, random.sample(l, 3)) # set list
tv.setItemDelegate(TableItemCompleter(tv))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I want to sort a QTableView when I click on the headers of my QHeaderView. I've found a several code sample on the internet like this one: Sort QTableView in pyqt5
but it doesn't work for me. I also look in the Rapid Gui programming Book from Summerfield but I could find something that was working either.
In this quick example how could I sort the table by name or age?
Here is my code :
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import pandas as pd
import operator
# This class was generated from the Qt Creator
class Ui_tableView_ex(object):
def setupUi(self, tableView_ex):
tableView_ex.setObjectName("tableView_ex")
tableView_ex.resize(800, 600)
self.centralwidget = QWidget(tableView_ex)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.myTable = QTableView(self.centralwidget)
self.myTable.setObjectName("monTablo")
self.gridLayout.addWidget(self.myTable, 0, 0, 1, 1)
tableView_ex.setCentralWidget(self.centralwidget)
self.retranslateUi(tableView_ex)
QMetaObject.connectSlotsByName(tableView_ex)
def retranslateUi(self, tableView_ex):
_translate = QCoreApplication.translate
tableView_ex.setWindowTitle(_translate("tableView_ex", "MainWindow"))
class TableTest(QMainWindow, Ui_tableView_ex):
def __init__(self, parent=None):
super(TableTest, self).__init__(parent)
self.setupUi(self)
self.model = TableModel()
self.myTable.setModel(self.model)
self.myTable.setShowGrid(False)
self.hView = HeaderView(self.myTable)
self.myTable.setHorizontalHeader(self.hView)
self.myTable.verticalHeader().hide()
# adding alternate colours
self.myTable.setAlternatingRowColors(True)
self.myTable.setStyleSheet("alternate-background-color: rgb(209, 209, 209)"
"; background-color: rgb(244, 244, 244);")
# self.myTable.setSortingEnabled(True)
# self.myTable.sortByColumn(1, Qt.AscendingOrder)
class HeaderView(QHeaderView):
def __init__(self, parent):
QHeaderView.__init__(self, Qt.Horizontal, parent)
self.model = TableModel()
self.setModel(self.model)
# Setting font for headers only
self.font = QFont("Helvetica", 12)
self.setFont(self.font)
# Changing section backgroud color. font color and font weight
self.setStyleSheet("::section{background-color: pink; color: green; font-weight: bold}")
self.setSectionResizeMode(1)
self.setSectionsClickable(True)
class TableModel(QAbstractTableModel):
def __init__(self):
QAbstractTableModel.__init__(self)
super(TableModel, self).__init__()
self.headers = ["Name", "Age", "Grades"]
self.stocks = [["George", "26", "80%"],
["Bob", "16", "95%"],
["Martha", "22", "98%"]]
self.data = pd.DataFrame(self.stocks, columns=self.headers)
def update(self, in_data):
self.data = in_data
def rowCount(self, parent=None):
return len(self.data.index)
def columnCount(self, parent=None):
return len(self.data.columns.values)
def setData(self, index, value, role=None):
if role == Qt.EditRole:
row = index.row()
col = index.column()
column = self.data.columns.values[col]
self.data.set_value(row, column, value)
self.update(self.data)
return True
def data(self, index, role=None):
if role == Qt.DisplayRole:
row = index.row()
col = index.column()
value = self.data.iloc[row, col]
return value
def headerData(self, section, orientation, role=None):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return self.data.columns.values[section]
# -----------------NOT WORKING!!!---------------
# =================================================
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
self.layoutAboutToBeChanged.emit()
self.data = sorted(self.data, key=operator.itemgetter(Ncol))
if order == Qt.DescendingOrder:
self.data.reverse()
self.layoutChanged.emit()
# =================================================
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
app.setStyle(QStyleFactory.create("Fusion"))
main_window = TableTest()
main_window.show()
app.exec_()
sys.exit()
I've try to modify a few things, but nothing worked out and when I uncomment the setSortingEnabled(True) line the window doesn't even open. So there is no way I can know if I'm closer to the solution or not! I would also like to change the text color depending on the grade(red under 50 an green over 50 for example). For that, I haven't search that much, so i will try it by myself before asking aquestion but if you have any hint It would be much appreciated!
Thank you for your help!
Your sort function is the problem, you are not using the pandas DataFrame sorting functions, and self.data become a python list, then other functions fail and the program crash.
To correctly sort the DataFrame use the sort_values function like this:
def sort(self, Ncol, order):
"""Sort table by given column number."""
self.layoutAboutToBeChanged.emit()
self.data = self.data.sort_values(self.headers[Ncol],
ascending=order == Qt.AscendingOrder)
self.layoutChanged.emit()
I am trying to build a program with two tabs. In Tab1 I select point coordinates (x,y) from an image into values self.a. Besides the image I also have some other UI in Tab1 (i.e. a table). Now, I want to pass the values self.a to Tab2 (without inheriting all the other stuff). Keep in mind that self.a can be constantly updated when a new point is clicked.
from PySide import QtGui, QtCore
import pandas as pd
import pyqtgraph as pg
import numpy as np
QVariant = lambda value=None: value
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
v_global_layout = QtGui.QVBoxLayout()
v_global_layout.addWidget(TabDialog())
v_global_layout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(v_global_layout)
class TabDialog(QtGui.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
tab_widget = QtGui.QTabWidget()
tab_widget.addTab(Tab1(), "1")
tab_widget.addTab(Tab2(), "2")
main_layout = QtGui.QVBoxLayout()
main_layout.addWidget(tab_widget)
self.setLayout(main_layout)
class Tab1(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
self.fig = pg.PlotWidget(name='Example: Selecting scatter points')
self.plot_area = self.fig.plotItem
self.a = pg.ScatterPlotItem(pxMode=False)
spots = []
for i in range(10):
for j in range(10):
spots.append({'pos': (1*i, 1*j), 'size': 1, 'pen': {'color': 'w', 'width': 2},
'brush': pg.intColor(i*10+j, 100)})
self.a.addPoints(spots)
self.plot_area.addItem(self.a)
self.a.dataModel = DataFrameModel()
self.a.dataTable = QtGui.QTableView()
self.a.dataTable.setModel(self.a.dataModel)
layout.addWidget(self.a.dataTable)
layout.addWidget(self.fig)
self.setLayout(layout)
self.a.array = np.zeros((0, 2))
def clicked(self, points):
for p in points:
p.setPen('b', width=2)
position = p.viewPos()
self.array = np.append(self.array, np.array([[position.x(), position.y()]]), axis=0)
c = range(len(self.array))
c = list(map(str, c))
self.dataModel.signalUpdate(self.array, columns=c)
self.dataModel.printValues() # also: print(self.array)
self.a.sigClicked.connect(clicked)
class Tab2(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
##### Here I want to use Tab1.a and not inherit all the other stuff(layout) #####
#print("values = ", Tab1.a.array) # a should change when a new point is selected in Tab1
#####################################
self.setLayout(layout)
class DataFrameModel(QtCore.QAbstractTableModel):
""" data model for a DataFrame class """
def __init__(self):
super(DataFrameModel, self).__init__()
self.df = pd.DataFrame()
def signalUpdate(self, dataIn, columns):
self.df = pd.DataFrame(dataIn, columns)
self.layoutChanged.emit()
def printValues(self):
print("DataFrame values:\n", self.df.values)
def values(self):
return self.df.values
#------------- table display functions -----------------
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self.df.columns.tolist()[section]
except (IndexError, ):
return QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self.df.index.tolist()[section]
except (IndexError, ):
return QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QVariant()
if not index.isValid():
return QVariant()
return QVariant(str(self.df.ix[index.row(), index.column()]))
def rowCount(self, index=QtCore.QModelIndex()):
return self.df.shape[0]
def columnCount(self, index=QtCore.QModelIndex()):
return self.df.shape[1]
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
main_window = Widget()
main_window.setGeometry(100, 100, 640, 480)
main_window.show()
sys.exit(app.exec_())
In this case, you can just use signals to get the job done.
Here you are trying to access Tab1.a like a static property, when it is not one. Ideally, we should try and decouple the different widgets. We should try and keep the dependency between them to a minimum and treat each of them as ignorant and unaware of each other. The TabDialog can be the one that knows about each of these widgets and the connections between them (In this case, Tab1 and Tab2). And hence, the TabDialog can take the responsibility of communication between these widgets.
To do this, we have the two tabs as properties of the TabDialog class like so:
# Have the tabs as this dialog's class properties
self.tab1 = Tab1(image)
self.tab2 = Tab2()
tab_widget.addTab(self.tab1, "1")
tab_widget.addTab(self.tab2, "2")
In the class Tab2, let us assume that the value you want to map with Tab1.a is points_from_tab1_a:
class Tab2(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
self.points_from_tab1_a = []
self.setLayout(layout)
Now, in TabDialog, we connect the sigClicked signal of tab1.a to a method that updates tab2.points_from_tab1_a:
self.tab1.a.sigClicked.connect(self.pointChanged)
def pointChanged(self, points):
tab2.points_from_tab1_a = tab1.a
And that should do the trick. So, your full code snippet, after these changes, would look like:
from PySide import QtGui, QtCore
import pandas as pd
import pyqtgraph as pg
import numpy as np
QVariant = lambda value=None: value
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
v_global_layout = QtGui.QVBoxLayout()
v_global_layout.addWidget(TabDialog())
v_global_layout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(v_global_layout)
class TabDialog(QtGui.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
tab_widget = QtGui.QTabWidget()
# Have the tabs as this dialog's class properties
self.tab1 = Tab1(image)
self.tab2 = Tab2()
tab_widget.addTab(self.tab1, "1")
tab_widget.addTab(self.tab2, "2")
self.tab1.a.sigClicked.connect(self.pointChanged)
main_layout = QtGui.QVBoxLayout()
main_layout.addWidget(tab_widget)
self.setLayout(main_layout)
def pointChanged(self, points):
tab2.points_from_tab1_a = tab1.a
class Tab1(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
self.fig = pg.PlotWidget(name='Example: Selecting scatter points')
self.plot_area = self.fig.plotItem
self.a = pg.ScatterPlotItem(pxMode=False)
spots = []
for i in range(10):
for j in range(10):
spots.append({'pos': (1*i, 1*j), 'size': 1, 'pen': {'color': 'w', 'width': 2},
'brush': pg.intColor(i*10+j, 100)})
self.a.addPoints(spots)
self.plot_area.addItem(self.a)
self.a.dataModel = DataFrameModel()
self.a.dataTable = QtGui.QTableView()
self.a.dataTable.setModel(self.a.dataModel)
layout.addWidget(self.a.dataTable)
layout.addWidget(self.fig)
self.setLayout(layout)
self.a.array = np.zeros((0, 2))
def clicked(self, points):
for p in points:
p.setPen('b', width=2)
position = p.viewPos()
self.array = np.append(self.array, np.array([[position.x(), position.y()]]), axis=0)
c = range(len(self.array))
c = list(map(str, c))
self.dataModel.signalUpdate(self.array, columns=c)
self.dataModel.printValues() # also: print(self.array)
self.a.sigClicked.connect(clicked)
class Tab2(QtGui.QTabWidget):
def __init__(self):
super().__init__()
layout = QtGui.QHBoxLayout()
self.points_from_tab1_a = []
##### Here I want to use Tab1.a and not inherit all the other stuff(layout) #####
#print("values = ", Tab1.a.array) # a should change when a new point is selected in Tab1
#####################################
self.setLayout(layout)
class DataFrameModel(QtCore.QAbstractTableModel):
""" data model for a DataFrame class """
def __init__(self):
super(DataFrameModel, self).__init__()
self.df = pd.DataFrame()
def signalUpdate(self, dataIn, columns):
self.df = pd.DataFrame(dataIn, columns)
self.layoutChanged.emit()
def printValues(self):
print("DataFrame values:\n", self.df.values)
def values(self):
return self.df.values
#------------- table display functions -----------------
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self.df.columns.tolist()[section]
except (IndexError, ):
return QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self.df.index.tolist()[section]
except (IndexError, ):
return QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QVariant()
if not index.isValid():
return QVariant()
return QVariant(str(self.df.ix[index.row(), index.column()]))
def rowCount(self, index=QtCore.QModelIndex()):
return self.df.shape[0]
def columnCount(self, index=QtCore.QModelIndex()):
return self.df.shape[1]
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
main_window = Widget()
main_window.setGeometry(100, 100, 640, 480)
main_window.show()
sys.exit(app.exec_())
Feel free to change it to suit your needs, using the signals and slots concept. Hope this was useful.
I started with the answer from a previous post but it doesn't seem to be working. All the rows render the same color.
I create a main window class
import sys
from PySide import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.settingsTree = SettingsTree()
self.setCentralWidget(self.settingsTree)
self.locationDialog = None
# self.autoRefreshAct.setChecked(True)
# self.fallbacksAct.setChecked(True)
self.setWindowTitle("Test")
self.resize(500, 600)
self.setTreeDataObject(ItemManifest())
self.settingsTree.setItemDelegate(SelectionColorDelegate(self.settingsTree))
def setTreeDataObject(self, treeData):
self.settingsTree.setTreeDataObject(treeData)
# self.refreshAct.setEnabled(True)
# self.autoRefreshAct.setEnabled(True)
The ItemManifest is used to hold data specific to list items (not really sure this is the Qt way of doing things)
class ItemManifest(QtCore.QObject):
def __init__(self, parent=None):
super(ItemManifest, self).__init__(parent)
self.myList = [{'name': 'a', 'vid':'1', 'pid': '1'},
{'name': 'b', 'vid':'2', 'pid': '1'},
{'name': 'c', 'vid':'3', 'pid': '1'}]
This is my actual tree widget with three columns:
class SettingsTree(QtGui.QTreeWidget):
def __init__(self, parent=None):
super(SettingsTree, self).__init__(parent)
self.setHeaderLabels(("Name", "vid", "pid"))
self.header().setResizeMode(0, QtGui.QHeaderView.Stretch)
self.header().setResizeMode(2, QtGui.QHeaderView.Stretch)
self.refreshTimer = QtCore.QTimer()
self.refreshTimer.setInterval(2000)
self.autoRefresh = False
self.groupIcon = QtGui.QIcon()
self.groupIcon.addPixmap(self.style().standardPixmap(QtGui.QStyle.SP_DirClosedIcon),
QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.groupIcon.addPixmap(self.style().standardPixmap(QtGui.QStyle.SP_DirOpenIcon),
QtGui.QIcon.Normal, QtGui.QIcon.On)
self.keyIcon = QtGui.QIcon()
self.keyIcon.addPixmap(self.style().standardPixmap(QtGui.QStyle.SP_FileIcon))
self.refreshTimer.timeout.connect(self.refresh)
def setTreeDataObject(self, treeData):
self.treeData = treeData
self.clear()
if self.treeData is not None:
self.treeData.setParent(self)
self.refresh()
if self.autoRefresh:
self.refreshTimer.start()
else:
self.refreshTimer.stop()
def sizeHint(self):
return QtCore.QSize(800, 600)
def setAutoRefresh(self, autoRefresh):
self.autoRefresh = autoRefresh
if self.treeData is not None:
if self.autoRefresh:
self.refresh()
self.refreshTimer.start()
else:
self.refreshTimer.stop()
def refresh(self):
if self.treeData is None:
return
# The signal might not be connected.
# try:
# self.itemChanged.disconnect(self.updateSetting)
# except:
# pass
self.updateChildItems(None)
# self.itemChanged.connect(self.updateSetting)
def event(self, event):
if event.type() == QtCore.QEvent.WindowActivate:
if self.isActiveWindow() and self.autoRefresh:
self.refresh()
return super(SettingsTree, self).event(event)
'''on change to settings update tree'''
def updateChildItems(self, parent):
dividerIndex = 0
for printer in self.treeData.myList:
childIndex = self.findChild(parent, printer['name'], 0)
if childIndex == -1 or childIndex >= dividerIndex:
if childIndex != -1:
child = self.childAt(parent, childIndex)
for i in range(child.childCount()):
self.deleteItem(child, i)
self.moveItemForward(parent, childIndex, dividerIndex)
else:
child = self.createItem(printer['name'], parent, dividerIndex)
child.setIcon(0, self.keyIcon)
dividerIndex += 1
else:
child = self.childAt(parent, childIndex)
child.setText(1, printer['vid'])
child.setText(2, printer['pid'])
def createItem(self, text, parent, index):
after = None
if index != 0:
after = self.childAt(parent, index - 1)
if parent is not None:
item = QtGui.QTreeWidgetItem(parent, after)
else:
item = QtGui.QTreeWidgetItem(self, after)
item.setText(0, text)
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
return item
def deleteItem(self, parent, index):
if parent is not None:
item = parent.takeChild(index)
else:
item = self.takeTopLevelItem(index)
del item
def childAt(self, parent, index):
if parent is not None:
return parent.child(index)
else:
return self.topLevelItem(index)
def childCount(self, parent):
if parent is not None:
return parent.childCount()
else:
return self.topLevelItemCount()
def findChild(self, parent, text, startIndex):
for i in range(self.childCount(parent)):
if self.childAt(parent, i).text(0) == text:
return i
return -1
def moveItemForward(self, parent, oldIndex, newIndex):
for int in range(oldIndex - newIndex):
self.deleteItem(parent, newIndex)
Create a color delegate for setting the background color of a row with text 'a' to green
class SelectionColorDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent):
super(SelectionColorDelegate, self).__init__(parent)
def initStyleOption(self, option, index):
# let the base class initStyleOption fill option with the default values
super(SelectionColorDelegate,self).initStyleOption(option, index)
# override what you need to change in option
if index.data() == 'a':
backColor = QtGui.QColor("green")
option.palette.setColor(QtGui.QPalette.Base, backColor)
option.backgroundBrush = backColor
make it work:
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
Neither of my attempts at setting background color appear to be working.
After learning further on Qt I refactored the code to use the model/view relationship. The model is inherited from QtCore.QAbstractItemModel and implements the data method as follows:
class MyTreeModel(QtCore.QAbstractItemModel):
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == QtCore.Qt.BackgroundRole:
if item.status.status == 'Not installed':
return QtGui.QBrush(QtCore.Qt.lightGray)
if item.status.head_test_failed:
return QtGui.QBrush(QtCore.Qt.red)
if item.status.status == 'failure':
return QtGui.QBrush(QtCore.Qt.red)
if item.status.status == 'running':
return QtGui.QBrush(QtCore.Qt.green)
return QtGui.QBrush(QtCore.Qt.transparent)
if role != QtCore.Qt.DisplayRole:
return None
return item.data(index.column())
This way I was able to use to standard TreeView (or any other view for that matter). The role == QtCore.Qt.BackgroundRole tells the model that this is a formatting request.