避免循环导入的同时,在pyqt5中的窗口之间来回切换

发布于 2025-02-04 10:41:17 字数 2669 浏览 2 评论 0 原文

因此,我有2个窗口,我希望能够在登录 mainWindow 之间切换,每个窗口 ,每个窗口都是 qwidget strong> loginui.py 和 mainui.py

通过创建MainWindow的新实例,我可以在正确的身份验证后轻松切换到MAIN。但是在MainWindow中,我想拥有一个显示登录屏幕的“断开连接”按钮。

由于两者都在不同的文件中,因此在这种情况下导入会在Python中引起循环导入错误。

我尝试使用的其他方法是:

  1. 使用信号并在中间文件中处理它们。这很好,但是当我添加更多按钮/窗口时,文件开始变得有些混乱。

  2. 将登录的实例传递到 mainWindow .__ INT __(self,login),并且仅使用 self.login.show()。这似乎是一种好方法,但是同样,当我添加越来越多的窗口时,我很害怕它可能会影响性能,因为许多实例只是在后台运行。

这些是纠正的方式还是我错过了更简单的方法


编辑:

  • login.py

    from PyQt5.QtWidgets import QWidget, QPushButton, QLineEdit
    from PyQt5.QtCore import QSize 
    from mainmenu import MainWindow
    from PyQt5 import QtWidgets
    import sys
    
    class Login(QWidget):
        def __init__(self):
            QWidget.__init__(self)
    
            self.setMinimumSize(QSize(300, 200))    
            self.setWindowTitle("Log in") 
    
            self.username = QLineEdit(self)
            self.username.move(50, 10)
            self.password = QLineEdit(self)
            self.password.move(50, 40)
            self.connect_button = QPushButton('Connect', self)
            self.connect_button.move(50, 100)
            self.connect_button.clicked.connect(self.handleConnexion)      
    
        def handleConnexion(self):
            if self.username.text() == "admin" and self.password.text()=="1":
                self.mw = MainWindow()
                self.mw.show()
                self.close()
                
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        mainWin = Login()
        mainWin.show()
        sys.exit( app.exec_() )

  • mainmenu.py.py

    from PyQt5.QtCore import QSize   
    from PyQt5.QtWidgets import QPushButton
    from PyQt5.QtWidgets import QMainWindow
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            QMainWindow.__init__(self)
    
            self.setMinimumSize(QSize(300, 200))    
            self.setWindowTitle("Main menu") 
    
            disconnect_button = QPushButton('Disconnect', self)
            disconnect_button.clicked.connect(self.handeDC)
    
        def handeDC(self):
            pass
            # here I either send a signal to handle it somewhere else
            # or 
            # if i pass the login instance in __init__, just do login.show()

So I have 2 windows I want to be able to switch between, Login and MainWindow, each one is QWidget in its own seperate file, loginUI.py and MainUI.py respectively.

I can easily switch from login to main upon correct authentification by creating a new instance of MainWindow. But in MainWindow I want to have a 'Disconnect' button that shows the Login screen.

Since both are in different files, importing in this scenario raises a circular import error in python.

Other approaches I tried are:

  1. Using signals and handling them in an intermediate file. This is fine but as I add more buttons/ windows the file started to become a bit of a mess.

  2. Passing the instance of Login to MainWindow.__init__(self, login), and just using self.login.show(). Which seems like a good way, but again as I add more and more windows, I'm scared it might affect performance as so many instances are just running in the background.

Is any of these the correcte way or am I missing an easier way


Edit:

  • login.py

    from PyQt5.QtWidgets import QWidget, QPushButton, QLineEdit
    from PyQt5.QtCore import QSize 
    from mainmenu import MainWindow
    from PyQt5 import QtWidgets
    import sys
    
    class Login(QWidget):
        def __init__(self):
            QWidget.__init__(self)
    
            self.setMinimumSize(QSize(300, 200))    
            self.setWindowTitle("Log in") 
    
            self.username = QLineEdit(self)
            self.username.move(50, 10)
            self.password = QLineEdit(self)
            self.password.move(50, 40)
            self.connect_button = QPushButton('Connect', self)
            self.connect_button.move(50, 100)
            self.connect_button.clicked.connect(self.handleConnexion)      
    
        def handleConnexion(self):
            if self.username.text() == "admin" and self.password.text()=="1":
                self.mw = MainWindow()
                self.mw.show()
                self.close()
                
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        mainWin = Login()
        mainWin.show()
        sys.exit( app.exec_() )

  • mainmenu.py

    from PyQt5.QtCore import QSize   
    from PyQt5.QtWidgets import QPushButton
    from PyQt5.QtWidgets import QMainWindow
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            QMainWindow.__init__(self)
    
            self.setMinimumSize(QSize(300, 200))    
            self.setWindowTitle("Main menu") 
    
            disconnect_button = QPushButton('Disconnect', self)
            disconnect_button.clicked.connect(self.handeDC)
    
        def handeDC(self):
            pass
            # here I either send a signal to handle it somewhere else
            # or 
            # if i pass the login instance in __init__, just do login.show()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

