pyqt example

https://github.com/pyqt/examples/

05 Qt Designer Python

加载 ui 布局配置文件的方式:

from PyQt6 import uic
from PyQt6.QtWidgets import QApplication

Form, Window = uic.loadUiType(r"dialog.ui")

app = QApplication([])
window = Window()
form = Form()
form.setupUi(window)
window.show()
app.exec() # 绘制窗口的

04 PyQt Signals and Slots

pyqt绑定信号和动作,点击按钮以后触发动作,button.clicked就是点击以后触发函数

from PyQt6.QtWidgets import *

app = QApplication([])
button = QPushButton('Click') # 一个点击按钮

def on_button_clicked(): # 点击以后调用的函数,点击动画不用管
    alert = QMessageBox()
    alert.setText('You clicked the button!')
    alert.exec()

button.clicked.connect(on_button_clicked)  # 点击动作绑定的函数
button.show() # 显示按钮的,不隐藏
app.exec() # 绘制窗口和按钮

02 PyQt Widgets

A widget is a GUI element: A button, a text field, … The sample application in this directory shows the most common PyQt widgets:

If you know HTML: Widgets are a little like HTML elements. They can be nested, and have a different appearance and behavior depending on their type. (Eg. a link <a> looks and behaves differently from an image <img>.)

Some of the widgets you can see in this screenshot are:

窗口继承了QMainWindow类,重写了关闭调用的函数,并给出了确认的弹窗,若修改了文本,需要保存以后才能关闭窗口,取消则关闭弹窗

window = MainWindow(),一个窗口可以包含各种子组件
包括了设置菜单栏,菜单栏的子菜单,子菜单触发的各种动作,感觉和Java类似的,Java的官方GUI菜单配置和这个很相似的

主要就是菜单栏加入菜单File,然后加入动作按钮open_action,并且定义触发函数open_action.triggered.connect(open_file),还绑定了相应的short key。menu.addAction(open_action)加入到子菜单栏内

menu = window.menuBar().addMenu("&File") # 窗口菜单栏加入一个菜单,名字是File
open_action = QAction("&Open")  # 动作名字是Open,也就是子菜单
def open_file():
    global file_path
    path = QFileDialog.getOpenFileName(window, "Open")[0]  # 选定文件返回文件的绝对路径
    if path: # 若路径非空
        text.setPlainText(open(path).read()) # 打开文本并且放置到文本框内
        file_path = path
open_action.triggered.connect(open_file) # 子菜单配置动作函数
open_action.setShortcut(QKeySequence.StandardKey.Open) # 配置 short cut key
menu.addAction(open_action) # 菜单加入子菜单Open

08 PyQt6 exe

项目打包到可以安装的exe,需要用到付费功能

09 Qt dark theme

调色板,给文字,菜单,子菜单配置不同的颜色

app = QApplication([])

# Force the style to be the same on all OSs:
app.setStyle("Fusion") 

# Now use a palette to switch to dark colors:
palette = QPalette() # 调色板
palette.setColor(QPalette.ColorRole.Window, QColor(53, 53, 53))  # 给窗口整体配置颜色
palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.white)  # 给窗口文字配置颜色
palette.setColor(QPalette.ColorRole.Button, QColor(53, 53, 53)) # 给按钮本身配置颜色
palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.white) # 给按钮文字配置颜色
palette.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red) # 给高亮文本配置颜色,
app.setPalette(palette)

10 QPainter Python example

将文本编辑区改写,重写这个类的某些函数,重定义了鼠标点击事件mousePressEvent,每次点击鼠标,就记录当前鼠标的坐标,每次点击还给出了特效—音效

另外还重定义了绘制事件paintEvent,绘制很多图片,之前所有点击的坐标处都绘制了图片

    def mousePressEvent(self, e):
        self._holes.append(e.pos()) # 记录当前鼠标的坐标
        super().mousePressEvent(e) # 调用父类的鼠标点击事件
        self.viewport().update() # 再次绘制窗口
        self.effect.play() # 播放音频特效
    def paintEvent(self, e):
        super().paintEvent(e) # 调用父类的绘制事件
        painter = QPainter(self.viewport()) # 拿到窗口的绘制工具
        for hole in self._holes:
            painter.drawPixmap(hole - self._offset, self._bullet) # 使用坐标绘制图片

11 PyQt Thread example

对话框的单线程和多线程版本,

  • 01_single_threaded.py does not use threads. Once per second, it fetches the latest messages from the server. It does this in the main thread. While fetching messages, it’s unable to process your key strokes. As a result, it sometimes lags a little as you type.
  • 02_multithreaded.py uses threads to fetch new messages in the background. It is considerably more responsive than the single threaded version.
  • 03_with_threadutil.py is a variation of the multithreaded version. It extracts the logic necessary for communicating between threads into a separate module that you can use in your own apps, threadutil.py. For an even more powerful implementation, see threadutil_blocking.py. This is the code which fman uses.

01_single_threaded
单线程版本,每隔1s从服务器获取文本,并且显示到文本框内append,之前的文本不删除,只绘制new add的文本

编辑区按了enter以后发送文本到服务器


