1 year ago

#347699

test-img

You-Min Lin

How to disable the highlight of QTableView Delegate widget-type Editor after perform the openPersistentEditor

I am developing a QTableView with ItemDelegate to display its specific widget Editor with openPersistentEditor. A pagination and filter mechanism been implemented with QSortFilterProxyModel, whenever there is a action of PageUp, PageDown or Filter changed, the program will close the previous rows of widget Editor and open new rows of widget Editor according to the "display_list" implemented.

The problem with current design is that the widget Editor will be highlighted after running the openPersistentEditor for each column cell in the display_list. I can not find a programmatic way to disable the highlighted cell.

I have tried the self.view.repaint(), self.view.update() without success. But manually bring forward/backward another window that the pyqt application MainWidonw refresh will disable the highlighted cells.

Following is the code segments of the TabWidget and CustomProxyModel been implemented.

Please note there is open_editor() and close_editor() in the class MyTabController will be triggered whenever there is a Page change action or a Filter action to be taken.

# PyQT5 and Filtering a Table Using Multiple Columns
# Refer to: https://www.onooks.com/pyqt5-and-filtering-a-table-using-multiple-columns/

import pandas as pd
from pandas_controller import *
from UI import MyTabWidget
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QTableView, QMenu, QAction, QFileDialog
from datetime import datetime

DATE_FORMAT = "%Y-%m-%d"
# Ref: https://www.programiz.com/python-programming/datetime/strptime

page_record = 20
total_record = 0
last_page = 0
current_page = 0

model_total_record = 0
model_last_page = 0

display_list = []


def count_page_num(total_record, page_record):
    page_num = int(total_record / page_record)
    mod_num = total_record % page_record
    if mod_num == 0 and page_num > 0:
        page_num -= 1
        
    return page_num 