红衣飘飘貌似仙 2025-02-11 10:41:18

注意:由于这是一个常见的问题,我将提供更多 broad 答案,可以更好地反映对象层次结构。

因为“主”窗口是,如名称所暗示的那样, main 一个,包含它的脚本应为 main 一个,而登录窗口应被导入并最终按需要显示。

层次结构很重要:您不必考虑显示窗户的顺序,而是它们的相关性。

考虑到这一点,主脚本将:

  • 创建主窗口;
  • 如果需要,显示登录;
  • 如果登录成功,请显示主窗口;
  • 如果用户断开连接,请再次显示登录;
  • 清除用户是否更改的内容(*请参见下文);

以上还表明了为什么连续创建新的Windows实例很少是一个好主意。

登录窗口也应该是一个qdialog,它使事情变得更容易: exec() 方法是“阻止”(用于函数执行,而不是事件循环),然后等待对话框被接受或 *被拒绝。

main.py

from PyQt5.QtWidgets import *
from login import Login

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

        self.setMinimumSize(300, 200)
        self.setWindowTitle('Main menu')

        disconnect_button = QPushButton('Disconnect')
        self.setCentralWidget(disconnect_button)

        # only for a *persistent* login dialog (*see below)
        self.login = Login()

        disconnect_button.clicked.connect(self.disconnect)

    def start(self):
        # put here some function that might check for a 'previous' logged in
        # state, possibly stored using QSettings.
        # in this case, we just assume that the user has never previously
        # logged in, so we automatically show the login window; if the above
        # function returns True instead, we can safely show the main window
        logged = False

        if logged:
            self.show()
        else:
            self.showLogin()

    def disconnect(self):
        self.hide()
        self.showLogin()

    def showLogin(self):
        if self.login.exec():
            self.show()

        # alternatively (*see below):
        login = Login()
        if login.exec():
            self.show()


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.start()
    sys.exit(app.exec())

login.py

from PyQt5.QtWidgets import *

