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



因此,我有两个窗口希望能够在它们之间切换,LoginMainWindow,每个窗口都是QWidget,分别位于其自己的独立文件中,loginUI.pyMainUI.py

通过创建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()

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

由于";主";顾名思义,该窗口是main窗口,包含该窗口的脚本应该是

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

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

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

上面还说明了为什么连续创建新的窗口实例很少是个好主意。

登录窗口也应该是一个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进行子类化来实现的
  • 关于我最初列表中的最后(*)点,并且与上面解释的内容相关:;控制器";可以/应该完全删除之前的";"主窗口";(这将更容易也更安全),并最终在用户断开连接时显示一个新实例

在创建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
...

最新更新