基于PANDAS的QAbstractTableModel实现高级TableView详细解析(四、setData与复制、黏贴)

📅 2026/6/27 3:06:42
基于PANDAS的QAbstractTableModel实现高级TableView详细解析(四、setData与复制、黏贴)
一、原理QAbstractTableModel的赋值是通过setData它有三个参数indexvalueEditRolesetData(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(fsetData 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 True2.批量操作时统一刷新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)六、下期本篇介绍了怎么来赋值下一篇我们继续延伸介绍一下怎么在实现撤销、重做以及权限管理