最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python使用PyQt5打造桌面應(yīng)用的入門指南

 更新時間:2026年01月30日 09:34:14   作者:諸神緘默不語  
PyQt5是Qt框架的Python綁定,Qt本身是一個久經(jīng)考驗的跨平臺C++應(yīng)用程序框架,通過PyQt5,我們可以用Python語言享受到Qt的所有功能,而無需與C++的復(fù)雜性打交道,下面我們就來看看PyQt5的具體使用吧

1. 引言:為什么選擇PyQt5開發(fā)桌面應(yīng)用

當(dāng)你掌握了Python基礎(chǔ)語法后,自然會想要創(chuàng)造一些有界面的實用工具。這時候,GUI(圖形用戶界面)開發(fā)就成為了必經(jīng)之路。在眾多Python GUI框架中,PyQt5以其強(qiáng)大的功能和優(yōu)雅的設(shè)計脫穎而出。

PyQt5是Qt框架的Python綁定,Qt本身是一個久經(jīng)考驗的跨平臺C++應(yīng)用程序框架。通過PyQt5,我們可以用Python語言享受到Qt的所有功能,而無需與C++的復(fù)雜性打交道。

PyQt5的核心優(yōu)勢

  • 功能全面:從簡單的窗口到復(fù)雜的3D圖形,應(yīng)有盡有
  • 真正的跨平臺:同一份代碼可在Windows、macOS、Linux上運(yùn)行
  • Pythonic的優(yōu)雅:既有Qt的強(qiáng)大,又有Python的簡潔
  • 豐富的控件庫:按鈕、表格、樹形視圖等上百種控件
  • 活躍的社區(qū):遇到問題容易找到解決方案

適合的應(yīng)用場景

  • 數(shù)據(jù)分析可視化工具
  • 日常工作效率工具
  • 原型快速開發(fā)
  • 中小型企業(yè)應(yīng)用

2. PyQt5與PySide,以及PyQt6簡介

在開始之前,你可能會遇到幾個相似的名字,這里簡單澄清一下:

PyQt5 vs PySide2

兩者都是Qt的Python綁定,功能幾乎完全相同。主要區(qū)別在于:

  • 許可證:PyQt5使用GPL/commercial,PySide2使用LGPL(對商業(yè)應(yīng)用更友好)
  • 歷史:PyQt出現(xiàn)較早,PySide是Qt官方后來推出的
  • API細(xì)微差別:方法名略有不同,如信號連接方式

PyQt6的登場

PyQt6是PyQt5的下一代版本,主要變化包括:

  • 默認(rèn)使用Qt6庫(PyQt5使用Qt5)
  • 一些API發(fā)生了變化和優(yōu)化
  • 移除了對Python 2的支持

為什么本文選擇PyQt5?

對于初學(xué)者,PyQt5有更豐富的教程資源、更穩(wěn)定的環(huán)境,而且目前大多數(shù)項目仍在使用PyQt5。掌握了PyQt5,遷移到PyQt6或PySide6也會很容易。

3. 快速開始:環(huán)境搭建

使用pip安裝(推薦大多數(shù)用戶)

pip install PyQt5

使用uv安裝(新一代Python包管理工具)

uv add pyqt5==5.15.11 pyqt5-qt5==5.15.2

這里限定版本是因為pyqt5的最新版本不適配于Windows uv庫,如果是其他設(shè)備可能不需要限定版本。

版本說明

  • PyQt5 5.15.x 是PyQt5的最后一個系列版本
  • 支持Python 3.5及以上版本
  • 建議使用Python 3.8+以獲得最佳體驗

驗證安裝

安裝完成后,可以運(yùn)行以下代碼驗證:

import sys
from PyQt5.QtWidgets import QApplication, QLabel
app = QApplication(sys.argv)
label = QLabel("Hello PyQt5!")
label.setMinimumSize(400, 50)
label.show()
app.exec_()

運(yùn)行效果:

(由于電腦分辨率不同,具體的大小效果可能不同)

我運(yùn)行完代碼之后會打印這個信息:

Can't find filter element
Can't find filter element

不知道是為什么,但是好像也不影響GUI應(yīng)用的展示,我就先不管了……

4. 第一個PyQt5窗口:Hello World

讓我們從一個最簡單的完整示例開始:

import sys
from PyQt5.QtWidgets import QApplication, QWidget
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    
    def initUI(self):
        # 設(shè)置窗口位置和大小
        self.setGeometry(300, 300, 750, 500)
        # 設(shè)置窗口標(biāo)題
        self.setWindowTitle('我的第一個PyQt5應(yīng)用')
        # 顯示窗口
        self.show()
if __name__ == '__main__':
    # 創(chuàng)建應(yīng)用實例
    app = QApplication(sys.argv)
    # 創(chuàng)建主窗口
    window = MainWindow()
    # 進(jìn)入主循環(huán)
    sys.exit(app.exec_())

運(yùn)行效果:

代碼解析

  • import sys:提供對系統(tǒng)相關(guān)功能的訪問
  • QApplication:每個PyQt5應(yīng)用都需要一個QApplication實例
  • QWidget:所有用戶界面對象的基類
  • self.setGeometry(x, y, width, height):設(shè)置窗口位置和大小
  • app.exec_():啟動應(yīng)用的事件循環(huán)

5. 理解PyQt5的核心"積木"

5.1 QApplication:應(yīng)用程序的心臟

QApplication是PyQt5應(yīng)用的"大腦"和"心臟",負(fù)責(zé):

  • 管理應(yīng)用程序的控制流:協(xié)調(diào)各個窗口和控件的工作
  • 處理系統(tǒng)級事件:接收和分發(fā)鼠標(biāo)點擊、鍵盤輸入、窗口重繪等事件
  • 提供全局設(shè)置:管理應(yīng)用程序的字體、樣式、調(diào)色板等

重要規(guī)則:一個應(yīng)用只能有一個QApplication實例!

app = QApplication(sys.argv)  # sys.argv用于接收命令行參數(shù)

