0

I use a QtableView similiar to this one. Everything works fine. My problem is that, that I can't scroll with the mousewheel. Also scrolling with pressing left mouse button and move the scrollbar don't work.

I can only use the arrows to scroll up and down.

Here is the code:

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)

From the main.py it's called like this:

        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()

And how is it possible to fit the size of the QTableView to the width of headers?

musicamante
  • 41,230
  • 6
  • 33
  • 58
sticki
  • 43
  • 1
  • 8
  • With your code I'm not able to reproduce your issue. Please provide a fully [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) (possibly with a basic test model that shows the current behavior). About the size, first of all you shouldn't ask two unrelated question in the same post; anyway, just search here on SO, as there's plenty of questions and answers about that. – musicamante Apr 08 '20 at 19:38
  • hmm, when I fill the PandasModel with a dataframe and it is too large, a scrollbar is shown but I can't use the mouse wheel and can't use the scrollbar. – sticki Apr 08 '20 at 20:47
  • Unfortunately, if you don't provide an MRE, we really can't help you. At least share the complete classes, including their methods, otherwise your question will remain unanswered. – musicamante Apr 08 '20 at 21:47
  • thank you for the link with the MRE. Working with a minimal, reproducible example I can't reproduce the 'mysterious' behavior. But doing so, I clean up some QWidgets which overlapped my PandasModel. As a consequence my mouse wheel doesn' t interact correct with the QTableView. Everything is fine now :) -.Closed.- – sticki Apr 09 '20 at 01:26
  • That's good, and it's also one of the actual reasons well written examples are required: most of the times you can realize where the problem is just by trying to explain it to others in the correct way. This is usually recognized as [Rubber duck debugging](https://it.wikipedia.org/wiki/Rubber_duck_debugging), which is a very well known way to solve problems on your own. Anyway, it could still be useful to know what was the source of your issue, and you might find it useful too as other would in the future. I'd suggest you to look for it and share it with us as your own answer. – musicamante Apr 09 '20 at 02:25

1 Answers1

0

First. The Class PandasModel is correct and make no troubles. What I have done in the main.py was wrong. I display the TableView in a QTabWidget and want to have an export button in the bottom of it. So my ugly solution is, to put a "layer" of empty QLabelWidgets on the Tab put then the TableView over it. Here is my complete example with some Data.

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_())

So when I create the #empty space QLabelWidget and export Button after the #QTableView section, like this, it comes to the strange behavior with the scrollbar because signals of mouse send to QLabelWidgets instead of the 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)

Maybe it will help someone.

sticki
  • 43
  • 1
  • 8