簡體   English   中英

pyqt5中windows之間來回切換同時避免循環導入

[英]Switch back and forth between windows in pyqt5 while avoiding circular imports

所以我有 2 個我希望能夠在LoginMainWindow之間切換的窗口,每個窗口都是QWidget在它自己的單獨文件loginUI.pyMainUI.py中。

通過創建 MainWindow 的新實例,我可以在正確的身份驗證后輕松地從登錄切換到主。 但在 MainWindow 中,我想要一個顯示登錄屏幕的“斷開連接”按鈕。

由於兩者都在不同的文件中,因此在這種情況下導入會在 python 中引發循環導入錯誤。

我嘗試的其他方法是:

  1. 使用信號並在中間文件中處理它們。 這很好,但是當我添加更多按鈕/窗口時,文件開始變得有點混亂。

  2. 將 Login 的實例傳遞給MainWindow.__init__(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

    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()

在創建MainWindow小部件時不必關閉Login小部件,您可以只隱藏該小部件,這將節省創建新實例的開銷並保持連接的插槽完好無損。

然后在您的MainWindow上,您可以創建一個diconnected連接的信號,當用戶單擊斷開連接按鈕時應該發出該信號。

登錄窗口可以監聽信號並調用它的show方法。

我在下面的示例中進行了更改的地方做了內聯注釋:

主窗口.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

登錄.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

...

注意:由於這是一個常見問題,我將提供更廣泛的答案,以更好地反映對象層次結構。

由於“主”窗口顧名思義是窗口,因此包含它的腳本應該是窗口,而登錄窗口應該被導入並最終根據需要顯示。

層次結構很重要:您不必考慮窗口的顯示順序,而是它們的相關性。

考慮到這一點,主腳本將:

  • 創建主窗口;
  • 如果需要,顯示登錄名;
  • 如果登錄成功則顯示主窗口;
  • 如果用戶斷開連接,再次顯示登錄信息;
  • 如果用戶已更改,則清除內容(*見下文);

以上還說明了為什么不斷創建新的窗口實例很少是一個好主意。

登錄窗口也應該是一個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!')

筆記:

  • 上面的代碼將始終顯示現有的Login實例,因此用戶名和密碼字段將“記住”以前的條目; 如果你不想這樣,你總是可以通過覆蓋exec() clear() ) (但記得調用基本實現並返回它的結果!); 或者,不要創建self.login並始終在showLogin()中創建一個新的本地Login()實例;
  • 您應始終使用布局管理器,並且永遠不要依賴固定的幾何圖形;
  • QMainWindow 應該始終有一個中央小部件,不鼓勵使用它作為父窗口創建主窗口的子窗口(除非您真的知道自己在做什么); 如果您需要更多小部件,請使用基本的 QWidget,為其設置布局,添加孩子,最后調用setCentralWidget()
  • 更復雜的層次結構可能需要一個“控制器”(閱讀有關MVC 模式的更多信息)來更好地組織整個程序並尊重 OOP 模式; 這通常是通過一個基本類或通過繼承 QApplication 來實現的;
  • 關於我的初始列表中的最后一個(*)點,並且與上面解釋的內容相關:“控制器”可以/應該完全刪除以前的“主窗口”(這將更容易和更安全)並最終顯示一個新實例用戶已斷開連接;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM