I'm creating a custom item delegate for a news feed I'm trying to create in pyside. I'm not quite sure how to make the textEdit auto adjust it's size to fit the contents of the text it's wrapping and secondly maintain the Text Interaction feature, where users can click and highlight text?
This is what I'm currently getting and you can see the text boxes are being drawn overtop and not vertically being sized correctly:
import os, sys
from Qt import QtWidgets, QtCore, QtGui
class NewsItem(object):
def __init__(self, **kwargs):
super(NewsItem, self).__init__()
self.title = kwargs.get('title', '')
self.date = kwargs.get('date', '')
self.content = kwargs.get('content', '')
class NewsItemDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None):
super(NewsItemDelegate, self).__init__(parent)
def paint(self, painter, option, index):
# rect = option.rect.adjusted(1, 1, -1, -1)
# painter.fillRect(rect, QtGui.QColor(20,40,170,50))
# QtWidgets.QItemDelegate.paint(self, painter, option, index)
# get data from userrole
data = index.data(role=QtCore.Qt.UserRole)
# Main Widget
title = QtWidgets.QLabel(data.title)
content = QtWidgets.QTextEdit(data.content)
content.setFixedHeight(content.sizeHint().height())
widget = QtWidgets.QWidget()
layout = QtWidgets.QGridLayout(widget)
layout.addWidget(title, 0, 0)
layout.addWidget(content, 1, 0)
widget.setGeometry(option.rect)
widget.render(painter, option.rect.topLeft())
# painter.save()
# painter.restore()
def sizeHint(self, option, index):
return QtCore.QSize(100, 50)
return QtWidgets.QItemDelegate.sizeHint(self, option, index)
class NewsModel(QtGui.QStandardItemModel):
def __init__(self, *args, **kwargs):
QtGui.QStandardItemModel.__init__(self, *args, **kwargs)
class NewsListView(QtWidgets.QListView):
def __init__(self, parent=None):
super(NewsListView, self).__init__(parent)
self.setModel(NewsModel(self))
self.setItemDelegate(NewsItemDelegate(self))
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
def setNewsItems(self, lst):
self.model().clear()
for x in lst:
item = QtGui.QStandardItem()
# item.setData(x.title, role=QtCore.Qt.DisplayRole)
item.setData(x, role=QtCore.Qt.UserRole)
self.model().appendRow(item)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(350, 500)
# Controls
self.uiListView = NewsListView()
self.setCentralWidget(self.uiListView)
def unitTest(self):
self.uiListView.setNewsItems([
NewsItem(title='Big Update', date='Today', content='Something goes here...'),
NewsItem(title='Smaller Update', date='Yesterday', content='Something goes here which should support word wrap'),
NewsItem(title='Another Update', date='Last Year', content='Something goes here...'),
NewsItem(title='Old Update', date='Unknown', content='Something goes here...'),
])
def main():
app = QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.unitTest()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In this type of cases is to create a widget as an editor, and for this you must make in the paint method call openPersistentEditor(). The createEditor(), setEditorData() and setModelData() methods must also be overwritten.
# ...
from functools import partial
# ...
class EditorWidget(QtWidgets.QWidget):
editingFinished = QtCore.Signal()
def __init__(self, data=None, parent=None):
super(EditorWidget, self).__init__(parent)
self.title_label = QtWidgets.QLabel()
self.content_textedit = QtWidgets.QTextEdit()
self.content_textedit.textChanged.connect(self.editingFinished)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.title_label)
lay.addWidget(self.content_textedit)
if data is not None:
self.data = data
#property
def data(self):
return NewsItem(
title=self.title_label.text(),
content=self.content_textedit.toPlainText(),
)
#data.setter
def data(self, d):
self.title_label.setText(d.title)
tc = self.content_textedit.textCursor()
self.content_textedit.setPlainText(d.content)
self.content_textedit.setTextCursor(tc)
class NewsItemDelegate(QtWidgets.QItemDelegate):
def paint(self, painter, option, index):
if isinstance(self.parent(), QtWidgets.QAbstractItemView):
self.parent().openPersistentEditor(index)
def createEditor(self, parent, option, index):
data = index.data(QtCore.Qt.UserRole)
editor = EditorWidget(data, parent)
wrapper = partial(self.commitData.emit, editor)
editor.editingFinished.connect(wrapper)
model = index.model()
model.setData(index, editor.sizeHint(), QtCore.Qt.SizeHintRole)
return editor
def setEditorData(self, editor, index):
data = index.data(QtCore.Qt.UserRole)
editor.data = data
def setModelData(self, editor, model, index):
model.setData(index, editor.data, QtCore.Qt.UserRole)
# ...
Related
I would like to add a column of checkbox to my qtableview. I need the checkboxes to be in the center of column (i.e. aligned center). I have this example which works fine, BUT the checkboxes are aligned left.
import sys
import pandas as pd
from PyQt5 import QtCore, Qt
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QCheckBox, QHBoxLayout, QItemDelegate, QTableView, QWidget, QApplication, QMainWindow
class MyCheckboxDelegate(QItemDelegate):
def __init__(self, parent):
QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
check = QCheckBox(parent)
check.clicked.connect(self.currentIndexChanged)
return check
def setModelData(self, editor, model, index):
model.setData(index, editor.checkState())
#pyqtSlot()
def stateChanged(self):
self.commitData.emit(self.sender())
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super().__init__()
print(data)
self._data = data
def rowCount(self, index=None):
return self._data.shape[0]
def columnCount(self, parnet=None):
return self._data.shape[1]
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
if self._data.columns[index.column()]=='Delete':
return ''
value = self._data.iloc[index.row(), index.column()]
return str(value)
class MyWindow(QMainWindow):
def __init__(self, *args):
QWidget.__init__(self, *args)
table_model = TableModel(pd.DataFrame([['', ''], ['','']]))
self.table_view = QTableView()
self.table_view.setModel(table_model)
self.table_view.setItemDelegateForColumn(0, MyCheckboxDelegate(self))
for row in range(0, table_model.rowCount()):
self.table_view.openPersistentEditor(table_model.index(row, 0))
self.setCentralWidget(self.table_view)
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
To force them to come at center, I create a widget, add a layout and add the check box to the layout. In other words, I change the createEditor function of MyCheckboxDelegate as follows:
def createEditor(self, parent, option, index):
w = QWidget(parent)
layout = QHBoxLayout(w)
check = QCheckBox(parent)
check.clicked.connect(self.currentIndexChanged)
check.setStyleSheet("color: red;")
layout.addWidget(check)
layout.setAlignment(Qt.AlignCenter)
return w
The problem is that now, the setModelData will not be called anymore. I need to access `model' after a checkbox is clicked.
Has anybody an idea how to fix it?
Item delegates are able to set model data as long as the editor has a user property, and a basic QWidget doesn't.
The solution is to create a QWidget subclass that implements that property, and connect the checkbox to a signal that will actually do the same as before:
class CenterCheckBox(QWidget):
toggled = pyqtSignal(bool)
def __init__(self, parent):
super().__init__(parent)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.check = QCheckBox()
layout.addWidget(self.check, alignment=Qt.AlignCenter)
self.check.setFocusProxy(self)
self.check.toggled.connect(self.toggled)
# set a 0 spacing to avoid an empty margin due to the missing text
self.check.setStyleSheet('color: red; spacing: 0px;')
#pyqtProperty(bool, user=True) # note the user property parameter
def checkState(self):
return self.check.isChecked()
#checkState.setter
def checkState(self, state):
self.check.setChecked(state)
class MyCheckboxDelegate(QStyledItemDelegate):
def __init__(self, parent):
super().__init__(parent)
def createEditor(self, parent, option, index):
check = CenterCheckBox(parent)
check.toggled.connect(lambda: self.commitData.emit(check))
return check
def setModelData(self, editor, model, index):
model.setData(index, editor.checkState)
Note that:
it's usually better to use QStyledItemDelegate, which is more consistent with the overall application appearance;
you should always check the column (or row) before returning the editor and do the same (or at least check the editor) in setModelData(), or, alternatively, use setItemDelegateForColumn();
I have created a simple menu using a custom widget. How can i make the menu QAction emit the color value of the swatch clicked? Each color swatch contains a property called 'color'. If the user clicks 'Reset' i would like the value emitted to be 'None'
I tried to overload the triggered signal to pass the color of the swatch clicked but it didn't work either.
import sys
from PySide import QtGui, QtCore
class QColorSwatch(QtGui.QPushButton):
colorClicked = QtCore.Signal(object)
colorChanged = QtCore.Signal(object)
def __init__(self, *args, **kwargs):
super(QColorSwatch, self).__init__(*args, **kwargs)
self.setFixedWidth(18)
self.setFixedHeight(18)
self.setAutoFillBackground(True)
self._color = None
self.color = QtGui.QColor(0,0,0)
self.pressed.connect(self.color_clicked)
#property
def color(self):
return self._color
#color.setter
def color(self, value):
self._color = value
self.setIconSize(self.size())
pixmap = QtGui.QPixmap(self.size())
pixmap.fill(QtCore.Qt.black)
painter = QtGui.QPainter(pixmap)
painter.setBrush(QtGui.QColor(self.color))
painter.setPen(QtGui.QPen(QtCore.Qt.white, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawRect(1,1,self.size().width()-3,self.size().height()-3)
painter.end()
self.setIcon(pixmap)
self.colorChanged.emit(value)
def color_clicked(self):
self.colorClicked.emit(self.color)
class ColorFilters(QtGui.QWidget):
def __init__(self, action):
super(ColorFilters,self).__init__()
self.action = action
self.ui_any_color = QtGui.QLabel('Reset')
self.ui_swatch_01 = QColorSwatch()
self.ui_swatch_01.color = QtGui.QColor(255,0,0)
self.ui_swatch_02 = QColorSwatch()
self.ui_swatch_02.color = QtGui.QColor(0,255,0)
self.ui_swatch_03 = QColorSwatch()
self.ui_swatch_03.color = QtGui.QColor(0,0,255)
lay_main = QtGui.QGridLayout()
lay_main.setSpacing(5)
lay_main.setContentsMargins(5,5,5,5)
lay_main.addWidget(self.ui_any_color,0,0,1,4)
lay_main.addWidget(self.ui_swatch_01,1,0)
lay_main.addWidget(self.ui_swatch_02,1,1)
lay_main.addWidget(self.ui_swatch_03,1,2)
self.setLayout(lay_main)
# connections
self.ui_swatch_01.colorClicked.connect(self.clicked_swatch)
self.ui_swatch_02.colorClicked.connect(self.clicked_swatch)
self.ui_swatch_03.colorClicked.connect(self.clicked_swatch)
def mouseReleaseEvent(self,e):
self.action.trigger()
def clicked_swatch(self, col):
col = self.sender().color
self.action.trigger()
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
colAction = QtGui.QWidgetAction(self)
ql = ColorFilters(colAction)
colAction.setDefaultWidget(ql)
colAction.triggered.connect(self.clicked_color)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(colAction)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Menubar')
self.show()
def clicked_color(self):
print 'Clicked'
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Instead of trying to use the triggered signal it creates a new signal that sends that information. Also you should know that a signal can also connect to another signal as I show below.
import sys
from PySide import QtGui, QtCore
class QColorSwatch(QtGui.QPushButton):
colorClicked = QtCore.Signal(QtGui.QColor)
colorChanged = QtCore.Signal(object)
def __init__(self, *args, **kwargs):
super(QColorSwatch, self).__init__(*args, **kwargs)
self.setFixedWidth(18)
self.setFixedHeight(18)
self.setAutoFillBackground(True)
self._color = None
self.color = QtGui.QColor(0,0,0)
self.pressed.connect(self.color_clicked)
#property
def color(self):
return self._color
#color.setter
def color(self, value):
self._color = value
self.setIconSize(self.size())
pixmap = QtGui.QPixmap(self.size())
pixmap.fill(QtCore.Qt.black)
painter = QtGui.QPainter(pixmap)
painter.setBrush(QtGui.QColor(self.color))
painter.setPen(QtGui.QPen(QtCore.Qt.white, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawRect(1,1,self.size().width()-3,self.size().height()-3)
painter.end()
self.setIcon(pixmap)
self.colorChanged.emit(value)
def color_clicked(self):
self.colorClicked.emit(self.color)
class ColorFilters(QtGui.QWidget):
colorSelected = QtCore.Signal(QtGui.QColor)
def __init__(self, parent=None):
super(ColorFilters, self).__init__(parent)
lay_main = QtGui.QGridLayout(self)
lay_main.setSpacing(5)
lay_main.setContentsMargins(5,5,5,5)
self.ui_any_color = QtGui.QLabel('Reset')
lay_main.addWidget(self.ui_any_color,0,0,1,4)
self.ui_any_color.installEventFilter(self)
for i, color in enumerate((QtGui.QColor(255,0,0), QtGui.QColor(0,255,0), QtGui.QColor(0,0,255))):
ui_swatch = QColorSwatch()
ui_swatch.color = color
lay_main.addWidget(ui_swatch,1,i+1)
ui_swatch.colorClicked.connect(self.colorSelected)
def eventFilter(self, obj, event):
if obj == self.ui_any_color and event.type() == QtCore.QEvent.Type.MouseButtonPress:
self.colorSelected.emit(QtGui.QColor)
return super(ColorFilters, self).eventFilter(obj, event)
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
colAction = QtGui.QWidgetAction(self)
ql = ColorFilters(self)
colAction.setDefaultWidget(ql)
ql.colorSelected.connect(self.clicked_color)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(colAction)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Menubar')
self.show()
def clicked_color(self, color):
if not color.isValid():
print("reset")
else:
print('Clicked', color)
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
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'm trying to create a QListView with QStyledItemDelegate to show data more organizing way.
I gone through this site, and its all in C++, and I have no idea about it, guessing from the syntax and calls which has been used in the post, I tried my way to achieve it, but I had not luck. Can someone please help me out with this?
import sys, os
from PyQt4 import QtGui, QtCore
class ListView(QtGui.QListView):
def __init__(self, parent=None):
super(ListView, self).__init__(parent)
self._model = None
self._data = [
[
'Header: King Arthur',
'Project: TBN',
'Asset: arthur',
'Task name: Design doc',
'Start Date: Today',
'End Date: Next Monday'
]
]
self.set_model()
item_delegate = ItemDelegate()
self.setItemDelegate(item_delegate)
self.openPersistentEditor(self._model.createIndex(0, 0))
def set_model(self):
self._model = ListModel(self._data, parent=self)
self.setModel(self._model)
class ListModel(QtCore.QAbstractItemModel):
def __init__(self, data=[], parent=None):
super(ListModel, self).__init__(parent)
self._data = data
def rowCount(self, *arg):
return 1
def columnCount(self, *arg):
return len(self._data)
def data(self, index, role):
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(' | '.join(self._data[column]))
return QtCore.QVariant()
def index(self, row, column, parent):
return self.createIndex(row, column)
def parent(self, index):
item = index.internalPointer()
if item:
return item.getParent()
else:
item = self.createIndex(index.row(), index.column()).internalPointer()
if item:
return item.getParent()
return QtCore.QModelIndex()
class ItemDelegate(QtGui.QStyledItemDelegate):
def createEditor(self, parent, option, index):
item_data = str(index.data().toString())
editor = Widget(item_data.split('|'), parent=parent)
return editor
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class Widget(QtGui.QWidget):
def __init__(self, widget_data=[], parent=None):
super(Widget, self).__init__(parent)
vbox = QtGui.QVBoxLayout(self)
key_font = QtGui.QFont()
key_font.setWeight(QtGui.QFont.Bold)
val_font = QtGui.QFont()
val_font.setWeight(QtGui.QFont.Normal)
for each_data in widget_data:
hbox = QtGui.QHBoxLayout()
key, value = each_data.split(':')
key_text = QtGui.QLabel(self)
val_text = QtGui.QLabel(self)
key_text.setToolTip('Key: %s' % key)
val_text.setToolTip('Value: %s' % value)
key_text.setText(key)
val_text.setText(value)
key_text.setFont(key_font)
val_text.setFont(val_font)
hbox.addWidget(key_text)
hbox.addWidget(val_text)
# vbox.addLayout(hbox)
if __name__ == '__main__':
qapp = QtGui.QApplication([])
app = ListView()
app.show()
sys.exit(qapp.exec_())
Unless you have a compelling reason to use the View/Model pattern, in most cases it's going to be easier to use the Widget/Item pattern -- QListWidget and QListWidgetItem.
The QItemDelegate and QStyledItemDelegate are designed to be subclassed. You then define methods that are responsible for handling events, sizing, and painting the views/widgets.
class MyWidget(QtGui.QWidget):
def __init__(self, parent):
super(MyWidget, self).__init__(parent)
self.listwidget = QtGui.QListWidget(self)
self.delegate = MyDelegate(self, self.listwidget)
self.listwidget.setItemDelegate(self.delegate)
datas = [
{'Header': 'Blah', 'Project': 'TBN'},
{'Header': 'Other', 'Project': 'Something'},
]
for data in datas:
MyItem(self.listwidget, data)
class MyItem(QtGui.QListWidgetItem):
def __init__(self, parent, data):
super(MyItem, self).__init__(parent)
self._data = data
class MyDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent, listwidget):
super(MyDelegate, self).__init__(parent)
self.listwidget = listwidget
def sizeHint(self, option, index):
if index:
item = self.listwidget.itemFromIndex(index)
print item._data
# Do fontmetrics stuff from C++ article you linked
# using the text in the data dictionary.
def paint(self, painter, option, index):
# same thing, get the item using the index
# get the data from the item
# paint the data however you want.
class ItemDelegate(QtGui.QStyledItemDelegate):
def createEditor(self, parent, option, index):
item_data = str(index.data().toString())
editor = Widget(item_data.split('|'), parent=parent)
return editor
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def paint(self, painter, option, index):
painter.save()
index.model().setData(index, option.rect.width(), Qt.UserRole+1)
fixed this issue
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.