###### tags: `python`
# PyQt 實作
## 環境安裝
```shell=
pip install PyQt5
pip install pyqt5-tools
pip install youtube_dl
```
[Qt Designer](https://build-system.fman.io/qt-designer-download)
## VSCode PyQt5擴充套件
1. 按Ctrl + Shift + X
2. 在搜尋框搜尋「Qt for Python」
3. 點安裝
## 執行pyqt designer
C:\Users\你的電腦帳號\AppData\Local\Programs\Python\Python36-32\Scripts\designer.exe
## 範例程式
flyingfish.ui (完全透過pyqt designer產生)

1. 建立功能烈表:檔案/結束
2. 建立上圖元件
3. 元件命名,方便python指定

4. 事件綁定
**當元件指定事件發生時呼叫指定函數**




最終事件綁定

儲存附檔名為.ui的檔案,內容如下
```xml=
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FlyingFish</class>
<widget class="QMainWindow" name="FlyingFish">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>158</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>800</width>
<height>158</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>800</width>
<height>174</height>
</size>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="windowTitle">
<string>FlyingFish 1.0</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>flyingfish.ico</normaloff>flyingfish.ico</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_youtube_url">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Youtube影片網址</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_file_path">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>影片儲存位置</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="input_download_file_path">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="toolTip">
<string>請點瀏覽選擇影片要儲存到哪個資料夾</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="button_browser">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>瀏覽</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="input_youtube_url">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="toolTip">
<string>請貼上youtube要下載影片的網址</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QPushButton" name="button_download">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>開始下載</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<widget class="QMenu" name="menu">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="title">
<string>檔案</string>
</property>
<addaction name="menu_exit"/>
</widget>
<addaction name="menu"/>
</widget>
<widget class="QStatusBar" name="statusbar">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
</widget>
<action name="menu_exit">
<property name="text">
<string>結束</string>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
</widget>
<tabstops>
<tabstop>input_youtube_url</tabstop>
<tabstop>input_download_file_path</tabstop>
<tabstop>button_browser</tabstop>
<tabstop>button_download</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>button_download</sender>
<signal>clicked()</signal>
<receiver>FlyingFish</receiver>
<slot>download_youtube_video()</slot>
<hints>
<hint type="sourcelabel">
<x>478</x>
<y>114</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>78</y>
</hint>
</hints>
</connection>
<connection>
<sender>menu_exit</sender>
<signal>triggered()</signal>
<receiver>FlyingFish</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>78</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_browser</sender>
<signal>clicked()</signal>
<receiver>FlyingFish</receiver>
<slot>setSaveFilePath()</slot>
<hints>
<hint type="sourcelabel">
<x>753</x>
<y>75</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>78</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>download_youtube_video()</slot>
<slot>setSaveFilePath()</slot>
</slots>
</ui>
```
5. 打開VS code
flyingfish.py
a. 載入pyqt套件
```python=
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from PyQt5 import uic
```
b. 載入designer設計好的ui檔
```python=
form_class = uic.loadUiType("youtube_dl.ui")[0]
```
c. 呈現UI介面、狀態列訊息
```python=
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.status = self.statusBar()
self.status.showMessage("狀態: 等待使用者") # 設定狀態訊息
```
d. 設定檔案儲存路徑
```python=
def setSaveFilePath(self):
options = QFileDialog.Options()
folderName = QFileDialog.getExistingDirectory(self, "Open a folder", "", QFileDialog.ShowDirsOnly) # 開啟資料夾選取視窗
self.input_download_file_path.setText(folderName)
```
e. 儲存youtube音樂檔、影片檔
```python=
def download_youtube_video(self, MainWindow):
self.status.showMessage("狀態: 開始下載...")
download_path = os.path.join(self.input_download_file_path.text(), '%(title)s.%(ext)s') # 設定影片儲存路徑
ydl = YoutubeDL({'outtmpl': download_path}) # 呼叫youtube-dl來下載影片
with ydl:
result = ydl.extract_info(self.input_youtube_url.text(), download=True)
self.status.showMessage("狀態: 影片下載完成! ")
```
------
## 完整python檔案
```python=
import sys, os
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from PyQt5 import uic
from youtube_dl import YoutubeDL
form_class = uic.loadUiType("youtube_dl.ui")[0] # 載入PyQt Designer設計好的畫面
class MainWindow(QMainWindow, form_class):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.status = self.statusBar()
self.status.showMessage("狀態: 等待使用者") # 設定狀態訊息
def setSaveFilePath(self):
options = QFileDialog.Options()
folderName = QFileDialog.getExistingDirectory(self, "Open a folder", "", QFileDialog.ShowDirsOnly) # 開啟資料夾選取視窗
self.input_download_file_path.setText(folderName)
def download_youtube_video(self, MainWindow):
self.status.showMessage("狀態: 開始下載...")
download_path = os.path.join(self.input_download_file_path.text(), '%(title)s.%(ext)s') # 設定影片儲存路徑
ydl = YoutubeDL({'outtmpl': download_path}) # 呼叫youtube-dl來下載影片
with ydl:
result = ydl.extract_info(self.input_youtube_url.text(), download=True)
self.status.showMessage("狀態: 影片下載完成! ")
if __name__ == "__main__":
app = QApplication(sys.argv)
Win = MainWindow()
Win.show()
sys.exit(app.exec_())
```
## 打包成exe及壓縮成zip
1. 安裝pyinstaller
```python=
pip install pyinstaller
```
3. 請準備ico格式圖檔
4. 打包成exe
```shell=
pyinstaller -F -w -i=flyingfish.ico flyingfish.py
```
4. 將相關資源檔複製到dist資料夾中
a. ico圖檔
b. ui介面檔
6. 執行dist中產生的exe檔,測試是否所有功能都正常
7. 將dist壓縮成zip
## 完成檔
[flyingfish.zip](https://drive.google.com/open?id=1wLLiDZvIQVYzx_NNMnT5PiXncjtylfWN)
## 異常排除
**Q: 程式圖示無法更換成指定ico檔**
1. 刪除C:\Users\<使用者帳號>\AppData\Local\Microsoft\Windows\Explorer的檔案(刪除圖示快取!)
2. 重新執行打包exe
## notepad範例
```python=
import sys, os
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from PyQt5 import uic
form_class = uic.loadUiType("notepad.ui")[0] # 載入PyQt Designer設計好的畫面
class MainWindow(QMainWindow, form_class):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.status = self.statusBar()
self.status.showMessage("狀態: 等待使用者") # 設定狀態訊息
def savefile(self):
text = self.textEdit.toPlainText()
options = QFileDialog.Options()
path, _ = QFileDialog.getSaveFileName(self, "Save file", "", "Text documents (*.txt);;All files (*.*)")
with open(path, 'w') as f:
f.write(text)
def openfile(self):
path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "Text documents (*.txt);;All files (*.*)")
with open(path, 'r') as f:
text = f.read()
self.textEdit.setPlainText(text)
if __name__ == "__main__":
app = QApplication(sys.argv)
Win = MainWindow()
Win.show()
sys.exit(app.exec_())
```
## 參考資料
- [安裝 PyQt](https://wwssllabcd.github.io/blog/2018/05/21/how-to-install-pyqt%20/)
- [【Python】將Python打包成exe檔](https://medium.com/pyladies-taiwan/python-%E5%B0%87python%E6%89%93%E5%8C%85%E6%88%90exe%E6%AA%94-32a4bacbe351)
- [No2Pads, a simple Notepad clone](https://www.learnpyqt.com/examples/no2pads-simple-notepad-clone/)
- [在 PyQt5 主視窗中開啟另一個子視窗](https://clay-atlas.com/blog/2020/02/14/pyqt5-chinese-tutorial-open-sub-window/)
- [進階範例程式](https://drive.google.com/drive/folders/1vS33DW3JQe8IXaBaUH8YHewHUaOo2gok?usp=sharing)