用PyQt打造具有專業(yè)外觀的GUI(上)
PyQt的布局管理器layout managers提供了一種用戶友好且高效的方式,可以在GUI上排列圖形組件或小部件。正確布置窗口小部件將使您的GUI應(yīng)用程序看起來更加優(yōu)美和專業(yè)。學(xué)習(xí)有效地做到這一點是您使用Python和PyQt進行GUI應(yīng)用程序開發(fā)和運行的一項基本技能。
在本教程中,您將學(xué)習(xí):
- 使用PyQt的layout managers有什么好處
- 如何使用PyQt的layout managers在GUI上以編程方式布置窗口小部件
- 如何為您的GUI應(yīng)用程序選擇正確的layout manager
- 如何在基于主窗口和基于對話框的應(yīng)用程序中布置小部件
有了這些知識和技能,您就可以使用Python和PyQt創(chuàng)建具有專業(yè)外觀的GUI應(yīng)用程序。
在GUI上布置圖形元素
在創(chuàng)建圖形用戶界面(GUI)應(yīng)用程序時,一個常見的問題是如何使圖形組件(按鈕,菜單,工具欄,標簽等)一致地放置在窗體和窗口上。此過程稱為GUI布局(GUI layout,),這是創(chuàng)建GUI應(yīng)用程序的重要步驟。
之前如果要在窗口上布置圖形組件或窗口小部件,則可以采用以下方法之一:
- 為窗口上的每個小部件確定并手動設(shè)置靜態(tài)大小和位置。
- 動態(tài)計算并設(shè)置每個小部件的大小和位置。
第一種方法相當直接,但是至少具有以下缺點:
- 您的窗口將無法調(diào)整大小,以不同的屏幕分辨率顯示它們時可能會導(dǎo)致問題。
- 您的標簽可能不能正確地支持本地化,因為給定文本的長度在不同語言之間會發(fā)生變化。
- 您的窗口小部件在不同平臺上的顯示方式將有所不同,這使得編寫看起來不錯的多平臺應(yīng)用程序變得困難。
第二種方法更靈活。但是,它也有缺點:
- 您必須進行大量手動計算才能確定每個小部件的正確大小和位置。
- 您必須做一些額外的計算才能正確響應(yīng)窗口大小調(diào)整。
- 每當您修改窗口布局時,都必須重做所有計算。
即使您仍然可以使用這兩種方法中的任何一種來布局GUI,大多數(shù)時候您可能還是希望使用由最先進的GUI框架或工具包實現(xiàn)的第三種且更方便的方法:布局管理器layout managers。注意:在某些GUI框架(例如Tkinter)中,布局管理器也稱為geometry managers。
layout managers會根據(jù)您的特定需求自動在GUI上排列小 部件。它避免了第一種方法的兼容性缺點以及第二種方法的復(fù)雜計算。
在以下各節(jié)中,您將了解PyQt的內(nèi)置布局管理器以及如何使用它們有效地布局GUI應(yīng)用程序的圖形組件。
多樣的PyQt布局形式
在PyQt中,小部件是用作GUI應(yīng)用程序構(gòu)建塊的圖形組件。將一堆小部件放在窗口上以創(chuàng)建GUI時,需要給它們一些順序。您需要設(shè)置窗口小部件的大小和在窗口上的位置,還需要定義當用戶調(diào)整基礎(chǔ)窗口大小時它們的行為。
注意:PyQt5的官方文檔中有一些不完整的部分。要解決此問題,您可以查看PyQt4文檔,Qt for Python文檔或原始Qt文檔。
要在PyQt的窗口或窗體上排列小部件,可以使用以下技術(shù):
- 在小部件上使用.resize()和.move()來提供絕對大小和位置。
- 重新實現(xiàn).resizeEvent()并動態(tài)計算小部件的大小和位置。
- 使用布局管理器,讓他們?yōu)槟鏊械挠嬎愫托量嗟墓ぷ鳌?/li>
這些技術(shù)通常對應(yīng)于上一節(jié)中介紹的用于布局GUI的三種不同方法。
再說一次,動態(tài)計算尺寸和位置可能是個好方法,但是大多數(shù)時候,使用布局管理器會更好。在PyQt中,布局管理器是提供所需功能以自動管理布局中小部件的大小,位置和調(diào)整大小行為的類。
使用布局管理器,您可以自動在任何母部件、容器、小部件內(nèi)排列子部件。使用布局管理器將確保您充分利用GUI上的可用空間,并確保當用戶調(diào)整窗口大小時,應(yīng)用程序仍然可用。
布局管理器充當小部件和其他布局的容器時,要將小部件添加到布局管理器,請在當前布局上調(diào)用.addWidget()。要將布局添加到另一個布局,請在當前布局上調(diào)用.addLayout()。您將在“嵌套布局以構(gòu)建復(fù)雜的GUI”一節(jié)中更深入地了解嵌套布局。
將所有必需的小部件添加到布局管理器后,即可使用.setLayout()在給定的小部件上設(shè)置布局管理器。您可以在QWidget的任何子類(包括窗口或窗體)上設(shè)置布局管理器。
注意:QMainWindow是一個PyQt類,可用于創(chuàng)建main widow–style applications.。此類具有其自己的內(nèi)置布局管理器。因此,如果您使用的是QMainWindow,則通常不需要在主窗口對象上設(shè)置布局管理器。
布局中的所有窗口小部件都會自動設(shè)置為安裝布局的窗口小部件的子級,而不是布局本身。這是因為窗口小部件只能將其他窗口小部件(而不是布局)作為其父級。
PyQt的布局管理器提供了一些很酷的功能,可以使您在創(chuàng)建美觀的GUI應(yīng)用程序時更加輕松:
- 處理小部件的大小和位置,無需任何計算
- 當用戶調(diào)整基礎(chǔ)窗口的大小時,處理窗口小部件的大小調(diào)整和重新定位
- 調(diào)整標簽大小以更好地支持國際化
- 為多平臺應(yīng)用程序提供本機窗口布局
從長遠來看,使用布局管理器還將極大地提高您的生產(chǎn)率并改善代碼的可維護性。
PyQt提供了四個通用布局管理器類:
- QHBoxLayout在水平框中排列小部件。
- QVBoxLayout在垂直框中排列小部件。
- QGridLayout將小部件排列在網(wǎng)格中。
- QFormLayout將小部件安排在兩列中。
在接下來的幾節(jié)中,您將學(xué)習(xí)如何使用這些通用布局管理器的基礎(chǔ)知識。
使用通用布局管理器
使用PyQt創(chuàng)建GUI應(yīng)用程序時,通常會使用上一節(jié)末尾看到的四種通用布局中的一種或多種方法,來將窗口小部件布置在窗口和窗體上。
在接下來的幾節(jié)中,您將在一些示例的幫助下學(xué)習(xí)如何創(chuàng)建和使用四個通用布局管理器。
構(gòu)建水平布局:QHBoxLayout
Box layout managers從父布局或窗口小部件獲得的空間被分成多個盒子或單元格,然后使布局中的每個窗口小部件填充一個盒子。
QHBoxLayout是PyQt中兩個可用的框布局之一。這個布局管理器允許您水平排列小部件,一個接一個。這些小部件從左到右添加到布局中。這意味著您首先在代碼中添加的小部件將是布局中最左側(cè)的小部件。
要將小部件添加到QHBoxLayout對象,請在布局對象上調(diào)用.addWidget(widget,stretch,alignment)。此方法接受一個必需參數(shù)和兩個可選參數(shù):
- widget是必填參數(shù),用于保存要添加到布局的特定widget。
- Stretch是一個可選參數(shù),其中包含一個整數(shù),該整數(shù)表示要應(yīng)用于小部件的拉伸因子。具有較高拉伸因子的小部件在調(diào)整窗口大小時會增長更多。默認為0,這表示未分配窗口小部件拉伸因子。
- alignment是一個可選參數(shù),其中包含水平和垂直標志。您可以組合這些標志,以在其包含的單元格內(nèi)產(chǎn)生所需的小部件對齊方式。默認為0,這意味著小部件將填充整個單元格。
下面是一個小型應(yīng)用程序,顯示了如何使用QHBoxLayout創(chuàng)建水平布局。在此示例中,您將使用QPushButton對象根據(jù)將小部件添加到代碼中的順序更好地可視化每個小部件在布局中的放置位置:
- import sys
- from PyQt5.QtWidgets import (
- QApplication,
- QHBoxLayout,
- QPushButton,
- QWidget,
- )
- class Window(QWidget):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("QHBoxLayout Example")
- # Create a QHBoxLayout instance
- layout = QHBoxLayout()
- # Add widgets to the layout
- layout.addWidget(QPushButton("Left-Most"))
- layout.addWidget(QPushButton("Center"), 1)
- layout.addWidget(QPushButton("Right-Most"), 2)
- # Set the layout on the application's window
- self.setLayout(layout)
- print(self.children())
- if __name__ == "__main__":
- app = QApplication(sys.argv)
- window = Window()
- window.show()
- sys.exit(app.exec_())
在第15行上,創(chuàng)建一個稱為layout的QHBoxLayout對象。在第17至19行,使用.addWidget()將三個按鈕添加到布局中。請注意,您分別在“居中”和“最右”按鈕中將1和2傳遞給了stretch參數(shù)。在第21行,使用.setLayout()將布局設(shè)置為窗口的頂級布局。
如果您運行此應(yīng)用程序,則會在屏幕上看到以下窗口:
該窗口包含三個水平排列的按鈕。請注意,“最左鍵”按鈕對應(yīng)于您在代碼中添加的第一個按鈕。因此,按鈕的顯示順序與您在代碼中添加按鈕的順序(從左到右)(從上到下)相同。
“居中”和“最右邊”按鈕具有不同的拉伸系數(shù),因此在調(diào)整窗口大小時,它們會按比例縮放。
此外,布局中的所有按鈕和布局本身都設(shè)置為Window的子級。這是由布局對象自動完成的,該布局對象在每個小部件上內(nèi)部調(diào)用.setParent()。在第17行上對print()的調(diào)用將在您的終端上打印Window的子代列表,以證明此行為。
構(gòu)建垂直布局:QVBoxLayout
QVBoxLayout垂直排列小部件,一個在另一個下方。您可以使用此類創(chuàng)建垂直布局,并從上到下排列窗口小部件。由于QVBoxLayout是另一個框布局,因此其.addWidget()方法的工作方式與QHBoxLayout中的相同。
下面是一個PyQt應(yīng)用程序,它顯示了如何創(chuàng)建和使用QVBoxLayout對象在GUI中創(chuàng)建小部件的垂直排列:
- import sys
- from PyQt5.QtWidgets import (
- QApplication,
- QPushButton,
- QVBoxLayout,
- QWidget,
- )
- class Window(QWidget):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("QVBoxLayout Example")
- self.resize(270, 110)
- # Create a QVBoxLayout instance
- layout = QVBoxLayout()
- # Add widgets to the layout
- layout.addWidget(QPushButton("Top"))
- layout.addWidget(QPushButton("Center"))
- layout.addWidget(QPushButton("Bottom"))
- # Set the layout on the application's window
- self.setLayout(layout)
- if __name__ == "__main__":
- app = QApplication(sys.argv)
- window = Window()
- window.show()
- sys.exit(app.exec_())
在第16行上,您將創(chuàng)建QVBoxLayout的實例。在第18至20行上,將三個按鈕添加到布局中。最后,將布局設(shè)置為窗口的頂層布局。
如果您運行此應(yīng)用程序,則將顯示以下窗口:
您的窗口顯示了三個垂直排列的按鈕,一個在另一個按鈕下面。這些按鈕的顯示順序(從上到下)與您在代碼中添加的順序(從上到下)相同。
在網(wǎng)格中排列小部件:QGridLayout
您可以使用QGridLayout將小部件排列在行和列的網(wǎng)格中。每個小部件將在網(wǎng)格中具有相對位置。要定義小部件的位置或網(wǎng)格中的單元格,請使用表格(行,列)的一對坐標。這些坐標應(yīng)該是從零開始的整數(shù)。
QGridLayout占用其父級的可用空間,將其劃分為行和列,然后將每個小部件放置在其自己的單元格或框中。QGridLayout根據(jù)小部件的數(shù)量及其坐標自動計算出最終布局將包含多少行和多少列。如果您不將小部件添加到給定的單元格,則QGridLayout將使該單元格為空。
要將小部件添加到網(wǎng)格布局,請在布局上調(diào)用.addWidget()。此方法有兩個不同的重載實現(xiàn):
- addWidget(widget, row, column, alignment)將widget添加到(row, column)的單元格中。
- addWidget(widget,fromRow,fromColumn,rowSpan,columnSpan,alignment)將widget添加到單元格中,跨越多行,多列或兩者。
第一個實現(xiàn)采用以下參數(shù):
- widget是必填參數(shù),用于保存您需要添加到布局的特定小部件。
- row是必填參數(shù),其中包含一個整數(shù),該整數(shù)表示網(wǎng)格中行的坐標。
- column是必填參數(shù),其中包含一個整數(shù),該整數(shù)表示網(wǎng)格中列的坐標。
- alignment是一個可選參數(shù),用于保存小部件在其包含的單元格內(nèi)的對齊方式。默認為0,這意味著小部件將填充整個單元格。
這是一個如何使用QGridLayout創(chuàng)建小部件網(wǎng)格的示例:
- import sys
- from PyQt5.QtWidgets import (
- QApplication,
- QGridLayout,
- QPushButton,
- QWidget,
- )
- class Window(QWidget):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("QGridLayout Example")
- # Create a QGridLayout instance
- layout = QGridLayout()
- # Add widgets to the layout
- layout.addWidget(QPushButton("Button at (0, 0)"), 0, 0)
- layout.addWidget(QPushButton("Button at (0, 1)"), 0, 1)
- layout.addWidget(QPushButton("Button at (0, 2)"), 0, 2)
- layout.addWidget(QPushButton("Button at (1, 0)"), 1, 0)
- layout.addWidget(QPushButton("Button at (1, 1)"), 1, 1)
- layout.addWidget(QPushButton("Button at (1, 2)"), 1, 2)
- layout.addWidget(QPushButton("Button at (2, 0)"), 2, 0)
- layout.addWidget(QPushButton("Button at (2, 1)"), 2, 1)
- layout.addWidget(QPushButton("Button at (2, 2)"), 2, 2)
- # Set the layout on the application's window
- self.setLayout(layout)
- if __name__ == "__main__":
- app = QApplication(sys.argv)
- window = Window()
- window.show()
- sys.exit(app.exec_())
在第15行上,創(chuàng)建QGridLayout對象。然后,在第17至25行上,使用.addWidget()將小部件添加到布局中。要查看沒有分配小部件的網(wǎng)格布局如何管理單元,請注釋掉其中的一行或多行,然后再次運行應(yīng)用程序。
如果您從命令行運行此代碼,則將獲得如下所示的窗口:
QGridLayout對象中的每個小部件都占據(jù)由您在.addWidget()中提供的一對坐標定義的單元格。每個按鈕上的文字均反映了這些坐標。坐標是從零開始的,因此第一個單元格位于(0,0)。
在.addWidget()的第二種實現(xiàn)中,widget和alignment參數(shù)保持不變,并且您還有四個其他參數(shù)可用于將小部件放置在多行或多列中:
- fromRow賦值一個整數(shù),該整數(shù)表示小部件將在其中開始的行。
- fromColumn賦值一個整數(shù),該整數(shù)表示小部件將在其中開始的列。
- rowSpan賦值一個整數(shù),該整數(shù)表示窗口小部件將在網(wǎng)格中占據(jù)的行數(shù)。
- columnSpan賦值一個整數(shù),該整數(shù)表示窗口小部件將在網(wǎng)格中占據(jù)的列數(shù)。
這是一個顯示.addWidget()變體如何工作的應(yīng)用程序:
- import sys
- from PyQt5.QtWidgets import (
- QApplication,
- QGridLayout,
- QPushButton,
- QWidget,
- )
- class Window(QWidget):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("QGridLayout Example")
- # Create a QGridLayout instance
- layout = QGridLayout()
- # Add widgets to the layout
- layout.addWidget(QPushButton("Button at (0, 0)"), 0, 0)
- layout.addWidget(QPushButton("Button at (0, 1)"), 0, 1)
- layout.addWidget(QPushButton("Button Spans two Cols"), 1, 0, 1, 2)
- # Set the layout on the application's window
- self.setLayout(layout)
- if __name__ == "__main__":
- app = QApplication(sys.argv)
- window = Window()
- window.show()
- sys.exit(app.exec_())
在第19行,使用.addWidget()的第二種實現(xiàn)來添加一個占用網(wǎng)格中兩列的按鈕。該按鈕從第二行(fromRow = 1)和第一列(fromColumn = 0)開始。最后,該按鈕占據(jù)一行(rowSpan = 1)和兩列(columnSpan = 2)。
如果您運行此應(yīng)用程序,則會在屏幕上看到以下窗口:
在這種布局中,您可以使一個小部件占用多個單元格,就像使用“跨越兩個列”的按鈕一樣。
在下篇中,我們將看到:
- 如何快速創(chuàng)建表單
- 嵌套布局以構(gòu)建復(fù)雜的GUI
- 使用多頁布局和小部件
- 布置應(yīng)用程序的主窗口
- 布置應(yīng)用程序的對話框
- 在PyQt布局中管理輸入框