Featured image of post PyQt5

PyQt5

该文主要介绍pyQt5的使用,PyQt5是一个用于创建图形用户界面(GUI)的Python库。它是基于Qt库的,Qt是一个用于创建跨平台应用程序的C++库

PyQt5能干什么

​ PyQt5的主要作用是创建UI界面,同时支持鼠标以及键盘的交互,不仅提供界面控件,还提供网络、数据库、多媒体等应用开发功能,具体来说:

UI控件(Widgets):

最基础也是最重要的,提供大量开箱即用的的UI元素,可以像搭积木一样构建界面

  • 基础控件:按钮(QPushButton)、标签(QLabel)、文本框(QLineEdit)、文本域(QTextEdit)、组合框(QComboBox)、列表(QListWidget)、表格(QTableWidget)等。
  • 布局管理器:水平布局(QHBoxLayout)、垂直布局(QVBoxLayout)、网格布局(QGridLayout)等,用于自动排列和调整控件大小,确保窗口缩放时界面依然美观。
  • 高级容器:选项卡(QTabWidget)、分组框(QGroupBox)、滚动区域(QScrollArea)、堆叠窗口(QStackedWidget)、停靠窗口(QDockWidget)等,用于组织复杂的界面结构。
  • 主窗口框架QMainWindow 类提供了标准应用程序主窗口的功能,包括菜单栏(QMenuBar)、工具栏(QToolBar)、状态栏(QStatusBar)和中心部件。

图形与视图框架:

处理大量的、交互式的2D图形对象,适合开发绘图软件、数据可视化、游戏编辑器。核心组件有:QGraphicsScene(场景,管理所有图形项)、QGraphicsView(视图,用于显示场景)、QGraphicsItem(图形项,如矩形、椭圆、自定义图形)

多媒体功能

  • 音频播放:使用 QMediaPlayerQAudioOutput 播放音频文件。
  • 视频播放:使用 QMediaPlayerQVideoWidget 播放视频文件。
  • 摄像头访问:使用 QCamera 来捕获实时摄像头画面。

网络功能

  • HTTP/HTTPS 请求:通过 QNetworkAccessManager 进行网络通信,支持 GET、POST 等操作,比 Python 标准的 urllib 更强大、更易用。
  • TCP/UDP 通信:提供了 QTcpSocket, QTcpServer, QUdpSocket 等类,用于开发低层的网络应用,如聊天程序、自定义协议客户端/服务器。
  • WebSocket:支持现代 WebSocket 协议。

数据库连接

  • 通过 QtSql 模块提供了统一的接口来操作各种数据库,如 SQLite、MySQL、PostgreSQL、ODBC 等。
  • 内置了非常方便的模型/视图类(如 QSqlTableModel),可以轻松地将数据库中的表与UI控件(如 QTableView)绑定,实现数据的自动显示和编辑。

多线程(QThread)

桌面应用的UI主线程必须保持响应,否则会“卡住”。PyQt5 提供了 QThread 类,可以轻松地将耗时的操作(如下载文件、复杂计算)放到子线程中运行,并通过信号(Signal)与槽(Slot) 机制与主线程通信,更新UI进度,保证程序的流畅性

样式与主题(QSS - Qt Style Sheets)

类似于 Web 开发中的 CSS,你可以使用 QSS 来极大地美化应用程序的界面,可以轻松地自定义控件的外观、颜色、字体、边框、背景等,从而打造出独一无二的、现代化的UI风格,而无需修改代码逻辑

基本控件介绍

QMainWindow:

主窗口类(主界面),可以包含菜单栏、工具栏、状态栏、中心窗口等各种窗口部件,其中中心窗口是最重要的,可以是任何Qt窗口部件,常用成员函数:

  • setCentralWidget():设置中心窗口部件
  • setMenuBar():设置菜单栏
  • addToolBar():添加工具栏
  • statusBar():获取状态栏
  • show():显示主窗口

Qwidget:

是Qt框架中所有用户界面的基类,包括窗口、对话框、按钮、标签、文本框等,此外还提供一些基本的用户界面功能,例如绘制、事件处理、布局等。

QWidget提供了一些常用的属性和方法,例如size()、pos()、setWindowTitle()等,以便于管理和操作界面部件。

在创建自定义用户界面部件时,我们可以从QWidget派生出我们自己的部件类,并通过重载其成员函数来实现自定义行为。例如,我们可以通过重载QWidget的paintEvent()函数来绘制自己的部件,或者通过重载其mousePressEvent()函数来处理鼠标点击事件

QMain Window、Qwidget、QDialog的区别

QDialog: 是对话窗口的基类,没有菜单栏、工具栏、状态栏。 QMainWindow:可以包含菜单栏、工具栏、状态栏和标题栏,是最常见的形式。 QWidget: 不确定窗口的用途,就使用Qwidget。

Spacers控件

用于布局的空白控件,在布局中创建空白区域来分隔其它控件,空白区域的大小由其内部设置size_policy控制。

该控件有两种类型:水平(QSpacerItem)和垂直(QSpacerItem),可以使用QHBoxLayout或QVBoxLayout将Spacer控件添加到布局中。在添加Spacer控件时,您可以指定其最小大小、最大大小和首选大小,以及其大小策略

Qlabel控件

显示文本或图像的控件,它通常被用于显示静态文本信息,可以通过设置其文本、字体、颜色、对齐方式等属性来自定义标签的样式和布局。可以将QLabel放置在主窗口、对话框或其他控件上,以便在应用程序中提供帮助文本、说明、状态消息等。

方法:

  • setText(text): 设置文本内容

  • setPixmap(pixmap): 设置图像内容

  • setAlignment(alignment): 设置文本或图像的对齐方式

  • setWordWrap( on): 当文本过长时是否自动换行

  • setFixedSize(width, height): 设置控件的固定大小

  • setStyleSheet(styleSheet): 设置控件的样式表

属性:

  • text(): 返回控件的文本内容
  • pixmap(): 返回控件的图像内容
  • alignment(): 返回控件的对齐方式
  • wordWrap(): 返回控件是否自动换行
  • font(): 返回控件的字体
  • color(): 返回控件的颜色

常用信号:

  • linkActivated:当控件中包含超链接时,用户单击链接时触发此信号。
  • linkHovered:当用户将鼠标悬停在超链接上时,触发此信号。
  • linkPressed:当用户按下并释放鼠标按钮时,同时鼠标位于超链接上时,触发此信号。

综合案例

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QVBoxLayout, QLineEdit, 
                             QPushButton, QHBoxLayout, QFrame)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QFont, QPalette, QColor, QPixmap, QLinearGradient, QBrush

# 自定义可点击QLabel(支持点击信号)
class ClickableLabel(QLabel):
    clicked = pyqtSignal()  # 自定义点击信号

    def __init__(self, text="", parent=None):
        super().__init__(text, parent)
        self.setCursor(Qt.PointingHandCursor)  # 设置手型光标

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.clicked.emit()  # 发射信号
        super().mousePressEvent(event)

class IntegratedDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QLabel功能集成示例")
        self.setGeometry(300, 300, 600, 500)
        self.initUI()
        
    def initUI(self):
        main_layout = QVBoxLayout()
        
        # ===== 1. 基础属性展示 =====
        frame1 = QFrame()
        frame1.setFrameShape(QFrame.Box)
        layout1 = QVBoxLayout()
        
        label_base = QLabel("基础属性示例:自定义字体/颜色/对齐")
        label_base.setFont(QFont("微软雅黑", 12, QFont.Bold))
        label_base.setStyleSheet("color: #FF0000; background-color: #FFFFCC;")
        label_base.setAlignment(Qt.AlignCenter)
        label_base.setFixedHeight(40)
        
        layout1.addWidget(label_base)
        frame1.setLayout(layout1)
        main_layout.addWidget(frame1)
        
        # ===== 2. 交互功能区域 =====
        frame2 = QFrame()
        frame2.setFrameShape(QFrame.Box)
        layout2 = QVBoxLayout()
        
        # 超链接交互
        link_label = QLabel('<a href="https://www.qt.io">访问Qt官网(悬停提示)</a>')
        link_label.setToolTip("点击打开Qt官方网站")
        link_label.linkHovered.connect(lambda link: print(f"链接悬停: {link}"))
        link_label.linkActivated.connect(self.open_link)
        
        # 自定义点击标签
        click_label = ClickableLabel("✨ 点我切换颜色(可点击标签)")
        click_label.setStyleSheet("border: 2px solid #9999FF; padding: 10px;")
        click_label.clicked.connect(self.toggle_label_color)
        
        layout2.addWidget(QLabel("交互功能区域:"))
        layout2.addWidget(link_label)
        layout2.addWidget(click_label)
        frame2.setLayout(layout2)
        main_layout.addWidget(frame2)
        
        # ===== 3. 实时同步区域 =====
        frame3 = QFrame()
        frame3.setFrameShape(QFrame.Box)
        layout3 = QVBoxLayout()
        
        self.sync_lineedit = QLineEdit()
        self.sync_label = QLabel("同步显示文本")
        
        # 实时连接信号与槽
        self.sync_lineedit.textChanged.connect(self.sync_label.setText)
        
        layout3.addWidget(QLabel("实时同步:输入即更新"))
        layout3.addWidget(self.sync_lineedit)
        layout3.addWidget(self.sync_label)
        frame3.setLayout(layout3)
        main_layout.addWidget(frame3)
        
        # ===== 4. 动态效果区域 =====
        frame4 = QFrame()
        frame4.setFrameShape(QFrame.Box)
        layout4 = QVBoxLayout()
        
        # 充电进度效果
        self.charge_label = QLabel()
        self.charge_label.setFixedHeight(30)
        self.charge_label.setText("充电中: 0%")
        self.charge_label.setAlignment(Qt.AlignCenter)
        
        # 点击变色效果
        self.color_label = QLabel("点击下方按钮变色")
        self.color_label.setAlignment(Qt.AlignCenter)
        self.color_label.setFixedHeight(50)
        
        # 控制按钮
        btn_toggle = QPushButton("启动充电动画")
        btn_toggle.clicked.connect(self.toggle_charge_animation)
        btn_color = QPushButton("随机颜色")
        btn_color.clicked.connect(self.change_random_color)
        
        btn_layout = QHBoxLayout()
        btn_layout.addWidget(btn_toggle)
        btn_layout.addWidget(btn_color)
        
        layout4.addWidget(QLabel("动态效果区域:"))
        layout4.addWidget(self.charge_label)
        layout4.addWidget(self.color_label)
        layout4.addLayout(btn_layout)
        frame4.setLayout(layout4)
        main_layout.addWidget(frame4)
        
        self.setLayout(main_layout)
        
        # 充电动画控制
        self.charge_timer = QTimer()
        self.charge_timer.timeout.connect(self.update_charge)
        self.charge_percent = 0
        self.is_charging = False

    # ===== 功能实现方法 =====
    def open_link(self, url):
        print(f"打开链接: {url}")  # 实际应用中可用QDesktopServices打开[1](@ref)
    
    def toggle_label_color(self):
        current_color = self.sender().palette().color(QPalette.Window)
        new_color = QColor("#FFCCFF") if current_color != QColor("#FFCCFF") else QColor("#CCFFFF")
        self.sender().setStyleSheet(f"background-color: {new_color.name()};")
        print("标签点击事件触发")
    
    def toggle_charge_animation(self):
        if not self.is_charging:
            self.charge_timer.start(200)  # 每200ms更新
            self.is_charging = True
        else:
            self.charge_timer.stop()
            self.is_charging = False
    
    def update_charge(self):
        self.charge_percent = (self.charge_percent + 5) % 105
        if self.charge_percent > 100:
            self.charge_label.setText("充电完成!")
            self.charge_timer.stop()
            self.is_charging = False
            return
        
        # 创建渐变进度条
        gradient = QLinearGradient(0, 0, self.charge_label.width(), 0)
        gradient.setColorAt(0, "#00FF00")
        gradient.setColorAt(self.charge_percent/100, "#00FF00")
        gradient.setColorAt(min(1.0, self.charge_percent/100 + 0.01), "#CCCCCC")
        
        # 应用渐变背景
        palette = self.charge_label.palette()
        palette.setBrush(QPalette.Window, QBrush(gradient))
        self.charge_label.setPalette(palette)
        self.charge_label.setText(f"充电中: {self.charge_percent}%")
    
    def change_random_color(self):
        color = QColor(randint(0,255), randint(0,255), randint(0,255))
        self.color_label.setStyleSheet(f"background-color: {color.name()}; color: white;")

