修改上面程序,保存修改只改内存里的不要改源文件的内容;搜索后,修改搜索到的内容表格,点击保存修改,点击清空搜索能够显示表格所有内容;搜索后,修改搜索到的内容表格,点击保存修改,再点击搜索,可以搜索到相关内容,点击清空搜索能够显示表格所有内容。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
from PyQt5.QtGui import QBrush, QColor, QFont
import os
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.current_file = None
self.table_data = []
self.original_table_data = []
self.row_counter = 1
self.type_options = set() # 存储type列的所有选项
self.data_type_id_options = set() # 存储data_type_id列的所有选项
# 定义列名列表
self.column_names = [
"path", "type", "variable", "id", "data_type_id",
"subsystem_id", "parts", "parts_id", "attribute",
"attribute_id", "mapping"
]
# 定义必填项列(除了mapping列)
self.required_columns = [col for col in self.column_names if col != "mapping"]
# 创建UI
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)
# 连接单元格更改信号(用于必填项背景色提示)
self.table.itemChanged.connect(self.update_cell_background)
# 设置表格样式
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) # 导出XML文件按钮放在保存按钮后面
top_layout.addStretch()
search_col_label = QLabel("搜索列:")
search_col_label.setStyleSheet("font-size: 14px;") # 设置字号为14像素
top_layout.addWidget(search_col_label)
top_layout.addWidget(self.column_combo)
search_col_label1 = QLabel("搜索内容:")
search_col_label1.setStyleSheet("font-size: 14px;") # 设置字号为14像素
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 update_cell_background(self, item):
"""更新单元格背景色(仅用于视觉提示,不弹出警告)"""
col_idx = item.column() - 1 # 减去序号列
if col_idx < 0 or col_idx >= len(self.column_names):
return
col_name = self.column_names[col_idx]
# 更新必填项背景色
if col_name in self.required_columns:
if not item.text().strip():
item.setBackground(QColor(255, 200, 200)) # 浅红色背景
else:
item.setBackground(QColor(255, 255, 255)) # 白色背景
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.table_data = []
self.original_table_data = []
self.row_counter = 1
self.type_options = set()
self.data_type_id_options = set()
# 解析XML并填充表格
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
# 收集type和data_type_id的值作为下拉选项
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.table_data.append(row_data)
# 保存原始数据副本
self.original_table_data = self.table_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):
# 暂时断开itemChanged信号
if hasattr(self, 'table') and self.table:
try:
self.table.itemChanged.disconnect(self.update_cell_background)
except:
pass
self.table.setRowCount(len(self.table_data))
# 为type和data_type_id列设置下拉框委托
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(self.table_data):
# 添加序号列
seq_item = QTableWidgetItem(str(self.row_counter + row_idx))
seq_item.setTextAlignment(Qt.AlignCenter)
seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable)
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)
# 设置下拉框列可编辑
if col_name in ['type', 'data_type_id']:
item.setFlags(item.flags() | Qt.ItemIsEditable)
# 设置必填项背景色
if col_name in self.required_columns and not value.strip():
item.setBackground(QColor(255, 200, 200)) # 浅红色背景
self.table.setItem(row_idx, col_idx + 1, item)
# 重新连接itemChanged信号
if hasattr(self, 'table') and self.table:
self.table.itemChanged.connect(self.update_cell_background)
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.table_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.table_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}
# 在table_data中插入新记录
self.table_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)
self.table.setItem(insert_row, 0, seq_item)
# 为新行创建数据单元格
for col_idx, col_name in enumerate(self.column_names):
item = QTableWidgetItem("")
# 设置下拉框列可编辑
if col_name in ['type', 'data_type_id']:
item.setFlags(item.flags() | Qt.ItemIsEditable)
# 设置必填项背景色(浅红色提示)
if col_name in self.required_columns:
item.setBackground(QColor(255, 200, 200))
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 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}
# 在table_data中插入新记录
self.table_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)
self.table.setItem(insert_row, 0, seq_item)
# 为新行创建数据单元格
for col_idx, col_name in enumerate(self.column_names):
item = QTableWidgetItem("")
# 设置下拉框列可编辑
if col_name in ['type', 'data_type_id']:
item.setFlags(item.flags() | Qt.ItemIsEditable)
# 设置必填项背景色(浅红色提示)
if col_name in self.required_columns:
item.setBackground(QColor(255, 200, 200))
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.table_data):
del self.table_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()):
seq_item = QTableWidgetItem(str(self.row_counter + row_idx))
seq_item.setTextAlignment(Qt.AlignCenter)
seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable)
self.table.setItem(row_idx, 0, seq_item)
def save_changes(self):
if not self.current_file:
QMessageBox.warning(self, "警告", "请先加载XML文件")
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]) # 最多显示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()):
if row_idx >= len(self.table_data):
self.table_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.table_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.table_data[row_idx][col_name] = ""
# 保存到原文件
with open(self.current_file, 'wb') as f:
f.write(b'<?xml version="1.0" ?>\n')
f.write(b'<DataRecords>\n')
for record in self.table_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.original_table_data = self.table_data.copy()
# self.row_counter = self.table.rowCount() + 1
# 更新下拉框委托
self.populate_table()
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().lower()
selected_column = self.column_combo.currentText()
if not search_text:
self.clear_search()
return
if not self.original_table_data:
self.original_table_data = self.table_data.copy()
matched_rows = []
matched_indices = []
for row_idx, record in enumerate(self.original_table_data):
match_found = False
if selected_column == "所有列":
for col_name in self.column_names:
value = record.get(col_name, '').lower()
if search_text in value:
match_found = True
break
else:
value = record.get(selected_column, '').lower()
if search_text in value:
match_found = True
if match_found:
matched_rows.append(record)
matched_indices.append(row_idx + 1)
self.table_data = matched_rows
self.populate_table_with_original_indices(matched_indices)
self.adjust_column_widths()
if matched_rows:
for row in range(self.table.rowCount()):
for col in range(1, self.table.columnCount()):
item = self.table.item(row, col)
if item:
col_name = self.column_names[col - 1]
value = item.text().lower()
if (selected_column == "所有列" and search_text in value) or \
(col_name == selected_column and search_text in value):
item.setBackground(QBrush(QColor(255, 255, 0)))
item.setFont(QFont("Arial", 10, QFont.Bold))
else:
# 恢复必填项背景色
if col_name in self.required_columns and not item.text().strip():
item.setBackground(QColor(255, 200, 200))
else:
item.setBackground(QBrush(Qt.white))
item.setFont(QFont("Arial", 9))
self.statusBar.showMessage(f"找到 {len(matched_rows)} 条匹配记录", 3000)
else:
QMessageBox.information(self, "搜索结果", "未找到匹配记录")
self.statusBar.showMessage("未找到匹配记录", 2000)
def populate_table_with_original_indices(self, indices):
# 暂时断开itemChanged信号
if hasattr(self, 'table') and self.table:
try:
self.table.itemChanged.disconnect(self.update_cell_background)
except:
pass
self.table.setRowCount(len(self.table_data))
# 为type和data_type_id列设置下拉框委托
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(self.table_data):
seq_item = QTableWidgetItem(str(indices[row_idx]))
seq_item.setTextAlignment(Qt.AlignCenter)
seq_item.setFlags(seq_item.flags() & ~Qt.ItemIsEditable)
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)
if col_name in ['type', 'data_type_id']:
item.setFlags(item.flags() | Qt.ItemIsEditable)
# 设置必填项背景色
if col_name in self.required_columns and not value.strip():
item.setBackground(QColor(255, 200, 200))
self.table.setItem(row_idx, col_idx + 1, item)
# 重新连接itemChanged信号
if hasattr(self, 'table') and self.table:
self.table.itemChanged.connect(self.update_cell_background)
def clear_search(self):
if self.original_table_data:
self.table_data = self.original_table_data.copy()
self.populate_table()
self.adjust_column_widths()
self.search_box.clear()
self.statusBar.showMessage("搜索已清除", 2000)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = XMLDataManager()
window.show()
sys.exit(app.exec_())
显示修改后的完整程序