Как обновлять QtableView новыми данными с сохранением всех настроек (делегаты в ячейках, заливка ячеек и так далее)?
У меня есть код на PyQt5, в котором есть QTableView, содержащий множество свойств. Я хочу сделать так, чтобы экземпляр этого класса можно было обновлять посредством нового df pandas. Как это сделать? Прилагаю малую часть кода. Также в Table_widget приведена только часть функций
class Table_widget(QTableView):
def __init__(
self,
table_name: str= None,
df: Union[pd.DataFrame, QStandardItemModel, str]= None,
columns_index: list= None,
columns_width: list= None,
visible_horizont_header: bool= True,
visible_vertical_header: bool= False,
name_columns_by_df: bool= True,
show_button_filter: bool= False,
columns_non_editable: list= None,
path_save_to_Data_lake: Union[bool, str]= None,
column_condition= str,
condition: str= None,
path_picture_condition: str= None,
count_frozen_columns: int= 0,
show_grid: bool= True,
table_style: str= 'Без_стиля',
level_1_table: list= None,
wrap_cells: list= None,
button_add_row_or_column: bool= False,
visible_horizontal_scroll: bool= True,
visible_vertical_scroll: bool= True,
path_scroll_position: str= None,
max_undo_steps: int= 80,
list_total_rows: list= None,
formulas_and_cells: list= None,
auto_recalculation_formulas: bool= True):
super().__init__()
self.table_name = table_name
self.df = df
self.columns_index = columns_index
self.show_button_filter = show_button_filter
self.columns_non_editable = columns_non_editable
self.path_save_to_Data_lake = path_save_to_Data_lake
self.count_frozen_columns = count_frozen_columns
self.table_style = table_style
self.level_1_table = level_1_table # Cтолбц первого уровня сводного вида
self.path_scroll_position = path_scroll_position
self.max_undo_steps = max_undo_steps
self.list_total_rows = list_total_rows
self.border_delegate = None
self.delegate_for_columns = {}
self.shared_delegate = Custom_delegate(self)
# Установка модели
self.table_model = self.create_table_model(
self.df,
path_picture_condition,
column_condition, condition,
name_columns_by_df)
self.setModel(self.table_model)
if columns_index == None:
columns_index = []
# Закрепление левого столбца
if self.count_frozen_columns > 0:
self.count_frozen_columns += len(columns_index)
self.frozen_table = QTableView(self)
self.frozen_table.setModel(self.table_model)
self.setHorizontalScrollMode(
self.ScrollMode.ScrollPerPixel)
self.frozen_table.horizontalHeader().setVisible(
visible_horizont_header)
self.frozen_table.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Fixed)
self.frozen_table.setVerticalHeader(
self.vertical_header(
Qt.Orientation.Vertical,
button_add_row_or_column,
self))
self.frozen_table.verticalHeader().setVisible(
visible_vertical_header)
for column in range(
0, len(columns_index)):
self.frozen_table.setColumnHidden(column, True)
for column in range(
self.count_frozen_columns, len(df.columns)):
self.frozen_table.setColumnHidden(column, True)
self.frozen_table.scale_factor = 100
self.frozen_table.setSelectionModel(
self.selectionModel())
if columns_width != None: # Ширина столбцов
for column in columns_width:
self.frozen_table.setColumnWidth(
column[0]+len(columns_index),
column[1])
self.frozen_table.setHorizontalScrollBarPolicy(
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.frozen_table.setVerticalScrollBarPolicy(
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.frozen_table.setVerticalScrollMode(
self.ScrollMode.ScrollPerPixel)
self.frozen_table.verticalScrollBar().valueChanged.connect(
self.verticalScrollBar().setValue)
self.frozen_table.setShowGrid(show_grid)
self.frozen_table.show()
self.update_frozen_TableGeometry()
self.viewport().stackUnder(
self.frozen_table)
self.horizontalHeader().sectionResized.connect(
self.update_section_width)
self.verticalHeader().sectionResized.connect(
self.update_section_height)
self.verticalScrollBar().valueChanged.connect(
self.frozen_table.verticalScrollBar().setValue)
for column in self.columns_non_editable:
self.frozen_table.setItemDelegateForColumn(
column, non_column_delegate)
# Настройка таблицы
self.horizontalHeader().setVisible(
visible_horizont_header)
self.horizontal_header_ = self.horizontal_header(
QT_layout_horizontal, self.df,
button_add_row_or_column, self)
self.setHorizontalHeader(
self.horizontal_header_) # Настройка горизонтальных заголовков таблицы
self.setHorizontalScrollMode(
self.ScrollMode.ScrollPerPixel)
self.setVerticalScrollMode(
self.ScrollMode.ScrollPerPixel)
self.installEventFilter(self)
self.show_buttons_filter(
self.show_button_filter)
self.setVerticalHeader(
self.vertical_header(
Qt.Orientation.Vertical,
button_add_row_or_column,
self))
self.verticalHeader().setVisible(
visible_vertical_header)
for column in range(0, len(columns_index)):
self.setColumnHidden(column, True)
self.scale_factor = 100 # Масштабирование (приближение и отдаление) - установка начального значения
self.selected_index = None # Переменная для хранения выбранного индекса
self.delegates_for_columns = {} # Словарь для хранения делегатов (нужно на случай, когда делегаты в нескольких столбцах)
self.undo_stack = []
self.data_cut = None # Хранение вырезанных данных (для шорткатов)
self.is_cut = False # Флаг, указывающий, вырезано ли что-либо (для шорткатов)
self.table_styling(table_style)
self.show_grid(show_grid)
self.cell_style()
if wrap_cells is not None: # Перенос текста
self.word_wrap(wrap_cells)
self.model().dataChanged.connect(
lambda: self.word_wrap(wrap_cells))
if columns_width != None: # Установка ширины столбцов
for column in columns_width:
self.setColumnWidth(
column[0]+len(columns_index),
column[1])
for column in self.columns_non_editable: # Нередактируемые столбцы
self.setItemDelegateForColumn(
column, non_column_delegate)
# if list_total_rows != None:
# self.add_total_row(list_total_rows) # Этот код нужно доработать, строка "Итого" в виде отдельной таблицы не раюотает
if self.path_scroll_position != None:
self.load_scroll_position()
self.vertical_scroll_bar(
visible_vertical_scroll)
self.horizontal_scroll_bar(
visible_horizontal_scroll)
# Сигналы для работы формул таблицы
self.formula_history = {} # Словарь для хранения формул, вбитых в ячейки
if formulas_and_cells != None: # Инициализация формул, если они переданы
self.initialize_formulas(formulas_and_cells)
self.doubleClicked.connect(
lambda: self.display_formula_in_active_cell())
if auto_recalculation_formulas:
self.table_model.dataChanged.connect(
lambda: self.auto_calculation_formulas())
# Сохранение измененной таблицы в БД
self._save_timer = None
if path_save_to_Data_lake is not False:
self.model().dataChanged.connect(
lambda: self.save_table_to_Data_lake(
path_save_to_Data_lake))
def create_table_model(
self, df,
path_picture_condition: str= None,
column_condition: int= None,
condition: str= None,
name_columns_by_df: bool= True):
if isinstance(df, QStandardItemModel):
table_model = df
return table_model
elif isinstance(df, str):
return self.create_table_from_excel(df)
table_model = QStandardItemModel()
if name_columns_by_df == True:
list_columns = list(df.columns)
table_model.setHorizontalHeaderLabels(list_columns)
for row in range(df.shape[0]): # Заполнение модели данными из датафрейма
for col in range(df.shape[1]):
if path_picture_condition is not None \
and df.iloc[row, column_condition] == condition \
and col == column_condition:
item = QStandardItem()
item.setIcon(QIcon(path_picture_condition))
elif path_picture_condition is not None \
and df.iloc[row, column_condition] != condition \
and col == column_condition:
item = QStandardItem('')
else:
item = QStandardItem(str(df.iloc[row, col]))
table_model.setItem(row, col, item)
return table_model
def create_table_from_excel(self, file_path):
'''Создание таблицы эксель из файла xlsx'''
wb = dl.read_excel(file_path)
ws = wb.active
table_model = QStandardItemModel(
ws.max_row, ws.max_column)
occupied_cells = set() # Множество для отслеживания занятых ячеек
merged_cells = list(ws.merged_cells.ranges)
enumerate_iter_rows = ws.iter_rows(
min_row= 1, max_row= ws.max_row,
min_col= 1, max_col= ws.max_column)
for i, row in enumerate(enumerate_iter_rows):
for j, cell in enumerate(row):
if (i, j) in occupied_cells:
continue # Пропустить, если ячейка уже занята
col_coordinate = cell.coordinate
if any(col_coordinate in merged_range \
for merged_range in merged_cells):
for merged_range in merged_cells:
if col_coordinate in merged_range:
start_col, start_row, end_col, end_row \
= merged_range.bounds
count_row_span = end_row - start_row + 1
count_col_span = end_col - start_col + 1
if all((row_idx, col_idx) not in occupied_cells
for row_idx in range(i, i + count_row_span)
for col_idx in range(j, j + count_col_span)): # Объединение только незананятых ячеек
self.setSpan(
i, j, count_row_span, count_col_span)
for row_idx in range(i, i + count_row_span): # Добавление занятых ячеек в множество
for col_idx in range(j, j + count_col_span):
occupied_cells.add((row_idx, col_idx))
table_item = QStandardItem(str(cell.value))
if cell.font.bold:
table_item.setFont(QFont('', -1, QFont.Weight.Bold))
if cell.alignment.horizontal == "center":
table_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
table_model.setItem(i, j, table_item)
# Установка ширины столбцов виджета в соответствии с шириной колонки в эксель
letter = get_column_letter(j + 1)
column_width = ws.column_dimensions[letter].width
self.setColumnWidth(j, int(column_width * 15))
for i in range(table_model.rowCount()):
self.resizeRowToContents(i)
return table_model
def setData(self, index, value):
item = self.table_model.itemFromIndex(index)
if item is not None:
old_data = item.text() # Сохранение старого значения
self.undo_stack.append((index, old_data)) # Добавление в стек отмены
if self.max_undo_steps != None \
and len(self.undo_stack) > self.max_undo_steps: # Проверка, превышает ли размер стека максимальное количество шагов
self.undo_stack.pop(0) # Удаление самого старого элемента
item.setText(value) # Установление нового значения
def border_table(
self, target_row, target_column,
table_border_mode, line_thickness, color):
target_column = target_column \
if isinstance(target_column, list) \
else [target_column]
target_column = target_column \
+ self.columns_index
self.shared_delegate.configure_for_border({
'mode': table_border_mode,
'target_row': target_row,
'target_column': target_column,
'color': color,
'line_thickness': line_thickness})
self.setItemDelegate(self.shared_delegate)
def delegate_for_column(self, column, regex, mask):
delegate = Custom_delegate(self)
delegate.configure_for_mask(regex, mask)
column = column + len(self.columns_index)
self.setItemDelegateForColumn(column, delegate)
if hasattr(self, 'frozen_table'):
self.frozen_table.setItemDelegateForColumn(
column, delegate)
# Сохранение кажого делегата в словарь
# Это чтобы у каждого столбцы был свой делегат
self.delegate_for_columns[column] = delegate
Вот экземпляр класса. Как сделать так, чтобы передал в экземпляр новый df_residents и всё обновилось, но свойства сохранились? Я пытался сделать через повторный вызов модели, в которую передаю df_residents и это сработало - виджет обновился новыми данными, но вот все свойства, настроенные функциями, стёрлись
self.table_residents = Table_widget(
df= df_residents,
columns_width= [
[0, 50], [1, 540], [2, 120],
[3, 170], [4, 120], [5, 120],
[6, 180], [7, 180], [8, 180],
[9, 220], [10, 110]],
columns_index= ['ID_resident', 'ID_house'],
visible_horizont_header= True,
visible_vertical_header= False,
name_columns_by_df= True,
show_button_filter= True,
columns_non_editable= [0],
path_save_to_Data_lake= \
path_df_residents,
column_condition= None,
condition= None,
path_picture_condition= None,
count_frozen_columns= 1,
level_1_table= ['№ кв'],
show_grid= True,
table_style= 'Стиль_Синий_заголовок',
wrap_cells= None,
button_add_row_or_column= False,
path_scroll_position= \
f'{path_Tab_receipts}{house_name}',
max_undo_steps= 50)
self.table_residents.setFixedSize(1292, 980) # Ширина, высота
self.table_residents.delegate_for_column(
2,
'\\,d{2}\\.\\d{2}\\.\\d{4}',
'31.07.8888')
self.table_residents.delegate_for_column(
4,
'\\d{2}\\.\\d{2}\\.\\d{4}',
'31.07.8888')
self.table_residents.delegate_for_column(
5,
'\\d{2}\\.\\d{2}\\.\\d{4}',
'31.07.8888')
self.table_residents.delegate_for_column(
8,
'\\d{1}\\(\\d{3}\\)\\d{3}-\\d{2}-\\d{2}',
'0(000)000-00-00')