事件循環(huán):GUI程序的"心臟跳動"

當(dāng)我們運(yùn)行一個PyQt5程序時,到底發(fā)生了什么?讓我用一個比喻來解釋:
想象一下,你的GUI程序就像一個餐廳:

  • QApplication是餐廳經(jīng)理
  • 事件循環(huán)(event loop)是餐廳的服務(wù)流程
  • 用戶操作(點擊、輸入)就是顧客的訂單

為什么需要事件循環(huán)?

# 啟動事件循環(huán)
app.exec_()

app.exec_() 啟動了一個無限循環(huán),這個循環(huán)不斷地做三件事:

  • 監(jiān)聽系統(tǒng)事件(鼠標(biāo)點擊、鍵盤輸入等)
  • 將事件分發(fā)給對應(yīng)的控件處理
  • 處理完事件后,等待下一個事件

這個循環(huán)會一直運(yùn)行,直到你關(guān)閉所有窗口或調(diào)用app.quit()。

sys.exit(app.exec_())到底是什么意思?

這是一個初學(xué)者常見的困惑點。讓我們分解來看:

sys.exit(app.exec_())

分解理解:

app.exec_()

  • 啟動事件循環(huán)
  • 返回一個退出狀態(tài)碼(通常是0表示成功,非0表示錯誤)
  • 事件循環(huán)會阻塞在這里,直到程序退出

sys.exit()

  • Python標(biāo)準(zhǔn)庫函數(shù)
  • 用于退出Python程序
  • 可以接收一個退出狀態(tài)碼作為參數(shù)

為什么要這樣寫?

# 不推薦的寫法
app.exec_()  # 程序會在這里卡住,但退出時可能不會清理資源
# 推薦的寫法
sys.exit(app.exec_())  # 確保程序正確退出,并返回狀態(tài)碼

工作原理示意圖:

開始
  ↓
創(chuàng)建 QApplication
  ↓
創(chuàng)建窗口和控件
  ↓
sys.exit(app.exec_())
  ├── 啟動事件循環(huán)(app.exec_())
  │     ├── 監(jiān)聽用戶操作
  │     ├── 處理事件
  │     └── 等待下一個事件...
  │
  └── 事件循環(huán)結(jié)束時返回狀態(tài)碼
        ↓
   sys.exit(狀態(tài)碼) 退出程序

完整示例:理解程序執(zhí)行流程

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    
    def initUI(self):
        self.setWindowTitle('QApplication示例')
        self.setGeometry(300, 300, 300, 200)
        
        btn = QPushButton('退出程序', self)
        btn.clicked.connect(self.close)  # 點擊按鈕關(guān)閉窗口
        btn.move(100, 80)
        
        self.show()
if __name__ == '__main__':
    print("1. 程序開始")
    
    app = QApplication(sys.argv)
    print("2. QApplication實例已創(chuàng)建")
    
    window = MyApp()
    print("3. 窗口已創(chuàng)建并顯示")
    
    print("4. 進(jìn)入事件循環(huán)...")
    exit_code = app.exec_()  # 在這里程序會"阻塞",等待事件
    print(f"5. 事件循環(huán)結(jié)束,退出碼: {exit_code}")
    
    sys.exit(exit_code)

運(yùn)行這個程序,你會看到:

  • 控制臺先打印1-4步的信息
  • 然后程序進(jìn)入事件循環(huán),GUI界面出現(xiàn)
  • 當(dāng)你關(guān)閉窗口時,事件循環(huán)結(jié)束
  • 最后打印第5步的信息,程序退出

常見問題解答

Q: 為什么要用sys.exit()包裝app.exec_()

A: 為了確保程序正確退出,并返回合適的退出狀態(tài)碼給操作系統(tǒng)。

Q: 可以不用sys.exit()嗎?

A: 在簡單程序中可能可以,但在復(fù)雜程序中,不使用sys.exit()可能導(dǎo)致資源未正確釋放。

Q: 什么時候事件循環(huán)會結(jié)束?

A: 當(dāng)調(diào)用app.quit()或關(guān)閉所有窗口時,事件循環(huán)會結(jié)束。

Q: 事件循環(huán)期間,我的代碼還能運(yùn)行嗎?

A: 不能直接運(yùn)行。所有代碼都必須在事件處理函數(shù)中執(zhí)行。如果需要執(zhí)行長時間任務(wù),應(yīng)該使用多線程。

記住這個模式

幾乎所有PyQt5程序都遵循這個模式:

import sys
from PyQt5.QtWidgets import QApplication, QWidget
def main():
    app = QApplication(sys.argv)      # 1. 創(chuàng)建應(yīng)用
    window = QWidget()                # 2. 創(chuàng)建窗口
    window.show()                     # 3. 顯示窗口
    return app.exec_()                # 4. 進(jìn)入事件循環(huán)
if __name__ == '__main__':
    sys.exit(main())                  # 5. 確保正確退出

掌握了QApplication和事件循環(huán)的概念,你就理解了PyQt5程序運(yùn)行的基礎(chǔ)機(jī)制。這是構(gòu)建更復(fù)雜應(yīng)用的基石!

5.2 QWidget與QMainWindow:窗口的兩大家族

QWidget:所有可視化組件的基類

  • 按鈕、標(biāo)簽、輸入框等都是QWidget的子類
  • 可以單獨作為窗口使用

QMainWindow:帶有菜單欄、工具欄、狀態(tài)欄的主窗口

  • 適用于復(fù)雜的應(yīng)用程序
  • 提供了標(biāo)準(zhǔn)的應(yīng)用程序框架

QMainWindow是QWidget的"加強(qiáng)版",但它們的定位和用途有很大區(qū)別。

核心關(guān)系:繼承與擴(kuò)展

# PyQt5中的繼承關(guān)系
object
  ↓
QObject
  ↓
QWidget  ← 所有可視化元素的基類
  ↓
QMainWindow  ← 專門用于主窗口的特殊QWidget

