Qt CheckBox 委托生成两个复选框
Qt CheckBox delegate generates two checkboxes
我正在尝试在 PySide GUI 中实现某种列表视图,让用户有机会在最终处理列表之前 enable/disable 列表的某些条目。
我决定将 QTableView 和 QAbstractTableModel 与 CheckBoxDelegate class 一起使用,后者为 table 视图中的每一行呈现一个复选框。选中和取消选中条目将相应地设置基础列表对象的启用属性。这使我可以在处理时轻松跳过条目。
我想画一个居中的复选框。因此,基于这个 SO 问题 ,我在 CheckBoxDelegate 中使用 QCheckbox 的子 class。
现在我的问题是我在第 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_())
任何人都可以解释为什么会发生这种情况(以及我该如何解决)?
你遇到问题是因为你的 MyCheckBox
class 是 一个 QCheckBox
(通过继承)和 通过在其初始化 (self.cb
).
中构造一个新的 QCheckBox
实例,拥有 个 QCheckBox
你真的只想做其中之一。只是为了演示,我像这样重写了 MyCheckBox class:
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
插槽将其添加到包含 class 中。您不需要区分模型中的 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
那么Delegate是这个样子的。如果标志说它是可编辑的,我已经安排它只提供一个编辑器。如果不是,那么它负责绘图,所以它也必须在 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())
另一种方法是使用继承而不是 inclusion/delegation。这是一个使用它的例子:
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
class 为中心的复选框 - 这需要我们覆盖 paintEvent
并且需要仔细绘制。它也不会完全覆盖委托的绘制。所以你可以把它拿出来。但是只有在为该行创建了编辑器时它才会起作用。所以第一个解决方案在这种情况下可能是最简单的。
我正在尝试在 PySide GUI 中实现某种列表视图,让用户有机会在最终处理列表之前 enable/disable 列表的某些条目。
我决定将 QTableView 和 QAbstractTableModel 与 CheckBoxDelegate class 一起使用,后者为 table 视图中的每一行呈现一个复选框。选中和取消选中条目将相应地设置基础列表对象的启用属性。这使我可以在处理时轻松跳过条目。
我想画一个居中的复选框。因此,基于这个 SO 问题 ,我在 CheckBoxDelegate 中使用 QCheckbox 的子 class。 现在我的问题是我在第 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_())
任何人都可以解释为什么会发生这种情况(以及我该如何解决)?
你遇到问题是因为你的 MyCheckBox
class 是 一个 QCheckBox
(通过继承)和 通过在其初始化 (self.cb
).
QCheckBox
实例,拥有 个 QCheckBox
你真的只想做其中之一。只是为了演示,我像这样重写了 MyCheckBox class:
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
插槽将其添加到包含 class 中。您不需要区分模型中的 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
那么Delegate是这个样子的。如果标志说它是可编辑的,我已经安排它只提供一个编辑器。如果不是,那么它负责绘图,所以它也必须在 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())
另一种方法是使用继承而不是 inclusion/delegation。这是一个使用它的例子:
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
class 为中心的复选框 - 这需要我们覆盖 paintEvent
并且需要仔细绘制。它也不会完全覆盖委托的绘制。所以你可以把它拿出来。但是只有在为该行创建了编辑器时它才会起作用。所以第一个解决方案在这种情况下可能是最简单的。