How to populate a QTextEdit, that is inside a QTabWidget, by opening a text file? [pyqt5, python]

Question:

I am trying to use the self.textedit = qtw.QTextEdit() widget that I declared from my MainWindow into my TextFileOpenedInNewTab class by doing this self.main_window = MainWindow() and using it like this self.main_window.textedit.setPlainText(content). It works, but it has a bug. It opens a new window. which I do not intend to happen.

How do I properly use or call a widget I declared from another class in PyQt?

import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg

import resources 
# how to import resources: https://www.youtube.com/watch?v=QdOoZ7edqXc&list=PLXlKT56RD3kBu2Wk6ajCTyBMkPIGx7O37&index=4


class TextFileOpenedInNewTab(qtw.QMainWindow):
    def __init__(self, content):
        super().__init__()

        # get the textedit from the MainWindow
        self.main_window = MainWindow()
         # text edit
        self.main_window.textedit.setPlainText(content)
        # making tabs as central widget
        self.setCentralWidget(self.main_window.textedit)

class BlankTab(qtw.QMainWindow):
    def __init__(self):
        super().__init__()
        self.textedit = qtw.QTextEdit()
        self.setCentralWidget(self.textedit)


class MainWindow(qtw.QMainWindow):
    def __init__(self):
        super().__init__()
        
        # text edit
        self.textedit = qtw.QTextEdit()
        
        # status bar
        self.statusbar = self.statusBar()
        self.statusbar.showMessage('Ready')
        
        self.setWindowTitle("Tab Widget Application")
        self.setWindowIcon(qtg.QIcon("./_icons/notepad.png"))
        
        """     Tabs        """ 
        # creating a tab widget
        self.tabs = qtw.QTabWidget()
        # making tabs closeable
        self.tabs.setTabsClosable(True)
        # this code allow the use of creating new tabs  
        self.tabs.setDocumentMode(True)
        # adding action when double clicked
        self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick)
        # adding action when tab close is requested
        self.tabs.tabCloseRequested.connect(self.close_current_tab)
        # making tabs as central widget
        self.setCentralWidget(self.tabs)
        # creating first tab
        self.add_new_tab("Untitled.txt")

        """     Menubar QMenus       """
        self.menuBar_open()
        self.menuBar_exit_file()

        self.initUI()
        self.show()
        
    def initUI(self):

        """     UI Functionalities       """
        menubar = self.menuBar()
        file_menu = menubar.addMenu('File')
        file_menu.addAction(self.open_file)
        file_menu.addSeparator()
        file_menu.addAction(self.exit_program) 

    def menuBar_open(self):
        self.open_file = qtw.QAction(qtg.QIcon(':/images/folder.png'),"Open...", self)
        self.open_file.setShortcut('Ctrl+O')
        self.open_file.setStatusTip('Open a file')
        self.open_file.triggered.connect(self.openFile)

    def menuBar_exit_file(self):
        self.exit_program = qtw.QAction(qtg.QIcon(':/images/close.png'), "Exit", self)
        self.exit_program.setShortcut('Ctrl+Q')
        self.exit_program.setStatusTip('Exit Program')
        self.exit_program.triggered.connect(self.close)


    # method for adding new tab
    def add_new_tab(self, label ="Untitled.txt"):

        # setting tab index
        index = self.tabs.addTab(BlankTab(), label)
        self.tabs.setCurrentIndex(index)


    # when double clicked is pressed on tabs
    def tab_open_doubleclick(self, index):
        
        # checking index i.e
        # No tab under the click
        if index == -1:
            # creating a new tab
            self.add_new_tab()


    # when tab is closed
    def close_current_tab(self, index):

        # if there is only one tab
        if self.tabs.count() < 2:
            # do nothing
            return
        # else remove the tab
        self.tabs.removeTab(index)


    def openFile(self):
        options = qtw.QFileDialog.Options()
        filenames, _ = qtw.QFileDialog.getOpenFileNames(
            self, 'Open a file', '',
            'All Files (*);;Python Files (*.py);;Text Files (*.txt)',
            options=options
        )
        if filenames:
            for filename in filenames:
                with open(filename, 'r') as file_o:
                    content = file_o.read()
                    self.tabs.addTab(TextFileOpenedInNewTab(str(content)), str(filename))


    def closeTab(self, index):
        tab = self.tabs.widget(index)
        tab.deleteLater()
        self.tabs.removeTab(index)


if __name__ == "__main__":
    app = qtw.QApplication.instance()
    if app is None:            
        # in every pyqt application it is required to create the object of QApplication
        app = qtw.QApplication(sys.argv)
    else:
        print('QApplication instance already exists: %s' % str(app))
        
    main = MainWindow()
    main.show()
    try:
        sys.exit(app.exec_())
    except SystemExit:
        print("Closing Window...") 
Asked By: adfinem_rising

||

Answers:

That is because you are creating a new QMainWindow every time you open a new tab.

Both your BlankTab class and TextFileOpenedInNewTab class are subclassing QMainWindow so every time their constructors are called it is creating a separate window for each of them. Both classes are unnecessary for what you are trying to achieve.