name = input("Please enter your name: ")
chat_url = "https://build-system.fman.io/chat"
server = Session() # 网络 connect

# GUI:
app = QApplication([])
text_area = QPlainTextEdit() # 文本区
text_area.setFocusPolicy(Qt.FocusPolicy.NoFocus) # 不聚焦
message = QLineEdit() # 文本行编辑区
layout = QVBoxLayout() # 布局排版器
layout.addWidget(text_area) # 加入文本组件
layout.addWidget(message) # 加入编辑区组件
window = QWidget()
window.setLayout(layout) # 配置整体布局
window.show()

# Event handlers:
def display_new_messages():
    new_message = server.get(chat_url).text # 从服务器获取文本
    if new_message:
        text_area.appendPlainText(new_message) # 绘制到文本框
def send_message():
    server.post(chat_url, {"name": name, "message": message.text()}) # 发消息给服务器
    message.clear() # 编辑区清空
# Signals:
message.returnPressed.connect(send_message) # 编辑区组件按了Enter按钮以后调用的函数,绑定事件函数
timer = QTimer() # 定时器
timer.timeout.connect(display_new_messages) # 固定时间以后调用的函数,绑定事件函数
timer.start(1000) # 固定时间配置到1000ms,也就是1s
app.exec() # 绘制整体效果

02_multithreaded
多线程版本,另外一个线程专门在后台每隔几秒获取一次服务器的文本,然后开启线程后台运行,daemon=True也就是后台运行,即使是按了Ctrl+C也没用,消息会放到new_messages列表中

thread = Thread(target=fetch_new_messages, daemon=True)
thread.start()

此时定期器是每隔1s输出一次服务器最后一条消息,也就是列表的最后一项new_messages,显示到文本框内append,之前的文本不删除,只绘制new add的文本

def display_new_messages():
    while new_messages:
        text_area.appendPlainText(new_messages.pop(0))

编辑区功能不变,按了enter就发送到服务器,并清空

03_with_threadutil

  • 03_with_threadutil.py is a variation of the multithreaded version. It extracts the logic necessary for communicating between threads into a separate module that you can use in your own apps, threadutil.py. For an even more powerful implementation, see threadutil_blocking.py. This is the code which fman uses.

线程之间沟通的逻辑单独提取出来当作一个模块,更好的实现是这个文件:threadutil_blocking.py

多线程版本的大多数复杂度来自同步主线程和后台线程,主线程QT在屏幕上绘制像素点,处置鼠标点击等事件。后台线程从服务器获取文本。当新消息来临时,后台线程不能绘制文本。QT只能在主线程,答案是后台线程要让QT来绘制文本。
[02_multithreaded.py]后台线程将文本放到列表最后,然后主线程拿到列表最后的文本来绘制
[03_with_threadutil.py]使用了传统的机制,让后台线程在主线程执行arbitrary code,arbitrary code在屏幕上绘制新消息的文本。

Most of the added complexity of the multithreaded versions comes from having to synchronize the main and background threads. In more detail: The main thread is the thread in which Qt draws pixels on the screen, processes events such as mouse clicks, etc. In the examples here, there is a single background thread which fetches messages from the server. But what should happen when a new message arrives? The background thread can’t just draw the text on the screen, because Qt might just be in the process of drawing itself. The answer is that the background thread must somehow get Qt to draw the text in the main thread. The second and third examples presented here (02_multithreaded.py and 03_with_threadutil.py) use different ways of achieving this. In the former, the background thread appends messages to a list, which is then processed in the main thread. The latter uses a custom mechanism that lets the background thread execute arbitrary code in the main thread. In this case, the “arbitrary code” draws the text for the new message on the screen.

  • 03_with_threadutil.py 使用后台线程获取服务器的文本,并且绘制文本,主线程仍然在编辑区绑定了发送消息的函数,按Enter以后发送消息到服务器并且清空编辑区。

最重要的修改,在主线程内运行text_area.appendPlainText函数
append_message = run_in_main_thread(text_area.appendPlainText)

def fetch_new_messages():
    while True:
        response = server.get(chat_url).text
        if response:
            append_message(response) 
        sleep(.5)
# Signals:
message.returnPressed.connect(send_message) # 绑定Enter以后发送消息的函数,并且清空编辑区
# 后台线程从服务器获取文本,并且在主线程内绘制文本
thread = Thread(target=fetch_new_messages, daemon=True)
thread.start()

最重要的还是这个文件[threadutil.py],拿到主线程,并且用主线程执行QT的绘制事件

from PyQt6.QtCore import QObject, pyqtSignal
#拿到当前的主线程,并在主线程执行函数
class CurrentThread(QObject):
    _on_execute = pyqtSignal(object, tuple, dict) #初始化pyqt的信号
    def __init__(self):
        super(QObject, self).__init__()
        self._on_execute.connect(self._execute_in_thread) # 
    def execute(self, f, args, kwargs):
        self._on_execute.emit(f, args, kwargs) # 提交事件
    def _execute_in_thread(self, f, args, kwargs):
        f(*args, **kwargs) # 执行,绑定到信号上的函数