簡單來說

  • QWidget是所有可視控件的"爺爺輩"基類
  • QMainWindow是QWidget的一個"大兒子",專門為應(yīng)用程序主窗口設(shè)計
  • 按鈕、標(biāo)簽、輸入框等都是QWidget的其他"兒子孫子"

QWidget:萬能的基礎(chǔ)窗口

QWidget是PyQt5中最基礎(chǔ)的窗口類,它可以扮演兩種角色:

1. 作為獨立的簡單窗口

from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QVBoxLayout, QApplication
import sys
class SimpleWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("我是一個QWidget窗口")
        self.resize(300, 200)
        # 創(chuàng)建布局和控件
        layout = QVBoxLayout()
        label = QLabel("這是一個簡單的對話框")
        button = QPushButton("確定")
        layout.addWidget(label)
        layout.addWidget(button)
        self.setLayout(layout)
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = SimpleWindow()
    window.show()
    sys.exit(app.exec_())

運(yùn)行效果:

2. 作為其他控件的容器

# QLabel、QPushButton、QLineEdit等都是QWidget的子類
# 它們都繼承了QWidget的所有功能
label = QLabel("我是QWidget的子類")
button = QPushButton("我也是QWidget的子類")

QWidget的特點

  • 輕量級,內(nèi)存占用小
  • 靈活,可以自由布局
  • 適合對話框、彈窗、簡單工具窗口

QMainWindow:專業(yè)的應(yīng)用程序主窗口

QMainWindow是專門為應(yīng)用程序主窗口設(shè)計的,它提供了"開箱即用"的標(biāo)準(zhǔn)主窗口結(jié)構(gòu):

from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QAction, QLabel
import sys

class MainAppWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        # 1. 設(shè)置中央部件(必需)
        text_edit = QTextEdit()
        self.setCentralWidget(text_edit)
        # 2. 創(chuàng)建菜單欄(可選)
        menubar = self.menuBar()
        file_menu = menubar.addMenu("文件(&F)")
        # 向菜單添加具體的動作
        open_action = QAction("打開", self)
        save_action = QAction("保存", self)
        exit_action = QAction("退出", self)
        exit_action.triggered.connect(self.close)
        file_menu.addAction(open_action)
        file_menu.addAction(save_action)
        file_menu.addSeparator()
        file_menu.addAction(exit_action)
        # 3. 創(chuàng)建工具欄并添加工具按鈕(必需,否則工具欄為空)
        toolbar = self.addToolBar("標(biāo)準(zhǔn)工具欄")
        # 添加工具欄按鈕(使用文本)
        toolbar.addAction("新建")
        toolbar.addAction("打開")
        toolbar.addAction("保存")
        toolbar.addSeparator()
        toolbar.addAction("打印")
        # 4. 創(chuàng)建狀態(tài)欄(可選)
        status_bar = self.statusBar()
        status_bar.showMessage("就緒", 3000)  # 顯示3秒
        # 添加永久顯示的狀態(tài)欄部件
        permanent_label = QLabel("永久狀態(tài)信息")
        status_bar.addPermanentWidget(permanent_label)
        # 5. 設(shè)置窗口屬性
        self.setWindowTitle("文本編輯器")
        self.setGeometry(100, 100, 800, 600)
        self.show()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainAppWindow()
    sys.exit(app.exec_())

運(yùn)行效果(前3秒會顯示就緒):

展開菜單”文件“,過3秒:

QMainWindow的標(biāo)準(zhǔn)結(jié)構(gòu)

+---------------------------------------------------+
|                   菜單欄 (Menu Bar)                |
+---------------------------------------------------+
|                   工具欄 (Tool Bar)                |
+---------------------------------------------------+
|                                                   |
|               中央部件 (Central Widget)            |
|                                                   |
|    +-----------------------------------------+    |
|    |                                         |    |
|    |          你的主要內(nèi)容在這里              |    |
|    |                                         |    |
|    +-----------------------------------------+    |
|                                                   |
+---------------------------------------------------+
|                   狀態(tài)欄 (Status Bar)              |
+---------------------------------------------------+

我們很容易發(fā)現(xiàn)QMainWindow里面又有菜單,又有工具欄,那我們很容易就會提問,不對啊,現(xiàn)在的軟件不是要么只有菜單(如VSCode),要么只有標(biāo)簽形式切換的工具欄(如Word)嗎?

這是一個相當(dāng)復(fù)雜的問題,首先Word老版(1984-2006)其實真的是又有菜單欄又有工具欄的:

┌─────────────────────────────────────┐
│ 文件(F) 編輯(E) 視圖(V) 幫助(H)       ← 固定菜單欄
├─────────────────────────────────────┤
│ [??] [??] [??] [???] [B] [I] [U]     ← 浮動工具欄
└─────────────────────────────────────┘

特點

  • 菜單欄是固定的、層級式的
  • 工具欄是獨立的、可拖動的
  • 功能隱藏在多層菜單中
  • 空間利用率較低

但2007年后改為了采用這樣的Ribbon界面:

┌─────────────────────────────────────┐
│ ?? 插入 設(shè)計 布局 引用 郵件 審閱 視圖    ← 情境化標(biāo)簽頁
├─────────────────────────────────────┤
│  當(dāng)前任務(wù)相關(guān)功能分組展示            ← 自適應(yīng)功能區(qū)
│  ┌─────────┐ ┌─────────┐ ┌─────────┐
│  │ 剪貼板  │ │ 字體    │ │ 段落    │
│  │         │ │         │ │         │
│  └─────────┘ └─────────┘ └─────────┘
└─────────────────────────────────────┘

核心創(chuàng)新

  • 情境化標(biāo)簽頁:根據(jù)當(dāng)前任務(wù)切換功能組
  • 功能分組可視化:圖標(biāo)+文字,直觀易懂
  • 自適應(yīng)空間:根據(jù)窗口大小調(diào)整顯示
  • 減少菜單層級:80%常用功能在第一層

而VSCode則采用的是側(cè)邊活動欄:

