2013-07-19 32 views
11

我从熊猫数据框动态创建QTableView。我有示例代码here.PyQt - QTableView中复选框的列

我可以使用复选框创建表格,但是我无法获得用于反映模型数据的复选框,甚至无法进行更改。

我从以前的question以下示例代码并以@raorao答案为指导。这将显示表格中的框,但非功能正在工作。

任何人都可以提出任何更改,或者这个代码有什么问题。它为什么不反映这个模型,为什么它不能改变?从Frodon评论后更新:

做检查我的所有示例代码here.

编辑一个 修正串变成一个比较XXX == '真'

class CheckBoxDelegate(QtGui.QStyledItemDelegate): 
    """ 
    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): 
     ''' 
     Important, otherwise an editor is created if the user clicks in this cell. 
     ** Need to hook up a signal to the model 
     ''' 
     return None 

    def paint(self, painter, option, index): 
     ''' 
     Paint a checkbox without the label. 
     ''' 
     checked = index.model().data(index, QtCore.Qt.DisplayRole) == 'True' 
     check_box_style_option = QtGui.QStyleOptionButton() 

     if (index.flags() & QtCore.Qt.ItemIsEditable) > 0: 
      check_box_style_option.state |= QtGui.QStyle.State_Enabled 
     else: 
      check_box_style_option.state |= QtGui.QStyle.State_ReadOnly 

     if checked: 
      check_box_style_option.state |= QtGui.QStyle.State_On 
     else: 
      check_box_style_option.state |= QtGui.QStyle.State_Off 

     check_box_style_option.rect = self.getCheckBoxRect(option) 

     # this will not run - hasFlag does not exist 
     #if not index.model().hasFlag(index, QtCore.Qt.ItemIsEditable): 
      #check_box_style_option.state |= QtGui.QStyle.State_ReadOnly 

     check_box_style_option.state |= QtGui.QStyle.State_Enabled 

     QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_CheckBox, check_box_style_option, painter) 

    def editorEvent(self, event, model, option, index): 
     ''' 
     Change the data in the model and the state of the checkbox 
     if the user presses the left mousebutton or presses 
     Key_Space or Key_Select and this cell is editable. Otherwise do nothing. 
     ''' 
     print 'Check Box editor Event detected : ' 
     if not (index.flags() & QtCore.Qt.ItemIsEditable) > 0: 
      return False 

     print 'Check Box edior Event detected : passed first check' 
     # Do not change the checkbox-state 
     if event.type() == QtCore.QEvent.MouseButtonRelease or event.type() == QtCore.QEvent.MouseButtonDblClick: 
      if event.button() != QtCore.Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()): 
       return False 
      if event.type() == QtCore.QEvent.MouseButtonDblClick: 
       return True 
     elif event.type() == QtCore.QEvent.KeyPress: 
      if event.key() != QtCore.Qt.Key_Space and event.key() != QtCore.Qt.Key_Select: 
       return False 
      else: 
       return False 

     # Change the checkbox-state 
     self.setModelData(None, model, index) 
     return True 
+2

时这是线,以防止闪烁复选框:(index.model()数据(索引,QtCore.Qt.DisplayRole))'检查布尔='确实给出了一个布尔?因为data()的结果是一个QVariant,所以我会写'checked = index.model().data(index,QtCore.Qt.DisplayRole).toBool()'。 – Frodon

+1

有趣的是,我确实先尝试了一下,但收到了一个错误:AttributeError:'str'对象没有属性'toBool',因此在演员阵容中得到了解决。谢谢。 – drexiya

+0

在进一步调查中,演员表不正确,因为如果值不为零,它将始终返回True。我已经修改为checked = index.model()。data(index,QtCore.Qt.DisplayRole)=='True'。现在这反映了正确的数据,但并没有完全解决问题,因为它仍然不会改变。再次感谢您的评论。我会更新这个问题。 – drexiya

回答

8

I BOOL为你找到了一个解决方案。诀窍是:

  1. 写模型的setData方法
  2. 总是在data方法返回的QVariant

这是(我不得不创建一个名为Dataframe模拟类熊猫Dataframe结构。请用您的所有if has_panda语句代替):

from PyQt4 import QtCore, QtGui 

has_panda = False 
try: 
    import pandas as pd 
    has_panda = True 
except: 
    pass 

class TableModel(QtCore.QAbstractTableModel): 
    def __init__(self, parent=None, *args): 
     super(TableModel, self).__init__() 
     self.datatable = None 
     self.headerdata = None 

    def update(self, dataIn): 
     print 'Updating Model' 
     self.datatable = dataIn 
     print 'Datatable : {0}'.format(self.datatable) 
     if has_panda: 
      headers = dataIn.columns.values 
     else: 
      headers = dataIn.columns 
     header_items = [ 
        str(field) 
        for field in headers 
     ] 
     self.headerdata = header_items 
     print 'Headers' 
     print self.headerdata 

    def rowCount(self, parent=QtCore.QModelIndex()): 
     return len(self.datatable.index) 

    def columnCount(self, parent=QtCore.QModelIndex()): 
     if has_panda: 
      return len(self.datatable.columns.values) 
     else: 
      return len(self.datatable.columns) 

    def data(self, index, role=QtCore.Qt.DisplayRole): 
     if role == QtCore.Qt.DisplayRole: 
      i = index.row() 
      j = index.column() 
      return QtCore.QVariant('{0}'.format(self.datatable.iget_value(i, j))) 
     else: 
      return QtCore.QVariant() 

    def setData(self, index, value, role=QtCore.Qt.DisplayRole): 
     if index.column() == 4: 
      self.datatable.iset_value(index.row(), 4, value) 
      return value 
     return value 

    def headerData(self, col, orientation, role): 
     if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: 
      return '{0}'.format(self.headerdata[col]) 

    def flags(self, index): 
     if index.column() == 4: 
      return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled 
     else: 
      return QtCore.Qt.ItemIsEnabled 


class TableView(QtGui.QTableView): 
    """ 
    A simple table to demonstrate the QComboBox delegate. 
    """ 
    def __init__(self, *args, **kwargs): 
     QtGui.QTableView.__init__(self, *args, **kwargs) 
     self.setItemDelegateForColumn(4, CheckBoxDelegate(self)) 


class CheckBoxDelegate(QtGui.QStyledItemDelegate): 
    """ 
    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): 
     ''' 
     Important, otherwise an editor is created if the user clicks in this cell. 
     ** Need to hook up a signal to the model 
     ''' 
     return None 

    def paint(self, painter, option, index): 
     ''' 
     Paint a checkbox without the label. 
     ''' 

     checked = index.data().toBool() 
     check_box_style_option = QtGui.QStyleOptionButton() 

     if (index.flags() & QtCore.Qt.ItemIsEditable) > 0: 
      check_box_style_option.state |= QtGui.QStyle.State_Enabled 
     else: 
      check_box_style_option.state |= QtGui.QStyle.State_ReadOnly 

     if checked: 
      check_box_style_option.state |= QtGui.QStyle.State_On 
     else: 
      check_box_style_option.state |= QtGui.QStyle.State_Off 

     check_box_style_option.rect = self.getCheckBoxRect(option) 

     # this will not run - hasFlag does not exist 
     #if not index.model().hasFlag(index, QtCore.Qt.ItemIsEditable): 
      #check_box_style_option.state |= QtGui.QStyle.State_ReadOnly 

     check_box_style_option.state |= QtGui.QStyle.State_Enabled 

     QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_CheckBox, check_box_style_option, painter) 

    def editorEvent(self, event, model, option, index): 
     ''' 
     Change the data in the model and the state of the checkbox 
     if the user presses the left mousebutton or presses 
     Key_Space or Key_Select and this cell is editable. Otherwise do nothing. 
     ''' 
     print 'Check Box editor Event detected : ' 
     print event.type() 
     if not (index.flags() & QtCore.Qt.ItemIsEditable) > 0: 
      return False 

     print 'Check Box editor Event detected : passed first check' 
     # Do not change the checkbox-state 
     if event.type() == QtCore.QEvent.MouseButtonPress: 
      return False 
     if event.type() == QtCore.QEvent.MouseButtonRelease or event.type() == QtCore.QEvent.MouseButtonDblClick: 
      if event.button() != QtCore.Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()): 
       return False 
      if event.type() == QtCore.QEvent.MouseButtonDblClick: 
       return True 
     elif event.type() == QtCore.QEvent.KeyPress: 
      if event.key() != QtCore.Qt.Key_Space and event.key() != QtCore.Qt.Key_Select: 
       return False 
      else: 
       return False 

     # Change the checkbox-state 
     self.setModelData(None, model, index) 
     return True 

    def setModelData (self, editor, model, index): 
     ''' 
     The user wanted to change the old state in the opposite. 
     ''' 
     print 'SetModelData' 
     newValue = not index.data().toBool() 
     print 'New Value : {0}'.format(newValue) 
     model.setData(index, newValue, QtCore.Qt.EditRole) 

    def getCheckBoxRect(self, option): 
     check_box_style_option = QtGui.QStyleOptionButton() 
     check_box_rect = QtGui.QApplication.style().subElementRect(QtGui.QStyle.SE_CheckBoxIndicator, check_box_style_option, None) 
     check_box_point = QtCore.QPoint (option.rect.x() + 
          option.rect.width()/2 - 
          check_box_rect.width()/2, 
          option.rect.y() + 
          option.rect.height()/2 - 
          check_box_rect.height()/2) 
     return QtCore.QRect(check_box_point, check_box_rect.size()) 