When creating a new tab the only widget constructor that needs to be called is the QTextEdit.

Try it like this: I made some additional notes in the code where I made changes

import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg

class MainWindow(qtw.QMainWindow):
    def __init__(self):
        super().__init__()
        self.textedit = qtw.QTextEdit()
        self.statusbar = self.statusBar()
        self.statusbar.showMessage('Ready')
        self.setWindowTitle("Tab Widget Application")
        self.setWindowIcon(qtg.QIcon("./_icons/notepad.png"))
        self.tabs = qtw.QTabWidget(self)
        self.tabs.setTabsClosable(True)
        self.tabs.setDocumentMode(True)
        self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick)
        self.tabs.tabCloseRequested.connect(self.close_current_tab)
        self.setCentralWidget(self.tabs)
        self.add_new_tab("Untitled.txt")
        self.menuBar_open()
        self.menuBar_exit_file()
        self.initUI()

    def initUI(self):
        menubar = self.menuBar()
        file_menu = menubar.addMenu('File')
        file_menu.addAction(self.open_file)
        file_menu.addSeparator()
        file_menu.addAction(self.exit_program)

    def menuBar_open(self):
        self.open_file = qtw.QAction(qtg.QIcon(':/images/folder.png'),"Open...", self)
        self.open_file.setShortcut('Ctrl+O')
        self.open_file.setStatusTip('Open a file')
        self.open_file.triggered.connect(self.openFile)

    def menuBar_exit_file(self):
        self.exit_program = qtw.QAction(qtg.QIcon(':/images/close.png'), "Exit", self)
        self.exit_program.setShortcut('Ctrl+Q')
        self.exit_program.setStatusTip('Exit Program')
        self.exit_program.triggered.connect(self.close)

    def add_new_tab(self, label ="Untitled.txt"):
        index = self.tabs.addTab(qtw.QTextEdit(), label)  # create a new blank text edit widget
        self.tabs.setCurrentIndex(index)

    def tab_open_doubleclick(self, index):
        if index == -1:
            self.add_new_tab()

    def close_current_tab(self, index):
        if self.tabs.count() < 2:
            return
        self.tabs.removeTab(index)


    def openFile(self):
        options = qtw.QFileDialog.Options()
        filenames, _ = qtw.QFileDialog.getOpenFileNames(
            self, 'Open a file', '',
            'All Files (*);;Python Files (*.py);;Text Files (*.txt)',
            options=options
        )
        if filenames:
            for filename in filenames:
                with open(filename, 'r') as file_o:
                    content = file_o.read()
                    editor = qtw.QTextEdit()   # construct new text edit widget
                    self.tabs.addTab(editor, str(filename))   # use that widget as the new tab
                    editor.setPlainText(content)  # set the contents of the file as the text


    def closeTab(self, index):
        tab = self.tabs.widget(index)
        tab.deleteLater()
        self.tabs.removeTab(index)


if __name__ == "__main__":
    app = qtw.QApplication(sys.argv)
    main = MainWindow()
    main.show()
    sys.exit(app.exec_())
Answered By: Alexander

EDIT (8/25/2022): i merged the solution above with the following functionalities:

  • new tab
  • close tab
  • save file

(since these things are implicitly needed for what the question is trying to achieve)

  • open tab

(modified the openFile method to match with the new changes. thanks to the insightful comments by the man, the myth, the legend himself, Alexander

the improvements start with these lines of codes:

    import sys
    from PyQt5 import QtWidgets as qtw
    from PyQt5 import QtCore as qtc
    from PyQt5 import QtGui as qtg
    
    class MainWindow(qtw.QMainWindow):
        def __init__(self):
            super().__init__()
    
            self.current_editor = self.create_editor()  # declares a variable for the create_editor method
            self.text_editors = [] # an array that stores the indexes of newly created QTextEdit object
    
            """ removed codes that are not part of the explanation"""

            self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick)
            self.tabs.tabCloseRequested.connect(self.remove_editor)
            self.tabs.currentChanged.connect(self.change_text_editor) # whenever a different tab is clicked the change_text_editor method is called
            self.setCentralWidget(self.tabs) 

this method creates and returns a new QTextEdit widget (or object)

    def create_editor(self):
        textedit = qtw.QTextEdit()
        return textedit

this method returns the index of the of the currently selected tab’s QTextEdit stored in the self.text_editors array

  def change_text_editor(self, index):
        if index < len(self.text_editors):
            self.current_editor = self.text_editors[index]

now, since we are able to keep tabs on exactly which QTextEdit is currently selected at a given time. we would now be capable to do whatever we want with it by just calling self.current_editor, as if it was just some normal QTextEdit without the tab functionality.

here is the full runnable code:

import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg

