2017-01-25 2 views
1

J'ai un QTableWidget avec des flottants ou des entrées complexes qui nécessitent beaucoup d'espace horizontal. L'affichage des valeurs avec un nombre réduit de chiffres via la mise en forme des chaînes fonctionne bien, mais il est évident que je perds la précision lors de l'édition et du stockage des entrées dans la table.QTableView n'envoie pas les événements FocusIn/FocusOut attendus à eventFilter

J'ai trouvé une solution pour les widgets QLineEdit en utilisant un eventFilter: A la valeur stockée des copies d'événement FocusIn avec toute la précision du champ de texte de QLineEdit, un événement FocusOut ou un Return_Key stocke la valeur modifiée et écrase le champ de texte avec un nombre réduit des chiffres.

En utilisant la même approche avec un QTableWidgets me donne les éléments suivants (éventuellement liés) problèmes:

  • événements FocusIn et FocusOut ne sont pas générés comme prévu: Lorsque je double-cliquez sur un élément, je reçois un FocusOut événement, en cliquant sur un autre élément produit un événement FocusIn
  • Je ne peux pas copier le contenu de mon élément édité, sélectionné, je reçois toujours la valeur non modifiée.
  • La sélection d'un élément en cliquant dessus ne produit pas d'événement.

J'ai essayé d'évaluer les événements QTableWidgetItem, mais je n'en reçois aucun. Dois-je configurer un filtre d'événements sur chaque QTableWidgetItem? Si oui, dois-je déconnecter les eventFilters QTableWidgetItem chaque fois que je redimensionne la table (ce qui est fréquent dans mon application)? Serait-il logique de remplir ma table avec des widgets QLineEdit à la place?

Le MWE ci-joint n'est pas exactement petit, mais je pourrais le réduire davantage.

# -*- coding: utf-8 -*- 
#from PyQt5.QWidgets import (...) 
from PyQt4.QtGui import (QApplication, QWidget, QTableWidget, QTableWidgetItem, 
         QLabel, QVBoxLayout) 
import PyQt4.QtCore as QtCore 
from PyQt4.QtCore import QEvent 
from numpy.random import randn 

class EventTable (QWidget): 
    def __init__(self, parent = None): 
     super(EventTable, self).__init__(parent) 
     self.myTable = QTableWidget(self) 
     self.myTable.installEventFilter(self) # route all events to self.eventFilter() 

     myQVBoxLayout = QVBoxLayout() 
     myQVBoxLayout.addWidget(self.myTable) 
     self.setLayout(myQVBoxLayout) 

     self.rows = 3; self.columns = 4 # table + data dimensions 
     self.data = randn(self.rows, self.columns) # initial data 
     self._update_table() # create table 

    def eventFilter(self, source, event): 
     if isinstance(source, (QTableWidget, QTableWidgetItem)): 
#   print(type(source).__name__, event.type()) #too much noise 
      if event.type() == QEvent.FocusIn: # 8: enter widget 
       print(type(source).__name__, "focus in") 
       self.item_edited = False 
       self._update_table_item() # focus: display data with full precision 
       return True # event processing stops here 

      elif event.type() == QEvent.KeyPress: 
       print(type(source).__name__, "key pressed") 
       self.item_edited = True # table item has been changed 
       key = event.key() # key press: 6, key release: 7 
       if key in {QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter}: # store entry 
        self._store_item() # store edited data in self.data 
        return True 
       elif key == QtCore.Qt.Key_Escape: # revert changes 
        self.item_edited = False 
        self._update_table() # update table from self.data 
        return True 

      elif event.type() == QEvent.FocusOut: # 9: leave widget 
       print(type(source).__name__, "focus out") 
       self._store_item() 
       self._update_table_item() # no focus: use reduced precision 
       return True 

     return super(EventTable, self).eventFilter(source, event) 

    def _update_table(self): 
     """(Re-)Create the table with rounded data from self.data """ 
     self.myTable.setRowCount(self.rows) 
     self.myTable.setColumnCount(self.columns) 
     for col in range(self.columns): 
      for row in range(self.rows): 
       self.myTable.setItem(row,col, 
         QTableWidgetItem(str("{:.3g}".format(self.data[row][col])))) 
     self.myTable.resizeColumnsToContents() 
     self.myTable.resizeRowsToContents() 

    def _update_table_item(self, source = None): 
     """ Re-)Create the current table item with full or reduced precision. """ 
     row = self.myTable.currentRow() 
     col = self.myTable.currentColumn() 
     item = self.myTable.item(row, col) 
     if item: # is something selected? 
      if not item.isSelected(): # no focus, show self.data[row][col] with red. precision 
       print("\n_update_item (not selected):", row, col) 
       item.setText(str("{:.3g}".format(self.data[row][col]))) 
      else: # in focus, show self.data[row][col] with full precision 
       item.setText(str(self.data[row][col])) 
       print("\n_update_item (selected):", row, col) 

    def _store_item(self): 
     """ Store the content of item in self.data """ 
     if self.item_edited: 
      row = self.myTable.currentRow() 
      col = self.myTable.currentColumn() 
      item_txt = self.myTable.item(row, col).text() 
      self.data[row][col] = float(str(item_txt)) 
      print("\n_store_entry - current item/data:", item_txt, self.data[row][col]) 