class MyTabController(MyTabWidget):

    def __init__(self, tab, schema_df, table_name):

        super().__init__()

        self.view = tab.view
        self.comboBox = tab.comboBox
        self.lineEdit = tab.lineEdit
        self.buttonImport = tab.buttonImport
        self.buttonExport = tab.buttonExport

        self.buttonPFirst = tab.buttonPFirst
        self.buttonPLast = tab.buttonPLast
        self.buttonPPrev = tab.buttonPPrev
        self.buttonPNext = tab.buttonPNext
        
        self.schema_df = schema_df[schema_df['Table'] == table_name]
        self.widget_list = list(self.schema_df.Widget)
        self.range_list = list(self.schema_df.Range)
        
        self.buttonImport.clicked.connect(self.import_file)

    # Event Handler

    def import_file(self):
    
        global page_record
        global total_record, model_total_record
        global last_page, model_last_page
    
        print ("import_file")
        self.filename, filetype = QFileDialog.getOpenFileName(self, "Open file", "../data")

        if len(self.filename) == 0:
            print ("Please select a file")
            return
            
        self.df = pd.read_excel(self.filename)
        self.df = self.df.fillna('None')
        
        self.header = self.df.columns.to_list()
        self.indexes = self.df.index.to_list()
        self.model = PandasModel(self.df, self.header, self.indexes)
        self.proxy = CustomProxyModel()   # Customized Filter
        self.proxy.setSourceModel(self.model)
        
        self.view.setModel(self.proxy)
        self.view.setAlternatingRowColors(True)
        self.view.setSelectionBehavior(QTableView.SelectRows)
        self.view.setWordWrap(True)
        self.view.resizeColumnsToContents()
        self.view.resizeRowsToContents()
        self.view.setShowGrid(True)
        self.view.isCornerButtonEnabled()
        
        #self.view.setItemDelegateForColumn(0,DateDelegate(self.view))

        self.view.setItemDelegate(Delegate(self.widget_list, self.range_list))
        
        self.comboBox.addItems(["{0}".format(col) for col in self.header])

        self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged)
        self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
        self.buttonExport.clicked.connect(self.export_file)

        self.buttonPFirst.clicked.connect(self.change_page)
        self.buttonPLast.clicked.connect(self.change_page)
        self.buttonPPrev.clicked.connect(self.change_page)
        self.buttonPNext.clicked.connect(self.change_page)

        self.horizontalHeader = self.view.horizontalHeader()
        self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked)
        
        print ("test-1")

        total_record = self.model.rowCount()
        last_page = count_page_num(total_record, page_record)
        
        model_total_record = self.model.rowCount()
        model_last_page = count_page_num(total_record, page_record)
        
        print ("model_total_record:", model_total_record)
        print ("model_last_page:", model_last_page)
        
        self.proxy.setPage()
        self.open_editor()

        print ("test-2")

        print ("import_file completed")

        
    def export_file(self):
        output_path = '../output/'
        output_filename = output_path + self.filename
        df = pd.DataFrame(self.model._data, columns = self.header)
        df.to_excel(output_filename)
        #self.model._df.to_excel(output_filename)  

    def change_page(self):
    
        global page_record
        #global total_record
        global last_page
        global current_page
        
        button_name = self.sender().text()
        print("button clicked:", button_name)
        
        if button_name == "First":
            if current_page == 0:
                take_action = False
            else:
                new_page = 0
                take_action = True
        elif button_name == "Last":
            if current_page == last_page:
                take_action = False
            else:
                new_page = last_page
                take_action = True
        elif button_name == "Next":
            if current_page == last_page:
                take_action = False
            else:
                new_page = current_page + 1
                take_action = True
        elif button_name == "Prev":
            if current_page == 0:
                take_action = False
            else:
                take_action = True
                new_page = current_page - 1

        if take_action == True:
            print (current_page, new_page)
            prev_page = current_page
            current_page = new_page
            self.close_editor()
            self.proxy.setPage()    # Trigger the invalidateFilter()
            self.open_editor()
            #self.view.repaint()           # Refresh the Tab Widget


    def open_editor(self):
        global display_list
        
        for row in display_list:
            for column in range(self.model.columnCount()):
                index = self.proxy.index(row, column, QModelIndex())
                self.view.openPersistentEditor(index)

    def close_editor(self):
        global display_list
        
        for row in display_list:
            for column in range(self.model.columnCount()):
                index = self.proxy.index(row, column, QModelIndex())
                self.view.closePersistentEditor(index)
        

    @QtCore.pyqtSlot(int)
    def on_view_horizontalHeader_sectionClicked(self, logicalIndex):

        self.logicalIndex   = logicalIndex
        self.menuValues     = QMenu(self)
        self.signalMapper   = QtCore.QSignalMapper(self)
        self.comboBox.blockSignals(True)
        self.comboBox.setCurrentIndex(self.logicalIndex)
        self.comboBox.blockSignals(False)  # True

        #valuesUnique = self.model._df.iloc[:, self.logicalIndex].unique()
        value_list = [str(item[self.logicalIndex]) for item in self.model._data]
        valuesUnique = list(set(value_list))    # To get the unique value list
        #print (valuesUnique)
        
        actionAll = QAction("All", self)
        actionAll.triggered.connect(self.on_actionAll_triggered)
        self.menuValues.addAction(actionAll)
        self.menuValues.addSeparator()
        for actionNumber, actionName in enumerate(sorted(valuesUnique)):
            action = QAction(str(actionName), self)
            self.signalMapper.setMapping(action, actionNumber)
            action.triggered.connect(self.signalMapper.map)
            self.menuValues.addAction(action)
        self.signalMapper.mapped.connect(self.on_signalMapper_mapped)
        headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
        posY = headerPos.y() + self.horizontalHeader.height()
        posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex)

        self.menuValues.exec_(QtCore.QPoint(posX, posY))

    @QtCore.pyqtSlot()
    def on_actionAll_triggered(self):
        self.close_editor()
        filterColumn = self.logicalIndex
        self.proxy.setFilter("", filterColumn)
        font = QtGui.QFont()
        self.model.setFont(filterColumn, font)
        self.open_editor()

    @QtCore.pyqtSlot(int)
    def on_signalMapper_mapped(self, i):
        self.close_editor()
        stringAction = self.signalMapper.mapping(i).text()
        filterColumn = self.logicalIndex
        self.proxy.setFilter(stringAction, filterColumn)
        font = QtGui.QFont()
        font.setBold(True)
        self.model.setFont(filterColumn, font)
        self.open_editor()
        
    @QtCore.pyqtSlot(str)
    def on_lineEdit_textChanged(self, text):
        self.close_editor()
        self.proxy.setFilter(text, self.proxy.filterKeyColumn())
        self.open_editor()
        
    @QtCore.pyqtSlot(int)
    def on_comboBox_currentIndexChanged(self, index):
        self.close_editor()
        self.proxy.setFilterKeyColumn(index)
        self.open_editor()
        
