Change background for new or changed records

本文介绍了一种使用TableViewer在Eclipse RCP中为新记录或已更改记录设置背景颜色的方法。通过重写hashCode方法来标识对象状态的变化,并利用HashMap存储已保存记录的状态。在显示时,根据记录是否为新记录或已修改来改变其背景颜色。
TableViewer: Change background for new or changed records

1) //override hashCode() of the class that displayed in the table.
/**
* Calculate these fields that affect the object state to change
*/
public int hashCode() {
return BaseUtils.hashCode(id, name, sex, date, value);
}

2) In UI
//<PrimaryKey, hashCode>
private Map<Object, Integer> states = new HashMap<Object, Integer>();

3) //Init or Save

//After persistenting, the Person.getID() is not null.
for (Person person : persons) {
if (person.getId() != null)
states.put(person.getId(), person.hashCode());
}

4) get changed objects
/** Before persistenting, get changed objects */
List<Person> getChangedObjects(List<Person> list, Map<Object,Integer> states) {
List<Person> changes = new ArrayList<Person>();
for (Person p : list) {
Integer hashCode = states.get(p.getId());
if (p.getId() == null || hashCode == null || hashCode != p.hashCode()) {
changes.add(p);
}
}
return changes;
}

5)//class Provider implements ITableColorProvider

public Color getBackground(Object element, int columnIndex) {
Person p = (Person) element;
boolean isNew = p.getId() == null;
boolean modified = states.containsKey(p.getId()) && states.get(p.getId()) != element.hashCode();

if (isNew || modified)
return RcpUI.COLOR_YELLOW;
else
return null;
}

