2013-07-19 59 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