基于PANDAS的QAbstractTableModel实现高级TableView详细解析(四、setData与复制、黏贴)
2026/6/27 3:06:31 网站建设 项目流程

一、原理

QAbstractTableModel的赋值是通过setData,它有三个参数index,value,EditRole

setData(self, index: QModelIndex, value: Any, role: int = Qt.ItemDataRole.EditRole) -> bool:

分别对应赋值位置、值、角色(目前我能用到的两种EditRole、CheckStateRole),返回True代表允许赋值,反之则是拒绝;后面篇章要讲的权限也是和setData有关;下面是相关代码,里面包含了复选框状态的赋值方法

def setData(self, index: QModelIndex, value: Any, role: int = Qt.ItemDataRole.EditRole) -> bool: """处理数据编辑操作,位置自动切换至真实点位""" if not index.isValid() or self._full_data.empty: return False row,col = index.row(),index.column() if row >= len(self._current_slice_index): return False idx = self._current_slice_index[row] real_row = self._visible_row_map[row] old_value = self._full_data.iat[real_row, col] col_name = self._columns[col] try: #更新复选框 if role == Qt.ItemDataRole.CheckStateRole: if self.checkbox_status and not self.row_select_enable and col == 0: new_value = 2 if Qt.CheckState(value) == Qt.CheckState.Checked else 0 self._full_data.iat[real_row, 0] = new_value self.dataChanged.emit(index, index) return True elif self.checkbox_status and self.row_select_enable: current = self._full_data.iat[real_row, 0] new_value = 0 if current == 2 else 2 self._full_data.iat[real_row, 0] = new_value checkbox_index = self.index(row, 0) self.dataChanged.emit(checkbox_index, checkbox_index) return True if role == Qt.ItemDataRole.EditRole: if not self.permission.can_edit(idx, col_name): return False self._full_data.iat[real_row, col] = value self.dataChanged.emit(index, index) #通知视图变更 self.valueChanged.emit(row, col, value) return True except Exception as e: self.errorOccurred.emit(f"setData error: {str(e)}") return False return False

二 、优化

之前的文章有讲过,视图的更新会消耗系统资源,因此优化点就有两个方向:

1.重复值跳过

def _should_update_value(self,a, b): """检查是否相同""" a_is_missing = pd.isna(a) # 检查 a 是否为缺失值 b_is_missing = pd.isna(b) # 检查 b 是否为缺失值 if a_is_missing and b_is_missing: # 情况1:两个都是缺失值 return False if a_is_missing or b_is_missing: # 情况2:一个是缺失值,另一个不是 return True try: return a != b except: return True

2.批量操作时统一刷新

def set_cells_data(self, changes: list[dict]): """ 批量修改单元格 参数: changes = [{"row": 1,"col": 2,"value": "abc"},] """ if not changes: return changed_indexes = [] for item in changes: row = item["row"] col = item["col"] value = item["value"] index = self.index(row, col) if not index.isValid(): continue old_value = self.data(index, Qt.DisplayRole) if old_value == value: continue self.setData(index,value,Qt.EditRole) changed_indexes.append(index) if changed_indexes: top_left = changed_indexes[0] bottom_right = changed_indexes[-1] self.dataChanged.emit(top_left,bottom_right,[Qt.DisplayRole])

三、复制

复制的本质就是按照选择的索引顺序获取数据,生成二维数组

def copy_selected(self): """ 复制选中区域 """ indexes = self.view.tableView.selectedIndexes() if not indexes: return text = self._build_copy_text(indexes) QtWidgets.QApplication.clipboard().setText(text) def _build_copy_text(self, indexes): """ 构建复制文本 """ rows = sorted(index.row() for index in indexes) cols = sorted(index.column() for index in indexes) min_row = rows[0] min_col = cols[0] row_count = rows[-1] - min_row + 1 col_count = cols[-1] - min_col + 1 table = [[''] * col_count for _ in range(row_count)] for index in indexes: r = index.row() - min_row c = index.column() - min_col value = index.data() table[r][c] = '' if value is None else str(value) return '\n'.join( '\t'.join(row) for row in table )

四、黏贴

黏贴就是反过来将数据写入到模型中,但因懒加载的缘故我们需要添加上原始位置的计算

def paste_clipboard(self): """ 粘贴数据 """ clipboard = QtWidgets.QApplication.clipboard() text = clipboard.text() if not text: return paste_data = self._parse_clipboard_data(text) if not paste_data: return anchor = self._get_selection_anchor() if anchor is None: return start_row, start_col = anchor changes = [] for row_offset, row_data in enumerate(paste_data): for col_offset, value in enumerate(row_data): row = start_row + row_offset col = start_col + col_offset if not self.model.index(row, col).isValid(): continue changes.append( { "row": row, "col": col, "value": value } ) if not changes: return self.model.set_cells_data(changes) def _parse_clipboard_data(self, text: str): """ 解析剪贴板数据 Excel复制格式: A\tB\tC 1\t2\t3 """ rows = [] for line in text.splitlines(): if not line.strip(): continue rows.append(line.split('\t')) return rows def _get_selection_anchor(self): """ 获取粘贴起始点 """ indexes = self.view.tableView.selectedIndexes() if not indexes: return None start_row = min(index.row() for index in indexes) start_col = min(index.column() for index in indexes) return start_row, start_col

五、清除

清除的实现逻辑也很简单,将NONE填充到选中区域内

def clear_selected(self): """ 清空选中区域 """ indexes = self.view.tableView.selectedIndexes() if not indexes: return changes = [] for index in indexes: changes.append( { "row": index.row(), "col": index.column(), "value": None } ) self.model.set_cells_data(changes)

六、下期

本篇介绍了怎么来赋值,下一篇我们继续延伸,介绍一下怎么在实现撤销、重做以及权限管理

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询