I am trying to create a tableWidget in PyQt that has copy-paste functionality and the ability to create new tabs. My table is loaded with a initialized sheet but gives the user the ability to create new tabs with new qTableWidgets which I handled using a for loop to create and initialize the widget everytime a new tab is created in my add_sheet()
function.
I wanted to also add functionality for copying and pasting inside each tab and across the tabs. Now when I added the key press event function to do this, I kept getting errors when trying to copy and paste in new tabs as out of index. I tried to fix this by keeping a pointer of which tab the selected indexes come from but this only allows me to edit on the first new tab created. The initial spreadsheet crashes when trying to do operations and the other tabs just do not work. It also does not copy and paste universally amongst the tabs.
I feel I have made my handling too complicated and I have a flaw or am missing something in my design pattern.
How can I properly implement my copy-paste function to work amongst all dynamically tabs with their own QTableWidget instances including the initial QTableWidget created both locally and universally?
import sys from PyQt6.QtWidgets import (QApplication, QMainWindow, QTableWidget, QTableWidgetItem,QVBoxLayout,QTabWidget,QWidget,QToolButton,QToolBar) from PyQt6.QtCore import Qt class MainWindow(QMainWindow): def __init__(self): super().__init__() self.tab_widget = MyTabWidget(self) self.setCentralWidget(self.tab_widget) self.initializeUI() def initializeUI(self): """Set up the application's GUI.""" self.setMinimumSize(1200, 500) self.setWindowTitle("Spreadsheet - QTableWidget Example") # Used for copy and paste actions self.item_text = None self.setUpMainWindow() self.show() def setUpMainWindow(self): """Create and arrange widgets in the main window.""" # Set initial row and column values main_spreadsheet_widget.setRowCount(10) main_spreadsheet_widget.setColumnCount(10) class MyTabWidget(QWidget): def __init__(self, parent): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) # Initialize tab screen self.sheets = QTabWidget() self.main_sheet = QWidget() self.sheets.resize(300, 200) self.extra_sheets_tracker = list() self.tab_index = [] self.copied_cells_list = [] self.paste_index = [] # Add sheets self.sheets.addTab(self.main_sheet, "Main Sheet") #self.sheets.addTab(self.tab3, "Geeks") # Create first tab self.main_sheet.layout = QVBoxLayout(self) self.main_sheet.layout.addWidget(main_spreadsheet_widget) self.main_sheet.setLayout(self.main_sheet.layout) self.tabButton = QToolButton(self) self.tabButton.setText('+') font = self.tabButton.font() font.setBold(True) self.tabButton.setFont(font) self.sheets.setCornerWidget(self.tabButton) self.tabButton.clicked.connect(self.add_sheet) # Add sheets to widget self.layout.addWidget(self.sheets) self.setLayout(self.layout) def add_sheet(self): self.sheet = QWidget() self.main_tab_sheet_widget = QTableWidget() self.extra_sheets_tracker.append(self.main_tab_sheet_widget) self.main_tab_sheet_widget.setRowCount(10) self.main_tab_sheet_widget.setColumnCount(10) self.sheet.layout = QVBoxLayout(self) self.sheet.layout.addWidget(self.main_tab_sheet_widget) self.sheet.setLayout(self.sheet.layout) self.sheets.addTab(self.main_tab_sheet_widget, "Sheet" + str(len(self.extra_sheets_tracker))) def keyPressEvent(self, event): super().keyPressEvent(event) if event.key() == Qt.Key.Key_C and (event.modifiers() & Qt.KeyboardModifier.ControlModifier): self.tab_index = [] for i in self.extra_sheets_tracker: if i.selectedIndexes() is not None: self.tab_index.append(self.extra_sheets_tracker.index(i)) self.copied_cells = sorted(self.extra_sheets_tracker[self.tab_index[0]].selectedIndexes()) self.copied_cells_list.append(self.copied_cells) self.copied_cells = None elif event.key() == Qt.Key.Key_V and (event.modifiers() & Qt.KeyboardModifier.ControlModifier): self.paste_index = [] for i in self.extra_sheets_tracker: self.paste_index.append(self.extra_sheets_tracker.index(i)) r = self.extra_sheets_tracker[self.paste_index[0]].currentRow() - self.copied_cells_list[0][0].row() c = self.extra_sheets_tracker[self.paste_index[0]].currentColumn() - self.copied_cells_list[0][0].column() for cell in self.copied_cells_list[0]: self.extra_sheets_tracker[self.paste_index[0]].setItem(cell.row() + r, cell.column() + c, QTableWidgetItem(cell.data())) if __name__ == "__main__": app = QApplication(sys.argv) main_spreadsheet_widget = QTableWidget() window = MainWindow() window.show() sys.exit(app.exec())
Advertisement
Answer
I have managed to do the following:
1- I treated the main sheet as if it was “just another sheet” and made sure it follows the same relative routine. This included removing unnecessary functions like setUpMainWindow
and introducing static values like MAX_ROWS
and MAX_COLS
for maximum rows and columns respectively. This also entailed renaming extra_sheets_tracker
to become sheets_tracker
since it will hold all of the sheets.
2- I have created two strategies for pasting: 1st-cell
for selecting the first cell in a table, or 1-to-1
for selecting the exact count of cells at the pasting side. This meant, also, checking for the dimensions beforehand and raising the exception Unmatching pasting size
if the sizes did not match.
3- I removed self.tab_index
and self.copied_cells_list
since they are not used. copy_list
is used globally and it is not a bad idea to stay in self
. However paste_list
is not used anywhere else than the paste routine and I removed it from self
. You need to look into your self
variables, you seem to overuse them.
import sys from PyQt6.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QVBoxLayout, QTabWidget, QWidget, QToolButton, QToolBar from PyQt6.QtCore import Qt class MainWindow(QMainWindow): def __init__(self): super().__init__() self.tab_widget = MyTabWidget(self) self.setCentralWidget(self.tab_widget) self.initializeUI() def initializeUI(self): """Set up the application's GUI.""" self.setMinimumSize(1200, 500) self.setWindowTitle("Spreadsheet - QTableWidget Example") # Used for copy and paste actions self.item_text = None self.show() class MyTabWidget(QWidget): def __init__(self, parent): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) # Initialize tab screen self.MAX_ROWS = 10 self.MAX_COLS = 10 self.STRATEGY = {0:'1st-cell', 1:'1-to-1'} self.sheets = QTabWidget() self.main_sheet = QWidget() self.sheets.resize(300, 200) self.sheets_tracker = list() self.copy_list = [] #self.copied_cells_list = [] #self.paste_list = [] # Add a sheet and create first tab self.main_sheet.layout = QVBoxLayout(self) # Setup main window self.add_sheet() tabButton = QToolButton(self) tabButton.setText('+') font = tabButton.font() font.setBold(True) tabButton.setFont(font) tabButton.clicked.connect(self.add_sheet) self.sheets.setCornerWidget(tabButton) # Add sheets to widget self.layout.addWidget(self.sheets) self.setLayout(self.layout) def add_sheet(self): sheets_count = len(self.sheets_tracker) if sheets_count < 1: label = "Main sheet" else: label = f"Sheet{sheets_count}" self.main_tab_sheet_widget = QTableWidget() self.main_tab_sheet_widget.setRowCount(self.MAX_ROWS) self.main_tab_sheet_widget.setColumnCount(self.MAX_COLS) self.sheets_tracker.append(self.main_tab_sheet_widget) self.sheet = QWidget() self.sheet.layout = QVBoxLayout(self) self.sheet.layout.addWidget(self.main_tab_sheet_widget) self.sheet.setLayout(self.sheet.layout) self.sheets.addTab(self.main_tab_sheet_widget, label) def keyPressEvent(self, event): super().keyPressEvent(event) if event.key() == Qt.Key.Key_C and (event.modifiers() & Qt.KeyboardModifier.ControlModifier): self.copy_list = sorted([(e.row(), e.column(), e.data()) for e in self.sheets_tracker[self.sheets.currentIndex()].selectedIndexes()]) if len(self.copy_list) != 0: # recalculate the indicies to absolute values least_row = self.copy_list[0][0] least_col = sorted(self.copy_list, key=lambda tup: tup[1])[0][1] self.copy_list = [(e[0]-least_row, e[1]-least_col, e[2]) for e in self.copy_list] print(self.copy_list) elif event.key() == Qt.Key.Key_V and (event.modifiers() & Qt.KeyboardModifier.ControlModifier): paste_list = sorted([(element.row(), element.column(), element.data()) for element in self.sheets_tracker[self.sheets.currentIndex()].selectedIndexes()]) if len(paste_list) != 0: if len(paste_list) == 1: # The given paste position is the first cell only current_r, current_c, _ = paste_list[-1] last_r, last_c, _ = self.copy_list[-1] if last_r + current_r - 1 < self.MAX_ROWS and last_c + current_c - 1 < self.MAX_COLS: strategy = self.STRATEGY[0] else: raise Exception('Unmatching pasting size') elif len(self.copy_list) == len(paste_list): # You can paste in one-to-one correspondence strategy = self.STRATEGY[1] else: raise Exception('Unmatching pasting size') if strategy == self.STRATEGY[0]: r, c, _ = paste_list[0] for index, e in enumerate(self.copy_list): _, _, d = self.copy_list[index] d = '' if d is None else d print(f"Pasting at index [{self.sheets.currentIndex()}] cell ({e[0]+r}, {e[1]+c})") textCell = QTableWidgetItem(); textCell.setText(f'{d}') self.sheets_tracker[self.sheets.currentIndex()].setItem(e[0]+r, e[1]+c, textCell) if strategy == self.STRATEGY[1]: for index, e in enumerate(paste_list): _, _, d = self.copy_list[index] d = '' if d is None else d print(f"Pasting at index [{self.sheets.currentIndex()}] cell ({e[0]}, {e[1]})") textCell = QTableWidgetItem(); textCell.setText(f'{d}') self.sheets_tracker[self.sheets.currentIndex()].setItem(e[0], e[1], textCell) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())