PyQt5, Невозможно использовать колесо мыши для прокрутки в QTableView, отображающем pandas фрейм данных - PullRequest
0 голосов
/ 08 апреля 2020

Я использую QtableView, похожий на этот . Все отлично работает Моя проблема в том, что я не могу прокрутить колесо мыши. Также не работает прокрутка нажатием левой кнопки мыши и перемещение полосы прокрутки.

Я могу использовать стрелки только для прокрутки вверх и вниз.

Вот код:

class PandasModel(QAbstractTableModel):

    def __init__(self, data, parent=None):
        """

        :param data: a pandas dataframe
        :param parent: 
        """
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._data = data

    def rowCount(self, parent=None):
        return len(self._data.values)

    def columnCount(self, parent=None):
        return self._data.columns.size

    def data(self, index, role=QtCore.Qt.DisplayRole):
        current_column = index.column()
        current_row = index.row()

        gold_color = QColor(235, 201, 52)
        lightred_color = QColor('#FF5858')
        knallrot = QColor('#FF0000')
        shine_yellow = QColor('#FFFF00')
        greeny = QColor(92, 235, 52)

        white_color = QColor(QtCore.Qt.white)
        black_color = QColor(QtCore.Qt.black)

        if index.isValid():
            if role == QtCore.Qt.DisplayRole:
                return str(self._data.iloc[index.row(), index.column()])

        if role == Qt.BackgroundColorRole:   # The role for backgrounbd color of a cell
            if current_column == 7:
                it = self._data.iloc[index.row(), current_column]  # Finds the specific data (second column) to test and assigns it to the variable "it"
                if 20 > it > 10:  
                    return QBrush(gold_color)  
                if it >= 20: 
                    return QBrush(shine_yellow)  

        if role == Qt.BackgroundColorRole:  # The role for backgrounbd color of a cell
            if current_column == 5:
                it = self._data.iloc[index.row(), current_column]  # Finds the specific data (second column) to test and assigns it to the variable "it"
                if  it > 100000:  
                    return QBrush(greeny)
                if 10000 > it >= 1000:  
                    return QBrush(lightred_color) 
                if it < 1000: 
                    return QBrush(knallrot) 

            if current_column == 2:
                it = self._data.iloc[index.row(), current_column]  # Finds the specific data (second column) to test and assigns it to the variable "it"
                if  it > 100000:  
                    return QBrush(greeny)
                if 10000 > it >= 1000:  
                    return QBrush(lightred_color) 
                if it < 1000: 
                    return QBrush(knallrot)    

        if role == Qt.TextColorRole:
            return QColor(39, 68, 209)

        if role == Qt.BackgroundColorRole and index == 7:
            return QColor(235, 201, 52)

        if role == Qt.FontRole and index == 7:
            return QFont("Helvetica", 12, QFont.Bold, );

        return None

    def headerData(self, rowcol, orientation, role):

        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self._data.columns[rowcol]
        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
            return self._data.index[rowcol]

        if role == Qt.BackgroundColorRole and rowcol == 7:
            return QColor(235, 201, 52) #gold
        elif role == Qt.BackgroundColorRole and rowcol == 6:
            return QColor(235, 67, 52) #red
        elif role == Qt.BackgroundColorRole and rowcol == 5:
            return QColor(235, 67, 52) #red
        elif role == Qt.BackgroundColorRole and rowcol == 4:
            return QColor(235, 67, 52) #red
        elif role == Qt.BackgroundColorRole and rowcol == 3:
            return QColor(92, 235, 52) #green
        elif role == Qt.BackgroundColorRole and rowcol == 2:
            return QColor(92, 235, 52) #green
        elif role == Qt.BackgroundColorRole and rowcol == 1:
            return QColor(92, 235, 52) #green
        return None

    def flags(self, index):
        flags = super(self.__class__, self).flags(index)
        #flags |= QtCore.Qt.ItemIsEditable
        flags |= QtCore.Qt.ItemIsSelectable
        flags |= QtCore.Qt.ItemIsEnabled
        flags |= QtCore.Qt.ItemIsDragEnabled
        flags |= QtCore.Qt.ItemIsDropEnabled
        return flags

    def sort(self, Ncol, order):
        """Sort table by given column number.
        """
        try:
            self.layoutAboutToBeChanged.emit()
            self._data = self._data.sort_values(self._data.columns[Ncol], ascending=not order)
            self.layoutChanged.emit()
        except Exception as e:
            print(e)

Из main.py он называется так:

        self.tabCrawledresult = QTabWidget()
        self.tabCrawledresult.layout = QGridLayout() 
        self.tabCrawledresult.setLayout(self.tabCrawledresult.layout)
        self.tabs.addTab(self.tabCrawledresult, "Results")

        self.view_minmax = QTableView(self.tabCrawledresult)
        self.modelminmax = gui_pandasModel_sort.PandasModel(df)
        self.view_minmax.setModel(self.modelminmax)  
        self.view_minmax.resize(1000,500)  # is it possible to fit the size to width of headers?  
        self.view_minmax.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)    
        self.view_minmax.setSortingEnabled(True)
        self.view_minmax.sortByColumn(7, Qt.DescendingOrder)
        self.view_minmax.setAlternatingRowColors(True)
        self.view_minmax.setStyleSheet("alternate-background-color: rgb(209, 209, 209)"
                           "; background-color: rgb(244, 244, 244);")

        self.view_minmax.show()

А как можно подогнать размер QTableView к ширине заголовков?

1 Ответ

0 голосов
/ 10 апреля 2020