public Color getForeground(Object element, int columnIndex) {
return null;
}
import sys import xml.etree.ElementTree as ET from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QPushButton, QFileDialog, QLineEdit, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QWidget, QHeaderView, QAbstractItemView, QComboBox, QSizePolicy, QStyledItemDelegate) from PyQt5.QtCore import Qt, QSize, pyqtSignal, QObject from PyQt5.QtGui import QBrush, QColor, QFont import os import traceback class Communication(QObject): """用于跨类通信的信号类""" data_changed = pyqtSignal() class ComboBoxDelegate(QStyledItemDelegate): def __init__(self, parent=None, options=None): super().__init__(parent) self.options = options or [] def createEditor(self, parent, option, index): editor = QComboBox(parent) editor.setEditable(True) editor.addItems(self.options) return editor def setEditorData(self, editor, index): value = index.data(Qt.DisplayRole) if value: editor.setCurrentText(value) def setModelData(self, editor, model, index): model.setData(index, editor.currentText(), Qt.EditRole) class XMLDataManager(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("RawDataConfig编辑器") self.setGeometry(100, 100, 1400, 800) # 通信信号 self.comm = Communication() self.comm.data_changed.connect(self.handle_data_change) self.current_file = None self.original_data = [] self.display_data = [] self.temp_data = [] self.row_counter = 1 self.type_options = set() self.data_type_id_options = set() self.is_searching = False self.search_results_indices = [] self.search_text = "" self.block_updates = False # 新增:用于阻止递归更新 # 定义字体 self.normal_font = QFont() self.bold_font = QFont() self.bold_font.setBold(True) self.column_names = [ "path", "type", "variable", "id", "data_type_id", "subsystem_id", "parts", "parts_id", "attribute", "attribute_id", "mapping" ] self.required_columns = [col for col in self.column_names if col != "mapping"] self.init_ui() def init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) self.load_btn = QPushButton("加载文件") self.add_btn = QPushButton("添加记录") self.delete_btn = QPushButton("删除记录") self.save_btn = QPushButton("保存修改") self.export_btn = QPushButton("导出文件") self.search_btn = QPushButton("搜索") self.clear_btn = QPushButton("清空搜索") button_style = """ QPushButton { background-color: rgb(65, 105, 225); color: white; border: none; padding: 8px 16px; text-align: center; font-size: 14px; margin: 4px 2px; border-radius: 4px; } QPushButton:hover { background-color: rgb(55, 95, 215); } QPushButton:pressed { background-color: rgb(45, 85, 205); } """ for btn in [self.load_btn, self.export_btn, self.add_btn, self.delete_btn, self.save_btn, self.search_btn, self.clear_btn]: btn.setStyleSheet(button_style) self.search_box = QLineEdit() self.search_box.setPlaceholderText("输入搜索内容...") self.search_box.setMinimumWidth(300) self.search_box.setStyleSheet(""" QLineEdit { padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } """) self.column_combo = QComboBox() self.column_combo.setMinimumWidth(120) self.column_combo.addItem("所有列") self.column_combo.addItems(self.column_names) self.column_combo.setCurrentIndex(0) self.column_combo.setStyleSheet(""" QComboBox { padding: 6px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } """) self.table = QTableWidget() self.table.setColumnCount(len(self.column_names) + 1) headers = ["序号"] + self.column_names self.table.setHorizontalHeaderLabels(headers) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) self.table.horizontalHeader().setStretchLastSection(True) self.table.setSelectionBehavior(QTableWidget.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) # 使用cellChanged信号替代itemChanged self.table.cellChanged.connect(self.handle_cell_change) self.table.setStyleSheet(""" QTableWidget { gridline-color: #ddd; font-size: 12px; } QHeaderView::section { background-color: #f2f2f2; padding: 4px; border: 1px solid #ddd; font-weight: bold; } QTableCornerButton::section { background-color: #f2f2f2; border: 1px solid #ddd; } """) self.table.setColumnWidth(0, 60) self.table.verticalHeader().setVisible(False) self.table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) top_layout = QHBoxLayout() top_layout.addWidget(self.load_btn) top_layout.addWidget(self.add_btn) top_layout.addWidget(self.delete_btn) top_layout.addWidget(self.save_btn) top_layout.addWidget(self.export_btn) top_layout.addStretch() search_col_label = QLabel("搜索列:") search_col_label.setStyleSheet("font-size: 14px;") top_layout.addWidget(search_col_label) top_layout.addWidget(self.column_combo) search_col_label1 = QLabel("搜索内容:") search_col_label1.setStyleSheet("font-size: 14px;") top_layout.addWidget(search_col_label1) top_layout.addWidget(self.search_box) top_layout.addWidget(self.search_btn) top_layout.addWidget(self.clear_btn) main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.table) central_widget.setLayout(main_layout) self.load_btn.clicked.connect(self.load_xml) self.export_btn.clicked.connect(self.export_xml) self.add_btn.clicked.connect(self.add_record) self.delete_btn.clicked.connect(self.delete_record) self.save_btn.clicked.connect(self.save_changes) self.search_btn.clicked.connect(self.search_records) self.clear_btn.clicked.connect(self.clear_search) self.statusBar = self.statusBar() self.statusBar.setStyleSheet("background-color: #f0f0f0; padding: 4px;") def handle_cell_change(self, row, col): """处理单元格内容改变""" if self.block_updates or col == 0: # 忽略序号列的修改 return try: # 临时断开信号连接,避免递归调用 self.table.cellChanged.disconnect() self.block_updates = True col_idx = col - 1 if col_idx < 0 or col_idx >= len(self.column_names): return col_name = self.column_names[col_idx] item = self.table.item(row, col) new_value = item.text() # 更新数据 if self.is_searching: # 在搜索模式下,需要找到原始数据中的对应位置 if row < len(self.search_results_indices): original_idx = self.search_results_indices[row] if original_idx < len(self.temp_data): self.temp_data[original_idx][col_name] = new_value # 同步更新显示数据 if row < len(self.display_data): self.display_data[row][col_name] = new_value else: # 非搜索模式直接更新 if row < len(self.temp_data): self.temp_data[row][col_name] = new_value if row < len(self.display_data): self.display_data[row][col_name] = new_value # 重新应用样式 self.apply_cell_style(item, col_name, new_value) except Exception as e: print(f"Error in handle_cell_change: {traceback.format_exc()}") QMessageBox.critical(self, "错误", f"修改数据时发生错误:\n{str(e)}") finally: # 恢复信号连接 self.block_updates = False self.table.cellChanged.connect(self.handle_cell_change) def apply_cell_style(self, item, col_name, cell_text): """应用单元格样式""" # 重置样式 item.setFont(self.normal_font) item.setBackground(QColor(255, 255, 255)) # 检查必填项 if col_name in self.required_columns and not cell_text.strip(): item.setBackground(QColor(255, 200, 200)) # 如果是搜索状态且单元格文本包含搜索词,则加粗 if self.is_searching and self.search_text: lower_text = cell_text.lower() if self.search_text in lower_text: item.setFont(self.bold_font) def handle_data_change(self): """处理数据变化后的更新""" if self.block_updates: return try: self.block_updates = True self.populate_table() except Exception as e: print(f"Error in handle_data_change: {traceback.format_exc()}") finally: self.block_updates = False def load_xml(self): file_path, _ = QFileDialog.getOpenFileName( self, "选择XML文件", "", "XML文件 (*.xml)" ) if not file_path: return try: tree = ET.parse(file_path) root = tree.getroot() self.current_file = file_path file_name = os.path.basename(file_path) self.setWindowTitle(f"RawDataConfig编辑器 - {file_name}") self.table.setRowCount(0) self.original_data = [] self.display_data = [] self.temp_data = [] self.row_counter = 1 self.type_options = set() self.data_type_id_options = set() self.is_searching = False self.search_results_indices = [] self.search_text = "" for record in root.findall('Record'): row_data = {} for col_name in self.column_names: value = record.get(col_name, '') row_data[col_name] = value if col_name == 'type' and value: self.type_options.add(value) elif col_name == 'data_type_id' and value: self.data_type_id_options.add(value) self.original_data.append(row_data) self.temp_data = [row.copy() for row in self.original_data] self.display_data = self.temp_data.copy() self.populate_table() self.adjust_column_widths() self.statusBar.showMessage(f"成功加载文件: {file_path}", 3000) except Exception as e: QMessageBox.critical(self, "错误", f"加载XML文件失败:\n{str(e)}") def populate_table(self, data=None, indices=None): if data is None: data = self.display_data try: self.table.cellChanged.disconnect() except: pass self.table.setRowCount(len(data)) type_col_index = self.column_names.index('type') + 1 data_type_id_col_index = self.column_names.index('data_type_id') + 1 type_delegate = ComboBoxDelegate(self.table, sorted(self.type_options)) data_type_id_delegate = ComboBoxDelegate(self.table, sorted(self.data_type_id_options)) self.table.setItemDelegateForColumn(type_col_index, type_delegate) self.table.setItemDelegateForColumn(data_type_id_col_index, data_type_id_delegate) for row_idx, record in enumerate(data): # 保留原始序号 if indices and row_idx < len(indices): original_idx = indices[row_idx] seq_item = QTableWidgetItem(str(original_idx + 1)) else: seq_item = QTableWidgetItem(str(self.row_counter + row_idx)) seq_item.setTextAlignment(Qt.AlignCenter) seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable) seq_item.setFont(self.normal_font) self.table.setItem(row_idx, 0, seq_item) for col_idx, col_name in enumerate(self.column_names): value = record.get(col_name, '') item = QTableWidgetItem(value) self.apply_cell_style(item, col_name, value) if col_name in ['type', 'data_type_id']: item.setFlags(item.flags() | Qt.ItemIsEditable) self.table.setItem(row_idx, col_idx + 1, item) self.table.cellChanged.connect(self.handle_cell_change) def adjust_column_widths(self): self.table.setColumnWidth(0, 60) for col in range(1, self.table.columnCount()): self.table.resizeColumnToContents(col) if self.table.columnWidth(col) < 100: self.table.setColumnWidth(col, 100) def export_xml(self): if not self.temp_data: QMessageBox.warning(self, "警告", "没有数据可导出") return file_path, _ = QFileDialog.getSaveFileName( self, "导出XML文件", "", "XML文件 (*.xml)" ) if not file_path: return try: with open(file_path, 'wb') as f: f.write(b'<?xml version="1.0" ?>\n') f.write(b'<DataRecords>\n') for record in self.temp_data: attrs = ' '.join([f'{k}="{v}"' for k, v in record.items()]) f.write(f' <Record {attrs}/>\n'.encode('utf-8')) f.write(b'</DataRecords>') self.statusBar.showMessage(f"成功导出到: {file_path}", 3000) except Exception as e: QMessageBox.critical(self, "错误", f"导出XML文件失败:\n{str(e)}") def add_record(self): selected_rows = set(index.row() for index in self.table.selectedIndexes()) insert_row = self.table.rowCount() if selected_rows: insert_row = min(selected_rows) + 1 self.table.insertRow(insert_row) new_record = {col_name: "" for col_name in self.column_names} # 根据当前状态添加到不同的数据列表 if self.is_searching: # 在搜索状态下添加记录到临时数据和显示数据 self.display_data.insert(insert_row, new_record) # 找到合适的原始索引位置插入 insert_original_pos = self.search_results_indices[insert_row] if insert_row < len( self.search_results_indices) else len(self.temp_data) self.temp_data.insert(insert_original_pos, new_record) # 更新后续的搜索结果索引 self.search_results_indices = [x if x < insert_original_pos else x + 1 for x in self.search_results_indices] self.search_results_indices.insert(insert_row, insert_original_pos) else: self.display_data.insert(insert_row, new_record) self.temp_data.insert(insert_row, new_record) seq_item = QTableWidgetItem(str(self.row_counter + insert_row)) seq_item.setTextAlignment(Qt.AlignCenter) seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable) seq_item.setFont(self.normal_font) self.table.setItem(insert_row, 0, seq_item) for col_idx, col_name in enumerate(self.column_names): item = QTableWidgetItem("") self.apply_cell_style(item, col_name, "") if col_name in ['type', 'data_type_id']: item.setFlags(item.flags() | Qt.ItemIsEditable) self.table.setItem(insert_row, col_idx + 1, item) self.update_sequence_numbers() self.table.scrollToItem(self.table.item(insert_row, 0)) self.table.selectRow(insert_row) self.statusBar.showMessage("已添加新记录", 2000) def delete_record(self): selected_rows = set(index.row() for index in self.table.selectedIndexes()) if not selected_rows: QMessageBox.warning(self, "警告", "请先选择要删除的记录") return reply = QMessageBox.question(self, "确认删除", f"确定要删除选中的 {len(selected_rows)} 条记录吗?", QMessageBox.Yes | QMessageBox.No) if reply != QMessageBox.Yes: return # 按降序删除以避免索引问题 for row in sorted(selected_rows, reverse=True): self.table.removeRow(row) if row < len(self.display_data): del self.display_data[row] if self.is_searching and row < len(self.search_results_indices): # 如果是搜索状态,需要从原始数据中删除 original_idx = self.search_results_indices[row] if original_idx < len(self.temp_data): del self.temp_data[original_idx] # 更新搜索结果索引 self.search_results_indices = [x if x < original_idx else x - 1 for x in self.search_results_indices] self.search_results_indices.pop(row) elif not self.is_searching and row < len(self.temp_data): del self.temp_data[row] self.update_sequence_numbers() self.statusBar.showMessage(f"已删除 {len(selected_rows)} 条记录", 3000) def update_sequence_numbers(self): for row_idx in range(self.table.rowCount()): if self.is_searching and row_idx < len(self.search_results_indices): original_idx = self.search_results_indices[row_idx] seq_item = QTableWidgetItem(str(original_idx + 1)) else: seq_item = QTableWidgetItem(str(self.row_counter + row_idx)) seq_item.setTextAlignment(Qt.AlignCenter) seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable) seq_item.setFont(self.normal_font) self.table.setItem(row_idx, 0, seq_item) def save_changes(self): if not self.display_data: QMessageBox.warning(self, "警告", "没有数据可保存") return empty_fields = [] for row_idx in range(self.table.rowCount()): for col_idx, col_name in enumerate(self.column_names): if col_name in self.required_columns: item = self.table.item(row_idx, col_idx + 1) if item is None or not item.text().strip(): seq_item = self.table.item(row_idx, 0) row_num = seq_item.text() if seq_item else str(row_idx + 1) empty_fields.append(f"行 {row_num} 的 '{col_name}' 列") if empty_fields: error_msg = "以下必填项为空,请填写完整后保存:\n" error_msg += "\n".join(empty_fields[:10]) if len(empty_fields) > 10: error_msg += f"\n...(共{len(empty_fields)}个错误)" QMessageBox.warning(self, "保存失败", error_msg) return try: # 更新显示数据 for row_idx in range(self.table.rowCount()): while row_idx >= len(self.display_data): self.display_data.append({col_name: "" for col_name in self.column_names}) for col_idx, col_name in enumerate(self.column_names): item = self.table.item(row_idx, col_idx + 1) if item: self.display_data[row_idx][col_name] = item.text() if col_name == 'type' and item.text(): self.type_options.add(item.text()) elif col_name == 'data_type_id' and item.text(): self.data_type_id_options.add(item.text()) else: self.display_data[row_idx][col_name] = "" # 更新临时数据 if self.is_searching: # 在搜索状态下,需要特殊处理以保持数据一致性 for display_idx, original_idx in enumerate(self.search_results_indices): if display_idx < len(self.display_data) and original_idx < len(self.temp_data): for col_name in self.column_names: self.temp_data[original_idx][col_name] = self.display_data[display_idx][col_name] else: # 非搜索状态下,直接更新临时数据 self.temp_data = [row.copy() for row in self.display_data] self.statusBar.showMessage("修改已保存到内存", 2000) except Exception as e: QMessageBox.critical(self, "错误", f"保存失败: {str(e)}") def search_records(self): search_text = self.search_box.text().strip() selected_column = self.column_combo.currentText() if not search_text: QMessageBox.warning(self, "警告", "请输入搜索内容") return self.is_searching = True self.search_text = search_text.lower() self.search_results_indices = [] matched_rows = [] # 搜索基于临时数据(包含所有修改) for original_idx, record in enumerate(self.temp_data): match_found = False if selected_column == "所有列": for col_name in self.column_names: value = record.get(col_name, '').lower() if self.search_text in value: match_found = True break else: value = record.get(selected_column, '').lower() if self.search_text in value: match_found = True if match_found: self.search_results_indices.append(original_idx) matched_rows.append(record) if matched_rows: self.display_data = matched_rows self.populate_table(self.display_data) self.adjust_column_widths() self.statusBar.showMessage(f"找到 {len(matched_rows)} 条匹配记录", 3000) else: QMessageBox.information(self, "搜索结果", "未找到匹配记录") self.statusBar.showMessage("未找到匹配记录", 2000) def clear_search(self): self.display_data = [row.copy() for row in self.temp_data] self.is_searching = False self.search_text = "" self.search_box.clear() self.search_results_indices = [] self.populate_table(self.display_data) self.adjust_column_widths() self.statusBar.showMessage("已显示所有记录", 2000) if __name__ == "__main__": app = QApplication(sys.argv) window = XMLDataManager() window.show() sys.exit(app.exec_()) 去除保存按钮,在添加记录后导出文件时,如果除了mapping字段的其他数值有缺失未填,则提示。搜索后显示搜索内容时,序号为原来表格中的序号。
最新发布
08-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值