if __name__ == "__main__":
    from random import randint
    app = QApplication(sys.argv)
    window = IntegratedDemo()
    window.show()
    sys.exit(app.exec_())

QLineEdit控件

该控件用于接收用户输入的单行文本编辑,允许输入和编辑文本信息; 同时也可以限定输入内容的形式比如只允许输入数字、字母或特定字符,或者限定输入内容长度; 可以使用信号和槽机制来处理用户输入的文本,以便在应用程序中执行特定的操作或验证用户输入的有效性。

方法:

  • setAlignment(alignment):设置文本的对齐方式,alignment可以是Qt.AlignLeft、Qt.AlignRight、Qt.AlignCenter等值之一
  • setPlaceholderText(text):设置控件的占位符文本,当控件没有内容时显示的文本
  • setReadOnly(readOnly):设置控件是否为只读模式
  • setValidator(validator):设置控件的输入验证器,用于限制用户输入的内容
  • textChanged.connect(slot):文本改变时的信号,连接到相应的槽函数

属性:

  • text():获取或设置控件的文本内容
  • setText(text):设置控件的文本内容
  • clear():清空控件的文本内容

综合案例

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLineEdit, QLabel, 
                            QPushButton, QHBoxLayout, QFormLayout, QCompleter)
from PyQt5.QtGui import QIntValidator, QDoubleValidator, QRegularExpressionValidator, QFont, QIcon
from PyQt5.QtCore import Qt, QRegularExpression

class LineEditDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle("QLineEdit 综合功能演示")
        self.setGeometry(300, 300, 600, 450)
        layout = QVBoxLayout()
        
        # ===== 1. 基础功能区域 =====
        base_layout = QFormLayout()
        
        # 基础文本输入(实时反馈)
        self.basic_edit = QLineEdit()
        self.basic_edit.setPlaceholderText("输入实时显示在右侧")
        self.basic_label = QLabel("等待输入...")
        self.basic_edit.textChanged.connect(lambda: self.basic_label.setText(self.basic_edit.text()))
        base_layout.addRow("基础输入:", self.basic_edit)
        base_layout.addRow("实时反馈:", self.basic_label)
        
        # 整数验证(范围限制)
        self.int_edit = QLineEdit()
        self.int_edit.setValidator(QIntValidator(1, 100))  # 限制1-100整数
        self.int_edit.setPlaceholderText("输入1-100的整数")
        base_layout.addRow("整数验证:", self.int_edit)
        
        # 浮点数验证(格式限制)
        self.float_edit = QLineEdit()
        float_validator = QDoubleValidator(0.0, 100.0, 2)  # 范围0-100,2位小数
        float_validator.setNotation(QDoubleValidator.StandardNotation)
        self.float_edit.setValidator(float_validator)
        self.float_edit.setPlaceholderText("0.00-100.00")
        base_layout.addRow("浮点数验证:", self.float_edit)
        
        layout.addLayout(base_layout)
        
        # ===== 2. 高级功能区域 =====
        adv_layout = QFormLayout()
        
        # 正则表达式验证(邮箱格式)
        self.email_edit = QLineEdit()
        email_regex = QRegularExpression("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
        self.email_edit.setValidator(QRegularExpressionValidator(email_regex))
        self.email_edit.setPlaceholderText("example@domain.com")
        adv_layout.addRow("邮箱验证:", self.email_edit)
        
        # 输入掩码(电话号码)
        self.phone_edit = QLineEdit()
        self.phone_edit.setInputMask("999-9999-9999;#")  # 格式: 138-8888-8888
        adv_layout.addRow("电话掩码:", self.phone_edit)
        
        # 密码输入(显示切换)
        self.pwd_layout = QHBoxLayout()
        self.pwd_edit = QLineEdit()
        self.pwd_edit.setEchoMode(QLineEdit.Password)
        self.toggle_btn = QPushButton("显示")
        self.toggle_btn.setCheckable(True)
        self.toggle_btn.toggled.connect(self.toggle_pwd_visibility)
        self.pwd_layout.addWidget(self.pwd_edit)
        self.pwd_layout.addWidget(self.toggle_btn)
        adv_layout.addRow("密码输入:", self.pwd_layout)
        
        # 自动补全
        self.auto_edit = QLineEdit()
        completer = QCompleter(["Python", "Java", "C++", "JavaScript"])
        self.auto_edit.setCompleter(completer)
        self.auto_edit.setPlaceholderText("输入编程语言")
        adv_layout.addRow("自动补全:", self.auto_edit)
        
        layout.addLayout(adv_layout)
        
        # ===== 3. 交互反馈区域 =====
        feedback_layout = QHBoxLayout()
        
        # 清除按钮
        self.clear_edit = QLineEdit()
        self.clear_edit.setClearButtonEnabled(True)  # 启用内置清除按钮
        self.clear_edit.setPlaceholderText("输入后点击右侧X清除")
        feedback_layout.addWidget(QLabel("清除按钮:"))
        feedback_layout.addWidget(self.clear_edit)
        
        # 样式反馈
        self.style_edit = QLineEdit()
        self.style_edit.setPlaceholderText("输入'error'触发红色边框")
        self.style_edit.textChanged.connect(self.update_style)
        feedback_layout.addWidget(QLabel("动态样式:"))
        feedback_layout.addWidget(self.style_edit)
        
        layout.addLayout(feedback_layout)
        
        # ===== 4. 自定义操作按钮 =====
        action_layout = QHBoxLayout()
        self.action_edit = QLineEdit()
        self.action_edit.setPlaceholderText("右侧按钮可转为大写")
        
        # 添加自定义动作按钮
        upper_btn = QPushButton("⇧")
        upper_btn.clicked.connect(lambda: self.action_edit.setText(self.action_edit.text().upper()))
        action_layout.addWidget(QLabel("动作按钮:"))
        action_layout.addWidget(self.action_edit)
        action_layout.addWidget(upper_btn)
        
        layout.addLayout(action_layout)
        self.setLayout(layout)

    # ===== 功能实现方法 =====
    def toggle_pwd_visibility(self, checked):
        """切换密码显示/隐藏"""
        if checked:
            self.pwd_edit.setEchoMode(QLineEdit.Normal)
            self.toggle_btn.setText("隐藏")
        else:
            self.pwd_edit.setEchoMode(QLineEdit.Password)
            self.toggle_btn.setText("显示")

    def update_style(self, text):
        """根据输入内容动态更新样式"""
        if "error" in text.lower():
            self.style_edit.setStyleSheet("border: 2px solid red;")
        else:
            self.style_edit.setStyleSheet("")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = LineEditDemo()
    demo.show()
    sys.exit(app.exec_())

QTextEdit

该控件可以用于显示和编辑富文本的多行文本编辑控件。它可以用于创建编辑器、日记、HTML文本查看器等。 可以在文本中插入多媒体内容,如图像、超链接、HTML表格等,并且可以在文本中使用样式来设置字体、颜色、背景、对齐等。QTextEdit还支持拼写检查、撤销/重做、自动缩进、文本选择等基本编辑功能

综合案例

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit, QAction, QFileDialog, 
                            QToolBar, QStatusBar, QMessageBox, QColorDialog, QFontDialog)
from PyQt5.QtGui import QTextCursor, QTextCharFormat, QFont, QTextBlockFormat, QIcon, QPixmap
from PyQt5.QtCore import Qt