class MainWindow(qtw.QMainWindow):
    def __init__(self):
        super().__init__()

        self.current_editor = self.create_editor()  # declares a variable for the create_editor method
        self.text_editors = [] # an array that stores the indexes of newly created tabs

        self.statusbar = self.statusBar()
        self.statusbar.showMessage('Ready')
        self.setWindowTitle("Notepad with tabs")
        self.setWindowIcon(qtg.QIcon("./_icons/notepad.png"))
        self.tabs = qtw.QTabWidget(self)
        self.tabs.setTabsClosable(True)
        self.tabs.setDocumentMode(True)
        self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick)
        self.tabs.tabCloseRequested.connect(self.remove_editor)
        self.tabs.currentChanged.connect(self.change_text_editor) # whenever a different tab is clicked the change_text_editor method is called
        self.setCentralWidget(self.tabs)
        
        self.newFile()
        
        self.menuBar_new()
        self.menuBar_open()
        self.menuBar_close()
        self.menuBar_save()
        self.menuBar_exit_program()
        self.initUI()

    def initUI(self):
        menubar = self.menuBar()
        file_menu = menubar.addMenu('File')
        file_menu.addAction(self.new_file)
        file_menu.addAction(self.open_file)
        file_menu.addAction(self.save_file)
        file_menu.addSeparator()
        file_menu.addAction(self.exit_program)

    def create_editor(self):
        textedit = qtw.QTextEdit()
        return textedit

    def change_text_editor(self, index):
        if index < len(self.text_editors):
            self.current_editor = self.text_editors[index]

    def menuBar_new(self):
        self.new_file = qtw.QAction(qtg.QIcon(':/images/folder.png'),"New", self)
        self.new_file.setShortcut('Ctrl+N')
        self.new_file.setStatusTip('New file')
        self.new_file.triggered.connect(self.newFile)

    def menuBar_open(self):
        self.open_file = qtw.QAction(qtg.QIcon(':/images/folder.png'),"Open", self)
        self.open_file.setShortcut('Ctrl+O')
        self.open_file.setStatusTip('Open a file')
        self.open_file.triggered.connect(self.openFile)
    
    def menuBar_save(self):
        self.save_file = qtw.QAction(qtg.QIcon(':/images/folder.png'),"Save", self)
        self.save_file.setShortcut('Ctrl+S')
        self.save_file.setStatusTip('Save a file')
        self.save_file.triggered.connect(self.saveFile)

    def menuBar_close(self):
        close_tab = qtw.QShortcut(qtg.QKeySequence("Ctrl+W"), self)
        close_tab.activated.connect(lambda:self.remove_editor(self.tabs.currentIndex()))

    def menuBar_exit_program(self):
        self.exit_program = qtw.QAction(qtg.QIcon(':/images/close.png'), "Exit", self)
        self.exit_program.setShortcut('Ctrl+Q')
        self.exit_program.setStatusTip('Exit Program')
        self.exit_program.triggered.connect(self.close)


    def remove_editor(self, index):
        if self.tabs.count() < 2:
            return
        self.tabs.removeTab(index)
        if index < len(self.text_editors):
            del self.text_editors[index]

    def create_editor(self):
        text_editor = qtw.QTextEdit()
        return text_editor

    def change_text_editor(self, index):
        if index < len(self.text_editors):
            self.current_editor = self.text_editors[index]

    # Input Functions
    def newFile(self, checked = False, title = "Untitled.txt"):
        self.current_editor = self.create_editor()
        self.text_editors.append(self.current_editor)
        self.tabs.addTab(self.current_editor, title)
        self.tabs.setCurrentWidget(self.current_editor)

    def tab_open_doubleclick(self, index):
        if index == -1:
            self.newFile()

    def openFile(self):
        options = qtw.QFileDialog.Options()
        filenames, _ = qtw.QFileDialog.getOpenFileNames(
            self, "Open a file", "",
            "All Files (*);;Python Files (*.py);;Text Files (*.txt)",
            options=options
        )
        if filenames:
            for filename in filenames:
                with open(filename, "r") as file_o:
                    content = file_o.read()
                    self.current_editor = self.create_editor() # similar to what the newFile method is doing
                    # editor = qtw.QTextEdit()  # construct new text edit widget
                    currentIndex = self.tabs.addTab(self.current_editor, str(filename))   # use that widget as the new tab
                    self.current_editor.setPlainText(content)  # set the contents of the file as the text
                    self.tabs.setCurrentIndex(currentIndex) # make current opened tab be on focus
    
        
    
    def saveFile(self):
        text = self.current_editor.toPlainText()
        filename, _ = qtw.QFileDialog.getSaveFileName(self, 'Save file', None, 'Text files(*.txt)')
        if filename:
            with open(filename, "w") as handle:
                handle.write(text)
                print(self.tabs.currentIndex())
                print(str(filename))
                self.tabs.setTabText(self.tabs.currentIndex(), str(filename)) # renames the current tab with the filename
                self.statusBar().showMessage(f"Saved to {filename}")


if __name__ == "__main__":
    app = qtw.QApplication(sys.argv)
    main = MainWindow()
    main.resize(650,500)
    main.setMinimumSize(550,450)
    main.show()
    sys.exit(app.exec_())

hope it helps 🙂 thanks again to mr. Alexander for helping me with the codes. i would have never have figured it out without his insightful help

Answered By: adfinem_rising