# VS Code的界面結(jié)構(gòu)
┌─────────────────────────────────────────────────────┐
│ File Edit View Go Run Terminal Help                 │ ← 頂部菜單欄(簡潔)
├─────────────────────────────────────────────────────┤
│ ?? ?? ?? ?? ??                                    │ ← 活動欄(側(cè)邊圖標(biāo)欄)
│                                                    │
│  側(cè)邊面板區(qū)域                                      │
│  (資源管理器、搜索、Git等)                         │
│                                                    │
├─────────────────────────────────────────────────────┤
│ [main.py]                                         │ ← 編輯器標(biāo)簽頁
│                                                    │
│  def main():                                       │ ← 主編輯區(qū)域
│      print("Hello World")                          │
│                                                    │
├─────────────────────────────────────────────────────┤
│ Python 3.12.4 • UTF-8 • LF • 2 spaces              │ ← 狀態(tài)欄(多信息顯示)
└─────────────────────────────────────────────────────┘

關(guān)鍵區(qū)別對比表

特性QWidgetQMainWindow
定位基礎(chǔ)窗口/控件應(yīng)用程序主窗口
內(nèi)存占用較小較大(包含更多組件)
內(nèi)置結(jié)構(gòu)有標(biāo)準(zhǔn)菜單欄、工具欄、狀態(tài)欄區(qū)域
布局管理需要手動設(shè)置布局中央部件區(qū)域可設(shè)置布局
靈活性高,完全自定義有一定結(jié)構(gòu)限制
典型用途對話框、彈窗、簡單窗口軟件主窗口、復(fù)雜應(yīng)用
是否能使用布局管理器可以,直接設(shè)置只能對中央部件使用布局
是否能有多個實例可以通常只有一個

關(guān)鍵注意事項

1. QMainWindow不能直接設(shè)置布局!

(布局的介紹見本文第6節(jié))

# ? 錯誤做法
class WrongWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        layout.addWidget(QLabel("測試"))
        self.setLayout(layout)  # 這不會工作!
# ? 正確做法
class CorrectWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        
        # 創(chuàng)建一個QWidget作為中央部件的容器
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 在容器上設(shè)置布局
        layout = QVBoxLayout()
        layout.addWidget(QLabel("測試"))
        central_widget.setLayout(layout)  # 在中央部件上設(shè)置布局

2. 實際應(yīng)用中的選擇策略

# 場景1:需要標(biāo)準(zhǔn)菜單欄/工具欄的軟件 → 選擇QMainWindow
class TextEditor(QMainWindow):
    """文本編輯器,需要菜單欄保存文件"""
    pass
# 場景2:簡單的配置對話框 → 選擇QWidget
class SettingsDialog(QWidget):
    """設(shè)置對話框,不需要復(fù)雜的菜單結(jié)構(gòu)"""
    pass
# 場景3:復(fù)雜應(yīng)用的子窗口 → 選擇QWidget
class PreviewWindow(QWidget):
    """預(yù)覽窗口,作為主窗口的子窗口"""
    pass

3. 混合使用示例

from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QWidget
import sys

class MainApp(QMainWindow):
    def __init__(self):
        super().__init__()
        # 主窗口使用QMainWindow
        self.setWindowTitle("主應(yīng)用程序")
        self.setCentralWidget(QTextEdit())
        # 但點擊按鈕可以彈出QWidget對話框
        self.settings_dialog = SettingsDialog(self)
    def show_settings(self):
        self.settings_dialog.exec_()  # 顯示模態(tài)對話框