class RichTextEditor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTextEdit 综合案例 - 富文本编辑器")
        self.setGeometry(300, 200, 900, 600)
        self.file_path = None
        self.init_ui()
        
    def init_ui(self):
        # ===== 1. 核心控件:QTextEdit =====
        self.text_edit = QTextEdit()
        self.text_edit.setAcceptRichText(True)  # 启用富文本支持
        self.text_edit.setPlaceholderText("输入内容或使用工具栏进行操作...")
        self.setCentralWidget(self.text_edit)
        
        # ===== 2. 菜单栏 =====
        self.create_menus()
        
        # ===== 3. 工具栏 =====
        toolbar = QToolBar("格式工具栏")
        self.addToolBar(toolbar)
        
        # 文本样式动作 - 使用文本代替图标
        bold_action = QAction("B", self)  # 使用文本代替图标
        bold_action.setToolTip("加粗")
        bold_action.triggered.connect(lambda: self.set_text_format("bold"))
        toolbar.addAction(bold_action)
        
        italic_action = QAction("I", self)
        italic_action.setToolTip("斜体")
        italic_action.triggered.connect(lambda: self.set_text_format("italic"))
        toolbar.addAction(italic_action)
        
        underline_action = QAction("U", self)
        underline_action.setToolTip("下划线")
        underline_action.triggered.connect(lambda: self.set_text_format("underline"))
        toolbar.addAction(underline_action)
        
        toolbar.addSeparator()
        
        # 字体选择
        font_action = QAction("字体", self)
        font_action.triggered.connect(self.choose_font)
        toolbar.addAction(font_action)
        
        # 颜色选择
        color_action = QAction("颜色", self)
        color_action.triggered.connect(self.choose_color)
        toolbar.addAction(color_action)
        
        toolbar.addSeparator()
        
        # 段落对齐
        align_left = QAction("左对齐", self)
        align_left.triggered.connect(lambda: self.set_alignment(Qt.AlignLeft))
        toolbar.addAction(align_left)
        
        align_center = QAction("居中", self)
        align_center.triggered.connect(lambda: self.set_alignment(Qt.AlignCenter))
        toolbar.addAction(align_center)
        
        align_right = QAction("右对齐", self)
        align_right.triggered.connect(lambda: self.set_alignment(Qt.AlignRight))
        toolbar.addAction(align_right)
        
        toolbar.addSeparator()
        
        # 插入功能
        insert_image = QAction("插入图片", self)
        insert_image.triggered.connect(self.insert_image)
        toolbar.addAction(insert_image)
        
        insert_link = QAction("插入链接", self)
        insert_link.triggered.connect(self.insert_link)
        toolbar.addAction(insert_link)
        
        # ===== 4. 状态栏 =====
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.text_edit.textChanged.connect(self.update_status_bar)
        
        # ===== 5. 信号连接 =====
        self.text_edit.cursorPositionChanged.connect(self.cursor_position_changed)
        self.text_edit.selectionChanged.connect(self.selection_changed)
        
    def create_menus(self):
        menubar = self.menuBar()
        
        # 文件菜单
        file_menu = menubar.addMenu("文件")
        
        new_action = QAction("新建", self)
        new_action.setShortcut("Ctrl+N")
        new_action.triggered.connect(self.new_file)
        file_menu.addAction(new_action)
        
        open_action = QAction("打开", self)
        open_action.setShortcut("Ctrl+O")
        open_action.triggered.connect(self.open_file)
        file_menu.addAction(open_action)
        
        save_action = QAction("保存", self)
        save_action.setShortcut("Ctrl+S")
        save_action.triggered.connect(self.save_file)
        file_menu.addAction(save_action)
        
        save_as_action = QAction("另存为", self)
        save_as_action.triggered.connect(self.save_file_as)
        file_menu.addAction(save_as_action)
        
        file_menu.addSeparator()
        
        exit_action = QAction("退出", self)
        exit_action.setShortcut("Ctrl+Q")
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)
        
        # 编辑菜单
        edit_menu = menubar.addMenu("编辑")
        
        undo_action = QAction("撤销", self)
        undo_action.setShortcut("Ctrl+Z")
        undo_action.triggered.connect(self.text_edit.undo)
        edit_menu.addAction(undo_action)
        
        redo_action = QAction("重做", self)
        redo_action.setShortcut("Ctrl+Y")
        redo_action.triggered.connect(self.text_edit.redo)
        edit_menu.addAction(redo_action)
        
        edit_menu.addSeparator()
        
        cut_action = QAction("剪切", self)
        cut_action.setShortcut("Ctrl+X")
        cut_action.triggered.connect(self.text_edit.cut)
        edit_menu.addAction(cut_action)
        
        copy_action = QAction("复制", self)
        copy_action.setShortcut("Ctrl+C")
        copy_action.triggered.connect(self.text_edit.copy)
        edit_menu.addAction(copy_action)
        
        paste_action = QAction("粘贴", self)
        paste_action.setShortcut("Ctrl+V")
        paste_action.triggered.connect(self.text_edit.paste)
        edit_menu.addAction(paste_action)
        
        # 视图菜单
        view_menu = menubar.addMenu("视图")
        
        readonly_action = QAction("只读模式", self, checkable=True)
        readonly_action.toggled.connect(self.toggle_readonly)
        view_menu.addAction(readonly_action)
        
        # 大文件优化功能
        large_file_action = QAction("加载大文件(分页)", self)
        large_file_action.triggered.connect(self.load_large_file)
        view_menu.addAction(large_file_action)

    # ===== 功能实现方法 =====
    def set_text_format(self, style):
        """设置文本格式(加粗/斜体/下划线)"""
        cursor = self.text_edit.textCursor()
        if not cursor.hasSelection():
            # 如果没有选择文本,则对即将输入的文本应用格式
            format = QTextCharFormat()
            if style == "bold":
                format.setFontWeight(QFont.Bold if not self.text_edit.fontWeight() == QFont.Bold else QFont.Normal)
            elif style == "italic":
                format.setFontItalic(not self.text_edit.fontItalic())
            elif style == "underline":
                format.setFontUnderline(not self.text_edit.fontUnderline())
                
            self.text_edit.setCurrentCharFormat(format)
            return
            
        fmt = QTextCharFormat()
        if style == "bold":
            fmt.setFontWeight(QFont.Bold if not cursor.charFormat().font().bold() else QFont.Normal)
        elif style == "italic":
            fmt.setFontItalic(not cursor.charFormat().fontItalic())
        elif style == "underline":
            fmt.setFontUnderline(not cursor.charFormat().fontUnderline())
        cursor.mergeCharFormat(fmt)

    def choose_font(self):
        """选择字体"""
        font, ok = QFontDialog.getFont(self.text_edit.font(), self)
        if ok:
            self.text_edit.setCurrentFont(font)

    def choose_color(self):
        """选择颜色"""
        color = QColorDialog.getColor()
        if color.isValid():
            self.text_edit.setTextColor(color)

    def set_alignment(self, alignment):
        """设置段落对齐方式"""
        cursor = self.text_edit.textCursor()
        block_fmt = QTextBlockFormat()
        block_fmt.setAlignment(alignment)
        cursor.mergeBlockFormat(block_fmt)

    def insert_image(self):
        """插入图片到光标位置"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif)"
        )
        if file_path:
            cursor = self.text_edit.textCursor()
            # 使用QPixmap加载图片并插入
            pixmap = QPixmap(file_path)
            if not pixmap.isNull():
                # 缩放图片以适应编辑器宽度
                pixmap = pixmap.scaledToWidth(300, Qt.SmoothTransformation)
                cursor.insertImage(pixmap.toImage())

    def insert_link(self):
        """插入超链接"""
        from PyQt5.QtWidgets import QInputDialog
        
        text, ok = QInputDialog.getText(self, "插入链接", "请输入链接URL:")
        if ok and text:
            cursor = self.text_edit.textCursor()
            # 如果选择了文本,使用选中文本作为链接文本,否则使用URL
            if cursor.hasSelection():
                link_text = cursor.selectedText()
            else:
                link_text = text
                
            cursor.insertHtml(f'<a href="{text}">{link_text}</a>')

    def new_file(self):
        """新建文件"""
        if self.check_unsaved_changes():
            self.text_edit.clear()
            self.file_path = None
            self.setWindowTitle("QTextEdit 综合案例 - 富文本编辑器")

    def open_file(self):
        """打开文件"""
        if not self.check_unsaved_changes():
            return
            
        path, _ = QFileDialog.getOpenFileName(
            self, "打开文件", "", 
            "文本文件 (*.txt);;HTML文件 (*.html *.htm);;所有文件 (*)"
        )
        if path:
            try:
                with open(path, 'r', encoding='utf-8') as f:
                    if path.endswith(('.html', '.htm')):
                        self.text_edit.setHtml(f.read())
                    else:
                        self.text_edit.setPlainText(f.read())
                self.file_path = path
                self.setWindowTitle(f"QTextEdit 综合案例 - {path}")
            except Exception as e:
                QMessageBox.critical(self, "打开错误", f"打开文件时出错:\n{str(e)}")

    def save_file(self):
        """保存文件"""
        if self.file_path:
            self._save_to_file(self.file_path)
        else:
            self.save_file_as()

    def save_file_as(self):
        """另存为文件"""
        path, _ = QFileDialog.getSaveFileName(
            self, "保存文件", "", 
            "文本文件 (*.txt);;HTML文件 (*.html);;所有文件 (*)"
        )
        if path:
            self._save_to_file(path)
            self.file_path = path
            self.setWindowTitle(f"QTextEdit 综合案例 - {path}")

    def _save_to_file(self, path):
        """实际保存操作"""
        try:
            with open(path, 'w', encoding='utf-8') as f:
                if path.endswith('.html'):
                    f.write(self.text_edit.toHtml())
                else:
                    f.write(self.text_edit.toPlainText())
            self.text_edit.document().setModified(False)
            self.status_bar.showMessage(f"文件已保存: {path}", 3000)
        except Exception as e:
            QMessageBox.critical(self, "保存错误", f"保存文件时出错:\n{str(e)}")

    def toggle_readonly(self, state):
        """切换只读模式"""
        self.text_edit.setReadOnly(state)
        self.status_bar.showMessage(f"只读模式: {'开启' if state else '关闭'}", 2000)

    def update_status_bar(self):
        """更新状态栏统计信息"""
        text = self.text_edit.toPlainText()
        char_count = len(text)
        word_count = len(text.split())
        line_count = self.text_edit.document().blockCount()
        self.status_bar.showMessage(f"字符: {char_count} | 单词: {word_count} | 行数: {line_count}")

    def cursor_position_changed(self):
        """光标位置变化时显示行列号"""
        cursor = self.text_edit.textCursor()
        line = cursor.blockNumber() + 1
        col = cursor.columnNumber() + 1
        self.status_bar.showMessage(f"行: {line}, 列: {col}", 2000)

    def selection_changed(self):
        """文本选中时显示选中信息"""
        cursor = self.text_edit.textCursor()
        if cursor.hasSelection():
            selected = cursor.selectedText()
            self.status_bar.showMessage(f"已选: {len(selected)} 字符", 2000)

    def load_large_file(self):
        """大文件分页加载优化"""
        path, _ = QFileDialog.getOpenFileName(self, "打开大文件", "", "文本文件 (*.txt)")
        if not path:
            return
            
        # 大文件优化:只加载最后100行
        try:
            with open(path, 'r', encoding='utf-8', errors='ignore') as f:
                lines = []
                # 高效读取最后100行
                for line in f:
                    lines.append(line)
                    if len(lines) > 100:
                        lines.pop(0)
                
                self.text_edit.setPlainText(''.join(lines))
            self.status_bar.showMessage(f"已加载大文件最后100行: {path}", 5000)
        except Exception as e:
            QMessageBox.critical(self, "文件错误", f"读取文件失败:\n{str(e)}")

    def check_unsaved_changes(self):
        """检查未保存的更改"""
        if self.text_edit.document().isModified():
            reply = QMessageBox.question(self, "未保存更改", 
                                        "当前文档有未保存的更改。是否保存?",
                                        QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
            if reply == QMessageBox.Save:
                self.save_file()
                return True
            elif reply == QMessageBox.Cancel:
                return False
        return True

if __name__ == '__main__':
    app = QApplication(sys.argv)
    editor = RichTextEditor()
    editor.show()
    sys.exit(app.exec_())

QDialog

该控件是常用的对话窗口,为QWidget的子类,例如询问用户是否需要保存文件,输入数据或显示一些信息,可以设置对话框的标题、大小、位置等属性,也可以添加各种控件,例如标签、按钮、文本框等,可以捕获对话框关闭事件和其他事件,可以返回一些数据或响应用户的操作。

方法:

  • exec_():将对话框设置为模态并运行对话框的事件循环,直到对话框被关闭。此方法通常用于从对话框中获取一些数据或响应用户的操作。
  • show():显示对话框,但不将其设置为模态。此方法通常用于显示一些信息或提供一些选项。
  • done(int result):设置对话框的返回值,并关闭对话框。此方法通常在用户进行某些操作后调用,以便通知调用方对话框的结果。
  • accept():设置对话框的返回值为QDialog.Accepted,并关闭对话框。此方法通常在用户确认某些操作后调用,以便通知调用方对话框的结果。
  • ect():设置对话框的返回值为QDialog.Rejected,并关闭对话框。此方法通常在用户取消某些操作后调用,以便通知调用方对话框的结果。
  • setButtonLayout(list):设置对话框底部的按钮布局。参数list应该是一个QDialogButtonBox中定义的一组按钮类型,例如QDialogButtonBox.Ok | QDialogButtonBox.Cancel
  • open():将对话框设置为模态并运行对话框的事件循环,直到对话框被关闭。此方法与exec_()方法类似,但可以方便地打开多个对话框

除了上述方法外,QDialog还提供了一些其他方法,例如resize()、move()、close()、setVisible()等,这些方法与QWidget类似,用于设置对话框的大小、位置、可见性等

属性:

  • windowTitle:对话框的标题
  • windowIcon:对话框的图标
  • sizeGripEnabled:是否显示调整大小的手柄,默认为False
  • layout:对话框的布局管理器,用于控制对话框内部控件的位置和大小
  • modal:是否将对话框设置为模态,即禁止用户与其他窗口交互,直到对话框被关闭,默认为True。
  • result:对话框的返回值,通常用于表示用户的操作或输入。如果对话框是通过exec_()方法调用的,则返回值是对话框的退出码(通常是QDialog.Accepted或QDialog.Rejected);如果对话框是通过show()方法调用的,则返回值始终是None
  • sizeHint:对话框的推荐大小。
  • acceptMode:对话框的接受模式,可以是QFileDialog.AcceptOpen(打开文件)、QFileDialog.AcceptSave(保存文件)或QFileDialog.AcceptSave(选择文件夹)
  • rejectAction:当用户按下Esc键或者点击“取消”按钮时,对话框执行的操作。默认情况下,对话框将执行reject()方法并关闭

信号:

  • finished(int result):当对话框关闭并且返回值已经设置时发出。参数result表示对话框的返回值
  • accepted():当用户确认某些操作并调用accept()方法时发出
  • rejected():当用户取消某些操作并调用reject()方法时发出
  • resizeEvent(event):当对话框的大小改变时发出。可以重写此方法来执行一些特定的操作

几种类型

  • 普通对话框(QDialog):最基本的对话框类型,用于显示消息、获取输入、提供选项等。它可以通过设置各种属性和使用布局管理器来控制对话框的大小、位置和内容
  • 文件对话框(QFileDialog):用于选择文件或文件夹的对话框。可以使用QFileDialog的静态方法创建各种类型的文件对话框,例如打开文件对话框、保存文件对话框、选择文件夹对话框等。
  • 字体对话框(QFontDialog):用于选择字体和字号的对话框。可以使用QFontDialog的静态方法创建字体对话框。
  • 颜色对话框(QColorDialog):用于选择颜色的对话框。可以使用QColorDialog的静态方法创建颜色对话框。
  • 输入对话框(QInputDialog):用于获取用户输入的对话框。可以使用QInputDialog的静态方法创建各种类型的输入对话框,例如文本输入对话框、整数输入对话框、浮点数输入对话框等。
  • 消息框(QMessageBox):用于显示消息和提供选项的对话框。可以使用QMessageBox的静态方法创建各种类型的消息框,例如信息框、警告框、错误框、询问框等.
  • 鼠标指针对话框(QCursor):用于显示不同类型的鼠标指针。可以使用QCursor的静态方法创建各种类型的鼠标指针对话框,例如箭头、十字形、等待指针等。

综合案例

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
import sys
import os
from PyQt5.QtWidgets import (QApplication, QDialog, QVBoxLayout, QHBoxLayout, 
                             QTextEdit, QPushButton, QMenuBar, QMenu, QAction, 
                             QFileDialog, QFontDialog, QInputDialog, QMessageBox)
from PyQt5.QtGui import QTextCursor, QCursor
from PyQt5.QtCore import Qt

class TextEditor(QDialog):
    def __init__(self):
        super().__init__()
        self.current_file = None
        self.init_ui()
        
    def init_ui(self):
        # 设置窗口属性
        self.setWindowTitle('简易文本编辑器 - PyQt5对话框示例')
        self.setGeometry(100, 100, 800, 600)
        
        # 创建主布局
        main_layout = QVBoxLayout()
        
        # 创建菜单栏
        menubar = QMenuBar()
        file_menu = QMenu('文件', self)
        edit_menu = QMenu('编辑', self)
        format_menu = QMenu('格式', self)
        help_menu = QMenu('帮助', self)
        
        # 添加文件菜单动作
        new_action = QAction('新建', self)
        new_action.triggered.connect(self.new_file)
        open_action = QAction('打开', self)
        open_action.triggered.connect(self.open_file)
        save_action = QAction('保存', self)
        save_action.triggered.connect(self.save_file)
        save_as_action = QAction('另存为', self)
        save_as_action.triggered.connect(self.save_as_file)
        exit_action = QAction('退出', self)
        exit_action.triggered.connect(self.close)
        
        file_menu.addAction(new_action)
        file_menu.addAction(open_action)
        file_menu.addAction(save_action)
        file_menu.addAction(save_as_action)
        file_menu.addSeparator()
        file_menu.addAction(exit_action)
        
        # 添加编辑菜单动作
        find_action = QAction('查找', self)
        find_action.triggered.connect(self.find_text)
        replace_action = QAction('替换', self)
        replace_action.triggered.connect(self.replace_text)
        
        edit_menu.addAction(find_action)
        edit_menu.addAction(replace_action)
        
        # 添加格式菜单动作
        font_action = QAction('字体', self)
        font_action.triggered.connect(self.change_font)
        
        format_menu.addAction(font_action)
        
        # 添加帮助菜单动作
        about_action = QAction('关于', self)
        about_action.triggered.connect(self.show_about)
        
        help_menu.addAction(about_action)
        
        # 将菜单添加到菜单栏
        menubar.addMenu(file_menu)
        menubar.addMenu(edit_menu)
        menubar.addMenu(format_menu)
        menubar.addMenu(help_menu)
        
        # 创建文本编辑区域
        self.text_edit = QTextEdit()
        
        # 创建底部按钮
        button_layout = QHBoxLayout()
        
        clear_btn = QPushButton('清空内容')
        clear_btn.clicked.connect(self.clear_text)
        
        status_btn = QPushButton('统计字数')
        status_btn.clicked.connect(self.count_words)
        
        button_layout.addWidget(clear_btn)
        button_layout.addWidget(status_btn)
        button_layout.addStretch()
        
        # 将组件添加到主布局
        main_layout.setMenuBar(menubar)
        main_layout.addWidget(self.text_edit)
        main_layout.addLayout(button_layout)
        
        self.setLayout(main_layout)
    
    def new_file(self):
        # 使用消息对话框确认是否保存当前文件 [7](@ref)
        if self.text_edit.document().isModified():
            reply = QMessageBox.question(self, '确认', 
                                       '当前文档已修改,是否保存?',
                                       QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
            
            if reply == QMessageBox.Save:
                self.save_file()
            elif reply == QMessageBox.Cancel:
                return
        
        self.text_edit.clear()
        self.current_file = None
        self.setWindowTitle('新建文件 - 简易文本编辑器')
    
    def open_file(self):
        # 使用文件对话框选择文件 [1](@ref)
        file_path, _ = QFileDialog.getOpenFileName(self, '打开文件', '', 
                                                 '文本文件 (*.txt);;所有文件 (*)')
        
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    self.text_edit.setText(f.read())
                self.current_file = file_path
                self.setWindowTitle(f'{os.path.basename(file_path)} - 简易文本编辑器')
            except Exception as e:
                QMessageBox.critical(self, '错误', f'无法打开文件: {str(e)}')
    
    def save_file(self):
        if self.current_file:
            try:
                with open(self.current_file, 'w', encoding='utf-8') as f:
                    f.write(self.text_edit.toPlainText())
                self.text_edit.document().setModified(False)
                QMessageBox.information(self, '成功', '文件已保存!')
            except Exception as e:
                QMessageBox.critical(self, '错误', f'无法保存文件: {str(e)}')
        else:
            self.save_as_file()
    
    def save_as_file(self):
        # 使用文件对话框选择保存路径 [1](@ref)
        file_path, _ = QFileDialog.getSaveFileName(self, '另存为', '', 
                                                 '文本文件 (*.txt);;所有文件 (*)')
        
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(self.text_edit.toPlainText())
                self.current_file = file_path
                self.setWindowTitle(f'{os.path.basename(file_path)} - 简易文本编辑器')
                self.text_edit.document().setModified(False)
                QMessageBox.information(self, '成功', '文件已保存!')
            except Exception as e:
                QMessageBox.critical(self, '错误', f'无法保存文件: {str(e)}')
    
    def find_text(self):
        # 使用输入对话框获取查找内容 [2](@ref)
        text, ok = QInputDialog.getText(self, '查找', '输入要查找的内容:')
        
        if ok and text:
            # 使用QCursor改变光标状态
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            content = self.text_edit.toPlainText()
            if text in content:
                # 定位到找到的文本
                cursor = self.text_edit.textCursor()
                cursor.movePosition(QTextCursor.Start)
                self.text_edit.setTextCursor(cursor)
                
                # 高亮所有匹配项
                self.text_edit.moveCursor(QTextCursor.Start)
                format = self.text_edit.textCursor().charFormat()
                format.setBackground(Qt.yellow)
                
                while self.text_edit.find(text):
                    self.text_edit.textCursor().setCharFormat(format)
                
                QMessageBox.information(self, '查找', f'找到 "{text}"!')
            else:
                QMessageBox.information(self, '查找', f'未找到 "{text}"')
            
            QApplication.restoreOverrideCursor()
    
    def replace_text(self):
        # 使用输入对话框获取替换内容 [2](@ref)
        find_text, ok1 = QInputDialog.getText(self, '替换', '输入要查找的内容:')
        
        if ok1 and find_text:
            replace_text, ok2 = QInputDialog.getText(self, '替换', '输入替换内容:')
            
            if ok2:
                content = self.text_edit.toPlainText()
                if find_text in content:
                    new_content = content.replace(find_text, replace_text)
                    self.text_edit.setPlainText(new_content)
                    QMessageBox.information(self, '替换', '替换完成!')
                else:
                    QMessageBox.information(self, '替换', f'未找到 "{find_text}"')
    
    def change_font(self):
        # 使用字体对话框选择字体 [8](@ref)
        current_font = self.text_edit.currentFont()
        font, ok = QFontDialog.getFont(current_font, self, '选择字体')
        
        if ok:
            self.text_edit.setCurrentFont(font)
    
    def clear_text(self):
        # 使用消息对话框确认清空操作 [7](@ref)
        reply = QMessageBox.question(self, '确认', 
                                   '确定要清空所有内容吗?',
                                   QMessageBox.Yes | QMessageBox.No)
        
        if reply == QMessageBox.Yes:
            self.text_edit.clear()
    
    def count_words(self):
        text = self.text_edit.toPlainText()
        char_count = len(text)
        word_count = len(text.split())
        line_count = text.count('\n') + 1 if text else 0
        
        # 使用消息对话框显示统计结果 [7](@ref)
        QMessageBox.information(self, '统计信息', 
                              f'字符数: {char_count}\n单词数: {word_count}\n行数: {line_count}')
    
    def show_about(self):
        # 使用消息对话框显示关于信息 [7](@ref)
        QMessageBox.about(self, '关于', 
                         '简易文本编辑器\n\n'
                         '演示PyQt5各种对话框的使用:\n'
                         '- QFileDialog: 文件打开和保存\n'
                         '- QFontDialog: 字体选择\n'
                         '- QInputDialog: 文本输入\n'
                         '- QMessageBox: 消息提示\n'
                         '- QCursor: 光标控制')
    
    def closeEvent(self, event):
        # 重写关闭事件,确认是否保存 [7](@ref)
        if self.text_edit.document().isModified():
            reply = QMessageBox.question(self, '确认退出', 
                                       '文档已修改,是否保存?',
                                       QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
            
            if reply == QMessageBox.Save:
                self.save_file()
                event.accept()
            elif reply == QMessageBox.Discard:
                event.accept()
            else:
                event.ignore()
        else:
            event.accept()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    editor = TextEditor()
    editor.show()
    sys.exit(app.exec_())

容器

常见的PyQt容器有:

  • QMainWindow:主窗口容器,通常包含菜单栏、工具栏、状态栏等
  • QWidget:基本的用户界面元素容器,可以作为其他容器的子容器
  • QGroupBox:分组框容器,可以将相关的组件放到一个分组框中,使它们更易于组织和查看
  • QTabWidget:选项卡容器,可以在多个选项卡中放置不同的组件,让用户轻松地在它们之间切换
  • QStackedWidget:堆栈容器,可以在同一位置上堆叠多个组件,只显示其中的一个,让用户可以轻松地在它们之间切换
  • QScrollArea:滚动区域容器,当容器中的组件太大,无法在当前视图中完全显示时,可以使用滚动区域容器
  • QSplitter:拆分器容器,可以将容器水平或垂直拆分为两个或更多子容器,让用户可以自由地调整它们的大小
  • QToolBar:工具栏容器,可以在主窗口或其他容器中放置多个工具栏,让用户可以快速访问常用功能

QGroupBox使用示例:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
import sys
from PyQt5.QtWidgets import (QApplication, QDialog, QVBoxLayout, QHBoxLayout,
                             QGroupBox, QRadioButton, QCheckBox, QPushButton,
                             QLineEdit, QLabel, QComboBox)

class GroupBoxExample(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('QGroupBox 示例')
        self.setGeometry(300, 300, 400, 300)
        self.init_ui()
        
    def init_ui(self):
        main_layout = QVBoxLayout()
        
        # 创建第一个分组框 - 单选按钮组
        radio_group = QGroupBox("用户类型")
        radio_layout = QVBoxLayout()
        
        self.student_radio = QRadioButton("学生")
        self.teacher_radio = QRadioButton("教师")
        self.admin_radio = QRadioButton("管理员")
        
        self.student_radio.setChecked(True)
        
        radio_layout.addWidget(self.student_radio)
        radio_layout.addWidget(self.teacher_radio)
        radio_layout.addWidget(self.admin_radio)
        radio_group.setLayout(radio_layout)
        
        # 创建第二个分组框 - 复选框组
        checkbox_group = QGroupBox("兴趣爱好")
        checkbox_layout = QVBoxLayout()
        
        self.reading_check = QCheckBox("阅读")
        self.sports_check = QCheckBox("运动")
        self.music_check = QCheckBox("音乐")
        self.travel_check = QCheckBox("旅行")
        
        checkbox_layout.addWidget(self.reading_check)
        checkbox_layout.addWidget(self.sports_check)
        checkbox_layout.addWidget(self.music_check)
        checkbox_layout.addWidget(self.travel_check)
        checkbox_group.setLayout(checkbox_layout)
        
        # 创建第三个分组框 - 表单输入
        form_group = QGroupBox("个人信息")
        form_layout = QVBoxLayout()
        
        name_layout = QHBoxLayout()
        name_layout.addWidget(QLabel("姓名:"))
        self.name_input = QLineEdit()
        name_layout.addWidget(self.name_input)
        form_layout.addLayout(name_layout)
        
        age_layout = QHBoxLayout()
        age_layout.addWidget(QLabel("年龄:"))
        self.age_combo = QComboBox()
        self.age_combo.addItems([str(i) for i in range(18, 66)])
        age_layout.addWidget(self.age_combo)
        form_layout.addLayout(age_layout)
        
        form_group.setLayout(form_layout)
        
        # 提交按钮
        submit_btn = QPushButton("提交信息")
        submit_btn.clicked.connect(self.show_info)
        
        # 添加到主布局
        main_layout.addWidget(radio_group)
        main_layout.addWidget(checkbox_group)
        main_layout.addWidget(form_group)
        main_layout.addWidget(submit_btn)
        
        self.setLayout(main_layout)
    
    def show_info(self):
        # 获取用户类型
        user_type = "学生" if self.student_radio.isChecked() else \
                   "教师" if self.teacher_radio.isChecked() else "管理员"
        
        # 获取兴趣爱好
        hobbies = []
        if self.reading_check.isChecked(): hobbies.append("阅读")
        if self.sports_check.isChecked(): hobbies.append("运动")
        if self.music_check.isChecked(): hobbies.append("音乐")
        if self.travel_check.isChecked(): hobbies.append("旅行")
        
        # 获取个人信息
        name = self.name_input.text() or "未填写"
        age = self.age_combo.currentText()
        
        # 显示结果
        print(f"用户类型: {user_type}")
        print(f"姓名: {name}, 年龄: {age}")
        print(f"兴趣爱好: {', '.join(hobbies) if hobbies else '无'}")
        print("-" * 30)


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

QTabWidget使用示例:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, 
                             QVBoxLayout, QLabel, QPushButton, QLineEdit,
                             QListWidget, QTextEdit, QHBoxLayout)

class TabWidgetExample(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('QTabWidget 示例')
        self.setGeometry(100, 100, 600, 400)
        self.init_ui()
        
    def init_ui(self):
        # 创建主窗口的中心部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 创建主布局
        main_layout = QVBoxLayout(central_widget)
        
        # 创建标签页部件
        self.tab_widget = QTabWidget()
        
        # 创建三个标签页
        self.create_tab1()
        self.create_tab2()
        self.create_tab3()
        
        # 将标签页添加到主布局
        main_layout.addWidget(self.tab_widget)
        
        # 添加状态标签
        self.status_label = QLabel("当前标签: 信息页")
        main_layout.addWidget(self.status_label)
        
        # 连接标签切换信号
        self.tab_widget.currentChanged.connect(self.tab_changed)
    
    def create_tab1(self):
        """创建第一个标签页 - 信息页"""
        tab1 = QWidget()
        layout = QVBoxLayout(tab1)
        
        # 添加一些控件
        layout.addWidget(QLabel("欢迎使用标签页示例程序"))
        layout.addWidget(QLabel("这是第一个标签页"))
        
        # 添加输入框
        self.name_input = QLineEdit()
        self.name_input.setPlaceholderText("请输入您的姓名")
        layout.addWidget(self.name_input)
        
        # 添加按钮
        greet_btn = QPushButton("打招呼")
        greet_btn.clicked.connect(self.greet_user)
        layout.addWidget(greet_btn)
        
        # 添加结果标签
        self.greet_label = QLabel("")
        layout.addWidget(self.greet_label)
        
        layout.addStretch()
        self.tab_widget.addTab(tab1, "信息页")
    
    def create_tab2(self):
        """创建第二个标签页 - 列表页"""
        tab2 = QWidget()
        layout = QVBoxLayout(tab2)
        
        # 添加列表控件
        layout.addWidget(QLabel("项目列表:"))
        self.list_widget = QListWidget()
        self.list_widget.addItems(["项目1", "项目2", "项目3", "项目4"])
        layout.addWidget(self.list_widget)
        
        # 添加按钮布局
        btn_layout = QHBoxLayout()
        
        add_btn = QPushButton("添加项目")
        add_btn.clicked.connect(self.add_item)
        btn_layout.addWidget(add_btn)
        
        remove_btn = QPushButton("删除项目")
        remove_btn.clicked.connect(self.remove_item)
        btn_layout.addWidget(remove_btn)
        
        layout.addLayout(btn_layout)
        layout.addStretch()
        self.tab_widget.addTab(tab2, "列表页")
    
    def create_tab3(self):
        """创建第三个标签页 - 编辑页"""
        tab3 = QWidget()
        layout = QVBoxLayout(tab3)
        
        # 添加文本编辑控件
        layout.addWidget(QLabel("编辑区域:"))
        self.text_edit = QTextEdit()
        self.text_edit.setPlaceholderText("在此输入文本...")
        layout.addWidget(self.text_edit)
        
        # 添加按钮
        clear_btn = QPushButton("清空内容")
        clear_btn.clicked.connect(self.clear_text)
        layout.addWidget(clear_btn)
        
        layout.addStretch()
        self.tab_widget.addTab(tab3, "编辑页")
    
    def greet_user(self):
        """打招呼按钮的槽函数"""
        name = self.name_input.text().strip()
        if name:
            self.greet_label.setText(f"你好, {name}!")
        else:
            self.greet_label.setText("请输入您的姓名")
    
    def add_item(self):
        """添加项目按钮的槽函数"""
        self.list_widget.addItem(f"项目{self.list_widget.count() + 1}")
    
    def remove_item(self):
        """删除项目按钮的槽函数"""
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            self.list_widget.takeItem(current_row)
    
    def clear_text(self):
        """清空文本按钮的槽函数"""
        self.text_edit.clear()
    
    def tab_changed(self, index):
        """标签切换时的槽函数"""
        tab_name = self.tab_widget.tabText(index)
        self.status_label.setText(f"当前标签: {tab_name}")


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

QStackedWidget使用示例:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QStackedWidget, QWidget,
                             QVBoxLayout, QLabel, QPushButton, QLineEdit,
                             QListWidget, QTextEdit, QHBoxLayout, QGroupBox)


class StackedWidgetExample(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('QStackedWidget 示例')
        self.setGeometry(100, 100, 600, 400)
        self.init_ui()

    def init_ui(self):
        # 创建主窗口的中心部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        # 创建主布局
        main_layout = QVBoxLayout(central_widget)

        # 创建堆叠窗口部件
        self.stacked_widget = QStackedWidget()

        # 创建三个页面
        self.create_page1()
        self.create_page2()
        self.create_page3()

        # 将堆叠窗口添加到主布局
        main_layout.addWidget(self.stacked_widget)

        # 创建导航按钮
        nav_layout = QHBoxLayout()

        prev_btn = QPushButton("上一页")
        prev_btn.clicked.connect(self.prev_page)
        nav_layout.addWidget(prev_btn)

        next_btn = QPushButton("下一页")
        next_btn.clicked.connect(self.next_page)
        nav_layout.addWidget(next_btn)

        main_layout.addLayout(nav_layout)

        # 添加状态标签
        self.status_label = QLabel("当前页面: 1/3")
        main_layout.addWidget(self.status_label)

    def create_page1(self):
        """创建第一个页面 - 信息页"""
        page1 = QWidget()
        layout = QVBoxLayout(page1)

        # 添加分组框
        group_box = QGroupBox("用户信息")
        group_layout = QVBoxLayout()

        # 添加输入框
        self.name_input = QLineEdit()
        self.name_input.setPlaceholderText("请输入您的姓名")
        group_layout.addWidget(self.name_input)

        # 添加按钮
        greet_btn = QPushButton("打招呼")
        greet_btn.clicked.connect(self.greet_user)
        group_layout.addWidget(greet_btn)

        # 添加结果标签
        self.greet_label = QLabel("")
        group_layout.addWidget(self.greet_label)

        group_box.setLayout(group_layout)
        layout.addWidget(group_box)
        layout.addStretch()

        self.stacked_widget.addWidget(page1)

    def create_page2(self):
        """创建第二个页面 - 列表页"""
        page2 = QWidget()
        layout = QVBoxLayout(page2)

        # 添加分组框
        group_box = QGroupBox("项目列表")
        group_layout = QVBoxLayout()

        # 添加列表控件
        self.list_widget = QListWidget()
        self.list_widget.addItems(["项目1", "项目2", "项目3", "项目4"])
        group_layout.addWidget(self.list_widget)

        # 添加按钮布局
        btn_layout = QHBoxLayout()

        add_btn = QPushButton("添加项目")
        add_btn.clicked.connect(self.add_item)
        btn_layout.addWidget(add_btn)

        remove_btn = QPushButton("删除项目")
        remove_btn.clicked.connect(self.remove_item)
        btn_layout.addWidget(remove_btn)

        group_layout.addLayout(btn_layout)
        group_box.setLayout(group_layout)
        layout.addWidget(group_box)
        layout.addStretch()

        self.stacked_widget.addWidget(page2)

    def create_page3(self):
        """创建第三个页面 - 编辑页"""
        page3 = QWidget()
        layout = QVBoxLayout(page3)

        # 添加分组框
        group_box = QGroupBox("文本编辑")
        group_layout = QVBoxLayout()

        # 添加文本编辑控件
        self.text_edit = QTextEdit()
        self.text_edit.setPlaceholderText("在此输入文本...")
        group_layout.addWidget(self.text_edit)

        # 添加按钮
        clear_btn = QPushButton("清空内容")
        clear_btn.clicked.connect(self.clear_text)
        group_layout.addWidget(clear_btn)

        group_box.setLayout(group_layout)
        layout.addWidget(group_box)
        layout.addStretch()

        self.stacked_widget.addWidget(page3)

    def greet_user(self):
        """打招呼按钮的槽函数"""
        name = self.name_input.text().strip()
        if name:
            self.greet_label.setText(f"你好, {name}!")
        else:
            self.greet_label.setText("请输入您的姓名")

    def add_item(self):
        """添加项目按钮的槽函数"""
        self.list_widget.addItem(f"项目{self.list_widget.count() + 1}")

    def remove_item(self):
        """删除项目按钮的槽函数"""
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            self.list_widget.takeItem(current_row)

    def clear_text(self):
        """清空文本按钮的槽函数"""
        self.text_edit.clear()

    def prev_page(self):
        """上一页按钮的槽函数"""
        current_index = self.stacked_widget.currentIndex()
        if current_index > 0:
            self.stacked_widget.setCurrentIndex(current_index - 1)
            self.update_status()

    def next_page(self):
        """下一页按钮的槽函数"""
        current_index = self.stacked_widget.currentIndex()
        if current_index < self.stacked_widget.count() - 1:
            self.stacked_widget.setCurrentIndex(current_index + 1)
            self.update_status()

    def update_status(self):
        """更新状态标签"""
        current_index = self.stacked_widget.currentIndex() + 1
        total_pages = self.stacked_widget.count()
        self.status_label.setText(f"当前页面: {current_index}/{total_pages}")


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

QSplitters使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QSplitter, QWidget, 
                             QVBoxLayout, QHBoxLayout, QLabel, QTextEdit, 
                             QListWidget, QTreeWidget, QTreeWidgetItem)

class SplitterExample(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('QSplitter 示例')
        self.setGeometry(100, 100, 800, 600)
        self.init_ui()
        
    def init_ui(self):
        # 创建主窗口的中心部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 创建主布局
        main_layout = QVBoxLayout(central_widget)
        
        # 创建水平分割器
        horizontal_splitter = QSplitter()
        horizontal_splitter.setOrientation(0)  # 0 表示水平方向
        
        # 创建左侧区域 - 文本编辑区
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        left_layout.addWidget(QLabel("文本编辑区"))
        self.text_edit = QTextEdit()
        self.text_edit.setPlaceholderText("在此输入文本...")
        left_layout.addWidget(self.text_edit)
        horizontal_splitter.addWidget(left_widget)
        
        # 创建垂直分割器(嵌套在水平分割器中)
        vertical_splitter = QSplitter()
        vertical_splitter.setOrientation(1)  # 1 表示垂直方向
        
        # 创建右上区域 - 列表区
        top_right_widget = QWidget()
        top_right_layout = QVBoxLayout(top_right_widget)
        top_right_layout.addWidget(QLabel("项目列表"))
        self.list_widget = QListWidget()
        self.list_widget.addItems(["项目1", "项目2", "项目3", "项目4"])
        top_right_layout.addWidget(self.list_widget)
        vertical_splitter.addWidget(top_right_widget)
        
        # 创建右下区域 - 树形结构区
        bottom_right_widget = QWidget()
        bottom_right_layout = QVBoxLayout(bottom_right_widget)
        bottom_right_layout.addWidget(QLabel("树形结构"))
        self.tree_widget = QTreeWidget()
        self.tree_widget.setHeaderLabels(["名称", "类型"])
        
        # 添加树形结构数据
        root = QTreeWidgetItem(self.tree_widget)
        root.setText(0, "根节点")
        
        child1 = QTreeWidgetItem(root)
        child1.setText(0, "子节点1")
        child1.setText(1, "文件夹")
        
        child2 = QTreeWidgetItem(root)
        child2.setText(0, "子节点2")
        child2.setText(1, "文件")
        
        self.tree_widget.expandAll()
        bottom_right_layout.addWidget(self.tree_widget)
        vertical_splitter.addWidget(bottom_right_widget)
        
        # 将垂直分割器添加到水平分割器
        horizontal_splitter.addWidget(vertical_splitter)
        
        # 设置初始比例
        horizontal_splitter.setSizes([300, 500])
        vertical_splitter.setSizes([200, 300])
        
        # 添加分割器到主布局
        main_layout.addWidget(horizontal_splitter)
        
        # 添加状态标签
        self.status_label = QLabel("拖动分隔条调整各区域大小")
        main_layout.addWidget(self.status_label)
        
        # 连接信号
        horizontal_splitter.splitterMoved.connect(self.update_status)
        vertical_splitter.splitterMoved.connect(self.update_status)
    
    def update_status(self, pos, index):
        """更新状态标签"""
        self.status_label.setText(f"当前分隔条位置: {pos}")


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

布局管理器

QFormLayout

用于构建图形用户界面(GUI)中表单的布局管理器。它可以在表单中自动地安排各种控件(例如文本框、复选框、下拉框等)的位置和大小,以便它们以最优化的方式显示在表单上。

  • 简化表单的设计和排版工作,以及提高表单的可读性和易用性
  • 根据表单中控件的类型和数量,自动地生成最佳的布局
  • 提供了一些常用的功能,例如表单边距、控件间距、对齐方式、自动换行等

QGridLayout

用于将控件以网格的形式排列在窗口中。每个单元格可以包含一个控件,且所有单元格大小相等。控件可以跨越多个行和列,这是通过指定控件的位置以及它在行和列中所占的单元格数量来实现的。重要特点:

  • 创建GridLayout对象:可以通过将QWidget作为参数传递给QGridLayout构造函数来创建一个GridLayout对象。然后可以使用addWidget()方法将控件添加到布局中
  • 指定控件的位置:可以使用addWidget()方法的第二个和第三个参数来指定控件的位置。例如,addWidget(button, 0, 0)将button添加到第0行和第0列的单元格中
  • 控件的大小和跨度:可以使用addWidget()方法的第四个和第五个参数来指定控件在行和列中所占的单元格数量。例如,addWidget(label, 0, 0, 1, 2)将label添加到第0行和第0列的单元格中,并让它跨越第0列和第1列的两个单元格
  • 添加空白单元格:可以使用addSpacing()方法添加空白的单元格,从而调整控件之间的距离
  • 对齐方式:可以使用setAlignment()方法设置控件在单元格中的对齐方式。可以指定水平和垂直方向的对齐方式,也可以将对齐方式设置为水平和垂直方向的组合
  • 自动调整大小:可以使用setColumnStretch()和setRowStretch()方法来设置单元格的大小。可以使用addStretch()方法添加一个伸缩项,以便在窗口大小改变时自动调整大小

信号与槽以及关联控件

信号与槽的介绍

信号是用于在对象之间传递信息的一种机制。一个信号表示了一个事件或状态的变化,当这个事件或状态变化时,信号被发射(emit)。可以将信号连接到一个或多个槽函数中,当信号被发射时,连接的槽函数会被调用执行。槽函数则是用于接收和处理信号的函数。

常用**connect()**方法来将信号连接到槽函数中,该方法的基本语法为:

1
sender.signal.connect(receiver.slot)

sender表示发送信号的对象,signal表示信号的名称,connect()方法将信号signal连接到receiver对象的槽函数slot中。槽函数可以是任何可调用的Python函数

高级的信号与槽机制:

  • 使用自定义信号:可以定义自己的信号,并将其连接到槽函数中。自定义信号可以通过QObject类的signal()方法定义,并使用emit()方法发射信号
  • 使用Lambda表达式:可以使用Lambda表达式作为槽函数,Lambda表达式可以简洁地表示一个函数
  • 使用信号参数:PyQt5中的信号可以带有参数,参数可以在信号发射时被传递给槽函数
  • 一个信号连接多个槽函数:可以将一个信号连接到多个槽函数中,所有的槽函数都会在信号被发射时被调用执行

QPushButton

Qt中常用的按钮控件,可以用于在GUI中创建各种类型的按钮,如普通按钮、复选框按钮、单选框按钮等。

其构造函数为:

1
2
3
QPushButton(parent=None)            #创建一个无标签的按钮
QPushButton(str, parent=None)		#创建一个有标签的按钮
QPushButton(QIcon, str, parent=None)#创建一个既有图标又有标签的按钮

可以调用的方法有:

  • setText()方法设置按钮的文本标签
  • setIcon()方法设置按钮的图标
  • setEnabled()用于设置按钮是否可用
  • setFlat()用于设置按钮是否平面
  • setCheckable()用于设置按钮是否可选中等

还可以通过信号与槽机制来响应用户的点击事件。当用户单击按钮时,会发出clicked()信号,可以通过连接这个信号来执行特定的操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QHBoxLayout, QVBoxLayout, QWidget
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt

class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('QPushButton Demo')
        self.setGeometry(300, 300, 400, 300)

        # 创建一个QPushButton并设置文本标签和图标
        btn = QPushButton('Click me!', self)
        btn.setIcon(QIcon('icon.png'))

        # 连接按钮的clicked信号到槽函数onBtnClicked
        btn.clicked.connect(self.onBtnClicked)

        # 创建一个QLabel用于显示按钮状态
        self.label = QLabel('Button not clicked', self)

        # 创建水平布局和垂直布局,并将按钮和标签添加到布局中
        hbox = QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(btn)
        hbox.addStretch(1)

        vbox = QVBoxLayout()
        vbox.addStretch(1)
        vbox.addLayout(hbox)
        vbox.addStretch(1)
        vbox.addWidget(self.label, alignment=Qt.AlignCenter)

        # 创建一个QWidget,并将垂直布局添加到QWidget中
        widget = QWidget()
        widget.setLayout(vbox)
        self.setCentralWidget(widget)

    def onBtnClicked(self):
        self.label.setText('Button clicked')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyMainWindow()
    win.show()
    sys.exit(app.exec_())

QRadioButton

一个单选按钮控件,可以用于从多个互斥的选项中选择一个选项。与QCheckBox不同,QRadioButton只允许选择一个选项。

基本属性和方法:

  • setText()设置按钮的文本
  • isChecked()检查按钮是否被选中
  • setChecked()设置按钮的选中状态
  • toggled()每当按钮的选中状态发生变化时,都会发出toggled()信号
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QRadioButton


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('QRadioButton Demo')
        self.setGeometry(300, 300, 400, 300)

        # 创建两个单选框按钮
        rb1 = QRadioButton('Option 1', self)
        rb1.move(50, 50)
        rb1.setChecked(True)

        rb2 = QRadioButton('Option 2', self)
        rb2.move(50, 80)

        # 绑定toggled()信号
        rb1.toggled.connect(self.onToggled)
        rb2.toggled.connect(self.onToggled)

    def onToggled(self, checked):
        sender = self.sender()
        if checked:
            print(sender.text() + ' is checked')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyMainWindow()
    win.show()
    sys.exit(app.exec_())

QcheckBox

一个复选框控件,可以用于从多个选项中选择一个或多个选项。与QRadioButton不同,QCheckBox允许选择多个选项。

基本属性和方法:

  • setText():设置复选框的文本。
  • isChecked():检查复选框是否被选中。
  • setChecked():设置复选框的选中状态。
  • stateChanged():每当复选框的选中状态发生变化时,都会发出stateChanged()信号
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QCheckBox

class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('QCheckBox Demo')
        self.setGeometry(300, 300, 400, 300)

        # 创建两个复选框
        cb1 = QCheckBox('Option 1', self)
        cb1.move(50, 50)

        cb2 = QCheckBox('Option 2', self)
        cb2.move(50, 80)

        # 绑定stateChanged()信号
        cb1.stateChanged.connect(self.onStateChanged)
        cb2.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self, state):
        sender = self.sender()
        if state == 2:
            print(sender.text() + ' is checked')
        else:
            print(sender.text() + ' is unchecked')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyMainWindow()
    win.show()
    sys.exit(app.exec_())

QComboBox

下拉列表控件,允许用户从预定义的一组选项中选择一个或多个选项。它通常用于表示枚举类型的值或选择一组预定义的选项。

基本属性和方法:

  • addItem():添加一个项到下拉列表中。
  • addItems():添加多个项到下拉列表中。
  • setCurrentIndex():设置当前选中的项的索引。
  • currentText():返回当前选中的项的文本。
  • currentIndexChanged():每当当前选中的项发生变化时,都会发出currentIndexChanged()信号。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QComboBox,
                             QPushButton, QVBoxLayout, QWidget, QLabel)


class ComboBoxDemo(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('QComboBox 演示')
        self.setGeometry(300, 300, 400, 300)
        self.initUI()

    def initUI(self):
        # 创建中心部件和布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)

        # 创建标签显示当前选择
        self.status_label = QLabel("当前选择: 无")
        layout.addWidget(self.status_label)

        # 创建下拉列表并添加选项
        self.combo_box = QComboBox()
        self.combo_box.addItem('选项 1')
        self.combo_box.addItem('选项 2')
        self.combo_box.addItem('选项 3')
        self.combo_box.addItem('选项 4')
        self.combo_box.addItem('选项 5')

        # 绑定信号
        self.combo_box.currentIndexChanged.connect(self.on_index_changed)
        layout.addWidget(self.combo_box)

        # 创建按钮用于设置当前索引
        self.create_set_index_buttons(layout)

        # 添加随机选择按钮
        random_btn = QPushButton("随机选择")
        random_btn.clicked.connect(self.set_random_index)
        layout.addWidget(random_btn)

        # 添加重置按钮
        reset_btn = QPushButton("重置选择")
        reset_btn.clicked.connect(self.reset_selection)
        layout.addWidget(reset_btn)

        # 初始化状态
        self.update_status()

    def create_set_index_buttons(self, layout):
        """创建设置索引的按钮"""
        # 水平布局用于放置按钮
        button_layout = QVBoxLayout()

        # 为每个选项创建设置按钮
        for i in range(self.combo_box.count()):
            btn = QPushButton(f"设置为选项 {i + 1}")
            # 使用lambda函数传递索引值
            btn.clicked.connect(lambda checked, idx=i: self.set_current_index(idx))
            button_layout.addWidget(btn)

        layout.addLayout(button_layout)

    def set_current_index(self, index):
        """设置当前选中的索引"""
        self.combo_box.setCurrentIndex(index)

    def set_random_index(self):
        """随机选择一个索引"""
        import random
        count = self.combo_box.count()
        if count > 0:
            random_index = random.randint(0, count - 1)
            self.combo_box.setCurrentIndex(random_index)

    def reset_selection(self):
        """重置选择(设置为无选择)"""
        self.combo_box.setCurrentIndex(-1)

    def on_index_changed(self, index):
        """当下拉列表选择改变时调用"""
        self.update_status()

    def update_status(self):
        """更新状态标签"""
        index = self.combo_box.currentIndex()
        text = self.combo_box.currentText()

        if index == -1:
            self.status_label.setText("当前选择: 无")
        else:
            self.status_label.setText(f"当前选择: 选项 {index + 1} ({text})")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = ComboBoxDemo()
    win.show()
    sys.exit(app.exec_())

独立组件

滑块控件

图形用户界面中常见的一种控件,也称为滑杆、拖动条、进度条等,用于调节数值类型的参数。用户通过拖动滑块的滑块块(Thumb)来改变滑块的值,滑块的范围和步长可以通过设置属性进行控制。在PyQt5中,QSlider是用于创建滑块控件的类。

常用的属性和方法:

  • value():获取当前滑块的值。
  • setValue(value):设置当前滑块的值。
  • minimum():获取滑块的最小值。
  • setMinimum(value):设置滑块的最小值。
  • maximum():获取滑块的最大值。
  • setMaximum(value):设置滑块的最大值。
  • singleStep():获取滑块的步长。
  • setSingleStep(value):设置滑块的步长。
  • sliderMoved.connect(slot):将slot函数连接到滑块拖动事件。
  • valueChanged.connect(slot):将slot函数连接到滑块值改变事件。 在使用QS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QSlider, QHBoxLayout, QVBoxLayout, QPushButton
from PyQt5.QtCore import Qt


class SliderDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # 创建一个水平布局和垂直布局
        hlayout = QHBoxLayout()
        vlayout = QVBoxLayout()

        # 创建一个标签和滑块控件,并添加到水平布局中
        self.label = QLabel('Value: 0')
        self.slider = QSlider(Qt.Horizontal)
        self.slider.valueChanged[int].connect(self.onSliderValueChanged)
        hlayout.addWidget(self.label)
        hlayout.addWidget(self.slider)

        # 创建一个重置按钮,并添加到垂直布局中
        reset_btn = QPushButton('Reset')
        reset_btn.clicked.connect(self.onResetBtnClicked)
        vlayout.addLayout(hlayout)
        vlayout.addWidget(reset_btn)

        # 设置窗口的布局
        self.setLayout(vlayout)

        # 设置窗口的标题和大小
        self.setWindowTitle('Slider Demo')
        self.resize(300, 200)

    def onSliderValueChanged(self, value):
        # 当滑块的值改变时更新标签的文本
        self.label.setText(f'Value: {value}')

    def onResetBtnClicked(self):
        # 重置滑块的值为0,并更新标签的文本
        self.slider.setValue(0)
        self.label.setText('Value: 0')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = SliderDemo()
    demo.show()
    sys.exit(app.exec_())

树控件

一种常见的用户界面控件,它可以用来展示层次化的数据结构,例如文件系统、目录结构、组织结构等等。树控件通常由多个节点(Node)组成,每个节点都可以包含多个子节点。在PyQt5中,树控件是通过QTreeWidget类来实现的。

操作树控件方法:

  • 添加节点:可以通过QTreeWidget.addTopLevelItem()方法或QTreeWidgetItem.addChild()方法来添加节点。
  • 删除节点:可以通过QTreeWidget.takeTopLevelItem()方法或QTreeWidgetItem.removeChild()方法来删除节点。
  • 获取节点:可以通过QTreeWidget.topLevelItem()方法或QTreeWidgetItem.child()方法来获取节点。
  • 设置节点属性:可以通过QTreeWidgetItem.setText()、QTreeWidgetItem.setIcon()等方法来设置节点的文本、图标等属性。
  • 选择节点:可以通过QTreeWidget.currentItem()方法获取当前选中的节点,也可以通过QTreeWidgetItem.setSelected
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTreeWidget, QTreeWidgetItem

class OrganizationChart(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("组织结构图")
        self.resize(500, 400)

        self.treeWidget = QTreeWidget()
        self.treeWidget.setHeaderLabels(["姓名", "职位", "部门"])

        self.populateTreeWidget()

        self.setCentralWidget(self.treeWidget)

    def populateTreeWidget(self):
        # 创建根节点
        root = QTreeWidgetItem(self.treeWidget, ["总经理", "总经理", ""])

        # 创建部门节点1
        department1 = QTreeWidgetItem(root, ["市场部", "部门经理", ""])
        employee1 = QTreeWidgetItem(department1, ["张三", "销售经理", "市场部"])
        employee2 = QTreeWidgetItem(department1, ["李四", "市场专员", "市场部"])

        # 创建部门节点2
        department2 = QTreeWidgetItem(root, ["技术部", "部门经理", ""])
        employee3 = QTreeWidgetItem(department2, ["王五", "技术总监", "技术部"])
        employee4 = QTreeWidgetItem(department2, ["赵六", "开发工程师", "技术部"])

        # 展开根节点
        self.treeWidget.expandItem(root)

if __name__ == "__main__":
    app = QApplication(sys.argv)

    organizationChart = OrganizationChart()
    organizationChart.show()

    sys.exit(app.exec_())

停靠控件

一种常用的界面布局控件,它允许用户在主窗口中创建可停靠的面板或工具栏,以便对应用程序进行灵活的布局和组织。停靠控件提供了一种便捷的方式来管理和切换应用程序的功能模块,使用户可以根据自己的需求动态调整界面布局。在 PyQt 中,停靠控件由QDockWidget类实现。它可以与 QMainWindow 或 QMainWindows 的派生类一起使用,使得应用程序的主窗口可以容纳多个停靠控件,并支持拖动、停靠和浮动等操作。

实现步骤:

  • 创建停靠控件对象:使用 QDockWidget 类创建一个停靠控件对象,并将需要承载的内容设置为其子部件。
  • 设置停靠属性:可以设置停靠控件的标题、图标、位置等属性,以及允许的停靠区域和停靠方式。
  • 将停靠控件添加到主窗口:使用 QMainWindow 或 QMainWindows 的派生类将停靠控件添加到主窗口中的合适位置,通常使用 addDockWidget() 方法来完成。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QDockWidget, QTextEdit, QAction, QFileDialog, QMessageBox, QVBoxLayout, QWidget, QLabel, QPushButton, QTreeWidget, QTreeWidgetItem

class NotePad(QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setWindowTitle("NotePad")
        self.setGeometry(100, 100, 800, 600)

        # 创建文本编辑区域
        self.textEdit = QTextEdit()
        self.setCentralWidget(self.textEdit)

        # 创建停靠控件
        dock = QDockWidget("File Actions", self)
        dock.setAllowedAreas(Qt.LeftDockWidgetArea)

        # 创建停靠控件中的内容,包括打开文件按钮和保存文件按钮
        openButton = QPushButton("Open File")
        openButton.clicked.connect(self.openFile)

        saveButton = QPushButton("Save File")
        saveButton.clicked.connect(self.saveFile)

        layout = QVBoxLayout()
        layout.addWidget(openButton)
        layout.addWidget(saveButton)

        widget = QWidget()
        widget.setLayout(layout)
        dock.setWidget(widget)

        # 将停靠控件添加到主窗口的左侧停靠区域
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)

        # 创建菜单和动作
        self.createActions()
        self.createMenus()

        # 创建侧边栏
        self.createSidebar()

        self.show()

    def createActions(self):
        self.openAction = QAction("Open File", self)
        self.openAction.triggered.connect(self.openFile)

        self.saveAction = QAction("Save File", self)
        self.saveAction.triggered.connect(self.saveFile)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("File")
        self.fileMenu.addAction(self.openAction)
        self.fileMenu.addAction(self.saveAction)

    def createSidebar(self):
        # 创建侧边栏
        sidebarDock = QDockWidget("Notes", self)
        sidebarDock.setAllowedAreas(Qt.LeftDockWidgetArea)

        # 创建树控件
        treeWidget = QTreeWidget()
        treeWidget.setHeaderLabels(["Notes"])

        # 添加示例笔记
        root = QTreeWidgetItem(treeWidget)
        root.setText(0, "Notebook")

        note1 = QTreeWidgetItem(root)
        note1.setText(0, "Note 1")

        note2 = QTreeWidgetItem(root)
        note2.setText(0, "Note 2")

        # 将树控件添加到侧边栏
        sidebarDock.setWidget(treeWidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, sidebarDock)

    def openFile(self):
        fileName, _ = QFileDialog.getOpenFileName(self, "Open File")
        if fileName:
            try:
                with open(fileName, 'r') as file:
                    content = file.read()
                self.textEdit.setPlainText(content)
            except Exception as e:
                QMessageBox.critical(self, "Error", str(e))

    def saveFile(self):
        fileName, _ = QFileDialog.getSaveFileName(self, "Save File")
        if fileName:
            try:
                with open(fileName, 'w') as file:
                    content = self.textEdit.toPlainText()
                    file.write(content)
                QMessageBox.information(self, "Success", "File saved successfully.")
            except Exception as e:
                QMessageBox.critical(self, "Error", str(e))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    notepad = NotePad()
    sys.exit(app.exec_())

恍如昨日,嗤笑今朝
使用 Hugo 构建
主题 StackJimmy 设计