main_thread = CurrentThread() #拿到主线程
def run_in_main_thread(f):
    def result(*args, **kwargs):
        # 主线程执行函数,相当在主线程执行了函数text_area.appendPlainText(response)
        # 只要后台线程在服务器拿到了新消息,就让主线程调用QT的绘制函数
        main_thread.execute(f, args, kwargs)
    return result

更好的实现是这个文件:threadutil_blocking.py
具体也是在主线程执行相关的函数

"""
A more powerful, synchronous implementation of run_in_main_thread(...).
It allows you to receive results from the function invocation:

    @run_in_main_thread
    def return_2():
        return 2
    
    # Runs the above function in the main thread and prints '2':
    print(return_2())
"""

from functools import wraps
from PyQt6.QtCore import pyqtSignal, QObject, QThread
from PyQt6.QtWidgets import QApplication
from threading import Event, get_ident

def run_in_thread(thread_fn):
    def decorator(f):
        @wraps(f)
        def result(*args, **kwargs):
            thread = thread_fn()
            return Executor.instance().run_in_thread(thread, f, args, kwargs)
        return result
    return decorator

def _main_thread(): # 拿到主线程
    app = QApplication.instance() # 拿到QApplication的实例
    if app: # 若存在实例的话
        return app.thread() # 返回instance主线程
    # We reach here in tests that don't (want to) create a QApplication.
    if int(QThread.currentThreadId()) == get_ident(): # 判断QThread的当前线程ID是否和主线程相同
        return QThread.currentThread() # 返回主线程
    raise RuntimeError('Could not determine main thread') #否则报错不能确定哪个是主线程

run_in_main_thread = run_in_thread(_main_thread)

def is_in_main_thread():
    return QThread.currentThread() == _main_thread() # 判断QThread的当前线程是否是主线程
class Executor:
    _INSTANCE = None
    @classmethod # 装饰器,静态函数不需要实例化类,就可以执行的函数
    def instance(cls):
        if cls._INSTANCE is None:
            cls._INSTANCE = cls(QApplication.instance()) # 拿到instance
        return cls._INSTANCE
    def __init__(self, app):
        self._pending_tasks = []           # 初始化推迟执行的列表
        self._app_is_about_to_quit = False # 是否已经退出
        app.aboutToQuit.connect(self._about_to_quit) # 绑定退出函数
    def _about_to_quit(self):
        self._app_is_about_to_quit = True
        for task in self._pending_tasks: #推迟执行的列表都配置异常------> 系统主动退出
            task.set_exception(SystemExit())
            task.has_run.set() # 配置已经运行过了,实际没有运行
    def run_in_thread(self, thread, f, args, kwargs):  # 在主线程运行函数
        if QThread.currentThread() == thread:
            return f(*args, **kwargs)
        elif self._app_is_about_to_quit: # 已经退出了
            # In this case, the target thread's event loop most likely is not
            # running any more. This would mean that our task (which is
            # submitted to the event loop via signals/slots) is never run.
            raise SystemExit()
        # 当前线程不是主线程,放入到推迟执行的列表内
        task = Task(f, args, kwargs)
        self._pending_tasks.append(task)
        try:
            receiver = Receiver(task)
            receiver.moveToThread(thread)
            sender = Sender()
            sender.signal.connect(receiver.slot)
            sender.signal.emit()
            task.has_run.wait()
            return task.result
        finally:
            self._pending_tasks.remove(task) # 移除执行完的task

class Task:
    def __init__(self, fn, args, kwargs):
        self._fn = fn
        self._args = args
        self._kwargs = kwargs
        self.has_run = Event()
        self._result = self._exception = None
    def __call__(self):
        try:
            self._result = self._fn(*self._args, **self._kwargs)
        except Exception as e:
            self._exception = e
        finally:
            self.has_run.set()
    def set_exception(self, exception):
        self._exception = exception
    @property
    def result(self):
        if not self.has_run.is_set():
            raise ValueError("Hasn't run.")
        if self._exception:
            raise self._exception
        return self._result

class Sender(QObject):
    signal = pyqtSignal()

# https://doc.qt.io/qtforpython-6/tutorials/basictutorial/signals_and_slots.html#signals-and-slots
class Receiver(QObject):
    def __init__(self, callback, parent=None):
        super().__init__(parent)
        self.callback = callback # 初始化回调函数
    def slot(self):
        '''
        slot
n. (机器或工具上的)狭缝,狭槽
〈非正〉(在表册、系统等中所占的)位置,职位,空位
vt. 把…放入狭长开口中;把…纳入其中,使有位置
time slot 时间空档;(电视或广播电台的)播放时段
slot machine 自动售货机
slot antenna 缝隙天线;槽孔天线
key slot n. 键槽
single slot 单插槽;单翼缝
slot width 齿缝宽度
stator slot 定子槽
card slot 板槽,插卡槽
slot wedge 槽楔;槽边缘
expansion slot 扩充槽,扩充插槽
rotor slot 转子线槽
        '''
        self.callback() # 调用回调函数

最后的几个example主要是几个不同的模块,像表格,目录树,QListView等几种不同的显示格式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九是否随机的称呼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值