Во-первых. Класс PandasModel правильный и не доставляет хлопот. То, что я сделал в main.py, было неправильно. Я отображаю TableView в QTabWidget и хочу иметь кнопку экспорта внизу. Поэтому мое уродливое решение состоит в том, чтобы поместить «слой» пустых QLabelWidgets на вкладку, а затем на нее поместить TableView. Вот мой полный пример с некоторыми данными.

import sys
from PyQt5 import QtCore
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QFileDialog, 
    QGridLayout, QPushButton, QTableView, QLabel, QHeaderView
import numpy as np
import pandas as pd
from functools import partial

def createDF():
    df = pd.DataFrame(np.random.randint(0,11,size=(50, 5)), columns=list(['A','B','C', 
            'D','Tree']))
    print(df)
    return df

trees = {  1: 'Arborvitae (Thuja occidentalis)',
             2: 'Black Ash (Fraxinus nigra)',
             3: 'White Ash (Fraxinus americana)',     
             4: 'Bigtooth Aspen (Populus grandidentata)',
             5: 'Quaking Aspen (Populus tremuloides)',
             6: 'Basswood (Tilia americana)', 
             7: 'American Beech (Fagus grandifolia)',  
             8: 'Black Birch (Betula lenta)',  
             9: 'Gray Birch (Betula populifolia)',  
             10: 'Paper Birch (Betula papyrifera)'} 

class PandasModel(QAbstractTableModel):

    def __init__(self, data, parent=None):
        """
        :param data: a pandas dataframe
        :param parent: 
        """
        QtCore.QAbstractTableModel.__init__(self, parent)        
        self._data = data

    def rowCount(self, parent=None):
        return len(self._data.values)

    def columnCount(self, parent=None):
        return self._data.columns.size

    def data(self, index, role=QtCore.Qt.DisplayRole):

        if index.isValid():
            if role == QtCore.Qt.DisplayRole:
                return str(self._data.iloc[index.row(), index.column()])    
        return None

    def headerData(self, rowcol, orientation, role):

        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self._data.columns[rowcol]
        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
            return self._data.index[rowcol]


    def flags(self, index):
        flags = super(self.__class__, self).flags(index)
        #flags |= QtCore.Qt.ItemIsEditable
        flags |= QtCore.Qt.ItemIsSelectable
        flags |= QtCore.Qt.ItemIsEnabled
        flags |= QtCore.Qt.ItemIsDragEnabled
        flags |= QtCore.Qt.ItemIsDropEnabled
        return flags

    def sort(self, Ncol, order):
        """Sort table by given column number.
        """
        try:
            self.layoutAboutToBeChanged.emit()
            self._data = self._data.sort_values(self._data.columns[Ncol], 
                ascending=not order)
            self.layoutChanged.emit()
        except Exception as e:
            print(e)


class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)

        self.resize(400, 600)        

        self.tabs = QTabWidget()
        self.tabs.layout = QGridLayout() 
        self.tabs.setLayout(self.tabs.layout)

        self.grid = QGridLayout()
        self.grid.addWidget(self.tabs)
        self.setLayout(self.grid)
        self.setCentralWidget(self.tabs)  

        self.df = createDF()

        self.displayDF()

    def displayDF(self):

        self.result = QTabWidget()
        self.result.layout = QGridLayout() 
        self.result.setLayout(self.result.layout)
        self.tabs.addTab(self.result, 'Dataframe')

        #empty space to put the export button in the bottom of the TableView
        positions = [(i,j) for i in range(20) for j in range(10)]
        space = '              '
        i = 0
        for position, leer in zip(positions, space): 
            emptyLabels = QLabel(leer)
            self.result.layout.addWidget(emptyLabels, *position)

        #export button     
        self.buttonExport = QPushButton("Export", self)
        self.buttonExport.clicked.connect(partial(self.writeToCSV, self.df))
        self.result.layout.addWidget(self.buttonExport, 21, 0)

        # QTableView
        self.view_minmax = QTableView(self.result)
        self.modelminmax = PandasModel(self.df)
        self.view_minmax.setModel(self.modelminmax)
        self.view_minmax.resize(360,500)    
        self.view_minmax.clicked.connect(self.onClickedRow)
        self.view_minmax.sortByColumn(4, Qt.DescendingOrder)
        self.view_minmax.show()    

    def onClickedRow(self, index=None):
        print("Click !")
        print(index.data())

    def writeToCSV(self, df):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getSaveFileName(self,"Export results to a\ 
                csv","","CSV (*.csv);;Text Files (*.txt)", options=options)
        if fileName:
            print(fileName)
            df.to_csv (fileName, index = False, header=True) 
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    mw = MainWindow()
    mw.show()
    sys.exit(app.exec_())

Так что, когда я создаю #empty space QLabelWidget и кнопку экспорта после секции #QTableView, как это, происходит странное поведение с полосой прокрутки, потому что сигналы мыши отправьте в QLabelWidgets вместо QTabelView

        # QTableView
        self.view_minmax = QTableView(self.result)
        self.modelminmax = PandasModel(self.df)
        self.view_minmax.setModel(self.modelminmax)
        self.view_minmax.resize(360,500)    
        self.view_minmax.clicked.connect(self.onClickedRow)
        self.view_minmax.sortByColumn(4, Qt.DescendingOrder)
        self.view_minmax.show() 

        #empty space to put the export button in the bottom of the TableView
        positions = [(i,j) for i in range(20) for j in range(10)]
        space = '              '
        i = 0
        for position, leer in zip(positions, space): 
            emptyLabels = QLabel(leer)
            self.result.layout.addWidget(emptyLabels, *position)

        #export button     
        self.buttonExport = QPushButton("Export", self)
        self.buttonExport.clicked.connect(partial(self.writeToCSV, self.df))
        self.result.layout.addWidget(self.buttonExport, 21, 0)

Возможно, это кому-нибудь поможет.

...