class SettingsDialog(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("設(shè)置")
        self.resize(300, 200)
        # 使用QWidget作為對話框

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainApp()
    window.show()
    sys.exit(app.exec_())

生成效果(我手動拖拽了一下界面大?。?/p>

實踐建議

什么時候用QWidget?

  • 對話框和彈窗:確認(rèn)框、消息框、設(shè)置窗口
  • 簡單工具:計算器、單位轉(zhuǎn)換器等小工具
  • 自定義控件:創(chuàng)建可重用的界面組件
  • 子窗口:主應(yīng)用中的浮動面板

什么時候用QMainWindow?

  • 應(yīng)用程序主窗口:編輯器、瀏覽器、IDE等
  • 復(fù)雜應(yīng)用:需要標(biāo)準(zhǔn)菜單和工具欄的軟件
  • 專業(yè)工具:圖像處理、數(shù)據(jù)分析等專業(yè)軟件

一個有用的技巧:從簡單開始

# 初期:從QWidget開始,快速原型
class SimpleApp(QWidget):
    def __init__(self):
        super().__init__()
        # 簡單布局和功能
# 后期:需要更多功能時,輕松遷移到QMainWindow
class EnhancedApp(QMainWindow):
    def __init__(self):
        super().__init__()
        # 將原來的QWidget內(nèi)容設(shè)為中央部件
        old_widget = SimpleApp()
        self.setCentralWidget(old_widget)
        # 添加菜單欄、工具欄等

總結(jié)

QWidget和QMainWindow不是"基礎(chǔ)版"和"高級版"的關(guān)系,而是不同用途的工具

  • QWidget像是白紙:給你最大自由度,想畫什么就畫什么
  • QMainWindow像是已經(jīng)畫好框架的畫布:提供了標(biāo)準(zhǔn)結(jié)構(gòu),你只需要填充內(nèi)容

記住這個簡單的選擇原則

  • 如果只需要一個簡單的窗口或?qū)υ捒?→ 選QWidget
  • 如果要創(chuàng)建有標(biāo)準(zhǔn)菜單/工具欄的應(yīng)用程序主窗口 → 選QMainWindow

理解它們的區(qū)別后,你就能根據(jù)實際需求做出合適的選擇,寫出更專業(yè)、更高效的PyQt5代碼!

5.3 信號與槽:Qt的"事件通信系統(tǒng)"

這是Qt最強(qiáng)大的特性之一!想象一下:

  • 信號(Signal):像電燈的開關(guān)
  • 槽(Slot):像電燈本身
  • 連接(Connect):像連接開關(guān)和燈的電線

工作原理:事件發(fā)生 → 發(fā)出信號 → 連接到槽 → 執(zhí)行函數(shù)

import sys
from PyQt5.QtWidgets import (
    QApplication,
    QWidget,
    QVBoxLayout,
    QLabel,
    QPushButton,
    QTextEdit,
)
from PyQt5.QtCore import pyqtSignal

class CustomButton(QPushButton):
    """
    自定義按鈕類
    演示如何創(chuàng)建和使用自定義信號
    """
    # 自定義信號 - 可以發(fā)送一個字符串
    message_signal = pyqtSignal(str)
    # 另一個自定義信號 - 可以發(fā)送兩個整數(shù)
    number_signal = pyqtSignal(int, int)
    def __init__(self, text, parent=None):
        super().__init__(text, parent)
        self.click_count = 0
    def mousePressEvent(self, event):
        """
        重寫鼠標(biāo)按下事件
        每次點擊都會發(fā)出兩個信號
        """
        self.click_count += 1
        # 發(fā)出第一個信號 - 帶字符串消息
        self.message_signal.emit(f"第{self.click_count}次點擊!")
        # 發(fā)出第二個信號 - 帶兩個數(shù)字
        x, y = event.x(), event.y()
        self.number_signal.emit(x, y)
        # 重要:調(diào)用父類方法確保正常行為
        super().mousePressEvent(event)

class ExampleApp(QWidget):
    def __init__(self):
        super().__init__()
        self.setup_ui()
    def setup_ui(self):
        self.setWindowTitle("自定義信號詳細(xì)示例")
        self.resize(500, 400)
        # 創(chuàng)建布局
        layout = QVBoxLayout()
        # 1. 信息顯示區(qū)域
        self.info_label = QLabel("點擊下面的按鈕查看效果", self)
        layout.addWidget(self.info_label)
        # 2. 文本顯示區(qū)域(用于顯示詳細(xì)信息)
        self.text_display = QTextEdit(self)
        self.text_display.setReadOnly(True)
        layout.addWidget(self.text_display)
        # 3. 創(chuàng)建自定義按鈕
        self.custom_btn = CustomButton("自定義按鈕 - 點擊我", self)
        layout.addWidget(self.custom_btn)
        # 4. 重置按鈕
        self.reset_btn = QPushButton("重置計數(shù)", self)
        layout.addWidget(self.reset_btn)
        self.setLayout(layout)
        # 連接信號
        self.connect_signals()
    def connect_signals(self):
        """連接所有信號到槽函數(shù)"""
        # 連接自定義按鈕的第一個信號
        self.custom_btn.message_signal.connect(self.update_info)
        # 連接自定義按鈕的第二個信號
        self.custom_btn.number_signal.connect(self.show_click_position)
        # 連接重置按鈕
        self.reset_btn.clicked.connect(self.reset_counter)
    def update_info(self, message):
        """更新信息標(biāo)簽"""
        self.info_label.setText(f"自定義信號: {message}")
        self.text_display.append(f"收到消息: {message}")
    def show_click_position(self, x, y):
        """顯示點擊位置"""
        self.text_display.append(f"點擊位置: x={x}, y={y}")
    def reset_counter(self):
        """重置計數(shù)器"""
        self.custom_btn.click_count = 0
        self.info_label.setText("計數(shù)器已重置")
        self.text_display.append("--- 計數(shù)器重置 ---")

# 運(yùn)行示例
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = ExampleApp()
    window.show()
    sys.exit(app.exec_())

首先設(shè)置帶信號(代碼里設(shè)置了兩種)的按鈕,然后將按鈕放到組件上,把信號連接到槽函數(shù)上(槽函數(shù)也可以是一個lambda函數(shù))。按鈕被觸發(fā)時,信號發(fā)射(emit),槽函數(shù)接受并處理信號,在代碼中就將處理結(jié)果展示在界面上:

5.4 常見控件快速上手

from PyQt5.QtWidgets import (
    QLabel,          # 標(biāo)簽 - 顯示文本或圖片
    QPushButton,     # 按鈕 - 點擊觸發(fā)動作
    QLineEdit,       # 單行輸入框
    QTextEdit,       # 多行文本編輯
    QCheckBox,       # 復(fù)選框
    QRadioButton,    # 單選按鈕
    QComboBox,       # 下拉框
    QSpinBox,        # 數(shù)字輸入框
    QProgressBar,    # 進(jìn)度條
    QSlider,         # 滑塊
)

6. 讓界面自動排列:布局管理

沒有布局管理器的GUI就像沒有CSS的HTML——元素會堆疊在一起。
布局管理器其實就是把一堆空間按布局組合到一起。

為什么需要布局管理器

  • 自動調(diào)整控件位置和大小
  • 適應(yīng)不同分辨率和窗口大小
  • 簡化界面設(shè)計

常用布局管理器

QVBoxLayout - 垂直排列

from PyQt5.QtWidgets import QVBoxLayout, QPushButton, QLabel
layout = QVBoxLayout()
layout.addWidget(QLabel("第一行"))
layout.addWidget(QPushButton("第二行"))
layout.addWidget(QLabel("第三行"))
self.setLayout(layout)  # 應(yīng)用到窗口

QHBoxLayout - 水平排列

from PyQt5.QtWidgets import QHBoxLayout
layout = QHBoxLayout()
layout.addWidget(QPushButton("左"))
layout.addWidget(QPushButton("中"))
layout.addWidget(QPushButton("右"))

布局嵌套 - 創(chuàng)建復(fù)雜界面

# 創(chuàng)建主垂直布局
main_layout = QVBoxLayout()
# 創(chuàng)建水平布局并添加控件
top_layout = QHBoxLayout()
top_layout.addWidget(QLabel("姓名:"))
top_layout.addWidget(QLineEdit())
# 將水平布局添加到垂直布局
main_layout.addLayout(top_layout)
main_layout.addWidget(QPushButton("提交"))
self.setLayout(main_layout)

7. 初學(xué)者常見錯誤與注意事項

錯誤1:忘記創(chuàng)建QApplication實例

# 錯誤寫法
window = QWidget()
window.show()
# 正確寫法
app = QApplication(sys.argv)
window = QWidget()
window.show()
app.exec_()

錯誤2:在子線程中直接更新GUI

在PyQt5(以及大多數(shù)GUI框架)中,有一個黃金規(guī)則所有GUI操作都必須在主線程(也稱為GUI線程)中進(jìn)行!

為什么有這個限制?

簡化的解釋:

PyQt5的GUI組件不是"線程安全"的

想象一下兩個線程同時修改同一個控件:

線程A: label.setText("Hello")     線程B: label.setText("World")
    ↓                                   ↓
同時寫入同一個內(nèi)存區(qū)域 → 數(shù)據(jù)競爭 → 程序崩潰!

錯誤示例分析

錯誤代碼:

import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import QThread, pyqtSignal
class WorkerThread(QThread):
    """工作線程"""
    def run(self):
        time.sleep(2)  # 模擬耗時操作
        # ? 危險!在子線程中直接更新GUI
        label.setText("處理完成!")  # 可能導(dǎo)致崩潰
        
# 在主線程中創(chuàng)建窗口
app = QApplication(sys.argv)
window = QWidget()
label = QLabel("等待中...")
button = QPushButton("開始任務(wù)")
layout = QVBoxLayout()
layout.addWidget(label)
layout.addWidget(button)
window.setLayout(layout)
# 創(chuàng)建工作線程
worker = WorkerThread()
def start_task():
    worker.start()
button.clicked.connect(start_task)
window.show()
sys.exit(app.exec_())

可能的結(jié)果

  • 程序可能直接崩潰
  • 界面可能不更新
  • 可能偶爾正常工作,但不可靠(最危險的情況?。?/li>

正確解決方案:使用信號

方案1:QThread + moveToThread

import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import QThread, pyqtSignal, QObject

class Worker(QObject):
    """工作對象,使用信號通信"""
    # 定義信號
    progress_signal = pyqtSignal(int)  # 傳遞進(jìn)度百分比
    result_signal = pyqtSignal(str)  # 傳遞結(jié)果字符串
    finished_signal = pyqtSignal()  # 完成信號(無參數(shù))
    def do_work(self):
        """執(zhí)行耗時任務(wù)"""
        for i in range(1, 11):
            time.sleep(0.5)  # 模擬耗時操作
            self.progress_signal.emit(i * 10)  # 發(fā)射進(jìn)度信號
        self.result_signal.emit("處理完成!")  # 發(fā)射結(jié)果信號
        self.finished_signal.emit()  # 發(fā)射完成信號

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.setup_worker()
    def init_ui(self):
        self.setWindowTitle("線程安全更新GUI示例")
        self.resize(400, 300)
        layout = QVBoxLayout()
        self.label = QLabel("準(zhǔn)備開始任務(wù)...")
        self.progress_label = QLabel("進(jìn)度: 0%")
        self.button = QPushButton("開始任務(wù)")
        self.status_label = QLabel("狀態(tài): 空閑")
        layout.addWidget(self.label)
        layout.addWidget(self.progress_label)
        layout.addWidget(self.status_label)
        layout.addWidget(self.button)
        self.setLayout(layout)
        self.button.clicked.connect(self.start_work)
    def setup_worker(self):
        """設(shè)置工作線程和信號連接"""
        # 創(chuàng)建工作對象和線程
        self.worker = Worker()
        self.thread = QThread()
        # 將工作對象移動到線程中
        self.worker.moveToThread(self.thread)
        # 連接信號
        self.worker.progress_signal.connect(self.update_progress)
        self.worker.result_signal.connect(self.update_result)
        self.worker.finished_signal.connect(self.work_finished)
        # 線程開始后,連接do_work方法
        self.thread.started.connect(self.worker.do_work)
        # 線程結(jié)束時,清理資源
        self.worker.finished_signal.connect(self.thread.quit)
        self.worker.finished_signal.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
    def start_work(self):
        """開始工作"""
        self.button.setEnabled(False)
        self.status_label.setText("狀態(tài): 處理中...")
        self.thread.start()
    def update_progress(self, progress):
        """更新進(jìn)度(在主線程中執(zhí)行)"""
        self.progress_label.setText(f"進(jìn)度: {progress}%")
    def update_result(self, result):
        """更新結(jié)果(在主線程中執(zhí)行)"""
        self.label.setText(result)
    def work_finished(self):
        """任務(wù)完成(在主線程中執(zhí)行)"""
        self.button.setEnabled(True)
        self.status_label.setText("狀態(tài): 完成")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

方案2:QRunnable

需要管理生命周期:

import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import QThreadPool, QRunnable, pyqtSignal, QObject, pyqtSlot, QMutex

class WorkerSignals(QObject):
    """定義工作線程的信號"""
    finished = pyqtSignal()
    progress = pyqtSignal(int)
    result = pyqtSignal(str)
    def __init__(self):
        super().__init__()
        self.is_valid = True  # 添加有效性標(biāo)志
    def delete_later(self):
        self.is_valid = False
        self.deleteLater()

class Worker(QRunnable):
    """工作線程類"""
    def __init__(self):
        super().__init__()
        self.signals = WorkerSignals()
        self.mutex = QMutex()  # 互斥鎖保護(hù)信號對象
    def run(self):
        """執(zhí)行耗時任務(wù)"""
        for i in range(10):
            time.sleep(0.3)
            progress = (i + 1) * 10
            # 使用互斥鎖保護(hù)信號對象
            self.mutex.lock()
            try:
                if (
                    self.signals
                    and hasattr(self.signals, "is_valid")
                    and self.signals.is_valid
                ):
                    self.signals.progress.emit(progress)
            except RuntimeError:
                # 信號對象已被刪除
                pass
            finally:
                self.mutex.unlock()
        self.mutex.lock()
        try:
            if (
                self.signals
                and hasattr(self.signals, "is_valid")
                and self.signals.is_valid
            ):
                self.signals.result.emit("任務(wù)完成!")
                self.signals.finished.emit()
        except RuntimeError:
            pass
        finally:
            self.mutex.unlock()

class SimpleThreadExample(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.threadpool = QThreadPool()
        self.workers = []  # 保持對worker的引用
    def init_ui(self):
        self.setWindowTitle("簡單多線程示例")
        self.resize(300, 200)
        layout = QVBoxLayout()
        self.label = QLabel("點擊按鈕開始任務(wù)")
        self.button = QPushButton("開始耗時任務(wù)")
        layout.addWidget(self.label)
        layout.addWidget(self.button)
        self.setLayout(layout)
        self.button.clicked.connect(self.start_task)
    def start_task(self):
        """啟動工作線程"""
        # 禁用按鈕,防止重復(fù)點擊
        self.button.setEnabled(False)
        # 創(chuàng)建工作對象
        worker = Worker()
        self.workers.append(worker)  # 保持引用
        # 連接信號
        worker.signals.progress.connect(self.on_progress)
        worker.signals.result.connect(self.on_result)
        worker.signals.finished.connect(lambda: self.on_finished(worker))
        # 在線程池中執(zhí)行
        self.threadpool.start(worker)
    def on_progress(self, progress):
        """更新進(jìn)度(自動在主線程中執(zhí)行)"""
        self.label.setText(f"處理中... {progress}%")
    def on_result(self, result):
        """顯示結(jié)果(自動在主線程中執(zhí)行)"""
        self.label.setText(result)
    def on_finished(self, worker):
        """任務(wù)完成(自動在主線程中執(zhí)行)"""
        self.button.setEnabled(True)
        # 清理worker
        if worker in self.workers:
            if worker.signals:
                worker.signals.delete_later()
            self.workers.remove(worker)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = SimpleThreadExample()
    window.show()
    sys.exit(app.exec_())

為什么信號能安全地更新GUI?

信號與槽的線程安全機(jī)制:

PyQt5的內(nèi)部機(jī)制:

當(dāng)信號從子線程發(fā)射時,PyQt5會自動:

  • 將信號放入主線程的事件隊列
  • 主線程在適當(dāng)?shù)臅r候處理這個事件
  • 調(diào)用連接的槽函數(shù)(在主線程中?。?/li>

所以:

worker.signals.result.emit("數(shù)據(jù)")  # 子線程中發(fā)射信號
    ↓
PyQt5內(nèi)部:跨線程傳遞信號
    ↓
label.setText("數(shù)據(jù)")  # 在主線程中執(zhí)行槽函數(shù)

其他安全更新GUI的方法

1. 使用QTimer在主線程中輪詢

from PyQt5.QtCore import QTimer
class SafeUpdateExample:
    def __init__(self):
        self.results_queue = []  # 線程安全的數(shù)據(jù)結(jié)構(gòu)
        
        # 定時器在主線程中運(yùn)行
        self.timer = QTimer()
        self.timer.timeout.connect(self.check_results)
        self.timer.start(100)  # 每100毫秒檢查一次
    
    def check_results(self):
        """在主線程中檢查并更新GUI"""
        if self.results_queue:
            result = self.results_queue.pop(0)
            label.setText(result)

2. 使用QMetaObject.invokeMethod

from PyQt5.QtCore import QMetaObject, Qt, pyqtSlot
class WorkerThread(QThread):
    result_ready = pyqtSignal(str)
    
    def run(self):
        # 耗時任務(wù)
        result = "處理完成"
        
        # 安全地調(diào)用主線程的方法
        QMetaObject.invokeMethod(
            main_window,  # 目標(biāo)對象
            "update_label",  # 方法名
            Qt.QueuedConnection,  # 異步連接
            result  # 參數(shù)
        )
class MainWindow:
    @pyqtSlot(str)
    def update_label(self, text):
        label.setText(text)  # 在主線程中執(zhí)行

實際應(yīng)用場景

場景1:網(wǎng)絡(luò)請求

class DownloadWorker(QThread):
    progress = pyqtSignal(int)
    finished = pyqtSignal(bytes)
    error = pyqtSignal(str)
    
    def run(self):
        try:
            # 下載文件(耗時操作)
            for chunk in download_file():
                self.progress.emit(chunk.progress)
            
            self.finished.emit(file_data)
        except Exception as e:
            self.error.emit(str(e))  # 發(fā)送錯誤信號,而不是直接彈窗

場景2:數(shù)據(jù)處理

class DataProcessorWorker(QObject):
    data_processed = pyqtSignal(pd.DataFrame)  # 發(fā)送處理后的數(shù)據(jù)
    error_occurred = pyqtSignal(str)
    
    def process_large_data(self, data):
        try:
            # 復(fù)雜的數(shù)據(jù)處理
            result = heavy_computation(data)
            self.data_processed.emit(result)
        except Exception as e:
            self.error_occurred.emit(f"處理失敗: {e}")

常見錯誤模式

錯誤1:忘記moveToThread

worker = Worker()
thread = QThread()
# ? 忘記移動對象到線程
thread.start()
worker.do_work()  # 還在主線程中執(zhí)行!

錯誤2:直接調(diào)用GUI方法

def worker_function():
    # 各種計算...
    window.update_ui(data)  # ? 危險!在子線程中調(diào)用GUI方法

錯誤3:忽略異常處理

def worker_function():
    try:
        # 可能失敗的操作
    except Exception as e:
        # ? 不要直接顯示錯誤對話框
        QMessageBox.critical(None, "錯誤", str(e))  # 可能崩潰!
        
        # ? 應(yīng)該發(fā)送信號
        self.error_signal.emit(str(e))

最佳實踐總結(jié)

  • 永遠(yuǎn)不在子線程中直接操作GUI控件
  • 使用信號/槽進(jìn)行線程間通信
  • 復(fù)雜的計算放在工作線程中
  • GUI更新只在主線程中進(jìn)行
  • 使用合適的錯誤處理機(jī)制
  • 使用moveToThread()確保對象在正確的線程中

簡單記憶法則

記住這句話:“信號發(fā)射是自由的,但槽函數(shù)執(zhí)行總是在主線程的懷抱中。”

這樣,您就能安全地在PyQt5中使用多線程了!

錯誤3:內(nèi)存泄漏(忘記設(shè)置父對象)

# 可能的內(nèi)存泄漏
def create_widget():
    widget = QWidget()  # 沒有父對象,需要手動管理
    return widget
# 更好的做法
def create_widget(parent=None):
    widget = QWidget(parent)  # 指定父對象,自動管理內(nèi)存
    return widget

重要注意事項

  • 所有GUI操作必須在主線程
  • 使用布局管理器,而不是固定坐標(biāo)
  • 合理使用信號與槽,避免過度耦合
  • 學(xué)習(xí)使用Qt Designer進(jìn)行可視化設(shè)計

9. 總結(jié)

通過本文,你已經(jīng)掌握了PyQt5的基礎(chǔ)知識:

  • 理解了PyQt5的基本架構(gòu)
  • 學(xué)會了創(chuàng)建窗口和基本控件
  • 掌握了信號與槽機(jī)制
  • 能夠使用布局管理器排列控件
  • 創(chuàng)建了第一個交互式應(yīng)用

PyQt5的學(xué)習(xí)曲線可能有些陡峭,但一旦掌握,你將擁有創(chuàng)建強(qiáng)大桌面應(yīng)用的能力。記住,最好的學(xué)習(xí)方式就是動手實踐。從今天開始,嘗試用PyQt5解決你遇到的實際問題吧!

到此這篇關(guān)于Python使用PyQt5打造桌面應(yīng)用的入門指南的文章就介紹到這了,更多相關(guān)Python PyQt5桌面應(yīng)用開發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Python使用random.shuffle()隨機(jī)打亂字典排序

    Python使用random.shuffle()隨機(jī)打亂字典排序

    本文主要介紹了Python使用random.shuffle()隨機(jī)打亂字典排序,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • python3利用smtplib通過qq郵箱發(fā)送郵件方法示例

    python3利用smtplib通過qq郵箱發(fā)送郵件方法示例

    python實現(xiàn)郵件發(fā)送較為簡單,主要用到smtplib這個模塊,所以下面這篇文章主要給大家介紹了關(guān)于python3利用smtplib通過qq郵箱發(fā)送郵件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起看看吧。
    2017-12-12
  • Python監(jiān)控系統(tǒng)資源(CPU/內(nèi)存/日志/磁盤/進(jìn)程)的完整指南

    Python監(jiān)控系統(tǒng)資源(CPU/內(nèi)存/日志/磁盤/進(jìn)程)的完整指南

    這篇文章主要為大家詳細(xì)介紹了Python監(jiān)控系統(tǒng)資源的相關(guān)方法,例如CPU,內(nèi)存,日志,磁盤和進(jìn)程,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-12-12
  • python實現(xiàn)自動清理重復(fù)文件

    python實現(xiàn)自動清理重復(fù)文件

    這篇文章主要介紹了python如何實現(xiàn)自動清理重復(fù)文件,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下
    2020-08-08
  • Django 解決阿里云部署同步數(shù)據(jù)庫報錯的問題

    Django 解決阿里云部署同步數(shù)據(jù)庫報錯的問題

    這篇文章主要介紹了Django 解決阿里云部署同步數(shù)據(jù)庫報錯的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-05-05
  • 運(yùn)用PyTorch動手搭建一個共享單車預(yù)測器

    運(yùn)用PyTorch動手搭建一個共享單車預(yù)測器

    這篇文章主要介紹了運(yùn)用PyTorch動手搭建一個共享單車預(yù)測器,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • Django中信號signals的簡單使用方法

    Django中信號signals的簡單使用方法

    這篇文章主要給大家介紹了關(guān)于Django中信號signals的簡單使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Django具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • Python曲線擬合詳解

    Python曲線擬合詳解

    這篇文章主要介紹了關(guān)于python曲線擬合,scipy.optimize中,curve_fit函數(shù)可調(diào)用非線性最小二乘法進(jìn)行函數(shù)擬合,文中有詳細(xì)的代碼作為參考,需要的朋友可以閱讀參考
    2023-04-04
  • Python實現(xiàn)多個圓和圓中圓的檢測

    Python實現(xiàn)多個圓和圓中圓的檢測

    這篇文章主要為大家詳細(xì)介紹了Python如何實現(xiàn)多個圓檢測和圓中圓的檢測,文中的實現(xiàn)方法講解詳細(xì),具有一定的借鑒價值,需要的可以參考一下
    2022-11-11
  • Pytorch加載數(shù)據(jù)集的方式總結(jié)及補(bǔ)充

    Pytorch加載數(shù)據(jù)集的方式總結(jié)及補(bǔ)充

    Pytorch自定義數(shù)據(jù)集方法,應(yīng)該是用pytorch做算法的最基本的東西,下面這篇文章主要給大家介紹了關(guān)于Pytorch加載數(shù)據(jù)集的方式總結(jié)及補(bǔ)充,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-11-11

最新評論

新宁县| 广西| 永修县| 邹城市| 上高县| 南漳县| 白朗县| 大英县| 防城港市| 庐江县| 石楼县| 景谷| 水富县| 蒙阴县| 柳州市| 桓台县| 乌审旗| 白朗县| 光山县| 新竹市| 屏东市| 长兴县| 前郭尔| 石河子市| 曲松县| 新余市| 汉中市| 凌云县| 辽源市| 工布江达县| 响水县| 博爱县| 鞍山市| 南城县| 南部县| 积石山| 金山区| 洛阳市| 西盟| 扎鲁特旗| 宁阳县|