class Delegate(QItemDelegate):
    def __init__(self, widget_list, range_list):
        QItemDelegate.__init__(self)
        self.widget_list = widget_list
        self.range_list = range_list

    def createEditor(self, parent, option, index):
        column = index.column()
        widget = self.widget_list[column]
        range_list = self.range_list[column]
        #print ("createEditor:", column, widget)
        
        if widget == "QDateEdit":
            editor = QDateEdit(parent)
            editor.setCalendarPopup(True)
            return editor

        if widget == "QSpinBox":
            editor = QSpinBox(parent)
            editor.setMinimum(int(range_list[0]))
            editor.setMaximum(int(range_list[1]))
            return editor

        if widget == "QDoubleSpinBox":
            editor = QDoubleSpinBox(parent)            
            editor.setDecimals(3)
            editor.setMinimum(float(range_list[0]))
            editor.setMaximum(float(range_list[1]))
            #editor.setSingleStep(0.1)
            return editor

        if widget == "QComboBox":
            comboBox = QComboBox(parent)
            comboBox.addItems(range_list)
            return comboBox

        # no need to check for the other columns, as Qt automatically creates a
        # QLineEdit for string values and QTimeEdit for QTime values;
        return super().createEditor(parent, option, index)
        
        
    def setEditorData(self, editor, index):
        if isinstance(editor, QDateEdit):
            #dt_str = index.data(QtCore.Qt.EditRole)           
            dt_str = index.data(QtCore.Qt.DisplayRole)  
            #print ("setEditorData, dt_str:", dt_str)
            dt = datetime.strptime(dt_str, DATE_FORMAT)
            editor.setDate(dt)
            return
        super().setEditorData(editor, index)
        
    def setModelData(self, editor, model, index):
        if isinstance(editor, QDateEdit):
            dt = editor.date().toPyDate()
            dt_str = dt.strftime(DATE_FORMAT)
            #print ("setModelData, dt_str:", dt_str)
            model.setData(index, dt_str, QtCore.Qt.EditRole)
            return
        super().setModelData(editor, model, index)

class CustomProxyModel(QSortFilterProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._filters = dict()

        self.fc = 0               # filter counter
        self.filter_flag = False
        self.filter_list = []
        
    @property
    def filters(self):
        return self._filters

    def setFilter(self, expresion, column):
        global total_record
        global last_page
        global model_total_record
        global model_last_page
        
        if expresion:
            self.filters[column] = expresion
        elif column in self.filters:
            del self.filters[column]

        if expresion:
            print ("setFilter invoke invalidateFilter()")
            total_record = model_total_record               # restore the original model total record
            last_page = model_last_page                     # restore the original model last page
            self.fc = 0                                     # reset filter counter
            self.filter_flag = True                         # Enabled to append the self.filter_list
            self.filter_list = []
            
            self.invalidateFilter()

            print ("filter counter:", self.fc)
            total_record = self.fc
            last_page = count_page_num(total_record, page_record)
            self.fc = 0
            self.filter_flag = False                         # Disabled to append the self.filter_list
        
        else:
            total_record = model_total_record
            last_page = model_last_page
            self.invalidateFilter()
            
    def setPage(self):

        self.invalidateFilter()
        
    def filterAcceptsRow(self, source_row, source_parent):
    
        global page_record
        global total_record
        #global last_page
        global current_page

        global display_list
        
        #print ("filterAcceptsRow:", source_row, source_parent)
        start_record = current_page * page_record
        end_record = start_record + page_record
        if end_record > total_record:
            end_record = total_record
        
        if len(self.filters.items()) == 0:    #None Filter Items
            # Filter by Current Page
            #print (source_row, start_record, end_record)
            if source_row < start_record:
                return False
            elif source_row >= end_record:
                return False        

        else:                                  # Filter Items
            for column, expresion in self.filters.items():
                text = self.sourceModel().index(source_row, column, source_parent).data()
                regex = QRegExp(
                    expresion, Qt.CaseInsensitive, QRegExp.RegExp
                )
                if regex.indexIn(text) == -1:
                    return False
            
                # Filter by Current Page
                #print ("Filter:", source_row, start_record, end_record)

                if self.filter_flag == True:
                    self.fc += 1                    
                    self.filter_list.append(source_row)
                
                    if self.fc < start_record:
                        return False
                    elif self.fc >= end_record:
                        return False
                else:
                    target_list = self.filter_list[start_record:end_record]
                    if source_row not in target_list:
                        return False

        display_list.append(source_row)

        return True

Tried self.view.repaint() and self.view.update() without success.

python

delegates

highlight

qtableview

0 Answers

Your Answer

Accepted video resources