if __name__ == "__main__": 
    import sys 

    app = QApplication(sys.argv) 
    mainw = EventTable() 
    app.setActiveWindow(mainw) 
    mainw.show() 
    sys.exit(app.exec_()) 

Répondre

1

Vous allez à ce sujet dans le mauvais sens. Ces types d'utilisation sont déjà pris en charge par les API existantes, il existe donc plusieurs solutions disponibles qui sont beaucoup plus simples que ce que vous avez actuellement.

Probablement le plus simple de tous serait d'utiliser un QStyledItemDelegate et réimplémenter sa méthode dispalyText. Cela vous permettra de stocker les valeurs complètes dans la table, mais les formater différemment pour l'affichage. Lors de l'édition, la valeur totale sera toujours affiché (comme une chaîne):

from PyQt4.QtGui import (QApplication, QWidget, QTableWidget, QTableWidgetItem, 
         QLabel, QVBoxLayout,QStyledItemDelegate) 
import PyQt4.QtCore as QtCore 
from PyQt4.QtCore import QEvent 
from numpy.random import randn 

class ItemDelegate(QStyledItemDelegate): 
    def displayText(self, text, locale): 
     return "{:.3g}".format(float(text)) 

class EventTable (QWidget): 
    def __init__(self, parent = None): 
     super(EventTable, self).__init__(parent) 
     self.myTable = QTableWidget(self) 
     self.myTable.setItemDelegate(ItemDelegate(self)) 
     myQVBoxLayout = QVBoxLayout() 
     myQVBoxLayout.addWidget(self.myTable) 
     self.setLayout(myQVBoxLayout) 
     self.rows = 3; self.columns = 4 # table + data dimensions 
     self.data = randn(self.rows, self.columns) # initial data 
     self._update_table() # create table 

    def _update_table(self): 
     self.myTable.setRowCount(self.rows) 
     self.myTable.setColumnCount(self.columns) 
     for col in range(self.columns): 
      for row in range(self.rows): 
       item = QTableWidgetItem(str(self.data[row][col])) 
       self.myTable.setItem(row, col, item) 
     self.myTable.resizeColumnsToContents() 
     self.myTable.resizeRowsToContents() 

if __name__ == "__main__": 
    import sys 

    app = QApplication(sys.argv) 
    mainw = EventTable() 
    app.setActiveWindow(mainw) 
    mainw.show() 
    sys.exit(app.exec_()) 

NB: il est tentant d'utiliser item roles pour résoudre ce problème. Cependant, QTableWidgetItem et QStandardItem traitent les DisplayRole et EditRole comme un seul rôle, ce qui signifie qu'il est nécessaire de réimplémenter leurs méthodes data et setData pour obtenir les fonctionnalités requises.

+0

Une solution vraiment élégante! Je suppose, je vais devoir mordre dans [delegate_classes] (http://doc.qt.io/qt-5/model-view-programming.html#delegate-classes) ... – Chipmuenk