class Login(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setMinimumSize(300, 200)
        self.setWindowTitle('Log in')

        self.username = QLineEdit()
        self.password = QLineEdit()
        self.password.setEchoMode(self.password.Password)
        self.connect_button = QPushButton('Connect', enabled=False)

        layout = QGridLayout(self)
        layout.addWidget(QLabel('Username:'), 0, 0)
        layout.addWidget(self.username, 0, 1)
        layout.addWidget(QLabel('Password:'), 1, 0)
        layout.addWidget(self.password, 1, 1)
        layout.addWidget(self.connect_button, 2, 0, 1, 2)

        self.connect_button.clicked.connect(self.handleConnexion)
        self.username.textChanged.connect(self.checkFields)
        self.password.textChanged.connect(self.checkFields)

    def checkFields(self):
        if self.username.text() and self.password.text():
            self.connect_button.setEnabled(True)
        else:
            self.connect_button.setEnabled(False)

    def handleConnexion(self):
        if self.username.text() == 'admin' and self.password.text() == '1':
            self.accept()
        else:
            QMessageBox.warning(self, 'Error', 
                'Invalid username or password!')

注意:

Note: since this is a common question, I'll provide a more broad answer that better reflects the object hierarchy.

Since the "main" window is, as the name suggests, the main one, the script containing it should be the main one, while the login window should be imported and eventually shown as required.

The hierarchy is important: you don't have to consider the order in which the windows are shown, but their relevance.

Considering this, the main script will:

  • create the main window;
  • show the login if required;
  • show the main window if the login is successful;
  • show again the login if the user disconnects;
  • clear the contents if the user has changed (*see below);

The above also shows why it's rarely a good idea to continuously create new instances of windows.

The login window should also be a QDialog, which makes things easier: the exec() method is "blocking" (for the function execution, not for the event loop), and waits until the dialog is accepted or *rejected.

main.py

from PyQt5.QtWidgets import *
from login import Login

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

        self.setMinimumSize(300, 200)
        self.setWindowTitle('Main menu')

        disconnect_button = QPushButton('Disconnect')
        self.setCentralWidget(disconnect_button)

        # only for a *persistent* login dialog (*see below)
        self.login = Login()

        disconnect_button.clicked.connect(self.disconnect)

    def start(self):
        # put here some function that might check for a 'previous' logged in
        # state, possibly stored using QSettings.
        # in this case, we just assume that the user has never previously
        # logged in, so we automatically show the login window; if the above
        # function returns True instead, we can safely show the main window
        logged = False

        if logged:
            self.show()
        else:
            self.showLogin()

    def disconnect(self):
        self.hide()
        self.showLogin()

    def showLogin(self):
        if self.login.exec():
            self.show()

        # alternatively (*see below):
        login = Login()
        if login.exec():
            self.show()


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.start()
    sys.exit(app.exec())

login.py

from PyQt5.QtWidgets import *

class Login(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setMinimumSize(300, 200)
        self.setWindowTitle('Log in')

        self.username = QLineEdit()
        self.password = QLineEdit()
        self.password.setEchoMode(self.password.Password)
        self.connect_button = QPushButton('Connect', enabled=False)

        layout = QGridLayout(self)
        layout.addWidget(QLabel('Username:'), 0, 0)
        layout.addWidget(self.username, 0, 1)
        layout.addWidget(QLabel('Password:'), 1, 0)
        layout.addWidget(self.password, 1, 1)
        layout.addWidget(self.connect_button, 2, 0, 1, 2)

        self.connect_button.clicked.connect(self.handleConnexion)
        self.username.textChanged.connect(self.checkFields)
        self.password.textChanged.connect(self.checkFields)

    def checkFields(self):
        if self.username.text() and self.password.text():
            self.connect_button.setEnabled(True)
        else:
            self.connect_button.setEnabled(False)

    def handleConnexion(self):
        if self.username.text() == 'admin' and self.password.text() == '1':
            self.accept()
        else:
            QMessageBox.warning(self, 'Error', 
                'Invalid username or password!')

Notes:

  • the above code will always show the existing Login instance, so the username and password fields will "remember" the previous entries; if you don't want that, you can always call clear() on those fields by overriding the exec() (but remember to call the base implementation and return its result!); alternatively, don't create the self.login and always create a new, local instance of Login() in showLogin();
  • you shall always use layout managers, and never rely on fixed geometries;
  • QMainWindow should always have a central widget, creating children of a main window using it as the parent is discouraged (unless you really know what you're doing); if you need more widgets, use a basic QWidget, set a layout for it, add the children, and finally call setCentralWidget();
  • more complex hierarchies can require a "controller" (read more about the MVC pattern) to better organize the whole program and respect the OOP patterns; this is normally achieved by a basic class or by subclassing the QApplication;
  • about the last (*) point in my initial list, and related to what explained above: a "controller" could/should completely delete the previous "main window" (it would be both easier and safer) and eventually show a new instance whenever the user has disconnected;
满天都是小星星 2025-02-11 10:41:18

与其关闭登录在创建 mainWindow 小部件时,您可以隐藏窗口小部件,该小部件可以节省创建新实例的开销,还可以使连接的插槽保持完整。

然后,在您的 mainWindow 上,您可以创建一个 diconnected 信号,当用户单击“断开连接”按钮时,应发出该信号。

登录窗口可以侦听信号,并调用其 show 方法。

我在以下示例中进行了内联评论:

mainwindow.py

from PyQt5.QtCore import pyqtSignal  # added this

class MainWindow(QMainWindow):

    disconnected = pyqtSignal()  # added this

    def __init__(self):
        QMainWindow.__init__(self)
        ...
        disconnect_button = QPushButton('Disconnect', self)
        disconnect_button.clicked.connect(self.handeDC)

    def handeDC(self):
        # ... do some stuff
        self.disconnected.emit() # added this

login.py

class Login(QWidget):

    def __init__(self):
        QWidget.__init__(self)
        ...

    def handleConnexion(self):
        if self.username.text() == "admin" and self.password.text()=="1":
            self.mw = MainWindow()
            self.mw.disconnected.connect(self.show)  # added this
            self.mw.show()
            self.hide()   # changed this

...

Instead of closing the Login widget when creating the MainWindow widget, you could just hide the widget, which will save the overhead of creating a new instance and also keep the connected slots intact.

Then on your MainWindow you can create a diconnected signal that should be emited when the user clicks the disconnect button.

The login window can listen for the signal and call it's show method.

I made inline comments where I made changes in the example below:

mainwindow.py

from PyQt5.QtCore import pyqtSignal  # added this

class MainWindow(QMainWindow):

    disconnected = pyqtSignal()  # added this

    def __init__(self):
        QMainWindow.__init__(self)
        ...
        disconnect_button = QPushButton('Disconnect', self)
        disconnect_button.clicked.connect(self.handeDC)

    def handeDC(self):
        # ... do some stuff
        self.disconnected.emit() # added this

login.py

class Login(QWidget):

    def __init__(self):
        QWidget.__init__(self)
        ...

    def handleConnexion(self):
        if self.username.text() == "admin" and self.password.text()=="1":
            self.mw = MainWindow()
            self.mw.disconnected.connect(self.show)  # added this
            self.mw.show()
            self.hide()   # changed this

...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文