# MainWindow
###### tags: `python` `pyqt` `tutorials`
## 參考
- [QT 5.13 doc - QMainWindow Class](https://doc.qt.io/qt-5/qmainwindow.html)
- [pyside2 doc - QMainWindow](https://doc-snapshots.qt.io/qtforpython/PySide2/QtWidgets/QMainWindow.html)
- [Qt學習之QMainWindow(一)QMainWindow簡介](https://www.itread01.com/content/1550176744.html)
- [Qt 学习之路 2(7):MainWindow 简介](https://www.devbean.net/2012/08/qt-study-road-2-mainwindow/)
## 簡介
QMainWindow是 Qt 框架带来的一个预定义好的主窗口类。所谓主窗口,就是一个普通意义上的应用程序(不是指游戏之类的那种)最顶层的窗口。比如你现在正在使用的浏览器,那么主窗口就是这个浏览器窗口。试着回想一下经典的主窗口,通常是由一个标题栏,一个菜单栏,若干工具栏和一个任务栏。
以上節錄自 https://www.devbean.net/2012/08/qt-study-road-2-mainwindow/
Qt中的頂層視窗稱為MainWindow,屬於類QMainWindow,QMainWindow也是繼承於QWidget。通過子類化QMainWindow可以建立一個應用程式的視窗。
MainWindow的結構分為五個部分:選單欄(Menu Bar)、工具欄(Toolbars)、停靠視窗(Dock Widgets)、狀態列(Status Bar)和中央視窗(Central Widget)。可以用下面的圖形表示之。

其中,中央視窗可以使用任何形式的widget來填充。一般不建議使中央視窗為空。可以使用setCentralWidget()函式來填充中央視窗。
以上節錄自 https://www.itread01.com/content/1550176744.html
## 操作範例
以下範例會用 第一個視窗程式 中的範例來介紹。專案在 [pyqt-example](https://gitlab.com/MVMC-lab/pyqt-example)的 exp01 中,可以自行 clone 下來研究。
### 觀察 ui/mainwindow.ui
以下是ui的呈現

看 ui/mainwindow.ui ,可以發現有三個物件
1. textBrowser
```xml
<item>
<widget class="QTextBrowser" name="textBrowser"/>
</item>
```
2. pushButton_1
```xml
<item>
<widget class="QPushButton" name="pushButton_1">
<property name="text">
<string>PushButton 1</string>
</property>
</widget>
</item>
```
3. pushButton_2
```xml
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>PushButton 1</string>
</property>
</widget>
</item>
```
### 觀察 app/ui_mainwindow.py
透過指令 `pyuic5 ui/mainwindow.ui -o app/ui_mainwindow.py` 轉換。
轉換會依據最上層物件的object name,去生成相對應的class名稱 `Ui_xxxxxx`,在此範例中最上層物件名稱是 `MainWindow`,所以會生成`Ui_MainWindow`的物件
接著,觀察轉換好的檔案,可以看到上述三的物件被轉換成 `class Ui_MainWindow` 的 member。
1. textBrowser
```py
self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
self.textBrowser.setObjectName("textBrowser")
```
2. pushButton_1
```py
self.pushButton_1 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_1.setObjectName("pushButton_1")
```
3. pushButton_2
```py
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setObjectName("pushButton_2")
```
所以如果我們用 `class Ui_MainWindow` 去創建一個物件,我們就可以使用上面三個 member。
Ex:
```py
a = Ui_MainWindow()
a.pushButton_1.setText('QQ')
text = a.textBrowser.currentText()
```
### 創建 QMainWindow 物件
在每個應用中通常會有一個 MainWindow ,也就是主視窗,通常會由一個class去管理。
創建一個 `class MainWindow` 繼承 `Ui_MainWindow` 以及 `QMainWindow` ,用來管理主視窗,而 `MainWindow` 因為繼承了上述兩個物件,所以繼承了兩個父類別的所有member,可以使用其中的元件及函式。
接著,在物件創立(\_\_init\_\_)時,使用繼承自 `Ui_MainWindow` 的函式 `setupUi()` 來初始化UI介面, setupUi 的參數是指我們要在哪創立、呈現其中的物件。
因為要初始化的是主視窗,所以要傳入的型態是 `QMainWindow` ,也就是`class MainWindow`本身。
```py
from PyQt5.QtWidgets import QMainWindow
from .ui_mainwindow import Ui_MainWindow
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
```
如此一來,我們就可以創立一個 `MainWindow` ,並去存取它其中ui的元件了。
```py
mw = Ui_MainWindow()
mw.pushButton_1.setText('QQ')
text = a.textBrowser.currentText()
```
上面是比較常見的寫法,為了讓主視窗相關的物件有較強的相依性才這樣處理。
下面是另一種寫法,缺點是a跟ui應該有強烈的相依性,卻不在同一物件中,會造成程式難以擴充。
```py
from PyQt5.QtWidgets import QMainWindow
from .ui_mainwindow import Ui_MainWindow
a = QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(a)
```
### 執行 MainWindow 物件
QWidget: Must construct a QApplication before a QWidget
以下是 qt 執行 mainwindow 的方法。
```py
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
```
先創立一個 `QApplication` 物件,它會在背景創立若干執行續以及先分配好一些資源,並透過此物件管理所有應用需要用到的資源。
接著再創立 `MainWindow` 物件,來處理主視窗,`window.show()`是顯示出來,繼承自 `QWidget` 。
最後透過 `app.exec_()` ,開始執行app這時,所有應用的資源、UI等,才會開始運作。會執行一個永久迴路,離開條件是觸發 Accepted 或 Rejected 等訊號。
離開 `app.exec_()` 時,會傳出錯誤訊號,再透過`sys.exit()`把這個訊號傳到我們的作業系統上,並離開python程序。
也可以寫成
```py
res = app.exec_()
sys.exit(res)
```
補充,`window.show()`會發送訊號給 ui處理執行續,ui處理執行續皆收到後排程、再執行,而ui處理執行續是在app中的。