對話方塊是有用的 GUI 元件,允許您與使用者進行通訊(因此命名為對話方塊)。 它們通常用於檔案開啟/儲存、設定、首選項或不適合應用程式主 UI 的功能。 它們是位於主應用程式前面的小型模態(或阻斷)視窗,直到它們被關閉。 Qt 實際上為最常見的用例提供了許多“特殊”對話方塊,允許您提供平臺原生體驗以獲得更好的使用者體驗。
在 Qt 中,對話方塊由 QDialog 類處理。 要建立一個新對話方塊,只需建立一個傳入父類小部件的 QDialog 型別的新物件,例如 QMainWindow,作為它的父級。
讓我們建立我們自己的 QDialog。 我們將從一個簡單的骨架應用程式開始,它帶有一個按鈕,可以按下連線到一個槽方法。
# dialogs_start.py
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
def button_clicked(self, s):
print("click", s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
在槽 button_clicked (從按鈕按下接收訊號)中,我們建立對話方塊例項,將 QMainWindow 例項作為父項傳遞。 這將使對話方塊成為 QMainWindow 的模式視窗。 這意味著對話方塊將完全阻止與父視窗的互動。
# dialogs_1.py
import sys
from PySide6.QtWidgets import QApplication, QDialog, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
def button_clicked(self, s):
print("click", s)
dlg = QDialog(self)
dlg.setWindowTitle("?")
dlg.exec_()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
執行! 單擊該按鈕,您將看到一個空對話方塊。
建立對話方塊後,我們使用 .exec_() 啟動它 - 就像我們為 QApplication 建立應用程式的主事件迴圈一樣。 這不是巧合:當您執行 QDialog 時,會建立一個全新的事件迴圈,特定於對話方塊 ,被建立。
一個事件迴圈來規則它們
還記得之前說過在任何時候都只能執行一個 Qt 事件迴圈嗎? QDialog 完全阻止您的應用程式執行。 不要開始對話並期望在您的應用程式中的其他任何地方發生任何其他事情。 稍後我們將看到如何使用多執行緒來擺脫這種麻煩。
就像我們的第一個視窗一樣,這不是很有趣。 讓我們透過新增一個對話方塊標題和一組 OK 和 Cancel 按鈕來解決這個問題,以允許使用者接受或拒絕。
要自定義 QDialog,我們可以將其子類化。
# dialogs_2a.py
class CustomDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("HELLO!")
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(buttons)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
message = QLabel("Something happened, is that OK?")
self.layout.addWidget(message)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
在上面的程式碼中,我們首先建立了 QDialog 的子類,我們稱之為 CustomDialog。 對於 QMainWindow,我們在類 __init__ 塊中應用我們的自定義,以便在建立物件時應用我們的自定義。 首先,我們使用 .setWindowTitle() 為 QDialog 設定標題,與我們為主視窗所做的完全相同。
下一個程式碼塊與建立和顯示對話方塊按鈕有關。 這可能比您預期的要複雜一些。 然而,這是由於 Qt 在處理不同平臺上的對話方塊按鈕定位方面的靈活性。
簡單的辦法?
您當然可以選擇忽略這一點並在佈局中使用標準 QButton,但此處概述的方法可確保您的對話方塊遵守主機桌面標準(例如左側和右側的 OK)。 這些行為會讓你的使用者非常惱火,所以我不推薦它。
建立對話方塊按鈕框的第一步是使用 QDialogButtonBox 的名稱空間屬性定義要顯示的按鈕。 可用按鈕的完整列表如下:
按鈕型別 |
QDialogButtonBox.Ok |
QDialogButtonBox.Open |
QDialogButtonBox.Save |
QDialogButtonBox.Cancel |
QDialogButtonBox.Close |
QDialogButtonBox.Discard |
QDialogButtonBox.Apply |
QDialogButtonBox.Reset |
QDialogButtonBox.RestoreDefaults |
QDialogButtonBox.Help |
QDialogButtonBox.SaveAll |
QDialogButtonBox.Yes |
QDialogButtonBox.YesToAll |
QDialogButtonBox.No |
QDialogButtonBox.NoToAll |
QDialogButtonBox.Abort |
QDialogButtonBox.Retry |
QDialogButtonBox.Ignore |
QDialogButtonBox.NoButton |
這些應該足以建立您能想到的任何對話方塊。 您可以透過使用管道 (|) 將它們組合在一起來構造一行多個按鈕。 Qt 將根據平臺標準自動處理訂單。 例如,要顯示我們使用的 OK 和 Cancel 按鈕:
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
變數按鈕現在包含代表這兩個按鈕的整數值。 接下來,我們必須建立 QDialogButtonBox 例項來儲存按鈕。 要顯示的按鈕的標誌作為第一個引數傳入。
要使按鈕產生任何效果,您必須將正確的 QDialogButtonBox 訊號連線到對話方塊上的插槽。 在我們的例子中,我們已經將 QDialogButtonBox 的 .accepted 和 .rejected 訊號連線到 QDialog 子類上的 .accept() 和 .reject() 處理程式。
最後,要使 QDialogButtonBox 出現在我們的對話方塊中,我們必須將它新增到對話方塊佈局中。 因此,對於主視窗,我們建立一個佈局,並將我們的 QDialogButtonBox 新增到其中(QDialogButtonBox 是一個小部件),然後在我們的對話方塊上設定該佈局。
最後,我們在 MainWindow.button_clicked 槽中啟動 CustomDialog。
# dialogs_2a.py
def button_clicked(self, s):
print("click", s)
dlg = CustomDialog()
if dlg.exec_():
print("Success!")
else:
print("Cancel!")
執行! 單擊以啟動對話方塊,您將看到一個帶有按鈕的對話方塊。
當您單擊按鈕啟動對話方塊時,您可能會注意到它出現在遠離父視窗的地方——可能在螢幕的中心。 通常,您希望對話框出現在其啟動視窗上,以便使用者更容易找到它們。 為此,我們需要為 Qt 提供對話方塊的父級。 如果我們將主視窗作為父視窗傳遞,Qt 將定位新對話方塊,使對話方塊的中心與視窗的中心對齊。
我們可以修改 CustomDialog 類以接受父引數。
# dialogs_2b.py
class CustomDialog(QDialog):
# 我們將預設值設定為 None 以便我們可以根據需要省略父級。
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("HELLO!")
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(buttons)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
message = QLabel("Something happened, is that OK?")
self.layout.addWidget(message)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
然後,當我們建立 CustomDialog 例項時,我們可以將主視窗作為引數傳入。 在我們的 button_clicked 方法中,self 是我們的主視窗物件。
# dialogs_2b.py
def button_clicked(self, s):
print("click", s)
dlg = CustomDialog(self)
if dlg.exec_():
print("Success!")
else:
print("Cancel!")
執行! 單擊以啟動對話方塊,您應該會在父視窗的中間看到彈出的對話方塊。
恭喜! 您已經建立了第一個對話方塊。 當然,您可以繼續在對話方塊中新增任何其他您喜歡的內容。 只需像往常一樣將其插入到佈局中。
QMessageBox 訊息對話方塊
有許多對話方塊遵循我們剛剛看到的簡單模式——帶有按鈕的訊息,您可以使用這些按鈕接受或取消對話方塊。 雖然您可以自己構建這些對話方塊,但 Qt 還提供了一個名為 QMessageBox 的內建訊息對話方塊類。 這可用於建立資訊、警告、關於或問題對話方塊。
下面的示例建立一個簡單的 QMessageBox 並顯示它。
# dialogs_3.py
def button_clicked(self, s):
dlg = QMessageBox(self)
dlg.setWindowTitle("I have a question!")
dlg.setText("This is a simple dialog")
button = dlg.exec_()
if button == QMessageBox.Ok:
print("OK!")
執行! 您將看到一個帶有 OK 按鈕的簡單對話方塊。
與我們已經看過的對話方塊按鈕一樣,顯示在 QMessageBox 上的按鈕也配置了一組常量,這些常量可以與 | 結合使用。 顯示多個按鈕。 可用按鈕型別的完整列表如下所示。
按鈕型別 |
QMessageBox.Ok |
QMessageBox.Open |
QMessageBox.Save |
QMessageBox.Cancel |
QMessageBox.Close |
QMessageBox.Discard |
QMessageBox.Apply |
QMessageBox.Reset |
QMessageBox.RestoreDefaults |
QMessageBox.Help |
QMessageBox.SaveAll |
QMessageBox.Yes |
QMessageBox.YesToAll |
QMessageBox.No |
QMessageBox.NoToAll |
QMessageBox.Abort |
QMessageBox.Retry |
QMessageBox.Ignore |
QMessageBox.NoButton |
您還可以透過使用以下選項之一設定圖示來調整對話方塊中顯示的圖示。
圖示 |
描述 |
QMessageBox.NoIcon |
訊息框沒有圖示 |
QMessageBox.Question |
該訊息是在問一個問題 |
QMessageBox.Information |
該訊息僅供參考 |
QMessageBox.Warning |
該訊息是警告 |
QMessageBox.Critical |
該訊息表明存在嚴重問題 |
例如,下面建立了一個帶有 Yes 和 No 按鈕的問題對話方塊。
# dialogs_4.py
import sys
# tag::imports[]
from PySide6.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox, QPushButton
# end::imports[]
# tag::MainWindow[]
class MainWindow(QMainWindow):
# end::MainWindow[]
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
# tag::button_clicked[]
# __init__ skipped for clarity
def button_clicked(self, s):
dlg = QMessageBox(self)
dlg.setWindowTitle("I have a question!")
dlg.setText("This is a question dialog")
dlg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
dlg.setIcon(QMessageBox.Question)
button = dlg.exec_()
if button == QMessageBox.Yes:
print("Yes!")
else:
print("No!")
# end::button_clicked[]
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
執行! 您將看到一個帶有 Yes 和 No 按鈕的問題對話方塊。
內建 QMessageBox 對話方塊
為了使事情更簡單,QMessageBox 有許多方法可用於構造這些型別的訊息對話方塊。 這些方法如下所示。
QMessageBox.about(parent, title, message)
QMessageBox.critical(parent, title, message)
QMessageBox.information(parent, title, message)
QMessageBox.question(parent, title, message)
QMessageBox.warning(parent, title, message)
parent 引數是對話方塊的子視窗。 如果您從主視窗啟動對話方塊,則只需傳入 self. 下面的示例建立一個問題對話方塊,和以前一樣,帶有 Yes 和 No 按鈕。
# dialogs_5.py
import sys
from PySide6.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
# tag::button_clicked[]
def button_clicked(self, s):
button = QMessageBox.question(self, "Question dialog", "The longer message")
if button == QMessageBox.Yes:
print("Yes!")
else:
print("No!")
# end::button_clicked[]
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
執行! 您將看到相同的結果,這次使用內建的 .question() 方法。
請注意,我們現在只調用對話方塊方法而不是呼叫 exec() 並建立對話方塊。 每個方法的返回值是被按下的按鈕。 我們可以透過將返回值與按鈕常量進行比較來檢測按下了什麼。
四個資訊、問題、警告和關鍵方法也接受可選按鈕和 defaultButton 引數,可用於調整對話方塊中顯示的按鈕並預設選擇一個。 通常,儘管您不想更改預設設定。
# dialogs_6.py
import sys
from PySide6.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
# tag::button_clicked[]
def button_clicked(self, s):
button = QMessageBox.critical(
self,
"Oh dear!",
"Something went very wrong.",
buttons=QMessageBox.Discard | QMessageBox.NoToAll | QMessageBox.Ignore,
defaultButton=QMessageBox.Discard,
)
if button == QMessageBox.Discard:
print("Discard!")
elif button == QMessageBox.NoToAll:
print("No to all!")
else:
print("Ignore!")
# end::button_clicked[]
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
執行! 您將看到一個帶有自定義按鈕的關鍵對話方塊。
在大多數情況下,這些簡單的對話方塊就是您所需要的。
Dialogs
建立糟糕的對話特別容易。 從用令人困惑的選項困住使用者的對話方塊到巢狀的永無止境的彈出視窗。 有很多方法可以傷害您的使用者。
對話方塊按鈕由系統標準定義。 您可能從未注意到 macOS 和 Linux 與 Windows 上的“確定”和“取消”按鈕位於不同的位置,但您的大腦確實注意到了。 如果你不遵循系統標準,你會混淆你的使用者,讓他們犯錯誤。
透過 Qt,您可以在使用內建 QDialogButtonBox 控制元件時免費獲得這種一致性。 但你必須使用它們!
錯誤對話方塊會惹惱使用者。 當您顯示錯誤對話方塊時,您就是在給使用者帶來壞訊息。 當你告訴某人壞訊息時,你需要考慮它對他們的影響。
以我們在文件中遇到錯誤時產生的這個(幸好是虛構的)對話方塊為例。 該對話方塊告訴您存在錯誤,但既不說明後果是什麼,也不說明如何處理。 讀到這裡你的使用者會問(可能會尖叫)“……現在怎麼辦?”
這個來自 Acrobat Reader DC 的真實對話更好。 這解釋了存在錯誤、可能產生的後果以及可能的解決方法。
但這仍然不完美。 錯誤顯示為資訊對話方塊,這並不表明有任何錯誤。 該錯誤會在每個頁面上觸發,並且可以在文件中多次出現 - 警告對話方塊應該只觸發一次。 也可以透過明確錯誤是永久性的來改善錯誤。
好的錯誤訊息應該解釋一下。
- 發生了什麼
- 受影響的內容
- 它的後果是什麼
- 可以做些什麼