############################################################################################################################### 
class Dataframe(dict): 
    def __init__(self, columns, values): 
    if len(values) != len(columns): 
     raise Exception("Bad values") 
    self.columns = columns 
    self.values = values 
    self.index = values[0] 
    super(Dataframe, self).__init__(dict(zip(columns, values))) 
    pass 

    def iget_value(self, i, j): 
    return(self.values[j][i]) 

    def iset_value(self, i, j, value): 
    self.values[j][i] = value 


if __name__=="__main__": 
    from sys import argv, exit 

    class Widget(QtGui.QWidget): 
     """ 
     A simple test widget to contain and own the model and table. 
     """ 
     def __init__(self, parent=None): 
      QtGui.QWidget.__init__(self, parent) 

      l=QtGui.QVBoxLayout(self) 
      cdf = self.get_data_frame() 
      self._tm=TableModel(self) 
      self._tm.update(cdf) 
      self._tv=TableView(self) 
      self._tv.setModel(self._tm) 
      for row in range(0, self._tm.rowCount()): 
       self._tv.openPersistentEditor(self._tm.index(row, 4)) 
      self.setGeometry(300, 300, 550, 200) 
      l.addWidget(self._tv) 

     def get_data_frame(self): 
      if has_panda: 
       df = pd.DataFrame({'Name':['a','b','c','d'], 
       'First':[2.3,5.4,3.1,7.7], 'Last':[23.4,11.2,65.3,88.8], 'Class':[1,1,2,1], 'Valid':[True, False, True, False]}) 
      else: 
       columns = ['Name', 'First', 'Last', 'Class', 'Valid'] 
       values = [['a','b','c','d'], [2.3,5.4,3.1,7.7], [23.4,11.2,65.3,88.8], [1,1,2,1], [True, False, True, False]] 
       df = Dataframe(columns, values) 
      return df 

    a=QtGui.QApplication(argv) 
    w=Widget() 
    w.show() 
    w.raise_() 
    exit(a.exec_()) 
+0

是的,我在修复数据反射之后找到了类似的解决方案。设置数据方法是明显的罪魁祸首。你的方法看起来很好,所以很高兴接受你的答案。可以做一些改进。例如,我不喜欢使setData()和flags()函数列成为特定的解决方案,但不应该很难对此进行编码。 – drexiya

+0

'AttributeError:'NoneType'对象没有'toBool''属性。很多其他的警告出现了,这可能应该重新调整,特别是适合PySide,它不使用QVariants ... – neuronet

+0

试图适应qstandarditemmodel,只有一个复选框显示,已发布后续问题:http:///stackoverflow.com/questions/31478121/view-qstandarditemmodel-with-column-of-checkboxes-in-pyside-pyqt – neuronet

2

我加了

if event.type() == QEvent.MouseButtonPress or event.type() == QEvent.MouseMove: 
     return False 

移动鼠标

+0

***好的提示+1 *** – drexiya