1 year ago
#347699
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