我正在尝试在PySide GUI中实现某种列表视图,这使用户有机会在最终处理列表之前启用/禁用列表的某些条目。
我决定将QTableView和QAbstractTableModel与CheckBoxDelegate类一起使用,该类为表视图中的每一行呈现一个复选框。选中和取消选中条目将相应地设置基础列表对象的enabled属性。这使我可以在处理时轻松跳过条目。
我想画一个居中的复选框。因此,我基于此SO问题https://stackoverflow.com/a/11802138/1504082在CheckBoxDelegate中使用QCheckbox的子类。 现在我的问题是我在第0列得到两个复选框。但我不明白为什么......
这是我的代码
# -*- coding: UTF-8 -*-
import sys
from sip import setdestroyonexit
from PySide import QtCore
from PySide import QtGui
def do_action(obj):
print "do stuff for", obj.data_value
class MyObject(object):
def __init__(self, data_value, enabled=True):
self.data_value = data_value
self.enabled = enabled
self.result = None
self.action = ''
class MyCheckBox(QtGui.QCheckBox):
def __init__(self, parent):
QtGui.QCheckBox.__init__(self, parent)
# create a centered checkbox
self.cb = QtGui.QCheckBox(parent)
cbLayout = QtGui.QHBoxLayout(self)
cbLayout.addWidget(self.cb, 0, QtCore.Qt.AlignCenter)
self.cb.clicked.connect(self.stateChanged)
def isChecked(self):
return self.cb.isChecked()
def setChecked(self, value):
self.cb.setChecked(value)
@QtCore.Slot()
def stateChanged(self):
print "sender", self.sender()
self.clicked.emit()
class CheckBoxDelegate(QtGui.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox in every
cell of the column to which it's applied
"""
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
cb = MyCheckBox(parent)
cb.clicked.connect(self.stateChanged)
return cb
def paint(self, painter, option, index):
value = index.data()
if value:
value = QtCore.Qt.Checked
else:
value = QtCore.Qt.Unchecked
self.drawCheck(painter, option, option.rect, value)
self.drawFocus(painter, option, option.rect)
def setEditorData(self, editor, index):
""" Update the value of the editor """
editor.blockSignals(True)
editor.setChecked(index.model().checked_state(index))
editor.blockSignals(False)
def setModelData(self, editor, model, index):
""" Send data to the model """
model.setData(index, editor.isChecked(), QtCore.Qt.EditRole)
@QtCore.Slot()
def stateChanged(self):
print "sender", self.sender()
self.commitData.emit(self.sender())
class TableView(QtGui.QTableView):
"""
A simple table to demonstrate the QCheckBox delegate.
"""
def __init__(self, *args, **kwargs):
QtGui.QTableView.__init__(self, *args, **kwargs)
# Set the delegate for column 0 of our table
self.setItemDelegateForColumn(0, CheckBoxDelegate(self))
class MyWindow(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
# setGeometry(x_pos, y_pos, width, height)
self.setGeometry(300, 200, 640, 480)
self.setWindowTitle("CheckBoxDelegate with two Checkboxes?")
self.object_list = [
MyObject('Task 1'),
MyObject('Task 2'),
MyObject('Task 3'),
]
self.header = ['Active', 'Data value', 'Result', 'Action']
table_model = MyTableModel(self,
self.object_list,
['enabled', 'data_value', 'result', 'action'],
self.header)
self.table_view = TableView()
self.table_view.setModel(table_model)
active_col = self.header.index('Active')
for row in range(0, table_model.rowCount()):
self.table_view.openPersistentEditor(table_model.index(row, active_col))
action_col = self.header.index('Action')
for i, bo in enumerate(self.object_list):
btn = QtGui.QPushButton(self.table_view)
btn.setText("View")
self.table_view.setIndexWidget(table_model.index(i, action_col), btn)
btn.clicked.connect(lambda obj=bo: do_action(obj))
# set font
font = QtGui.QFont("Calibri", 10)
self.table_view.setFont(font)
# set column width to fit contents (set font first!)
self.table_view.resizeColumnsToContents()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table_view)
self.setLayout(layout)
class MyTableModel(QtCore.QAbstractTableModel):
def __init__(self, parent, rows, columns, header, *args):
QtCore.QAbstractTableModel.__init__(self, parent, *args)
self.rows = rows
self.columns = columns
self.header = header
self.CB_COL = 0
assert len(columns) == len(header), "Header names dont have the same " \
"length as supplied columns"
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.rows)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.columns)
def checked_state(self, index):
if not index.isValid():
return None
elif index.column() == self.CB_COL:
attr_name = self.columns[index.column()]
row = self.rows[index.row()]
return getattr(row, attr_name)
else:
return None
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
elif role == QtCore.Qt.DisplayRole:
attr_name = self.columns[index.column()]
row = self.rows[index.row()]
if index.column() == self.CB_COL:
# no text for checkbox column's
return None
else:
return getattr(row, attr_name)
elif role == QtCore.Qt.CheckStateRole:
return None
else:
return None
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
attr_name = self.columns[index.column()]
row = self.rows[index.row()]
if ((index.column() == self.CB_COL)
and (value != self.rows[index.row()].enabled)):
if value:
print "Enabled",
else:
print "Disabled",
print self.rows[index.row()].data_value
setattr(row, attr_name, value)
self.emit(QtCore.SIGNAL("dataChanged(const QModelIndex&, const QModelIndex &)"),
index, index)
return True
else:
return False
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.header[col]
return None
def flags(self, index):
if (index.column() == self.CB_COL):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
else:
return QtCore.Qt.ItemIsEnabled
if __name__ == "__main__":
# avoid crash on exit
setdestroyonexit(False)
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
有人可以解释为什么会发生这种情况(以及我如何解决)?
答案 0 :(得分:2)
您遇到了问题,因为您的MyCheckBox
课程 一个QCheckBox
(通过继承), 一个QCheckBox
通过在其init(QCheckBox
)中构建一个新的self.cb
实例。
你真的只想做一个或另一个。为了演示,我重写了MyCheckBox类,如下所示:
class MyCheckBox(QtGui.QWidget):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
# create a centered checkbox
self.cb = QtGui.QCheckBox(parent)
cbLayout = QtGui.QHBoxLayout(self)
cbLayout.addWidget(self.cb, 0, QtCore.Qt.AlignCenter)
self.cb.clicked.connect(self.amClicked)
clicked = QtCore.Signal()
def amClicked(self):
self.clicked.emit()
这解决了问题(虽然你也需要做一些其他的改变)。
请注意,您使用的点击信号需要来自MyCheckBox
而非QCheckBox
,因此我已通过amClicked
广告位将其添加到包含的类中。您无需区分模型中的data()
和checked_state()
方法,因此我将它们合并为一个:
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
elif role == QtCore.Qt.DisplayRole:
attr_name = self.columns[index.column()]
row = self.rows[index.row()]
return getattr(row, attr_name)
elif role == QtCore.Qt.CheckStateRole:
return None
else:
return None
然后代表看起来像这样。我已经安排它只提供一个编辑器,如果标志说它是可编辑的。如果没有,那么它负责绘图,所以它也必须在paint方法中做正确的事情。
class CheckBoxDelegate(QtGui.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox in every
cell of the column to which it's applied
"""
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
if not (QtCore.Qt.ItemIsEditable & index.flags()):
return None
cb = MyCheckBox(parent)
cb.clicked.connect(self.stateChanged)
return cb
def setEditorData(self, editor, index):
""" Update the value of the editor """
editor.blockSignals(True)
editor.setChecked(index.data())
editor.blockSignals(False)
def setModelData(self, editor, model, index):
""" Send data to the model """
model.setData(index, editor.isChecked(), QtCore.Qt.EditRole)
def paint(self, painter, option, index):
value = index.data()
if value:
value = QtCore.Qt.Checked
else:
value = QtCore.Qt.Unchecked
self.drawCheck(painter, option, option.rect, value)
self.drawFocus(painter, option, option.rect)
@QtCore.Slot()
def stateChanged(self):
print "sender", self.sender()
self.commitData.emit(self.sender())
另一种方法是使用继承而不是包含/委托。以下是使用该示例的示例:
class MyCheckBox(QtGui.QCheckBox):
def __init__(self, parent):
QtGui.QCheckBox.__init__(self, parent)
# Do some customisation here
# Might want to customise the paint here
# def paint(self, painter, option, index):
class CheckBoxDelegate(QtGui.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox in every
cell of the column to which it's applied
"""
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
这似乎更直接,但在这种情况下,它有几个问题。很难在MyCheckBox
类中居中绘制复选框 - 这需要我们覆盖paintEvent
并且要做到这一点需要仔细绘制。它也不会完全覆盖Delegate的绘制。所以你可以把它拿出来。但是,只有为该行创建了编辑器,它才会起作用。因此,在这种情况下,第一种解决方案可能最简单。