PyQt5 QTableView with QItemSelectionModel and setCursor method populated by Pandas DataFrame

20 views Asked by At

Wondering if my approach is good. Code works as intended, just want to know if there is a better approach. Want to be able to add this widget to any future project. Is there a better way to make it compatible with future projects? Or are there any other glaring problems with the code?

My 3 requirements for the table:

  1. Populate from a Pandas DataFrame.
  2. Allow for a specified column to have a selection event.
  3. Change mouse pointer to a PointingHandCursor when hovering a cell that is clickable

Thank you for any suggestions!

import sys
import pandas as pd
import sqlalchemy as sa
from PyQt5.QtWidgets import QApplication, QTableView, QMainWindow
from PyQt5.QtCore import Qt, QItemSelectionModel
from PyQt5.QtGui import QStandardItemModel, QStandardItem

class Database123:
    def __init__(self):
        self.engine = None

    def create_engine(self):
        self.engine = sa.create_engine('postgresql://postgres:pass@localhost:5432/db')

    def tbl_list(self, table_name):
        try:
            self.create_engine()
            sql = f"SELECT * FROM {table_name};"
            df = pd.read_sql_query(sql, self.engine)
            return df
        except (sa.exc.OperationalError, sa.exc.DatabaseError) as e:
            print(f"Error accessing the database: {e}")
            return pd.DataFrame()
        finally:
            if self.engine:
                self.engine.dispose()
                self.engine = None

class CellTableModel(QStandardItemModel):
    def __init__(self, data):
        
        super(CellTableModel, self).__init__()
        self._data = data
        self.setHorizontalHeaderLabels(data.columns)

        for row in range(data.shape[0]):
            for col in range(data.shape[1]):
                item = QStandardItem(str(data.iloc[row, col]))
                self.setItem(row, col, item)

class MainWindow(QMainWindow):
    def __init__(self, data):
        super().__init__()

        self.initUI(data)

    def initUI(self, data):
        # Create a table view and set it as central widget
        tableView = QTableView(self)
        self.setCentralWidget(tableView)

        # Create a CellTableModel and set it as the model for the table view
        model = CellTableModel(data)
        tableView.setModel(model)

        # Create a selection model and set it for the table view
        selectionModel = QItemSelectionModel(model)
        tableView.setSelectionModel(selectionModel)

        # Connect the selection changed signal to a custom slot
        selectionModel.selectionChanged.connect(self.selectionChanged)

        # Enable mouse tracking to receive mouse move events
        tableView.setMouseTracking(True)

        # Connect the custom slot to handle mouse move events
        tableView.mouseMoveEvent = self.mouseMoveEvent

        # Set up the main window
        self.setGeometry(100, 100, 600, 400)
        self.setWindowTitle('QTableView with Pandas DataFrame')
        self.show()

    def selectionChanged(self, selected, deselected):
        # Custom slot to handle selection changes
        selected_indexes = selected.indexes()
        for index in selected_indexes:
            # Check if the selected column is 'name'
            if index.column() == data.columns.get_loc('name'):
                print(f"Selected 'name': {index.data()}")

    def mouseMoveEvent(self, event):
        # Custom slot to handle mouse move events and change cursor
        index = self.centralWidget().indexAt(event.pos())
        if index.isValid() and index.column() == data.columns.get_loc('name'):
            self.centralWidget().setCursor(Qt.PointingHandCursor)
        else:
            self.centralWidget().setCursor(Qt.ArrowCursor)

if __name__ == '__main__':
    # create a pandas Data Frame
    list_cell = Database123()
    data = list_cell.tbl_list('cell')

    app = QApplication(sys.argv)
    ex = MainWindow(data)

    app.exec_()
    del app

I have tried multiple methods. This is the only one I could get to work.

0

There are 0 answers