diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index 645d11d1..00000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Pages - -on: - push: - branches: - - dev # default branch - -jobs: - pages: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v2 - - name: Use Node.js 18.x - uses: actions/setup-node@v2 - with: - node-version: "18" - - name: Cache NPM dependencies - uses: actions/cache@v2 - with: - path: node_modules - key: ${{ runner.OS }}-npm-cache - restore-keys: | - ${{ runner.OS }}-npm-cache - - name: Update - run: git submodule update --init --recursive - - name: Install Dependencies - run: npm install - - name: Build - run: npm run build - - name: page deploy - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - force_orphan: true - publish_branch: master - publish_dir: ./public - - name: ssh deploy - uses: easingthemes/ssh-deploy@v5.0.3 - with: - SSH_PRIVATE_KEY: ${{ secrets.REMOTE_PRIVATE_KEY }} - REMOTE_HOST: ${{ secrets.REMOTE_HOST }} - REMOTE_USER: ${{ secrets.REMOTE_USER }} - SOURCE: "public/*" - TARGET: ${{ secrets.REMOTE_TARGET }} \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index cbcb0f70..00000000 --- a/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.DS_Store -Thumbs.db -db.json -*.log -node_modules/ -public/ -tmp/ -.deploy*/ -package-lock.json diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 7204821c..00000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "themes/anzhiyu"] - path = themes/anzhiyu - url = https://github.com/anzhiyu-c/hexo-theme-anzhiyu.git - branch = 1.6.12 diff --git a/source/static/api/js/trans/data.js b/.nojekyll similarity index 100% rename from source/static/api/js/trans/data.js rename to .nojekyll diff --git a/404.html b/404.html new file mode 100644 index 00000000..64881ecf --- /dev/null +++ b/404.html @@ -0,0 +1,270 @@ +页面没有找到 | PyQt + + + + + + + + + + + + + +
Page not found

404

页面没有找到
\ No newline at end of file diff --git a/source/CNAME b/CNAME similarity index 100% rename from source/CNAME rename to CNAME diff --git a/QPropertyAnimation.html b/QPropertyAnimation.html new file mode 100644 index 00000000..c4348423 --- /dev/null +++ b/QPropertyAnimation.html @@ -0,0 +1,435 @@ +PyQt属性动画(QPropertyAnimation) | PyQt + + + + + + + + + + + + + + +

PyQt属性动画(QPropertyAnimation)

QPropertyAnimation 继承自 QVariantAnimation ,其作为 Qt 的属性动画用于针对控件的属性或者继承自 QObject 的对象中定义的属性做修改,
+简单来说就是基类是 QObject 且定义了属性变量,就可以用 QPropertyAnimation 来做属性动画。同时也可以通过 pyqtProperty 来增加自定义属性。

+ +

首先,通过构造函数 QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None) 创建一个对象,其中

+
    +
  1. 第一个参数是动画作用的对象,也可以通过 setTargetObject 设置
  2. +
  3. 第二个参数是属性名,在 py3 中类型是 bytes,也可以通过 setPropertyName 设置
  4. +
+

# 函数

+

一些常见的设置函数

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
setPropertyName设置属性名
setTargetObject设置动画作用对象
setDuration设置动画持续时间(毫秒)
setStartValue设置开始值
setEndValue设置结束值
setEasingCurve设置动画曲线
setKeyValueAt插入线性值
setLoopCount设置循环次数(-1 为永久)
+

# 示例

+

比如这个例子:

+
    +
  1. 修改控件的 geometry 大小
  2. +
  3. 修改自定义属性
  4. +
  5. 修改进度条的 value 值
  6. +
+

QPropertyAnimation

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2019年5月8日
+@author: Irony
+@site: https://pyqt5.com https://github.com/892768447
+@email: 892768447@qq.com
+@file: 
+@description: 
+"""
+from PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurve
+from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\
+    QLabel, QProgressBar, QSpacerItem, QSizePolicy
+
+
+__Author__ = 'Irony'
+__Copyright__ = 'Copyright (c) 2019 Irony'
+__Version__ = 1.0
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        self.resize(400, 400)
+        self._value = 0
+        self.button = QPushButton('属性动画测试', self)
+        self.button.clicked.connect(self.doStart)
+        self.button.setGeometry(0, 0, 80, 40)
+
+        self.buttonc = QPushButton('自定义属性 测试', self)
+        self.buttonc.clicked.connect(self.doStartCustom)
+
+        self.label = QLabel('', self)
+
+        self.progressbar = QProgressBar(self)
+        self.progressbar.setRange(0, 99)
+
+        layout = QVBoxLayout(self)
+        layout.addItem(QSpacerItem(
+            20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed))
+        layout.addWidget(self.buttonc)
+        layout.addWidget(self.label)
+        layout.addWidget(self.progressbar)
+
+        # 进度条动画
+        self.progressStart()
+
+    # 此处是自定义属性,并通过动画修改后,设置QLabel的值
+    @pyqtProperty(int)
+    def value(self):
+        return self._value
+
+    @value.setter
+    def value(self, v):
+        self._value = v
+        self.label.setText('当前值:{}'.format(v))
+
+    def doStart(self):
+        # 第一个参数是要执行的对象
+        animation = QPropertyAnimation(self.button, b'geometry', self)
+        animation.setDuration(2000)  # 持续时间
+        # 缓和曲线风格,加了曲线动画会很大程度影响
+        animation.setEasingCurve(QEasingCurve.OutBounce)
+        animation.setStartValue(QRect(0, 0, 40, 40))
+        animation.setEndValue(QRect(250, 250, 80, 80))
+        animation.start(animation.DeleteWhenStopped)
+
+    def doStartCustom(self):
+        # 自定义属性动画
+        # 由于定义的属性是在继承的QWidget, 所以第一个参数是self
+        # 第二个参数就是 value
+        animation = QPropertyAnimation(self, b'value', self)
+        animation.setDuration(2000)  # 持续时间
+        animation.setStartValue(0)
+        animation.setEndValue(100)
+        animation.start(animation.DeleteWhenStopped)
+
+    def progressStart(self):
+        # 进度条动画
+        # 这里 value是QProgressBar自带的属性,具体可以看文档
+        # https://doc.qt.io/qt-5/qprogressbar.html#properties
+        animation = QPropertyAnimation(self.progressbar, b'value', self)
+        animation.setDuration(2000)  # 持续时间
+        animation.setLoopCount(-1)
+        # 这里采用插入线性值,第一个参数的范围是(0-1)
+        # 第二个参数的范围是进度(最小值-最大值)
+        animation.setKeyValueAt(0, self.progressbar.minimum())
+        animation.setKeyValueAt(0.1, 10)
+        animation.setKeyValueAt(0.2, 30)
+        animation.setKeyValueAt(0.5, 60)
+        animation.setKeyValueAt(0.7, 80)
+        animation.setKeyValueAt(1, self.progressbar.maximum())
+        animation.start(animation.DeleteWhenStopped)
+
+
+if __name__ == '__main__':
+    import sys
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
+
文章作者: Irony
文章链接: https://pyqt5.com/QPropertyAnimation.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index f81cba59..00000000 --- a/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# 博客项目 - -https://pyqt5.com - -## 微信博客小程序 - - - -## 提交文章说明 - -1. 先 fork 本项目 或者 作为协作者加入本项目 -2. 在 [source/_posts](source/_posts) 目录中编写文章 -3. 图片文件放到[source/images](source/images),在文章中使用的时候 `![xx](/images/xx.png)` -4. 其它文件放到[source/files](source/files),在文章中使用的时候 `[xx](/files/xx.zip)` -5. 文章格式见下面 -6. 提交代码 - -## 文章格式 - -1. 文件名必须为英文,且小于50字符 -2. 内容开头格式如下 -``` ---- -author: Irony -title: PyQtClient例子客户端 -date: 2019-02-02 15:15:06 -top: 1 -tags: - - Example -categories: 随笔 ---- - -文章内容简介,注意!!!下面的``一定要加上 - - -这里是正文内容 -``` - -直接复制上面内容到新建的文档中,修改编辑 - -## 字段说明 - -| 字段 | 说明 | -| :------:| :------: | -| author | 作者名字 | -| title | 中文标题 | -| date | 文章日期 | -| top | 排序 (默认为1)| -| tags | 文章标签(可以多个) | -| categories | 文章分类(只能一个) | - -## 注意 - -tags可以是多个,yaml语法要注意格式对齐,比如: -``` -tags: - - PyQt - - 动画 -``` - -目前常用的categories有:随笔,教程,例子,笔记 diff --git a/_config.anzhiyu.yml b/_config.anzhiyu.yml deleted file mode 100644 index ed69ed3e..00000000 --- a/_config.anzhiyu.yml +++ /dev/null @@ -1,1282 +0,0 @@ -menu: - 文章: - 隧道: /archives/ || anzhiyu-icon-box-archive - 分类: /categories/ || anzhiyu-icon-shapes - 标签: /tags/ || anzhiyu-icon-tags - - 友链: - 友人帐: /link/ || anzhiyu-icon-link - 朋友圈: /fcircle/ || anzhiyu-icon-artstation - 留言板: /comments/ || anzhiyu-icon-envelope - - 我的: - 音乐馆: /music/ || anzhiyu-icon-music - 追番页: /bangumis/ || anzhiyu-icon-bilibili - 相册集: /album/ || anzhiyu-icon-images - 小空调: /air-conditioner/ || anzhiyu-icon-fan - - 关于: - 关于本人: /about/ || anzhiyu-icon-paper-plane - 闲言碎语: /essay/ || anzhiyu-icon-lightbulb - 随便逛逛: javascript:toRandomPost() || anzhiyu-icon-shoe-prints1 - -# nav相关配置 -nav: - enable: true - travelling: true - clock: true - menu: - - title: 项目 - item: - - name: PyQt - link: https://github.com/PyQt5/PyQt - icon: favicon.ico - -# mourn (哀悼日,指定日期网站简单变灰,不包括滚动条) -# 注意: 仅网站首页变灰,其他页面正常显示 -mourn: - enable: false - days: [4-5, 5-12, 7-7, 9-18, 12-13] - -# Code Blocks (代码相关) -# -------------------------------------- - -highlight_theme: light # darker / pale night / light / ocean / mac / mac light / false -highlight_copy: true # copy button -highlight_lang: true # show the code language -highlight_shrink: false # true: shrink the code blocks / false: expand the code blocks | none: expand code blocks and hide the button -highlight_height_limit: 330 # unit: px -code_word_wrap: true - -# copy settings -# copyright: Add the copyright information after copied content (複制的内容后面加上版权信息) -# copy: enable 复制后弹窗提示版权信息 -copy: - enable: true - copyright: - enable: false - limit_count: 50 - -# social settings (社交图标设置) -# formal: -# name: link || icon -social: - Github: https://github.com/PyQt5 || anzhiyu-icon-github - # BiliBili: https://space.bilibili.com/372204786 || anzhiyu-icon-bilibili - -# 作者卡片 状态 -author_status: - enable: false - # 可以是任何图片,建议放表情包或者emoji图片,效果都很好,[表情包速查](https://emotion.xiaokang.me/) - statusImg: "https://bu.dusays.com/2023/08/24/64e6ce9c507bb.png" - skills: - # - 🤖️ 数码科技爱好者 - # - 🔍 分享与热心帮助 - # - 🏠 智能家居小能手 - - 🔨 设计开发一条龙 - - 🤝 专修交互与设计 - - 🏃 脚踏实地行动派 - - 🧱 团队小组发动机 - - 💢 壮汉人狠话不多 - -# search (搜索) -# see https://blog.anheyu.com/posts/c27d.html#搜索系统 -# -------------------------------------- - -# Algolia search -algolia_search: - enable: false - hits: - per_page: 6 - tags: - # - 前端 - # - Hexo - -# Docsearch -# Apply and Docs: see https://docsearch.algolia.com/ -# Crawler Admin Console: see https://crawler.algolia.com/ -# Settings: https://www.algolia.com/ -docsearch: - enable: false - appId: # see email - apiKey: # see email - indexName: # see email - option: - -# Local search -local_search: - enable: false - preload: true - CDN: - -# Math (数学) -# -------------------------------------- -# About the per_page -# if you set it to true, it will load mathjax/katex script in each page (true 表示每一页都加载js) -# if you set it to false, it will load mathjax/katex script according to your setting (add the 'mathjax: true' in page's front-matter) -# (false 需要时加载,须在使用的 Markdown Front-matter 加上 mathjax: true) - -# MathJax -mathjax: - enable: false - per_page: false - -# KaTeX -katex: - enable: false - per_page: false - hide_scrollbar: true - -# Image (图片设置) -# -------------------------------------- - -# Favicon(网站图标) -favicon: /favicon.ico - -# Avatar (头像) -avatar: - img: https://bu.dusays.com/2023/04/27/64496e511b09c.jpg - effect: false - -# Disable all banner image -disable_top_img: false - -# The banner image of home page -index_img: false # "background: url() top / cover no-repeat" - -# If the banner of page not setting, it will show the top_img -default_top_img: false - -cover: - # display the cover or not (是否显示文章封面) - index_enable: true - aside_enable: true - archives_enable: true - # the position of cover in home page (封面显示的位置) - # left/right/both - position: left - # When cover is not set, the default cover is displayed (当没有设置cover时,默认的封面显示) - default_cover: - # - /img/default_cover.jpg - -# Replace Broken Images (替换无法显示的图片) -error_img: - flink: /img/friend_404.gif - post_page: /img/404.jpg - -# A simple 404 page -error_404: - enable: true - subtitle: "页面没有找到" - background: - -post_meta: - page: # Home Page - date_type: created # created or updated or both 主页文章日期是创建日或者更新日或都显示 - date_format: simple # date/relative/simple 显示日期还是相对日期 或者 简单日期 - categories: true # true or false 主页是否显示分类 - tags: true # true or false 主页是否显示标籤 - label: false # true or false 显示描述性文字 - post: - date_type: both # created or updated or both 文章页日期是创建日或者更新日或都显示 - date_format: date # date/relative 显示日期还是相对日期 - categories: true # true or false 文章页是否显示分类 - tags: true # true or false 文章页是否显示标籤 - label: true # true or false 显示描述性文字 - unread: false # true or false 文章未读功能 - -# 主色调相关配置 -mainTone: - enable: false # true or false 文章是否启用获取图片主色调 - mode: api # cdn/api/both cdn模式为图片url+imageAve参数获取主色调,api模式为请求API获取主色调,both模式会先请求cdn参数,无法获取的情况下将请求API获取,可以在文章内配置main_color: '#3e5658',使用十六进制颜色,则不会请求both/cdn/api获取主色调,而是直接使用配置的颜色 - # 项目地址:https://github.com/anzhiyu-c/img2color-go - api: https://img2color-go.vercel.app/api?img= # mode为api时可填写 - cover_change: true # 整篇文章跟随cover修改主色调 - -# wordcount (字数统计) -# see https://blog.anheyu.com/posts/c27d.html#字数统计 -wordcount: - enable: false - post_wordcount: true - min2read: true - total_wordcount: true - -# Display the article introduction on homepage -# 1: description -# 2: both (if the description exists, it will show description, or show the auto_excerpt) -# 3: auto_excerpt (default) -# false: do not show the article introduction -index_post_content: - method: 3 - length: 500 # if you set method to 2 or 3, the length need to config - -# anchor -# when you scroll in post, the URL will update according to header id. -anchor: false - -# Post -# -------------------------------------- - -# toc (目录) -toc: - post: true - page: false - number: true - expand: false - style_simple: false # for post - -post_copyright: - enable: true - decode: false - author_href: - location: 成都 - license: CC BY-NC-SA 4.0 - license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ - avatarSinks: false # hover时头像下沉 - copyright_author_img_back: - copyright_author_img_front: - copyright_author_link: / - -# Sponsor/reward -reward: - enable: true - QR_code: - - img: images/weixin.png - link: - text: 微信 - - img: images/zhifubao.png - link: - text: 支付宝 - -# Post edit -# Easily browse and edit blog source code online. -post_edit: # 目前仅可选择一个平台在线编辑 - enable: true - # github: https://github.com/user-name/repo-name/edit/branch-name/subdirectory-name/ - # For example: https://github.com/jerryc127/butterfly.js.org/edit/main/source/ - github: https://github.com/PyQt5/pyqt5.github.io/edit/dev/source/_posts/ - - # yuque: https://www.yuque.com/user-name/repo-name/ - # 示例: https://www.yuque.com/yuque/yuque/ - # 你需要在语雀文章 Front Matter 添加参数 id 并确保其唯一性(例如 “id: yuque”, “id: 01”) - yuque: false - -# Related Articles -related_post: - enable: true - limit: 6 # Number of posts displayed - date_type: created # or created or updated 文章日期显示创建日或者更新日 - -# figcaption (图片描述文字) -photofigcaption: false - -# post_pagination (分页) -# value: 1 || 2 || 3 || 4 || false -# 1: The 'next post' will link to old post -# 2: The 'next post' will link to new post -# 3: 只有下一篇,并且只在文章滚动到评论区时显示下一篇文章(旧文章) -# 4: 只有下一篇,并且只在文章滚动到评论区时显示下一篇文章(旧文章) 显示图片cover -# false: disable pagination -post_pagination: 2 - -# Displays outdated notice for a post (文章过期提醒) -noticeOutdate: - enable: false - style: flat # style: simple/flat - limit_day: 365 # When will it be shown - position: top # position: top/bottom - message_prev: It has been - message_next: days since the last update, the content of the article may be outdated. - -# Share System (分享功能) -# -------------------------------------- - -# Share.js -# https://github.com/overtrue/share.js -sharejs: - enable: true - sites: facebook,twitter,wechat,weibo,qq - -# AddToAny -# https://www.addtoany.com/ -addtoany: - enable: false - item: facebook,twitter,wechat,sina_weibo,email,copy_link - -# Comments System -# -------------------------------------- - -comments: - # Up to two comments system, the first will be shown as default - # Choose: Valine/Waline/Twikoo/Artalk - use: # Twikoo/Waline - text: true # Display the comment name next to the button - # lazyload: The comment system will be load when comment element enters the browser's viewport. - # If you set it to true, the comment count will be invalid - lazyload: false - count: false # Display comment count in post's top_img - card_post_count: false # Display comment count in Home Page - -# valine -# https://valine.js.org -valine: - appId: xxxxx # leancloud application app id - appKey: xxxxx # leancloud application app key - pageSize: 10 # comment list page size - avatar: mp # gravatar style https://valine.js.org/#/avatar - lang: zh-CN # i18n: zh-CN/zh-TW/en/ja - placeholder: 填写QQ邮箱就会使用QQ头像喔~. # valine comment input placeholder (like: Please leave your footprints) - guest_info: nick,mail,link # valine comment header info (nick/mail/link) - recordIP: false # Record reviewer IP - serverURLs: # This configuration is suitable for domestic custom domain name users, overseas version will be automatically detected (no need to manually fill in) - bg: /img/comment_bg.png # valine background - emojiCDN: //i0.hdslb.com/bfs/emote/ # emoji CDN - enableQQ: true # enable the Nickname box to automatically get QQ Nickname and QQ Avatar - requiredFields: nick,mail # required fields (nick/mail) - visitor: false - master: - - xxxxx - friends: - - xxxxxx - tagMeta: "博主,小伙伴,访客" - option: - -# waline - A simple comment system with backend support fork from Valine -# https://waline.js.org/ -waline: - serverURL: # Waline server address url - bg: # waline background - pageview: false - meta: false # 归属地, 操作系统 前是否显示图标 - imageUploader: true # 是否启用图片上传功能,默认开启,限制为 128kb 的 base64 图片 - # 以下为可选配置,后续若有新增/修改配置参数可在此自行添加/修改 - option: - -# Twikoo -# https://github.com/imaegoo/twikoo -twikoo: - envId: - region: - visitor: false - option: - -# Artalk -# https://artalk.js.org/guide/frontend/config.html -artalk: - server: - site: - visitor: false - option: - -# Chat Services -# -------------------------------------- - -# Chat Button [recommend] -# It will create a button in the bottom right corner of website, and hide the origin button -chat_btn: false - -# The origin chat button is displayed when scrolling up, and the button is hidden when scrolling down -chat_hide_show: false - -# chatra -# https://chatra.io/ -chatra: - enable: false - id: - -# tidio -# https://www.tidio.com/ -tidio: - enable: false - public_key: - -# daovoice -# http://daovoice.io/ -daovoice: - enable: false - app_id: - -# crisp -# https://crisp.chat/en/ -crisp: - enable: false - website_id: - -# Footer Settings -# -------------------------------------- -footer: - owner: - enable: true - since: 2018 - custom_text: - runtime: - enable: true - launch_time: 01/01/2018 00:00:00 # 网站上线时间 - work_img: https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-上班摸鱼中.svg - work_description: 距离月入30k也就还差一个大佬带我~ - offduty_img: https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-下班啦.svg - offduty_description: 下班了就该开开心心的玩耍,嘿嘿~ - # 徽标部分配置项 https://shields.io/ - # https://img.shields.io/badge/CDN-jsDelivr-orange?style=flat&logo=jsDelivr - bdageitem: - enable: true - list: - - link: https://hexo.io/ #徽标指向网站链接 - shields: https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Frame-Hexo.svg #徽标API - message: 博客框架为Hexo_v5.4.0 #徽标提示语 - - link: https://blog.anheyu.com/ - shields: https://npm.elemecdn.com/anzhiyu-theme-static@1.0.9/img/Theme-AnZhiYu-2E67D3.svg - message: 本站使用AnZhiYu主题 - # - link: https://www.dogecloud.com/ - # shields: https://npm.elemecdn.com/anzhiyu-blog@2.2.0/img/badge/CDN-多吉云-3693F3.svg - # message: 本站使用多吉云为静态资源提供CDN加速 - - link: https://github.com/ - shields: https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Source-Github.svg - message: 本站项目由Github托管 - - link: http://creativecommons.org/licenses/by-nc-sa/4.0/ - shields: https://npm.elemecdn.com/anzhiyu-blog@2.2.0/img/badge/Copyright-BY-NC-SA.svg - message: 本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可 - socialBar: - enable: true - centerImg: - left: - - title: email - link: mailto:892768447@qq.com - icon: anzhiyu-icon-envelope - - title: RSS - link: atom.xml - icon: anzhiyu-icon-rss - right: - - title: Github - link: https://github.com/PyQt5 - icon: anzhiyu-icon-github - - title: CC - link: /copyright - icon: anzhiyu-icon-copyright-line - list: - enable: false - randomFriends: 3 - project: - # - title: 导航 - # links: - # - title: 即刻短文 - # link: /essay/ - # - title: 友链文章 - # link: /fcircle/ - # - title: 留言板 - # link: /comments/ - # - title: 协议 - # links: - # - title: 隐私协议 - # link: /privacy/ - # - title: Cookies - # link: /cookies/ - # - title: 版权协议 - # link: /copyright/ - footerBar: - enable: true - authorLink: / - cc: - enable: false - link: /copyright - linkList: - - link: https://github.com/anzhiyu-c/hexo-theme-anzhiyu - text: 主题 - # - link: https://beian.miit.gov.cn/ - # text: 湘ICP备-xxxxxxx号 - subTitle: - enable: false - # Typewriter Effect (打字效果) - effect: true - # Effect Speed Options (打字效果速度参数) - startDelay: 300 # time before typing starts in milliseconds - typeSpeed: 150 # type speed in milliseconds - backSpeed: 50 # backspacing speed in milliseconds - # loop (循环打字) - loop: true - # source 调用第三方服务 - # source: false 关闭调用 - # source: 1 调用一言网的一句话(简体) https://hitokoto.cn/ - # source: 2 调用一句网(简体) http://yijuzhan.com/ - # source: 3 调用今日诗词(简体) https://www.jinrishici.com/ - # subtitle 会先显示 source , 再显示 sub 的内容 - source: 1 - # 如果关闭打字效果,subtitle 只会显示 sub 的第一行文字 - sub: - # - 生活明朗, 万物可爱, 人间值得, 未来可期. - -# Analysis -# -------------------------------------- - -# Baidu Analytics -# https://tongji.baidu.com/web/welcome/login -baidu_analytics: f7dfd656bdac4e76da8b54bba2978f0b - -# Google Analytics -# https://analytics.google.com/analytics/web/ -google_analytics: - -# CNZZ Analytics -# https://www.umeng.com/ -cnzz_analytics: - -# Cloudflare Analytics -# https://www.cloudflare.com/zh-tw/web-analytics/ -cloudflare_analytics: - -# Microsoft Clarity -# https://clarity.microsoft.com/ -microsoft_clarity: j6xye2x6np - -# Advertisement -# -------------------------------------- - -# Google Adsense (谷歌广告) -google_adsense: - enable: false - auto_ads: true - js: https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js - client: - enable_page_level_ads: true - -# Insert ads manually (手动插入广告) -# ad: -# index: -# aside: -# post: - -# Verification (站长验证) -# -------------------------------------- - -site_verification: - - name: google-site-verification - content: xxx - - name: baidu-site-verification - content: code-xxx - - name: msvalidate.01 - content: xxx - -# Beautify/Effect (美化/效果) -# -------------------------------------- - -# Theme color for customize -# Notice: color value must in double quotes like "#000" or may cause error! - -theme_color: - enable: true - main: "#425AEF" - dark_main: "#f2b94b" - paginator: "#425AEF" - # button_hover: "#FF7242" - text_selection: "#2128bd" - link_color: "var(--anzhiyu-fontcolor)" - meta_color: "var(--anzhiyu-fontcolor)" - hr_color: "#4259ef23" - code_foreground: "#fff" - code_background: "var(--anzhiyu-code-stress)" - toc_color: "#425AEF" - # blockquote_padding_color: "#425AEF" - # blockquote_background_color: "#425AEF" - scrollbar_color: "var(--anzhiyu-scrollbar)" - meta_theme_color_light: "#f7f9fe" - meta_theme_color_dark: "#18171d" - -# 移动端侧栏 -sidebar: - site_data: - archive: true - tag: true - category: true - menus_items: true - tags_cloud: true - display_mode: true - nav_menu_project: true - -# 文章h2添加分隔线 -h2Divider: false - -# 表格隔行变色 -table_interlaced_discoloration: false - -# 首页双栏显示 -article_double_row: true - -# The top_img settings of home page -# default: top img - full screen, site info - middle (默认top_img全屏,site_info在中间) -# The position of site info, eg: 300px/300em/300rem/10% (主页标题距离顶部距离) -index_site_info_top: -# The height of top_img, eg: 300px/300em/300rem (主页top_img高度) -index_top_img_height: - -# The user interface setting of category and tag page (category和tag页的UI设置) -# index - same as Homepage UI (index 值代表 UI将与首页的UI一样) -# default - same as archives UI 默认跟archives页面UI一样 -category_ui: # 留空或 index -tag_ui: # 留空或 index - -# Footer Background -footer_bg: false - -# the position of bottom right button/default unit: px (右下角按钮距离底部的距离/默认单位为px) -rightside-bottom: 100px - -# Background effects (背景特效) -# -------------------------------------- - -# canvas_ribbon (静止彩带背景) -# See: https://github.com/hustcc/ribbon.js -canvas_ribbon: - enable: false - size: 150 - alpha: 0.6 - zIndex: -1 - click_to_change: false - mobile: false - -# Fluttering Ribbon (动态彩带) -canvas_fluttering_ribbon: - enable: false - mobile: false - -# canvas_nest -# https://github.com/hustcc/canvas-nest.js -canvas_nest: - enable: false - color: "0,0,255" #color of lines, default: '0,0,0'; RGB values: (R,G,B).(note: use ',' to separate.) - opacity: 0.7 # the opacity of line (0~1), default: 0.5. - zIndex: -1 # z-index property of the background, default: -1. - count: 99 # the number of lines, default: 99. - mobile: false - -# Typewriter Effect (打字效果) -# https://github.com/disjukr/activate-power-mode -activate_power_mode: - enable: false - colorful: true # open particle animation (冒光特效) - shake: false # open shake (抖动特效) - mobile: false - -# Mouse click effects: fireworks (鼠标点击效果: 烟火特效) -fireworks: - enable: false - zIndex: 9999 # -1 or 9999 - mobile: false - -# Mouse click effects: Heart symbol (鼠标点击效果: 爱心) -click_heart: - enable: false - mobile: false - -# Mouse click effects: words (鼠标点击效果: 文字) -ClickShowText: - enable: false - text: - # - I - # - LOVE - # - YOU - fontSize: 15px - random: false - mobile: false - -# Default display mode (网站默认的显示模式) -# light (default) / dark -display_mode: light - -# Beautify (美化页面显示) -beautify: - enable: true - field: post # site/post - title-prefix-icon: '\f0c1' - title-prefix-icon-color: "#F47466" - -# Global font settings -# Don't modify the following settings unless you know how they work (非必要不要修改) -font: - global-font-size: 16px - code-font-size: - font-family: - code-font-family: consolas, Menlo, "PingFang SC", "Microsoft JhengHei", "Microsoft YaHei", sans-serif - -# Font settings for the site title and site subtitle -# 左上角网站名字 主页居中网站名字 -blog_title_font: - font_link: - font-family: PingFang SC, 'Hiragino Sans GB', 'Microsoft JhengHei', 'Microsoft YaHei', sans-serif - -# The setting of divider icon (水平分隔线图标设置) -hr_icon: - enable: true - icon: \f0c4 # the unicode value of Font Awesome icon, such as '\f0c4' - icon-top: - -# the subtitle on homepage (主页subtitle) -subtitle: - enable: false - # Typewriter Effect (打字效果) - effect: true - # Effect Speed Options (打字效果速度参数) - startDelay: 300 # time before typing starts in milliseconds - typeSpeed: 150 # type speed in milliseconds - backSpeed: 50 # backspacing speed in milliseconds - # loop (循环打字) - loop: true - # source 调用第三方服务 - # source: false 关闭调用 - # source: 1 调用一言网的一句话(简体) https://hitokoto.cn/ - # source: 2 调用一句网(简体) http://yijuzhan.com/ - # source: 3 调用今日诗词(简体) https://www.jinrishici.com/ - # subtitle 会先显示 source , 再显示 sub 的内容 - source: 1 - # 如果关闭打字效果,subtitle 只会显示 sub 的第一行文字 - sub: - # - 生活明朗,万物可爱,人间值得,未来可期. - -# Loading Animation (加载动画) -preloader: - enable: true - # source - # 1. fullpage-loading - # 2. pace (progress bar) - # else all - source: 3 - # pace theme (see https://codebyzach.github.io/pace/) - pace_css_url: - avatar: https://npm.elemecdn.com/anzhiyu-blog-static@1.0.4/img/avatar.jpg # 自定加载动画义头像 - -# aside (侧边栏) -# -------------------------------------- - -aside: - enable: true - hide: false - button: true - mobile: true # display on mobile - position: right # left or right - display: # 控制对应详情页面是否显示侧边栏 - archive: true - tag: true - category: true - card_author: - enable: true - description: #
这有关于产品、设计、开发相关的问题和看法,还有文章翻译分享
相信你可以在这里找到对你有用的知识教程
# 默认为站点描述 - name_link: / - - card_announcement: - enable: false - content: 欢迎来看我的博客鸭~ - card_weixin: - enable: true - face: https://bu.dusays.com/2023/01/13/63c02edf44033.png - backFace: https://bu.dusays.com/2023/05/13/645fa415e8694.png - card_recent_post: - enable: true - limit: 5 # if set 0 will show all - sort: date # date or updated - sort_order: # Don't modify the setting unless you know how it works - card_categories: - enable: false - limit: 8 # if set 0 will show all - expand: none # none/true/false - sort_order: # Don't modify the setting unless you know how it works - card_tags: - enable: true - limit: 40 # if set 0 will show all - color: false - sort_order: # Don't modify the setting unless you know how it works - highlightTags: - # - Hexo - # - 前端 - card_archives: - enable: true - type: monthly # yearly or monthly - format: MMMM YYYY # eg: YYYY年MM月 - order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending - limit: 8 # if set 0 will show all - sort_order: # Don't modify the setting unless you know how it works - card_webinfo: - enable: true - post_count: true - last_push_date: false - sort_order: # Don't modify the setting unless you know how it works - -# busuanzi count for PV / UV in site -# 访问人数 -busuanzi: - site_uv: true - site_pv: true - page_pv: true - -# Time difference between publish date and now (网页运行时间) -# Formal: Month/Day/Year Time or Year/Month/Day Time -runtimeshow: - enable: true - publish_date: 1/1/2018 00:00:00 - -# Console - Newest Comments -newest_comments: - enable: false - sort_order: # Don't modify the setting unless you know how it works - limit: 6 - storage: 10 # unit: mins, save data to localStorage - avatar: true - -# Bottom right button (右下角按钮) -# -------------------------------------- - -# Conversion between Traditional and Simplified Chinese (简繁转换) -translate: - enable: true - # The text of a button - default: 繁 - # Right-click menu default text - rightMenuMsgDefault: "轉為繁體" - # the language of website (1 - Traditional Chinese/ 2 - Simplified Chinese) - defaultEncoding: 2 - # Time delay - translateDelay: 0 - # The text of the button when the language is Simplified Chinese - msgToTraditionalChinese: "繁" - # The text of the button when the language is Traditional Chinese - msgToSimplifiedChinese: "简" - # Right-click the menu to traditional Chinese - rightMenuMsgToTraditionalChinese: "转为繁体" - # Right-click menu to simplified Chinese - rightMenuMsgToSimplifiedChinese: "转为简体" - -# Read Mode (閲读模式) -readmode: true - -# 中控台 -centerConsole: - enable: true - card_tags: - enable: true - limit: 40 # if set 0 will show all - color: false - sort_order: # Don't modify the setting unless you know how it works - highlightTags: - # - Hexo - # - 前端 - card_archives: - enable: true - type: monthly # yearly or monthly - format: MMMM YYYY # eg: YYYY年MM月 - order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending - limit: 8 # if set 0 will show all - sort_order: # Don't modify the setting unless you know how it works - -# dark mode -darkmode: - enable: true - # Toggle Button to switch dark/light mode - button: true - # Switch dark/light mode automatically (自动切换 dark mode和 light mode) - # autoChangeMode: 1 Following System Settings, if the system doesn't support dark mode, it will switch dark mode between 6 pm to 6 am - # autoChangeMode: 2 Switch dark mode between 6 pm to 6 am - # autoChangeMode: false - autoChangeMode: 1 - start: # 8 - end: # 22 - -# Don't modify the following settings unless you know how they work (非必要请不要修改 ) -# Choose: readmode,translate,darkmode,hideAside,toc,chat,comment -# Don't repeat 不要重複 -rightside_item_order: - enable: false - hide: # readmode,translate,darkmode,hideAside - show: # toc,chat,comment - -# Lightbox (图片大图查看模式) -# -------------------------------------- -# You can only choose one, or neither (只能选择一个 或者 两个都不选) - -# medium-zoom -# https://github.com/francoischalifour/medium-zoom -medium_zoom: false - -# fancybox -# http://fancyapps.com/fancybox/3/ -fancybox: true - -# Tag Plugins settings (标籤外挂) -# -------------------------------------- - -# mermaid -# see https://github.com/mermaid-js/mermaid -mermaid: - enable: false - # built-in themes: default/forest/dark/neutral - theme: - light: default - dark: dark - -# Note (Bootstrap Callout) -note: - # Note tag style values: - # - simple bs-callout old alert style. Default. - # - modern bs-callout new (v2-v3) alert style. - # - flat flat callout style with background, like on Mozilla or StackOverflow. - # - disabled disable all CSS styles import of note tag. - style: flat - icons: true - border_radius: 3 - # Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6). - # Offset also applied to label tag variables. This option can work with disabled note tag. - light_bg_offset: 0 - -icons: - ali_iconfont_js: # 阿里图标symbol 引用链接,主题会进行加载 symbol 引用 - fontawesome: false #是否启用fontawesome6图标 - fontawesome_animation_css: https://npm.elemecdn.com/hexo-butterfly-tag-plugins-plus@1.0.17/lib/assets/font-awesome-animation.min.css - -# other -# -------------------------------------- - -# Pjax -# It may contain bugs and unstable, give feedback when you find the bugs. -# https://github.com/MoOx/pjax -pjax: - enable: true - exclude: - - /music/ - - /no-pjax/ - -# Inject the css and script (aplayer/meting) -aplayerInject: - enable: true - per_page: true - -# Snackbar (Toast Notification 弹窗) -# https://github.com/polonel/SnackBar -# position 弹窗位置 -# 可选 top-left / top-center / top-right / bottom-left / bottom-center / bottom-right -snackbar: - enable: true - position: top-center - bg_light: "#425AEF" # The background color of Toast Notification in light mode - bg_dark: "#1f1f1f" # The background color of Toast Notification in dark mode - -# https://instant.page/ -# prefetch (预加载) -instantpage: true - -# https://github.com/vinta/pangu.js -# Insert a space between Chinese character and English character (中英文之间添加空格) -pangu: - enable: false - field: site # site/post - -# Lazyload (图片懒加载) -# https://github.com/verlok/vanilla-lazyload -lazyload: - enable: true - field: site # site/post - placeholder: - blur: true - progressive: true - -# PWA -# See https://github.com/JLHwung/hexo-offline -# --------------- -pwa: - enable: false - startup_image_enable: true - manifest: /manifest.json - theme_color: var(--anzhiyu-main) - mask_icon: /img/siteicon/apple-icon-180.png - apple_touch_icon: /img/siteicon/apple-icon-180.png - bookmark_icon: /img/siteicon/apple-icon-180.png - favicon_32_32: /img/siteicon/32.png - favicon_16_16: /img/siteicon/16.png - -# Open graph meta tags -# https://developers.facebook.com/docs/sharing/webmasters/ -Open_Graph_meta: true - -# Add the vendor prefixes to ensure compatibility -css_prefix: true - -# 首页顶部相关配置 -home_top: - enable: true # 开关 - timemode: date #date/updated - title: 生活明朗 - subTitle: 万物可爱。 - siteText: anheyu.com - category: - - name: 前端 - path: /categories/前端开发/ - shadow: var(--anzhiyu-shadow-blue) - class: blue - icon: anzhiyu-icon-dove - - name: 大学 - path: /categories/大学生涯/ - shadow: var(--anzhiyu-shadow-red) - class: red - icon: anzhiyu-icon-fire - - name: 生活 - path: /categories/生活日常/ - shadow: var(--anzhiyu-shadow-green) - class: green - icon: anzhiyu-icon-book - default_descr: 再怎么看我也不知道怎么描述它的啦! - swiper: - enable: false - swiper_css: https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/swiper/swiper.min.css #swiper css依赖 - swiper_js: https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/swiper/swiper.min.js #swiper js依赖 - banner: - tips: 新品主题 - title: Theme-AnZhiYu - image: https://bu.dusays.com/2023/05/13/645fa3cf90d70.webp - link: https://docs.anheyu.com/ - -# 朋友圈配置 -friends_vue: - enable: false - vue_js: https://npm.elemecdn.com/anzhiyu-theme-static@1.1.1/friends/index.4f887d95.js - apiurl: # 朋友圈后端地址 - top_tips: 使用 友链朋友圈 订阅友链最新文章 - top_background: - -# 深色模式粒子效果canvas -universe: - enable: true - -# 页面卡片顶部气泡升起效果 -bubble: - enable: false - -# 控制台打印信息 -console: - enable: true - -# 51a统计配置 -LA: - enable: false - ck: - LingQueMonitorID: - -# 标签卖萌 -diytitle: - enable: true - leaveTitle: w(゚Д゚)w 不要走!再看看嘛! - backTitle: ♪(^∇^*)欢迎肥来! - -# 留言弹幕配置 -comment_barrage_config: - enable: false - # 同时最多显示弹幕数 - maxBarrage: 1 - # 弹幕显示间隔时间ms - barrageTime: 4000 - # token,在控制台中获取 - accessToken: "" - # 博主邮箱md5值 - mailMd5: "" - -# 左下角音乐配置项 -# https://github.com/metowolf/MetingJS -nav_music: - enable: true - console_widescreen_music: false # 宽屏状态控制台显示音乐而不是标签 enable为true 控制台依然会显示 - id: 8152976493 - server: netease - volume: 0.7 # 默认音量 - all_playlist: https://y.qq.com/n/ryqq/playlist/8802438608 - -# 路径为 /music 的音乐页面默认加载的歌单 1. nav_music 2. custom -music_page_default: nav_music - -# 评论匿名邮箱 -visitorMail: - enable: true - mail: "" - -# ptool 文章底部工具 -ptool: - enable: true - share_mobile: true - share_weibo: true - share_copyurl: true - categories: false # 是否显示分类 - mode: # 运营模式与责任,不配置不显示 - -# 欢迎语配置 -greetingBox: - enable: false #开启后必须配置下面的list对应的时间段,不然会出现小白条 - default: 晚上好👋 - list: - # - greeting: 晚安😴 - # startTime: 0 - # endTime: 5 - # - greeting: 早上好鸭👋, 祝你一天好心情! - # startTime: 6 - # endTime: 9 - # - greeting: 上午好👋, 状态很好,鼓励一下~ - # startTime: 10 - # endTime: 10 - # - greeting: 11点多啦, 在坚持一下就吃饭啦~ - # startTime: 11 - # endTime: 11 - # - greeting: 午安👋, 宝贝 - # startTime: 12 - # endTime: 14 - # - greeting: 🌈充实的一天辛苦啦! - # startTime: 14 - # endTime: 18 - # - greeting: 19点喽, 奖励一顿丰盛的大餐吧🍔。 - # startTime: 19 - # endTime: 19 - # - greeting: 晚上好👋, 在属于自己的时间好好放松😌~ - # startTime: 20 - # endTime: 24 - -# 文章顶部ai摘要 -post_head_ai_description: - enable: true - gptName: AnZhiYu - mode: local # 默认模式 可选值: tianli/local - switchBtn: false # 可以配置是否显示切换按钮 以切换tianli/local - btnLink: https://afdian.net/item/886a79d4db6711eda42a52540025c377 - randomNum: 3 # 按钮最大的随机次数,也就是一篇文章最大随机出来几种 - basicWordCount: 1000 # 最低获取字符数, 最小1000, 最大1999 - key: xxxx - Referer: https://xx.xx/ - -# 快捷键配置 -shortcutKey: - enable: false - delay: 100 # 所有键位延时触发而不是立即触发(包括shift,以解决和浏览器键位冲突问题) - shiftDelay: 200 # shift按下延时多久开启 - -# 无障碍优化(在首页按下「shift + ?」以查看效果) -accesskey: - enable: true - -# 友情链接顶部相关配置 -linkPageTop: - enable: false - title: 与数百名博主无限进步 - # 添加博主友链的评论自定义格式 - addFriendPlaceholder: "昵称(请勿包含博客等字样):\n网站地址(要求博客地址,请勿提交个人主页):\n头像图片url(请提供尽可能清晰的图片,我会上传到我自己的图床):\n描述:\n站点截图(可选):\n" - -# 缩略图后缀 archive/tag/category 页面单独开启后缀 -pageThumbnailSuffix: "" - -# 隐私协议弹窗 -agreementPopup: - enable: false - url: /privacy - -# 右键菜单 -rightClickMenu: - enable: false - -# 首页随便逛逛people模式 而非技能点模式,关闭后为技能点模式需要配置creativity.yml -peoplecanvas: - enable: true - img: https://upload-bbs.miyoushe.com/upload/2023/09/03/125766904/ee23df8517f3c3e3efc4145658269c06_5714860933110284659.png - -# 动效 -dynamicEffect: - postTopWave: true # 文章顶部波浪效果 - postTopRollZoomInfo: false # 文章顶部滚动时缩放 - pageCommentsRollZoom: false # 非文章页面评论滚动时缩放显示(仅仅Twikoo生效) - -# Inject -# Insert the code to head (before '' tag) and the bottom (before '' tag) -# 插入代码到头部 之前 和 底部 之前 -inject: - head: - # 自定义css - # - - - bottom: - # 自定义js - # - - -# CDN -# Don't modify the following settings unless you know how they work -# 非必要请不要修改 -CDN: - # The CDN provider of internal scripts (主题内部 js 的 cdn 配置) - # option: local/elemecdn/jsdelivr/unpkg/cdnjs/onmicrosoft/cbd/anheyu/custom - # Dev version can only choose. ( dev版的主题只能设置为 local ) - internal_provider: local - - # The CDN provider of third party scripts (第三方 js 的 cdn 配置) - # option: elemecdn/jsdelivr/unpkg/cdnjs/onmicrosoft/cbd/anheyu/custom - third_party_provider: cbd - - # Add version number to CDN, true or false - version: true - - # Custom format - # For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file} - custom_format: # https://npm.elemecdn.com/${name}@latest/${file} - - option: - # main_css: - # main: - # utils: - # translate: - # random_friends_post_js: - # right_click_menu_js: - # comment_barrage_js: - # ai_abstract_js: - # people_js: - # local_search: - # algolia_js: - # algolia_search: - # instantsearch: - # docsearch_js: - # docsearch_css: - # pjax: - # blueimp_md5: - # valine: - # twikoo: - # waline_js: - # waline_css: - # sharejs: - # sharejs_css: - # mathjax: - # katex: - # katex_copytex: - # mermaid: - # canvas_ribbon: - # canvas_fluttering_ribbon: - # canvas_nest: - # lazyload: - # instantpage: - # typed: - # pangu: - # fancybox_css: - # fancybox: - # medium_zoom: - # snackbar_css: - # snackbar: - # activate_power_mode: - # fireworks: - # click_heart: - # ClickShowText: - # fontawesome: - # flickr_justified_gallery_js: - # flickr_justified_gallery_css: - # aplayer_css: - # aplayer_js: - # meting_js: - # meting_api: - # prismjs_js: - # prismjs_lineNumber_js: - # prismjs_autoloader: - # artalk_js: - # artalk_css: - # pace_js: - # pace_default_css: - # countup_js: - # gsap_js: - busuanzi: https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js - # rightmenu: - # waterfall: - # ali_iconfont_css: - # accesskey_js: diff --git a/_config.butterfly.yml b/_config.butterfly.yml deleted file mode 100644 index 3af50869..00000000 --- a/_config.butterfly.yml +++ /dev/null @@ -1,1028 +0,0 @@ -# Navigation bar settings (導航欄設置) -# see https://butterfly.js.org/posts/4aa8abbe/##導航欄設置-Navigation-bar-settings -# -------------------------------------- - -prismjs: - enable: true - preprocess: true - line_number: false - tab_replace: "" - -nav: - logo: # image - display_title: true - fixed: false # fixed navigation bar - -# Menu 目錄 -menu: - 首页: / || fas fa-home - 归档: /archives/ || fas fa-archive - 标签: /tags/ || fas fa-tags - 分类: /categories/ || fas fa-folder-open - # List||fas fa-list: - # Music: /music/ || fas fa-music - # Movie: /movies/ || fas fa-video - 友链: /link/ || fas fa-link - 关于: /about/ || fas fa-heart - -# Code Blocks (代碼相關) -# -------------------------------------- - -highlight_theme: "mac light" # darker / pale night / light / ocean / mac / mac light / false -highlight_copy: true # copy button -highlight_lang: true # show the code language -highlight_shrink: false # true: shrink the code blocks / false: expand the code blocks | none: expand code blocks and hide the button -highlight_height_limit: 220 # unit: px -code_word_wrap: true - -# Social Settings (社交圖標設置) -# formal: -# icon: link || the description || color -social: - fab fa-qq: https://jq.qq.com/?_wv=1027&k=5QVVEdF || QQ || '#68B2F6' - far fa-comment: https://pd.qq.com/s/157c1hiay || Comment || '#4a7dbe' - fab fa-github: https://github.com/PyQt5 || Github || '#24292e' - fas fa-envelope: mailto:892768447@qq.com || Email || '#4a7dbe' - fas fa-rss: /atom.xml || RSS - -# Image (圖片設置) -# -------------------------------------- - -# Favicon(網站圖標) -favicon: favicon.ico - -# Avatar (頭像) -avatar: - img: /images/avatar.png - effect: false - -# Disable all banner image -disable_top_img: false - -# The banner image of home page -index_img: /images/bg-2.jpg - -# If the banner of page not setting, it will show the top_img -default_top_img: /images/bg-2.jpg - -# The banner image of archive page -archive_img: /images/bg-2.jpg - -# If the banner of tag page not setting, it will show the top_img -# note: tag page, not tags page (子標籤頁面的 top_img) -tag_img: /images/bg-2.jpg - -# The banner image of tag page -# format: -# - tag name: xxxxx -tag_per_img: - -# If the banner of category page not setting, it will show the top_img -# note: category page, not categories page (子分類頁面的 top_img) -category_img: /images/bg-2.jpg - -# The banner image of category page -# format: -# - category name: xxxxx -category_per_img: - -cover: - # display the cover or not (是否顯示文章封面) - index_enable: true - aside_enable: true - archives_enable: true - # the position of cover in home page (封面顯示的位置) - # left/right/both - position: both - # When cover is not set, the default cover is displayed (當沒有設置cover時,默認的封面顯示) - default_cover: # https://jsd.012700.xyz/gh/jerryc127/butterfly_cdn@2.1.0/top_img/default.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-2.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-1.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-3.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-6.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-5.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-4.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-7.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-9.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-8.png - - https://jsd.012700.xyz/gh/jerryc127/CDN/img/material-10.png - -# Replace Broken Images (替換無法顯示的圖片) -error_img: - flink: /img/friend_404.gif - post_page: /img/404.jpg - -# A simple 404 page -error_404: - enable: true - subtitle: '页面没有找到' - background: https://i.loli.net/2020/05/19/aKOcLiyPl2JQdFD.png - -post_meta: - page: # Home Page - date_type: created # created or updated or both 主頁文章日期是創建日或者更新日或都顯示 - date_format: date # date/relative 顯示日期還是相對日期 - categories: true # true or false 主頁是否顯示分類 - tags: true # true or false 主頁是否顯示標籤 - label: true # true or false 顯示描述性文字 - post: - date_type: created # created or updated or both 文章頁日期是創建日或者更新日或都顯示 - date_format: date # date/relative 顯示日期還是相對日期 - categories: true # true or false 文章頁是否顯示分類 - tags: true # true or false 文章頁是否顯示標籤 - label: true # true or false 顯示描述性文字 - -# Display the article introduction on homepage -# 1: description -# 2: both (if the description exists, it will show description, or show the auto_excerpt) -# 3: auto_excerpt (default) -# false: do not show the article introduction -index_post_content: - method: 3 - length: 500 # if you set method to 2 or 3, the length need to config - -# anchor -anchor: - # when you scroll, the URL will update according to header id. - auto_update: true - # Click the headline to scroll and update the anchor - click_to_scroll: true - -# figcaption (圖片描述文字) -photofigcaption: true - -# copy settings -# copyright: Add the copyright information after copied content (複製的內容後面加上版權信息) -copy: - enable: true - copyright: - enable: true - limit_count: 150 - -# Post -# -------------------------------------- - -# toc (目錄) -toc: - post: true - page: false - number: true - expand: true - style_simple: false # for post - scroll_percent: true - -post_copyright: - enable: true - decode: true - author_href: - license: CC BY-NC-SA 4.0 - license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ - -# Sponsor/reward -reward: - enable: true - text: - QR_code: - - img: /images/weixin.png - link: - text: 微信付 - - img: /images/zhifubao.png - link: - text: 支付宝 - -# Post edit -# Easily browse and edit blog source code online. -post_edit: - enable: true - # url: https://github.com/user-name/repo-name/edit/branch-name/subdirectory-name/ - # For example: https://github.com/jerryc127/butterfly.js.org/edit/main/source/ - url: https://github.com/PyQt5/blog/edit/dev/source/ - -# Related Articles -related_post: - enable: true - limit: 6 # Number of posts displayed - date_type: created # or created or updated 文章日期顯示創建日或者更新日 - -# post_pagination (分頁) -# value: 1 || 2 || false -# 1: The 'next post' will link to old post -# 2: The 'next post' will link to new post -# false: disable pagination -post_pagination: 1 - -# Displays outdated notice for a post (文章過期提醒) -noticeOutdate: - enable: true - style: flat # style: simple/flat - limit_day: 730 # When will it be shown - position: top # position: top/bottom - message_prev: 距离上次更新已经过了 - message_next: 天,文章所描述的内容可能已经发生变化,请留意。 - -# Footer Settings -# -------------------------------------- -footer: - owner: - enable: true - since: 2018 - custom_text: '
蜀ICP备18031575号-1蜀ICP备18031575号-2
' - copyright: true # Copyright of theme and framework - -# aside (側邊欄) -# -------------------------------------- - -aside: - enable: true - hide: false - button: true - mobile: true # display on mobile - position: left # left or right - display: - archive: true - tag: true - category: true - card_author: - enable: true - description: - button: - enable: true - icon: fab fa-github - text: PyQt Github - link: https://github.com/PyQt5 - card_announcement: - enable: true - content: - '
- - - -

or

- - - -
' - card_recent_post: - enable: true - limit: 5 # if set 0 will show all - sort: date # date or updated - sort_order: # Don't modify the setting unless you know how it works - card_categories: - enable: true - limit: 0 # if set 0 will show all - expand: false # none/true/false - sort_order: # Don't modify the setting unless you know how it works - card_tags: - enable: true - limit: 40 # if set 0 will show all - color: true - orderby: random # Order of tags, random/name/length - order: 1 # Sort of order. 1, asc for ascending; -1, desc for descending - sort_order: # Don't modify the setting unless you know how it works - card_archives: - enable: true - type: monthly # yearly or monthly - format: YYYY年MM月 # eg: YYYY年MM月 - order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending - limit: 8 # if set 0 will show all - sort_order: # Don't modify the setting unless you know how it works - card_webinfo: - enable: true - post_count: true - last_push_date: true - sort_order: # Don't modify the setting unless you know how it works - card_post_series: - enable: true - orderBy: 'date' # Order by title or date - order: -1 # Sort of order. 1, asc for ascending; -1, desc for descendin - -# busuanzi count for PV / UV in site -# 訪問人數 -busuanzi: - site_uv: true - site_pv: true - page_pv: true - -# Time difference between publish date and now (網頁運行時間) -# Formal: Month/Day/Year Time or Year/Month/Day Time -runtimeshow: - enable: true - publish_date: 2018/1/1 00:00:00 - -# Aside widget - Newest Comments -newest_comments: - enable: true - sort_order: # Don't modify the setting unless you know how it works - limit: 6 - storage: 10 # unit: mins, save data to localStorage - avatar: true - -# Bottom right button (右下角按鈕) -# -------------------------------------- - -# Conversion between Traditional and Simplified Chinese (簡繁轉換) -translate: - enable: false - # The text of a button - default: 简 - # the language of website (1 - Traditional Chinese/ 2 - Simplified Chinese) - defaultEncoding: 2 - # Time delay - translateDelay: 0 - # The text of the button when the language is Simplified Chinese - msgToTraditionalChinese: '繁' - # The text of the button when the language is Traditional Chinese - msgToSimplifiedChinese: '简' - -# Read Mode (閲讀模式) -readmode: true - -# dark mode -darkmode: - enable: true - # Toggle Button to switch dark/light mode - button: true - # Switch dark/light mode automatically (自動切換 dark mode和 light mode) - # autoChangeMode: 1 Following System Settings, if the system doesn't support dark mode, it will switch dark mode between 6 pm to 6 am - # autoChangeMode: 2 Switch dark mode between 6 pm to 6 am - # autoChangeMode: false - autoChangeMode: true - # Set the light mode time. The value is between 0 and 24. If not set, the default value is 6 and 18 - start: 8 - end: 22 - -# show scroll percent in scroll-to-top button -rightside_scroll_percent: true - -# Don't modify the following settings unless you know how they work (非必要請不要修改 ) -# Choose: readmode,translate,darkmode,hideAside,toc,chat,comment -# Don't repeat 不要重複 -rightside_item_order: - enable: false - hide: # readmode,translate,darkmode,hideAside - show: # toc,chat,comment - -# Math (數學) -# -------------------------------------- -# About the per_page -# if you set it to true, it will load mathjax/katex script in each page (true 表示每一頁都加載js) -# if you set it to false, it will load mathjax/katex script according to your setting (add the 'mathjax: true' in page's front-matter) -# (false 需要時加載,須在使用的 Markdown Front-matter 加上 mathjax: true) - -# MathJax -mathjax: - enable: false - per_page: false - -# KaTeX -katex: - enable: false - per_page: false - hide_scrollbar: true - -# search (搜索) -# see https://butterfly.js.org/posts/ceeb73f/#搜索系統 -# -------------------------------------- - -# Algolia search -algolia_search: - enable: false - hits: - per_page: 6 - -# Local search -local_search: - enable: true - # Preload the search data when the page loads. - preload: false - # Show top n results per article, show all results by setting to -1 - top_n_per_article: 1 - # Unescape html strings to the readable one. - unescape: false - CDN: - -# Docsearch -docsearch: - enable: false - appId: - apiKey: - indexName: - option: - -# Share System (分享) -# -------------------------------------- - -# Share.js -# https://github.com/overtrue/share.js -sharejs: - enable: true - sites: facebook,twitter,google,douban,wechat,weibo,qq,qzone - -# AddToAny -# https://www.addtoany.com/ -addtoany: - enable: false - item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link - -# Comments System -# -------------------------------------- - -comments: - # Up to two comments system, the first will be shown as default - # Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/Artalk - # use: giscus # Valine,Disqus - text: true # Display the comment name next to the button - # lazyload: The comment system will be load when comment element enters the browser's viewport. - # If you set it to true, the comment count will be invalid - lazyload: true - count: false # Display comment count in post's top_img - card_post_count: false # Display comment count in Home Page - -# disqus -# https://disqus.com/ -disqus: - shortname: - apikey: # For newest comments widget - -# Alternative Disqus - Render comments with Disqus API -# DisqusJS 評論系統,可以實現在網路審查地區載入 Disqus 評論列表,兼容原版 -# https://github.com/SukkaW/DisqusJS -disqusjs: - shortname: - apikey: - option: - -# livere (來必力) -# https://www.livere.com/ -livere: - uid: - -# gitalk -# https://github.com/gitalk/gitalk -gitalk: - client_id: - client_secret: - repo: - owner: - admin: - option: - -# valine -# https://valine.js.org -valine: - appId: # leancloud application app id - appKey: # leancloud application app key - avatar: monsterid # gravatar style https://valine.js.org/#/avatar - serverURLs: # This configuration is suitable for domestic custom domain name users, overseas version will be automatically detected (no need to manually fill in) - bg: # valine background - visitor: false - option: - -# waline - A simple comment system with backend support fork from Valine -# https://waline.js.org/ -waline: - serverURL: # Waline server address url - bg: # waline background - pageview: false - option: - -# utterances -# https://utteranc.es/ -utterances: - repo: - # Issue Mapping: pathname/url/title/og:title - issue_term: pathname - # Theme: github-light/github-dark/github-dark-orange/icy-dark/dark-blue/photon-dark - light_theme: github-light - dark_theme: photon-dark - -# Facebook Comments Plugin -# https://developers.facebook.com/docs/plugins/comments/ -facebook_comments: - app_id: - user_id: # optional - pageSize: 10 # The number of comments to show - order_by: social # social/time/reverse_time - lang: zh_TW # Language en_US/zh_CN/zh_TW and so on - -# Twikoo -# https://github.com/imaegoo/twikoo -twikoo: - envId: - region: - visitor: false - option: - -# Giscus -# https://giscus.app/ -giscus: - repo: PyQt5/pyqt5.github.io - repo_id: MDEwOlJlcG9zaXRvcnkxODM3MTg4NDA= - category_id: DIC_kwDOCvNTuM4CZ8zK - theme: - light: light - dark: dark - option: - data-strict: 0 - data-emit-metadata: 1 - data-lang: zh-CN - data-loading: lazy - data-category: Announcements - -# Remark42 -# https://remark42.com/docs/configuration/frontend/ -remark42: - host: # Your Host URL - siteId: # Your Site ID - option: - -# Artalk -# https://artalk.js.org/guide/frontend/config.html -artalk: - server: - site: - visitor: false - option: - -# Chat Services -# -------------------------------------- - -# Chat Button [recommend] -# It will create a button in the bottom right corner of website, and hide the origin button -chat_btn: false - -# The origin chat button is displayed when scrolling up, and the button is hidden when scrolling down -chat_hide_show: false - -# chatra -# https://chatra.io/ -chatra: - enable: false - id: - -# tidio -# https://www.tidio.com/ -tidio: - enable: false - public_key: - -# daovoice -# http://dashboard.daovoice.io/app -daovoice: - enable: false - app_id: - -# crisp -# https://crisp.chat/en/ -crisp: - enable: false - website_id: - -# messenger -# https://developers.facebook.com/docs/messenger-platform/discovery/facebook-chat-plugin/ -messenger: - enable: false - pageID: - lang: zh_TW # Language en_US/zh_CN/zh_TW and so on - -# Analysis -# -------------------------------------- - -# Baidu Analytics -# https://tongji.baidu.com/web/welcome/login -baidu_analytics: f7dfd656bdac4e76da8b54bba2978f0b - -# Google Analytics -# https://analytics.google.com/analytics/web/ -google_analytics: - -# Cloudflare Analytics -# https://www.cloudflare.com/zh-tw/web-analytics/ -cloudflare_analytics: - -# Microsoft Clarity -# https://clarity.microsoft.com/ -microsoft_clarity: j6xye2x6np - -# Advertisement -# -------------------------------------- - -# Google Adsense (谷歌廣告) -google_adsense: - enable: false - auto_ads: true - js: https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js - client: - enable_page_level_ads: true - -# Insert ads manually (手動插入廣告) -# ad: -# index: -# aside: -# post: - -# Verification (站長驗證) -# -------------------------------------- - -site_verification: - - name: google-site-verification - content: "L09MDi9gA7mFqsqWfcVVtK_yhV7tPcfBIthuPs-zK4g" - # - name: baidu-site-verification - # content: xxxxxxx - -# Beautify/Effect (美化/效果) -# -------------------------------------- - -# Theme color for customize -# Notice: color value must in double quotes like "#000" or may cause error! - -# theme_color: -# enable: true -# main: "#49B1F5" -# paginator: "#00c4b6" -# button_hover: "#FF7242" -# text_selection: "#00c4b6" -# link_color: "#99a9bf" -# meta_color: "#858585" -# hr_color: "#A4D8FA" -# code_foreground: "#F47466" -# code_background: "rgba(27, 31, 35, .05)" -# toc_color: "#00c4b6" -# blockquote_padding_color: "#49b1f5" -# blockquote_background_color: "#49b1f5" -# scrollbar_color: "#49b1f5" -# meta_theme_color_light: "ffffff" -# meta_theme_color_dark: "#0d0d0d" - -# The top_img settings of home page -# default: top img - full screen, site info - middle (默認top_img全屏,site_info在中間) -# The position of site info, eg: 300px/300em/300rem/10% (主頁標題距離頂部距離) -index_site_info_top: -# The height of top_img, eg: 300px/300em/300rem (主頁top_img高度) -index_top_img_height: - -# The user interface setting of category and tag page (category和tag頁的UI設置) -# index - same as Homepage UI (index 值代表 UI將與首頁的UI一樣) -# default - same as archives UI 默認跟archives頁面UI一樣 -category_ui: index # 留空或 index -tag_ui: index # 留空或 index - -# Stretches the lines so that each line has equal width(文字向兩側對齊,對最後一行無效) -text_align_justify: false - -# Website Background (設置網站背景) -# can set it to color or image (可設置圖片 或者 顔色) -# The formal of image: url(http://xxxxxx.com/xxx.jpg) -background: /images/bg-2.jpg - -# Footer Background -footer_bg: true - -# Add mask to header or footer (为 header 或 footer 添加黑色半透遮罩) -mask: - header: true - footer: true - -# the position of bottom right button/default unit: px (右下角按鈕距離底部的距離/默認單位為px) -rightside-bottom: - -# Enter transitions (開啓網頁進入效果) -enter_transitions: true - -# Typewriter Effect (打字效果) -# https://github.com/disjukr/activate-power-mode -activate_power_mode: - enable: false - colorful: true # open particle animation (冒光特效) - shake: true # open shake (抖動特效) - mobile: false - -# Background effects (背景特效) -# -------------------------------------- - -# canvas_ribbon (靜止彩帶背景) -# See: https://github.com/hustcc/ribbon.js -canvas_ribbon: - enable: true - size: 150 - alpha: 0.6 - zIndex: -1 - click_to_change: true - mobile: true - -# Fluttering Ribbon (動態彩帶) -canvas_fluttering_ribbon: - enable: false - mobile: false - -# canvas_nest -# https://github.com/hustcc/canvas-nest.js -canvas_nest: - enable: false - color: '0,0,255' #color of lines, default: '0,0,0'; RGB values: (R,G,B).(note: use ',' to separate.) - opacity: 0.7 # the opacity of line (0~1), default: 0.5. - zIndex: -1 # z-index property of the background, default: -1. - count: 99 # the number of lines, default: 99. - mobile: false - -# Mouse click effects: fireworks (鼠標點擊效果: 煙火特效) -fireworks: - enable: false - zIndex: 9999 # -1 or 9999 - mobile: false - -# Mouse click effects: Heart symbol (鼠標點擊效果: 愛心) -click_heart: - enable: false - mobile: false - -# Mouse click effects: words (鼠標點擊效果: 文字) -clickShowText: - enable: false - text: - # - I - # - LOVE - # - YOU - fontSize: 15px - random: false - mobile: false - -# Default display mode (網站默認的顯示模式) -# light (default) / dark -display_mode: light - -# Beautify (美化頁面顯示) -beautify: - enable: true - field: post # site/post - title-prefix-icon: '\f0c1' - title-prefix-icon-color: '#F47466' - -# Global font settings -# Don't modify the following settings unless you know how they work (非必要不要修改) -font: - global-font-size: - code-font-size: - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Lato, Roboto, "PingFang SC", "Microsoft JhengHei", "Microsoft YaHei", sans-serif - code-font-family: consolas, Menlo, "PingFang SC", "Microsoft JhengHei", "Microsoft YaHei", sans-serif - -# Font settings for the site title and site subtitle -# 左上角網站名字 主頁居中網站名字 -blog_title_font: - font_link: https://fonts.googleapis.com/css?family=Titillium+Web&display=swap - font-family: Titillium Web, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft JhengHei', 'Microsoft YaHei', sans-serif - -# The setting of divider icon (水平分隔線圖標設置) -hr_icon: - enable: true - icon: # the unicode value of Font Awesome icon, such as '\3423' - icon-top: - -# the subtitle on homepage (主頁subtitle) -subtitle: - enable: true - # Typewriter Effect (打字效果) - effect: true - # Customize typed.js (配置typed.js) - # https://github.com/mattboldt/typed.js/#customization - typed_option: - loop: true - # source 調用第三方服務 - # source: false 關閉調用 - # source: 1 調用一言網的一句話(簡體) https://hitokoto.cn/ - # source: 2 調用一句網(簡體) https://yijuzhan.com/ - # source: 3 調用今日詩詞(簡體) https://www.jinrishici.com/ - # subtitle 會先顯示 source , 再顯示 sub 的內容 - source: 1 - # 如果關閉打字效果,subtitle 只會顯示 sub 的第一行文字 - sub: - - 今日事,今日毕 - - Never put off till tomorrow what you can do today - -# Loading Animation (加載動畫) -preloader: - enable: false - # source - # 1. fullpage-loading - # 2. pace (progress bar) - source: 1 - # pace theme (see https://codebyzach.github.io/pace/) - pace_css_url: - -# wordcount (字數統計) -# see https://butterfly.js.org/posts/ceeb73f/#字數統計 -wordcount: - enable: true - post_wordcount: true - min2read: true - total_wordcount: true - -# Lightbox (圖片大圖查看模式) -# -------------------------------------- -# You can only choose one, or neither (只能選擇一個 或者 兩個都不選) - -# medium-zoom -# https://github.com/francoischalifour/medium-zoom -medium_zoom: false - -# fancybox -# https://fancyapps.com/fancybox/ -fancybox: true - -# Tag Plugins settings (標籤外掛) -# -------------------------------------- - -# series (系列文章) -series: - enable: true - orderBy: 'title' # Order by title or date - order: 1 # Sort of order. 1, asc for ascending; -1, desc for descending - number: true - -# abcjs (樂譜渲染) -# See https://github.com/paulrosen/abcjs -abcjs: - enable: true - per_page: false - -# mermaid -# see https://github.com/mermaid-js/mermaid -mermaid: - enable: true - # built-in themes: default/forest/dark/neutral - theme: - light: default - dark: dark - -# Note (Bootstrap Callout) -note: - # Note tag style values: - # - simple bs-callout old alert style. Default. - # - modern bs-callout new (v2-v3) alert style. - # - flat flat callout style with background, like on Mozilla or StackOverflow. - # - disabled disable all CSS styles import of note tag. - style: flat - icons: true - border_radius: 3 - # Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6). - # Offset also applied to label tag variables. This option can work with disabled note tag. - light_bg_offset: 0 - -# other -# -------------------------------------- - -# Pjax -# It may contain bugs and unstable, give feedback when you find the bugs. -# https://github.com/MoOx/pjax -pjax: - enable: true - exclude: - - /talking/ - -# Inject the css and script (aplayer/meting) -aplayerInject: - enable: false - per_page: true - -# Snackbar (Toast Notification 彈窗) -# https://github.com/polonel/SnackBar -# position 彈窗位置 -# 可選 top-left / top-center / top-right / bottom-left / bottom-center / bottom-right -snackbar: - enable: false - position: bottom-center - bg_light: '#49b1f5' # The background color of Toast Notification in light mode - bg_dark: '#1f1f1f' # The background color of Toast Notification in dark mode - -# https://instant.page/ -# prefetch (預加載) -instantpage: true - -# https://github.com/vinta/pangu.js -# Insert a space between Chinese character and English character (中英文之間添加空格) -pangu: - enable: false - field: site # site/post - -# Lazyload (圖片懶加載) -# https://github.com/verlok/vanilla-lazyload -lazyload: - enable: true - field: site # site/post - placeholder: /images/2.gif - blur: false - -# PWA -# See https://github.com/JLHwung/hexo-offline -# --------------- -pwa: - enable: false -# manifest: /pwa/manifest.json -# apple_touch_icon: /pwa/apple-touch-icon.png -# favicon_32_32: /pwa/32.png -# favicon_16_16: /pwa/16.png -# mask_icon: /pwa/safari-pinned-tab.svg - -# Open graph meta tags -# https://developers.facebook.com/docs/sharing/webmasters/ -Open_Graph_meta: - enable: true - option: - # twitter_card: - # twitter_image: - # twitter_id: - # twitter_site: - # google_plus: - # fb_admins: - # fb_app_id: - -# Add the vendor prefixes to ensure compatibility -css_prefix: true - -# Inject -# Insert the code to head (before '' tag) and the bottom (before '' tag) -# 插入代码到头部 之前 和 底部 之前 -inject: - head: - - - - - bottom: - - - -# CDN -# Don't modify the following settings unless you know how they work -# 非必要請不要修改 -CDN: - # The CDN provider of internal scripts (主題內部 js 的 cdn 配置) - # option: local/jsdelivr/unpkg/cdnjs/custom - # Dev version can only choose. ( dev版的主題只能設置為 local ) - internal_provider: custom - - # The CDN provider of third party scripts (第三方 js 的 cdn 配置) - # option: local/jsdelivr/unpkg/cdnjs/custom - # when set it to local, you need to install hexo-butterfly-extjs - third_party_provider: custom - - # Add version number to url, true or false - version: false - - # Custom format - # For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file} - custom_format: https://jsd.012700.xyz/npm/${name}@latest/${min_file} - - option: - # abcjs_basic_js: - # activate_power_mode: - # algolia_js: - # algolia_search: - # aplayer_css: - # aplayer_js: - # artalk_css: - # artalk_js: - # blueimp_md5: - # busuanzi: - # canvas_fluttering_ribbon: - # canvas_nest: - # canvas_ribbon: - # click_heart: - # clickShowText: - # disqusjs: - # disqusjs_css: - # docsearch_css: - # docsearch_js: - # egjs_infinitegrid: - # fancybox: - # fancybox_css: - # fireworks: - # fontawesome: - # gitalk: - # gitalk_css: - # giscus: - # instantpage: - # instantsearch: - # katex: - # katex_copytex: - # lazyload: - # local_search: - # main: - # main_css: - # mathjax: - # medium_zoom: - # mermaid: - # meting_js: - # pangu: - # prismjs_autoloader: - # prismjs_js: - # prismjs_lineNumber_js: - # pjax: - # sharejs: - # sharejs_css: - # snackbar: - # snackbar_css: - # translate: - # twikoo: - # typed: - # utils: - # valine: - # waline_css: - # waline_js: diff --git a/_config.fluid.yml b/_config.fluid.yml deleted file mode 100644 index 38794875..00000000 --- a/_config.fluid.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Hexo Configuration -## Docs: https://hexo.io/docs/configuration.html -## Source: https://github.com/hexojs/hexo/ diff --git a/_config.matery.yml b/_config.matery.yml deleted file mode 100644 index 38794875..00000000 --- a/_config.matery.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Hexo Configuration -## Docs: https://hexo.io/docs/configuration.html -## Source: https://github.com/hexojs/hexo/ diff --git a/_config.nexmoe.yml b/_config.nexmoe.yml deleted file mode 100644 index 38794875..00000000 --- a/_config.nexmoe.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Hexo Configuration -## Docs: https://hexo.io/docs/configuration.html -## Source: https://github.com/hexojs/hexo/ diff --git a/_config.shoka.yml b/_config.shoka.yml deleted file mode 100644 index f6fb6601..00000000 --- a/_config.shoka.yml +++ /dev/null @@ -1,105 +0,0 @@ -# Hexo Configuration -## Docs: https://hexo.io/docs/configuration.html -## Source: https://github.com/hexojs/hexo/ - -# Site -alternate: PyQt - -# Writing -highlight: - enable: false -prismjs: - enable: false - -autoprefixer: - exclude: - - "*.min.css" - -markdown: - render: # 渲染器设置 - html: false # 过滤 HTML 标签 - xhtmlOut: true # 使用 '/' 来闭合单标签 (比如
)。 - breaks: true # 转换段落里的 '\n' 到
。 - linkify: true # 将类似 URL 的文本自动转换为链接。 - typographer: - quotes: "“”‘’" - plugins: # markdown-it插件设置 - - plugin: - name: markdown-it-toc-and-anchor - enable: true - options: # 文章目录以及锚点应用的class名称,shoka主题必须设置成这样 - tocClassName: "toc" - anchorClassName: "anchor" - - plugin: - name: markdown-it-multimd-table - enable: true - options: - multiline: true - rowspan: true - headerless: true - - plugin: - name: ./markdown-it-furigana - enable: true - options: - fallbackParens: "()" - - plugin: - name: ./markdown-it-spoiler - enable: true - options: - title: "你知道得太多了" - -minify: - html: - enable: true - stamp: false - exclude: - - "**/json.ejs" - - "**/atom.ejs" - - "**/rss.ejs" - css: - enable: true - stamp: false - exclude: - - "**/*.min.css" - js: - enable: true - stamp: false - mangle: - toplevel: true - output: - compress: - exclude: - - "**/*.min.js" - -# algolia: -# appId: -# apiKey: -# adminApiKey: -# chunkSize: 5000 -# indexName: -# fields: -# - title #必须配置 -# - path #必须配置 -# - categories #推荐配置 -# - content:strip:truncate,0,4000 -# - gallery -# - photos -# - tags - -feed: - limit: 20 - order_by: "-date" - tag_dir: false - category_dir: false - rss: - enable: true - template: "themes/shoka/layout/_alternate/rss.ejs" - output: "rss.xml" - atom: - enable: true - template: "themes/shoka/layout/_alternate/atom.ejs" - output: "atom.xml" - jsonFeed: - enable: true - template: "themes/shoka/layout/_alternate/json.ejs" - output: "feed.json" diff --git a/_config.volantis.yml b/_config.volantis.yml deleted file mode 100644 index 38794875..00000000 --- a/_config.volantis.yml +++ /dev/null @@ -1,3 +0,0 @@ -# Hexo Configuration -## Docs: https://hexo.io/docs/configuration.html -## Source: https://github.com/hexojs/hexo/ diff --git a/_config.yelee.yml b/_config.yelee.yml deleted file mode 100644 index 993fcfe5..00000000 --- a/_config.yelee.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Hexo Configuration -## Docs: https://hexo.io/docs/configuration.html -## Source: https://github.com/hexojs/hexo/ - -# Site -language: zh-Hans - -# hexo-tag-cloud -tag_cloud: - textFont: Trebuchet MS, Helvetica - textColour: \#333 - textHeight: 25 - outlineColour: \#ffffff - -# Writing -highlight: - enable: false - -#prettify 插件位置 -# enable 启用和不启用 -# theme 使用prettify高亮主题名称 -prettify: - enable: true - theme: tomorrow-night-eighties ##这里你可以定义上面下载的themes主题包里面样式文件名,不带.css后缀 - -lazyload: - enable: false - onlypost: false - loadingImg: /img/loading0_2.gif - -# Markdown-it config 表情支持 -## Docs: https://github.com/celsomiranda/hexo-renderer-markdown-it/wiki -## http://www.cnblogs.com/fsong/p/5929773.html -markdown: - render: - html: true - xhtmlOut: false - breaks: true - linkify: true - typographer: true - quotes: "“”‘’" - plugins: - - markdown-it-abbr - - markdown-it-footnote - - markdown-it-ins - - markdown-it-sub - - markdown-it-sup - - markdown-it-emoji # add emoji - anchors: - level: 2 - collisionSuffix: "v" - permalink: true - permalinkClass: header-anchor - permalinkSymbol: "" diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 98d3a134..00000000 --- a/_config.yml +++ /dev/null @@ -1,277 +0,0 @@ -# Hexo Configuration -## Docs: https://hexo.io/docs/configuration.html -## Source: https://github.com/hexojs/hexo/ - -# Site -title: PyQt -subtitle: 个人学习经验分享 -description: Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 -keywords: Python,PyQt,PyQt6,PyQt5,PyQt4,PySide,PySide2,PySide6 -author: Irony -language: zh-CN -timezone: "Asia/Shanghai" -email: "892768447@qq.com" - -# URL -## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project' -url: https://pyqt5.com -root: / -# permalink: :year/:month/:day/:title/ -permalink: :title.html -permalink_defaults: -pretty_urls: - trailing_index: true # Set to false to remove trailing 'index.html' from permalinks - trailing_html: true # Set to false to remove trailing '.html' from permalinks - -# Directory -source_dir: source -public_dir: public -tag_dir: tags -archive_dir: archives -category_dir: categories -code_dir: downloads/code -i18n_dir: :lang -##要告诉hexo对plugins目录下的所有文件跳过解析渲染,因为测试时发现如果不配置,加载prettify的相关js会报脚本错误 -skip_render: - - "plugins/**" - - "search.html" - -# Writing -new_post_name: :title.md # File name of new posts -default_layout: post -titlecase: false # Transform title into titlecase -external_link: - enable: true # Open external links in new tab - field: site # Apply to the whole site - exclude: "" -filename_case: 0 -render_drafts: false -post_asset_folder: false -relative_link: false -future: true -highlight: - enable: false - line_number: false - auto_detect: true - tab_replace: "" - wrap: true - hljs: false -prismjs: - enable: true - preprocess: true - line_number: false - tab_replace: "" - -# Home page setting -# path: Root path for your blogs index page. (default = '') -# per_page: Posts displayed per page. (0 = disable pagination) -# order_by: Posts order. (Order by date descending by default) -index_generator: - path: "" - per_page: 10 - order_by: -date - -# Category & Tag -default_category: uncategorized -category_map: -tag_map: - -# Metadata elements -## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta -meta_generator: true - -# Date / Time format -## Hexo uses Moment.js to parse and display date -## You can customize the date format as defined in -## http://momentjs.com/docs/#/displaying/format/ -date_format: YYYY-MM-DD -time_format: HH:mm:ss -## updated_option supports 'mtime', 'date', 'empty' -updated_option: "mtime" - -# Pagination -## Set per_page to 0 to disable pagination -per_page: 10 -pagination_dir: page - -# Include / Exclude file(s) -## include:/exclude: options only apply to the 'source/' folder -include: -exclude: -ignore: - -# Extensions -## Plugins: https://hexo.io/plugins/ -## Themes: https://hexo.io/themes/ -theme: butterfly - -# Deployment -## Docs: https://hexo.io/docs/deployment.html -deploy: - - type: git - repo: https://github.com/PyQt5/blog - branch: [master] - message: [message] - token: $GITHUB_TOKEN - -search: - path: search.xml - field: post - content: true - format: html - -# 备案 -record: "蜀ICP备18031575号-1" - -archive_generator: - per_page: 10 ##归档页面默认10篇文章标题 - yearly: true ##生成年视图 - monthly: true ##生成月视图 - -tag_generator: - per_page: 10 ##标签分类页面默认10篇文章 - -category_generator: - per_page: 10 ###分类页面默认10篇文章 - -feed: - type: atom ##feed类型 atom或者rss2 - path: atom.xml ##feed路径 - limit: 20 ##feed文章最小数量 - -nofollow: - enable: true - field: site - exclude: - -#sitemap -sitemap: - path: sitemap.xml - rel: false - tags: true - categories: true - -marked: - smartypants: false - descriptionLists: false - -baidusitemap: - path: baidusitemap.xml - -# # json api -# jsonContent: -# meta: true -# dafts: false -# pages: false -# dateFormat: YYYY-MM-DD HH:mm:ss -# file: content.json -# posts: -# title: true -# slug: true -# date: true -# updated: false -# comments: false -# path: true -# link: true -# permalink: true -# excerpt: true -# keywords: false -# text: false -# raw: true -# content: false -# author: true -# categories: true -# tags: true - -# # restful api -# restful: -# # site 可配置为数组选择性生成某些属性 -# site: -# [ -# "title", -# "subtitle", -# "description", -# "author", -# "since", -# "email", -# "favicon", -# "avatar", -# ] -# # site: true # hexo.config mix theme.config -# posts_size: 10 # 文章列表分页,0 表示不分页 -# posts_props: # 文章列表项的需要生成的属性 -# title: true -# slug: true -# date: true -# updated: true -# comments: false -# path: true -# excerpt: true -# cover: true # 封面图,取文章第一张图片 -# content: true -# raw: true -# keywords: false -# categories: true -# tags: true -# categories: true # 分类数据 -# use_category_slug: false # Use slug for filename of category data -# tags: true # 标签数据 -# use_tag_slug: false # Use slug for filename of tag data -# post: true # 文章数据 -# pages: true # 额外的 Hexo 页面数据, 如 About - -# envelope_comment -# see https://akilar.top/posts/e2d3c450/ -envelope_comment: - enable: true #控制开关 - custom_pic: - cover: https://npm.elemecdn.com/hexo-butterfly-envelope/lib/violet.jpg #信笺头部图片 - line: https://npm.elemecdn.com/hexo-butterfly-envelope/lib/line.png #信笺底部图片 - beforeimg: https://npm.elemecdn.com/hexo-butterfly-envelope/lib/before.png # 信封前半部分 - afterimg: https://npm.elemecdn.com/hexo-butterfly-envelope/lib/after.png # 信封后半部分 - message: #信笺正文,多行文本,写法如下 - - 有什么想问的? - - 有什么想说的? - - 有什么想吐槽的? - - 哪怕是有什么想吃的,都可以告诉我哦~ - bottom: 自动书记人偶竭诚为您服务! #仅支持单行文本 - height: #1024px,信封划出的高度 - path: #【可选】comments 的路径名称。默认为 comments,生成的页面为 comments/index.html - front_matter: #【可选】comments页面的 front_matter 配置 - title: 留言板 - comments: true - top_img: false - type: envelope - -# 追番插件 -# https://github.com/HCLonely/hexo-bilibili-bangumi -bangumi: # 追番设置 - enable: false - source: bili - path: - vmid: 372204786 - title: "追番列表" - quote: "生命不息,追番不止!" - show: 1 - lazyload: false - loading: - showMyComment: false - pagination: false - metaColor: - color: - webp: - progress: - extraOrder: - proxy: - host: "代理host" - port: "代理端口" - extra_options: - top_img: false - lazyload: - enable: false - -# APlayer -# https://github.com/MoePlayer/hexo-tag-aplayer/blob/master/docs/README-zh_cn.md -aplayer: - meting: true - asset_inject: false \ No newline at end of file diff --git a/about/index.html b/about/index.html new file mode 100644 index 00000000..80788458 --- /dev/null +++ b/about/index.html @@ -0,0 +1,292 @@ +关于社区 | PyQt + + + + + + + + + + + + + +

# 为什么要搭建这个社区

+

也许是因为喜爱 PyQt,喜欢用它做很多东西吧,也想为大家提供更多关于 PyQt 的帮助。

+

内容主要是 记录 一些开发日志,分享些 我认为有趣的东西,还有 各种教程、例子 等等。

+

还有就是收集和整理记录下网友们平时遇到的问题,提出的需求等等,方便其他人搜索。

+

如需帮助可以前往 issues 留下您的问题

+

如需发表文章可以前往 这里 发表新文章,同时需要查看 发文要求

+

# 投稿

+

欢迎投稿: https://github.com/PyQt5/blog/blob/dev/README.md

+

# 关于我?

+

信息很少…

+

# 联系我们?

+

点击链接加入群聊【PyQt5 学习】:https://jq.qq.com/?_wv=1027&k=5Y29SHz

+

# 捐助支持

+

微信 or 支付宝

+
\ No newline at end of file diff --git a/source/ads.txt b/ads.txt similarity index 100% rename from source/ads.txt rename to ads.txt diff --git a/animateshadow.html b/animateshadow.html new file mode 100644 index 00000000..09915dc3 --- /dev/null +++ b/animateshadow.html @@ -0,0 +1,406 @@ +PyQt5动画边框阴影 | PyQt + + + + + + + + + + + + + + + +

PyQt5动画边框阴影

为子控件增加动画阴影效果,结合 QGraphicsDropShadowEffectQPropertyAnimation 动态改变阴影半径达到效果,在旧版本的 Qt 中 QGraphicsDropShadowEffect 可能会有点问题(父控件会影响子控件)

+ +

# 原理

+

原理是利用 QGraphicsDropShadowEffect 添加边框阴影,然后使用动画不停改变阴影的模糊半径来达到效果,如图:

+

ShadowEffect

+

# 简单说明

+
    +
  1. 继承 QGraphicsDropShadowEffect 增加动态属性 radius
  2. +
  3. 通过 setGraphicsEffect 方法设置控件的边框阴影
  4. +
  5. 通过 QPropertyAnimation 属性动画不断改变 radius 的值并调用 setBlurRadius 更新半径值
  6. +
+

https://github.com/PyQt5/PyQt/blob/master/QGraphicsDropShadowEffect/ShadowEffect.py

+

# 自定义类

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2018年9月25日
+@author: Irony
+@site: https://pyqt5.com, https://github.com/892768447
+@email: 892768447@qq.com
+@file: AnimationShadowEffect
+@description: 边框动画阴影动画
+"""
+from PyQt5.QtCore import QPropertyAnimation, pyqtProperty
+from PyQt5.QtWidgets import QGraphicsDropShadowEffect
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = 'Copyright (c) 2018 Irony'
+__Version__ = 1.0
+
+
+class AnimationShadowEffect(QGraphicsDropShadowEffect):
+
+    def __init__(self, color, *args, **kwargs):
+        super(AnimationShadowEffect, self).__init__(*args, **kwargs)
+        self.setColor(color)
+        self.setOffset(0, 0)
+        self.setBlurRadius(0)
+        self._radius = 0
+        self.animation = QPropertyAnimation(self)
+        self.animation.setTargetObject(self)
+        self.animation.setDuration(2000)  # 一次循环时间
+        self.animation.setLoopCount(-1)  # 永久循环
+        self.animation.setPropertyName(b'radius')
+        # 插入线行值
+        self.animation.setKeyValueAt(0, 1)
+        self.animation.setKeyValueAt(0.5, 30)
+        self.animation.setKeyValueAt(1, 1)
+
+    def start(self):
+        self.animation.start()
+
+    @pyqtProperty(int)
+    def radius(self):
+        return self._radius
+
+    @radius.setter
+    def radius(self, r):
+        self._radius = r
+        self.setBlurRadius(r)
+

# 测试代码

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2018年9月25日
+@author: Irony
+@site: https://pyqt5.com, https://github.com/892768447
+@email: 892768447@qq.com
+@file: Test
+@description: 
+"""
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap
+from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit
+
+from AnimationShadowEffect import AnimationShadowEffect  # @UnresolvedImport
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = 'Copyright (c) 2018 Irony'
+__Version__ = 1.0
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+
+        # 绿色边框
+        labelGreen = QLabel(self, pixmap=QPixmap('1.jpg').scaled(100, 100))
+        layout.addWidget(labelGreen)
+        aniGreen = AnimationShadowEffect(Qt.darkGreen, labelGreen)
+        labelGreen.setGraphicsEffect(aniGreen)
+        aniGreen.start()
+
+        # 红色边框,圆形图片
+        labelRed = QLabel(self)
+        labelRed.setMinimumSize(100, 100)
+        labelRed.setMaximumSize(100, 100)
+        labelRed.setStyleSheet('border-image: url(1.jpg);border-radius: 50px;')
+        layout.addWidget(labelRed)
+        aniRed = AnimationShadowEffect(Qt.red, labelGreen)
+        labelRed.setGraphicsEffect(aniRed)
+        aniRed.start()
+
+        # 蓝色边框按钮
+        button = QPushButton('按钮', self)
+        aniButton = AnimationShadowEffect(Qt.blue, button)
+        layout.addWidget(button)
+        button.setGraphicsEffect(aniButton)
+        aniButton.start()
+
+        # 青色边框输入框
+        lineedit = QLineEdit(self)
+        aniEdit = AnimationShadowEffect(Qt.cyan, lineedit)
+        layout.addWidget(lineedit)
+        lineedit.setGraphicsEffect(aniEdit)
+        aniEdit.start()
+
+
+if __name__ == '__main__':
+    import sys
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
文章作者: Irony
文章链接: https://pyqt5.com/animateshadow.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/archives/2018/09/index.html b/archives/2018/09/index.html new file mode 100644 index 00000000..98ccdc22 --- /dev/null +++ b/archives/2018/09/index.html @@ -0,0 +1,276 @@ +2018年09月 | PyQt + + + + + + + + + + + +
文章总览 - 3
2018
PyQt5动画边框阴影
PyQt5动画边框阴影
PyQt5圆形图片
PyQt5圆形图片
异常捕获之cgitb模块
异常捕获之cgitb模块
\ No newline at end of file diff --git a/archives/2018/10/index.html b/archives/2018/10/index.html new file mode 100644 index 00000000..54c073f0 --- /dev/null +++ b/archives/2018/10/index.html @@ -0,0 +1,276 @@ +2018年10月 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2018/11/index.html b/archives/2018/11/index.html new file mode 100644 index 00000000..f6c19aa8 --- /dev/null +++ b/archives/2018/11/index.html @@ -0,0 +1,276 @@ +2018年11月 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2018/index.html b/archives/2018/index.html new file mode 100644 index 00000000..c22a3b74 --- /dev/null +++ b/archives/2018/index.html @@ -0,0 +1,276 @@ +2018 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2018/page/2/index.html b/archives/2018/page/2/index.html new file mode 100644 index 00000000..c54927c6 --- /dev/null +++ b/archives/2018/page/2/index.html @@ -0,0 +1,276 @@ +2018 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2019/01/index.html b/archives/2019/01/index.html new file mode 100644 index 00000000..134b87e8 --- /dev/null +++ b/archives/2019/01/index.html @@ -0,0 +1,276 @@ +2019年01月 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2019/02/index.html b/archives/2019/02/index.html new file mode 100644 index 00000000..85307bfd --- /dev/null +++ b/archives/2019/02/index.html @@ -0,0 +1,276 @@ +2019年02月 | PyQt + + + + + + + + + + + +
文章总览 - 1
2019
PyQtClient例子客户端
PyQtClient例子客户端
\ No newline at end of file diff --git a/archives/2019/03/index.html b/archives/2019/03/index.html new file mode 100644 index 00000000..1ada7f89 --- /dev/null +++ b/archives/2019/03/index.html @@ -0,0 +1,276 @@ +2019年03月 | PyQt + + + + + + + + + + + +
文章总览 - 1
2019
Python调用Java对Excel截图
Python调用Java对Excel截图
\ No newline at end of file diff --git a/archives/2019/04/index.html b/archives/2019/04/index.html new file mode 100644 index 00000000..ac20ffbf --- /dev/null +++ b/archives/2019/04/index.html @@ -0,0 +1,276 @@ +2019年04月 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2019/05/index.html b/archives/2019/05/index.html new file mode 100644 index 00000000..c9601a9e --- /dev/null +++ b/archives/2019/05/index.html @@ -0,0 +1,276 @@ +2019年05月 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2019/07/index.html b/archives/2019/07/index.html new file mode 100644 index 00000000..a2f1f05d --- /dev/null +++ b/archives/2019/07/index.html @@ -0,0 +1,276 @@ +2019年07月 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2019/08/index.html b/archives/2019/08/index.html new file mode 100644 index 00000000..f2e88c9f --- /dev/null +++ b/archives/2019/08/index.html @@ -0,0 +1,276 @@ +2019年08月 | PyQt + + + + + + + + + + + +
文章总览 - 1
2019
PyQt学习心得
PyQt学习心得
\ No newline at end of file diff --git a/archives/2019/12/index.html b/archives/2019/12/index.html new file mode 100644 index 00000000..ba282394 --- /dev/null +++ b/archives/2019/12/index.html @@ -0,0 +1,276 @@ +2019年12月 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2019/index.html b/archives/2019/index.html new file mode 100644 index 00000000..71bbccea --- /dev/null +++ b/archives/2019/index.html @@ -0,0 +1,276 @@ +2019 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2019/page/2/index.html b/archives/2019/page/2/index.html new file mode 100644 index 00000000..da4f8de0 --- /dev/null +++ b/archives/2019/page/2/index.html @@ -0,0 +1,276 @@ +2019 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2019/page/3/index.html b/archives/2019/page/3/index.html new file mode 100644 index 00000000..affe00ac --- /dev/null +++ b/archives/2019/page/3/index.html @@ -0,0 +1,276 @@ +2019 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/2023/10/index.html b/archives/2023/10/index.html new file mode 100644 index 00000000..7b22ae42 --- /dev/null +++ b/archives/2023/10/index.html @@ -0,0 +1,276 @@ +2023年10月 | PyQt + + + + + + + + + + + +
文章总览 - 1
2023
如何在Mac M1上快速安装PyQt5
如何在Mac M1上快速安装PyQt5
\ No newline at end of file diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 00000000..2ec8d2ec --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1,276 @@ +2023 | PyQt + + + + + + + + + + + +
文章总览 - 1
2023
如何在Mac M1上快速安装PyQt5
如何在Mac M1上快速安装PyQt5
\ No newline at end of file diff --git a/archives/2024/04/index.html b/archives/2024/04/index.html new file mode 100644 index 00000000..09969502 --- /dev/null +++ b/archives/2024/04/index.html @@ -0,0 +1,276 @@ +2024年04月 | PyQt + + + + + + + + + + + +
文章总览 - 1
2024
pytest-qt 测试模态窗体.
pytest-qt 测试模态窗体.
\ No newline at end of file diff --git a/archives/2024/index.html b/archives/2024/index.html new file mode 100644 index 00000000..c18b7851 --- /dev/null +++ b/archives/2024/index.html @@ -0,0 +1,276 @@ +2024 | PyQt + + + + + + + + + + + +
文章总览 - 1
2024
pytest-qt 测试模态窗体.
pytest-qt 测试模态窗体.
\ No newline at end of file diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 00000000..d2b7193a --- /dev/null +++ b/archives/index.html @@ -0,0 +1,276 @@ +归档 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 00000000..aa94434a --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,276 @@ +归档 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/page/3/index.html b/archives/page/3/index.html new file mode 100644 index 00000000..98b5dea6 --- /dev/null +++ b/archives/page/3/index.html @@ -0,0 +1,276 @@ +归档 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/page/4/index.html b/archives/page/4/index.html new file mode 100644 index 00000000..1c1f09c9 --- /dev/null +++ b/archives/page/4/index.html @@ -0,0 +1,276 @@ +归档 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/archives/page/5/index.html b/archives/page/5/index.html new file mode 100644 index 00000000..04edac15 --- /dev/null +++ b/archives/page/5/index.html @@ -0,0 +1,276 @@ +归档 | PyQt + + + + + + + + + + + +
文章总览 - 41
2018
异常捕获之cgitb模块
异常捕获之cgitb模块
\ No newline at end of file diff --git a/atom.xml b/atom.xml new file mode 100644 index 00000000..40e631d2 --- /dev/null +++ b/atom.xml @@ -0,0 +1,508 @@ + + + PyQt + + 个人学习经验分享 + + + + 2024-04-30T01:46:52.392Z + https://pyqt5.com/ + + + Irony + + + + Hexo + + + pytest-qt 测试模态窗体. + + https://pyqt5.com/pytest_qt_modal_625781186.html + 2024-04-30T01:46:52.392Z + 2024-04-30T01:46:52.392Z + + 步骤分别是 :

  1. 点击 开始扫描 弹出 选择路径窗口;

  2. 勾选路基;

3. 点击确定;

大概想测一下这个界面 :

image.png

步骤分别是 :

  1. 点击 开始扫描 弹出 选择路径窗口;

  2. 勾选路基;

3. 点击确定;

需要测试的函数 :

image.png

测试函数 :

image.png

可以发现断言失败 .

image.png

官方文档:测试模态窗体.

https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html

用的是官方的 monkeypatch 方式 .

大致意思就是替换 FileSelectPathDialog 类的 exec 函数.

]]>
+ + + <p>步骤分别是 :</p> +<ol> +<li> +<p>点击 开始扫描 弹出 选择路径窗口;</p> +</li> +<li> +<p>勾选路基;</p> +</li> +</ol> +<p>3. 点击确定;</p> + + + + + + + + +
+ + + 如何在Mac M1上快速安装PyQt5 + + https://pyqt5.com/macm1pyqt.html + 2023-10-07T06:08:06.000Z + 2024-04-30T01:46:52.392Z + + 由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。
M1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。

1. 安装 Python
python-3.9.13-macosx10.9.pkg

2. 勾选自定义同时只勾选安装 pip

step1.png

step1.png

3. 设置 pip 源

/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna

4. 安装 PyQt5

/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5

5. 测试

/Library/Frameworks/Python.framework/Versions/3.9/bin/python3

step3.png

📢📢📢

也可以直接安装 Miniconda

然后:conda install -c conda-forge pyqt

]]>
+ + + <p>由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。<br> +M1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。</p> + + + + + + + + + + + + +
+ + + python 判断屏幕等宽字符串的长度   + + https://pyqt5.com/equal_str_width_625781186.html + 2019-12-26T11:49:41.000Z + 2024-04-30T01:46:52.392Z + + 判断屏幕等宽字符串的长度?

判断屏幕等宽字符串的长度?

image.png

【新手】重庆 - 搬砖 - NoWait 22:41:50 @北京 - BUG 开发 - 黑择明 求指点
【专家】北京 - BUG 开发 - 黑择明 22:43:04 fontMetrics
【专家】 https://pyqt.site (892768447) 22:43:54 QFontMetrics
【专家】 https://pyqt.site (892768447) 22:44:09 通过 QLabel.font ().fontMetrics () 得到

【新手】重庆 - 搬砖 - NoWait 22:52:00
https://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size
image.png

【新手】重庆 - 搬砖 - NoWait 22:53:15 感觉和 fontMetrics 应该是差不多的

image.png


【专家】北京 - BUG 开发 - 黑择明 (996742224) 11:29:04
fm = QFontMetrics(QFont())
fm.width(“qweqwe”)

]]>
+ + + <p>判断屏幕等宽字符串的长度?</p> + + + + + + + + +
+ + + 修改pyuic代替pyside2-uic. + + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + 2019-12-26T11:49:41.000Z + 2024-04-30T01:46:52.396Z + + 修改 pyuic 代替 pyside2-uic

修改 pyuic 代替 pyside2-uic.

最近看到挺多人用 pyside2 的 uic 编译 ui 文件有问题 .
写个解决办法.

首先,
pip install qtpy ,
这个是兼容 pyqt5 和 pyside2 的,无缝转换 .

然后,
修改 pyqt5 的 uic ,

image.png

最后用 pyuic5 , 生成 Ui_XXX.py 文件 .

]]>
+ + + <p>修改 pyuic 代替 pyside2-uic</p> + + + + + + + + +
+ + + PyQt学习心得 + + https://pyqt5.com/studynotes.html + 2019-08-26T01:00:00.000Z + 2024-04-30T01:46:52.392Z + + 在学习 PyQt 的过程中由于资料的缺乏或者没有中文导致大多数人感叹资料太少,学习困难,又或者急于求进,赶鸭子上架的情况,此时有系统的学习方法很重要。每个人都需要有自己的学习方法,别人的学习方法并不一定适合自己但可以采纳一些。笔者在这里列举了一些当初自己自学的一些心得和方法,希望帮助大家建立一套自己的学习 PyQt 的方法,提高自身的学习能力。

# Python 基础

在学习和使用 PyQt 之前需要熟练使用 Python,经过对 QQ 群里经常提问的问题的分析,发现大部分人对 Python 中的基础知识掌握不牢固导致很多基础问题,如果要想更好的使用 Python 以及它的扩展必需要进行系统的学习。这里列举一下常用的知识点。

  1. 类         参考资料
  2. 类的继承
  3. 类的多继承
  4. 类方法重写     参考资料
  5. 类中的 super 函数  参考资料
  6. 函数调用 / 参数类型
  7. 对象调用 (参考第 1 点)

必须熟练掌握上面的知识点后入门 PyQt 才比较容易,如果初学者对上面的知识点还不是很了解,本文不适合继续往下阅读。

# 设计师

Qt 设计师除了方便快速设计一些简单的界面外,其实笔者觉得更大的作用在于帮助用户熟悉各类控件、属性、信号等

  1. 这里建议初学者不要急于求成,打开设计师新建一个 Widget 的窗口,比如

desiger_create

  1. 然后把左侧的所有控件挨个拖动到中间的窗口中,比如这里拖动一个 Push Button 按钮

desiger_drag

  1. 在设计师右下角的属性编辑器中列举了该控件的所有父类,意味着可以调用和重写父类的所有方法,建议初学者把这个属性编辑器的所有属性挨个调整看看效果,部分控件可能需要 Ctrl+R 预览界面才能看到,同时像 QListWidget,QTreeWidget,QTableWidget 等某些控件需要在控件上右键增加数据才可以

desiger_property
desiger_property2

  1. 两个控件之间简单的信号槽关联可以通过设计师快速的设置

desiger_signal
desiger_signal2

  1. 提高进阶的方法,当你需要手动写代码实现界面的时候,不妨把 UI 文件转出 PY 文件,看看是如何构造的(这里涉及到布局等知识见后文)

# 布局

Qt 界面提供了方便的 4 种基本布局,QVboxLayout,QHboxLayout,QFormLayout,QGridLayout,初学者需要数量掌握这 4 种布局外加 2 种拉伸器(占位挤压)

首先需要知道 Qt 界面的中控件的层级顺序以及 parent,parent 的作用既作为子控件的父元素也可以自动管理 Qt 的对象(具体可以搜索下关于 Qt parent 的资料)

  1. 在没有布局的情况下,在设计师中拖动摆放的控件是一层一层的叠加覆盖,此时每个添加的子控件的 parent 都是最外层的控件

desiger_stack

  1. 如果需要界面中的控件自动适应高度宽度,此时则需要使用 4 种布局来包裹里面的子控件,注意的是:布局不是控件不能设置高度宽度和样式等,是一个抽象的东西,就好比是一根橡皮筋包裹几个矩形的物品;布局也可以设置一些属性(在设计师属性编辑器中),比如设置两者直接的间距,设置距离上下左右的间距,设置比例等

desiger_layout

  1. 在没有布局或者有布局的时候。可以添加容器控件(QWidget,QFrame,QGroupBox,QScrollArea,QToolBox,QTabWidget,QStackedWidget,QMidArea,QDockWidget)这些容器可以放置子控件,从而循环嵌套。

# 例子

在 PyQt5.5 的时候自带了一个例子文件夹(后面的版本没有的话可以下载 PyQt5 源码,里面有个 examples 文件夹),想要熟练的掌握 PyQt 还需要从自带的例子中学习,必须要每个例子都运行一次然后看看这个例子实现了什么,这样才能记忆深刻。
同时很多开发者在 https://github.com/PyQt5/PyQt 分享了各类进阶例子,同时也欢迎大家共同完善该项目,提供更多更好的例子。另外也可以下载该项目的客户端 PyQtClient 软件,支持运行其中的例子

建议在更深入的学习 PyQt 之前多看看一些例子。

# 文档

接下来要说的就是 Qt 的 api 文档,官网文档,这里其实不要害怕是英文就不想看,觉得看不懂了,其实官网的文档还是比较简洁的,而且函数名也比较直观就能知道意思。也可以用谷歌浏览器打开右键翻译,基本上都能看懂。笔者前期写过一篇如何查阅 Qt 文档的文档可以阅读学习一番。

这里就拿 QWebEngineView 举一个例子,首先初学者在使用这个浏览器控件时候,会有诸多的问题比如:Cookie,拦截器等就不知道如何去调用函数来设置

  1. 首先打开官网文档 https://doc.qt.io/qt-5/qwebengineview.html,可以看到只有少量的函数可以调用,寻找一番并没有发现和 Cookie 相关的东西,这个时候就需要把重点放在有特俗返回值的函数上,比如:
QWebEngineHistory *          history() constQWebEnginePage *          page() constQWebEngineSettings *      settings() const

这三个函数返回了一个类实例,就意味着可以调用其中的方法。

  1. 点击 page () 打开 https://doc.qt.io/qt-5/qwebenginepage.html,发现没有 cookie 相关的东西,只有 QWebEngineProfile *profile () const 这个函数比较可疑。

  2. 点击 **profile ()** 打开 https://doc.qt.io/qt-5/qwebengineprofile.html,在浏览器中搜索 cookie 发现这个类中包含大量和 cookie 相关的东西,比如:**QWebEngineCookieStore *cookieStore ()`** 从名字上可以猜测大概意思为 cookie 储存

  3. 点击 **cookieStore ()** 打开 https://doc.qt.io/qt-5/qwebenginecookiestore.html,此时就会发现这个类里面包含了删除和设置 cookie 的方法。

  4. 但是找到了这些方法后,面对初学者又一个问题来了,该如何去用?根据上面 4 点整理一下,把他们当做简单的 Python 对象,方法和操作方法和 class 一样的。

self.webview = QWebEngineView()# 得到pagepage = self.webview.page()# 得到profileprofile = page.profile()# 得到cookieStorecookieStore = profile.cookieStore()# 清空cookiecookieStore.deleteAllCookies()# 用简短代码来表达就是cookieStore = self.webview.page().profile().cookieStore()cookieStore.deleteAllCookies()

# 异常调试

可能有时候由于粗心,或者调用了一些非法函数,参数错误等会导致程序出现一些异常,首先第一步复制最后一行的错误去百度或者谷歌搜索,大多时候能找到问题所在。其次如果搜索不到或者自己的异常可能是由于某个变量的值不对引起的,就需要在编辑器中打断点使用 DEBUG 模式调试变量值(如果不会可以采用麻烦一点的办法:用 print 打印出变量值)

遇到问题后首先需要自己多调试排查问题,不要一遇到问题就去问,自己多尝试一个一个排查直到找到问题所在并解决,这也是一种提高自身能力的地方。

# 检索资料

作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。信息检索是每个程序猿必备的能力之一,其好处在于可以更快更准确的在茫茫网络海洋中找到自己所需要的东西,这个过程需要长期不断积累和练习。

  1. 中文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt 拖拽
  2. 英文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt Drag Drop

# 片尾

好了,笔者基本上的学习过程就整理如上,这并不是说每个人都适合这样的方法,但至少笔者是这样一步一步走过来的。当你养成了一个学习、发现和解决问题的好习惯时就会慢慢得心应手。

]]>
+ + + <p>在学习 PyQt 的过程中由于资料的缺乏或者没有中文导致大多数人感叹资料太少,学习困难,又或者急于求进,赶鸭子上架的情况,此时有系统的学习方法很重要。每个人都需要有自己的学习方法,别人的学习方法并不一定适合自己但可以采纳一些。笔者在这里列举了一些当初自己自学的一些心得和方法,希望帮助大家建立一套自己的学习 PyQt 的方法,提高自身的学习能力。</p> + + + + + + + + +
+ + + python 状态机模块   + + https://pyqt5.com/python_statemachine_625781186.html + 2019-07-17T09:03:33.000Z + 2024-04-30T01:46:52.392Z + + 用状态来取代 if…else 判断。

GUI 涉及到挺多的状态改变,以前一直用 if…else 来判断,最近读了设计模式,发现有个状态模式,随后发现了状态机这个东西 .

python 的状态机模块挺多的,不过好像很多都不更新了.
推荐 2 个状态机模块,但是也没有太深入的使用经验,就跑跑例子,以后有更详细的 pyqt 例子再补上 .

1: pip install python-statemachine

官方例子 : https://github.com/fgmacedo/python-statemachine

2. pip install state_machine

官方例子 : https://github.com/jtushman/state_machine

1 的 最近一次更新在 6 个月以前,使用 类继承mixin 方式,不过有些地方不如 2 个人性化;

2 的设计更人性化一些,包括状态改变 beforeafter , 不过由于是装饰器实现的动态增加属性,有些地方编辑器智能提示可能就靠不上了.

两者实现实现方式不一样,有兴趣可以读读源码 .

  1. qt 内置状态机框架

https://blog.csdn.net/amnes1a/article/details/62418196

https://blog.csdn.net/dongfenghuojian/article/details/78187131

http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程)

]]>
+ + + <p>用状态来取代 if…else 判断。</p> + + + + + + + + + + +
+ + + 在pyqt中使用python全局钩子模块 + + https://pyqt5.com/pyqt5_hook_key_625781186.html + 2019-07-06T17:37:22.000Z + 2024-04-30T01:46:52.392Z + + 在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像 QQ 一样可以全局热键截图。这里介绍几个方法实现在 PyQt 中使用 Python 全局钩子模块实现全局热键功能。

  1. pyHook3

安装命令 : pip install pyhook3

https://blog.csdn.net/q871063970/article/details/86648386

似乎将 pyhook 支持 py3 版本的了?没有太多研究.

缺点:只支持 win 平台.

2. keyboard & mouse

安装命令: pip install keyboard mouse

from PyQt5 import  QtGui, QtWidgets, QtCorefrom PyQt5.QtCore import *from PyQt5.QtGui import *from PyQt5.QtWidgets import *import keyboardclass Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        self.testBtn = QPushButton(self)        layout.addWidget(self.testBtn)        keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey'))        keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc"))    def abc(self,a,b,c):        print(a,b,c)        if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

更详细例子 : pyqt 中使用 keyboard 全局热键

优点:跨平台;

缺点:模块名字取得太差,不容易被发现.

]]>
+ + + <p>在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像 QQ 一样可以全局热键截图。这里介绍几个方法实现在 PyQt 中使用 Python 全局钩子模块实现全局热键功能。</p> + + + + + + + + + + +
+ + + 像读文章一样读源码 + + https://pyqt5.com/read_open_source.html + 2019-07-06T17:37:22.000Z + 2024-04-30T01:46:52.392Z + + 使用 snoop, 像读文章一样读源码。

不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
image.png

没有目的地概览 , 不知不觉就追究到细节里面去了。

image.png

所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。

开始 :

  1. python DEBUG 模块介绍 :
     前段时间看过挺多文章提到 pysoonper 这个调试模块,有兴趣的可以百度一下.
    个人尝试了一下,篇幅过大的 DEBUG 不适合用 pysoonper , 因为没有缩进!
     这几天偶然遇到一个二次封装的模块 snoop, 完美地解决了这个问题.
  2. 操作步骤 :
  • 1 . 在 eric6.pymain() 函数上加 snoop 装饰器;
    image.png
  • 2 . 用 vscode 打开 eric6start_.log 文件 (8 层深度 log 文件 34W 行,pycharm 对大文件支持很差);
    log文件

发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。

  • 3 . vscode 配置 log 文件关键字高亮;
    安装高亮插件
    image.png
    配置高亮关键字
    image.png

callreturn 给加进去.

  • 4 . 增加阶段关键字;

eric6启动阶段

image.png

#000 是为了方便搜索 。
需要自己手动折叠 。
可以发现 每个 splash.showMessage() 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。


# ps: vscode 阅读 log 文件还是有一些不方便的地方,除了在 2. 中提到的,还有包括关闭文件再打开,折叠状态不会保留,有其他更好的方式 请留言告诉我,谢谢.

]]>
+ + + <p>使用 snoop, 像读文章一样读源码。</p> + + + + + + + + + + + + +
+ + + python 获取子进程print信息   + + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + 2019-05-24T06:39:44.000Z + 2024-04-30T01:46:52.392Z + + 在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。

问题所在:

image.png

如果模块都由自己开发, 正常操作

image.png

但是因为不能改,所以只能拦截:
代码:

pythonPath = self.pythonPath_cb.currentText()if suffix == "py":    # 首次    self.pyCommand = [pythonPath, path]    self.modifiedReloadPython(path)def modifiedReloadPython(self, path_):    os.chdir(os.path.dirname(path_))    # 子进程调用    self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)    # self.stdoutWorker.p = self.p    self.stdoutWorker = Worker(self.p)    self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append("PYDEBUG:\n" + x))    self.stdoutWorker.start()class Worker(QThread):    stdout_signal = pyqtSignal(str)    def __init__(self, p, parent=None):        super().__init__(parent)        self.p = p    def run(self):        while True:            QApplication.processEvents()            if self.p is not None:                line = self.p.stdout.readline()                # line = line.strip()                if line != b'':                    try:                        info = line.decode()                        self.stdout_signal.emit(info)                    except:                        self.stdout_signal.emit(repr(line))    
]]>
+ + + <p>在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。</p> + + + + + + + + + + + + +
+ + + QtWebkit和QWebEngineView与Javascript交互 + + https://pyqt5.com/qtwebjs.html + 2019-05-22T03:30:36.000Z + 2024-04-30T01:46:52.392Z + + 以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

# 说明

  1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
  2. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
  3. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
@pyqtSlot(str)def callFromJs(self, text):    QMessageBox.information(self, "提示", "来自js调用:{}".format(text))
  1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
// 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)Bridge.windowTitleChanged.connect({fun: function(title) {    showLog("标题被修改为:" + title);}}, "fun");// 绑定自定义的信号customSignalBridge.customSignal.connect({fun: function(text) {    showLog("收到自定义信号内容:" + text);}}, "fun");
  1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
new QWebChannel(qt.webChannelTransport,    function(channel) {        window.Bridge = channel.objects.Bridge;                // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)        Bridge.windowTitleChanged.connect(function(title) {            showLog("标题被修改为:" + title);        });                // 绑定自定义的信号customSignal        Bridge.customSignal.connect(function(text) {           showLog("收到自定义信号内容:" + text);        });    });

# 代码

QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

  1. 针对 QWebView 的核心实现
class WebView(QWebView):    customSignal = pyqtSignal(str)    def __init__(self, *args, **kwargs):        super(WebView, self).__init__(*args, **kwargs)        self.initSettings()        # 暴露接口对象        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)    def _exposeInterface(self):        """向Js暴露调用本地方法接口        """        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)    # 注意pyqtSlot用于把该函数暴露给js可以调用    @pyqtSlot(str)    def callFromJs(self, text):        QMessageBox.information(self, "提示", "来自js调用:{}".format(text))    def sendCustomSignal(self):        # 发送自定义信号        self.customSignal.emit('当前时间: ' + str(time()))
  1. 针对 QWebEngineView 的核心实现
class WebEngineView(QWebEngineView):    customSignal = pyqtSignal(str)    def __init__(self, *args, **kwargs):        super(WebEngineView, self).__init__(*args, **kwargs)        self.channel = QWebChannel(self)        # 把自身对象传递进去        self.channel.registerObject('Bridge', self)        # 设置交互接口        self.page().setWebChannel(self.channel)    # 注意pyqtSlot用于把该函数暴露给js可以调用    @pyqtSlot(str)    def callFromJs(self, text):        QMessageBox.information(self, "提示", "来自js调用:{}".format(text))    def sendCustomSignal(self):        # 发送自定义信号        self.customSignal.emit('当前时间: ' + str(time()))

# 效果图

JsSignals

]]>
+ + + <p>以前还是 <code>QWebView</code> 的时候和 <code>Javascript</code> 交互起来很方便,但是到了 Qt5.6 以后改用了 <code>QWebEngineView</code> ,并通过其提供的 <code>qwebchannel.js</code> 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 <code>QWebView</code> 和 <code>QWebEngineView</code> 与 Js 交互都做了一个示例。</p> + + + + + + + + + + + + + + +
+ + + PyQt5窗口翻转动画 + + https://pyqt5.com/flipwidgetanimation.html + 2019-05-15T14:48:00.000Z + 2024-04-30T01:46:52.392Z + + QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 QStackedWidget 包含两个控件做切换,同时单独使用一个窗口做动画绘制。

# 原理说明

  1. 用了两个 QLabel 来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
  2. 用了 QStackedWidget 来存放上面的两个界面 QLabel
  3. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口
  4. 通过 setWindowOpacity 控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
  5. 动画窗口 FlipWidget.py 主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画

# 核心实现

  1. 主要是在 paintEvent 方法中使用 QTransformQPainter 进行圆心变换以及 rotate 设置翻转角度
  2. 同时根据翻转的角度范围对图片进行切换和缩放
def paintEvent(self, event):    super(FlipWidget, self).paintEvent(event)    if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible():        painter = QPainter(self)        painter.setRenderHint(QPainter.Antialiasing, True)        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)        # 变换        transform = QTransform()        # 把圆心设置为矩形中心        transform.translate(self.width() / 2, self.height() / 2)        if self._angle >= -90 and self._angle <= 90:            # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程            painter.save()            # 设置翻转角度            transform.rotate(self._angle, Qt.YAxis)            painter.setTransform(transform)            # 缩放图片高度            width = self.image1.width() / 2            height = int(self.image1.height() *                         (1 - abs(self._angle / self.Scale) / 100))            image = self.image1.scaled(                self.image1.width(), height,                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)            painter.drawPixmap(                QPointF(-width, -height / 2), image)            painter.restore()        else:            # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程            painter.save()            if self._angle > 0:                angle = 180 + self._angle            else:                angle = self._angle - 180            # 设置翻转角度, 注意这里角度有差异            transform.rotate(angle, Qt.YAxis)            painter.setTransform(transform)            # 缩放图片高度            width = self.image2.width() / 2            height = int(self.image2.height() *                         (1 - ((360 - abs(angle)) / self.Scale / 100)))            image = self.image2.scaled(                self.image2.width(), height,                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)            painter.drawPixmap(                QPointF(-width, -height / 2), image)            painter.restore()

# 代码

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py

# 效果图

FlipWidgetAnimation

]]>
+ + + <p>QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 <code>QStackedWidget</code> 包含两个控件做切换,同时单独使用一个窗口做动画绘制。</p> + + + + + + + + + + + + +
+ + + PyQt属性动画(QPropertyAnimation) + + https://pyqt5.com/QPropertyAnimation.html + 2019-05-08T07:43:06.000Z + 2024-04-30T01:46:52.392Z + + QPropertyAnimation 继承自 QVariantAnimation ,其作为 Qt 的属性动画用于针对控件的属性或者继承自 QObject 的对象中定义的属性做修改,
简单来说就是基类是 QObject 且定义了属性变量,就可以用 QPropertyAnimation 来做属性动画。同时也可以通过 pyqtProperty 来增加自定义属性。

首先,通过构造函数 QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None) 创建一个对象,其中

  1. 第一个参数是动画作用的对象,也可以通过 setTargetObject 设置
  2. 第二个参数是属性名,在 py3 中类型是 bytes,也可以通过 setPropertyName 设置

# 函数

一些常见的设置函数

setPropertyName设置属性名
setTargetObject设置动画作用对象
setDuration设置动画持续时间(毫秒)
setStartValue设置开始值
setEndValue设置结束值
setEasingCurve设置动画曲线
setKeyValueAt插入线性值
setLoopCount设置循环次数(-1 为永久)

# 示例

比如这个例子:

  1. 修改控件的 geometry 大小
  2. 修改自定义属性
  3. 修改进度条的 value 值

QPropertyAnimation

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年5月8日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: @description: """from PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurvefrom PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\    QLabel, QProgressBar, QSpacerItem, QSizePolicy__Author__ = 'Irony'__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        self.resize(400, 400)        self._value = 0        self.button = QPushButton('属性动画测试', self)        self.button.clicked.connect(self.doStart)        self.button.setGeometry(0, 0, 80, 40)        self.buttonc = QPushButton('自定义属性 测试', self)        self.buttonc.clicked.connect(self.doStartCustom)        self.label = QLabel('', self)        self.progressbar = QProgressBar(self)        self.progressbar.setRange(0, 99)        layout = QVBoxLayout(self)        layout.addItem(QSpacerItem(            20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed))        layout.addWidget(self.buttonc)        layout.addWidget(self.label)        layout.addWidget(self.progressbar)        # 进度条动画        self.progressStart()    # 此处是自定义属性,并通过动画修改后,设置QLabel的值    @pyqtProperty(int)    def value(self):        return self._value    @value.setter    def value(self, v):        self._value = v        self.label.setText('当前值:{}'.format(v))    def doStart(self):        # 第一个参数是要执行的对象        animation = QPropertyAnimation(self.button, b'geometry', self)        animation.setDuration(2000)  # 持续时间        # 缓和曲线风格,加了曲线动画会很大程度影响        animation.setEasingCurve(QEasingCurve.OutBounce)        animation.setStartValue(QRect(0, 0, 40, 40))        animation.setEndValue(QRect(250, 250, 80, 80))        animation.start(animation.DeleteWhenStopped)    def doStartCustom(self):        # 自定义属性动画        # 由于定义的属性是在继承的QWidget, 所以第一个参数是self        # 第二个参数就是 value        animation = QPropertyAnimation(self, b'value', self)        animation.setDuration(2000)  # 持续时间        animation.setStartValue(0)        animation.setEndValue(100)        animation.start(animation.DeleteWhenStopped)    def progressStart(self):        # 进度条动画        # 这里 value是QProgressBar自带的属性,具体可以看文档        # https://doc.qt.io/qt-5/qprogressbar.html#properties        animation = QPropertyAnimation(self.progressbar, b'value', self)        animation.setDuration(2000)  # 持续时间        animation.setLoopCount(-1)        # 这里采用插入线性值,第一个参数的范围是(0-1)        # 第二个参数的范围是进度(最小值-最大值)        animation.setKeyValueAt(0, self.progressbar.minimum())        animation.setKeyValueAt(0.1, 10)        animation.setKeyValueAt(0.2, 30)        animation.setKeyValueAt(0.5, 60)        animation.setKeyValueAt(0.7, 80)        animation.setKeyValueAt(1, self.progressbar.maximum())        animation.start(animation.DeleteWhenStopped)if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())
]]>
+ + + <p><code>QPropertyAnimation</code> 继承自 <code>QVariantAnimation</code> ,其作为 Qt 的属性动画用于针对控件的属性或者继承自 <code>QObject</code> 的对象中定义的属性做修改,<br> +简单来说就是基类是 <code>QObject</code> 且定义了属性变量,就可以用 <code>QPropertyAnimation</code> 来做属性动画。同时也可以通过 <code>pyqtProperty</code> 来增加自定义属性。</p> + + + + + + + + + + +
+ + + 如何查阅Qt文档 + + https://pyqt5.com/viewapi.html + 2019-05-04T12:50:20.000Z + 2024-04-30T01:46:52.396Z + + 很多网友在问有没有 PyQt5 的文档之类的问题,在 PyQt4 的时候 PyQt 官网有了英文版的文档,随后有网友翻译成了中文。不过现在 PyQt5 官方的文档都指向了 C 的 Qt 文档,其实 C 的 Qt API 文档结构很清晰,翻阅很容易的,需要注意几点。

作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。

拿 Qt C++ 文档来说,官网地址是:https://doc.qt.io/qt-5/qtwidgets-module.html 这里面记录了所有控件的详细函数文档。

比如拿 输入框 QLineEdit 来说,怎么去查询它的用法和信号槽等资料?

https://doc.qt.io/qt-5/qlineedit.html

# 左侧目录

在文档左侧目录中有如下几个:

Properties - 控件里的属性(比如宽高等,通常需要当作函数调用)

Public Slots - 这个是控件自己的槽函数(当作普通函数就行)

Signals - 这个是输入框的包含的信号

Public Functions、Reimplemented Public Functions、Static Public Members、Protected Functions、Reimplemented Protected Functions - 这几个都是函数列表

howtoviewapi1

# 类说明

howtoviewapi2

这里有两个注意点

  1. 红色方框内的表示该控件(输入框)继承于 QWidget ,所以该控件(输入框)拥有父类的所有方法和信号,当当前文档找不到相关资料和函数时,可以去父类找找看。
  2. 紫色方框内表示列举所有的方法(包括父类)

# 函数列表

howtoviewapi3

这里列举的就是该控件(输入框)的函数,同理点击上面的紫色方框是查看所有方法,一般这里主要用来查询你需要的功能函数,Qt 的函数名比较容易理解,比如:只读 ReadOnly,选择文字:setSelection。

所以再查下这部分资料的时候建议在浏览器中 Ctrl + F 打开浏览器的搜索框,并输入英文关键词来检索你所需要的函数在哪里。

howtoviewapi8

# 槽函数

howtoviewapi4

这部分列举的是槽函数,其实在 PyQt 中槽函数可以当作普通的函数。普通的函数也可以作为槽函数,直接通过信号连接即可,注意方框所示,还有很多函数是在父类里面。

# 信号

howtoviewapi5

这部分列举了该控件(输入框)所定义的信号,主要还是看名字,大多都能知道是做什么的,比如:

  1. editingFinished - 编辑完成信号
  2. returnPressed - 回车键信号
  3. textChanged (const QString &text) - 内容改变信号

这里还有个问题就是参数问题,一般 & 后面的 text 作为参数传递到槽函数中

# 函数详细说明

当不明确这个函数是做什么的,可以点击该函数跳转到下面的说明,比如回车键信号 returnPressed

howtoviewapi6

如图上所示,用翻译插件翻译,大部分就明白了,如下:

howtoviewapi7

# 关于如何搜索资料

比如当你要搜索输入框内容改变事件,一般建议两种搜索,且搜索的时候用空格把关键词分开搜索,而且直接用控件名

  1. 中文搜索引擎:QLineEdit 内容 改变
  2. 英文搜索引擎:QLineEdit text change
]]>
+ + + <p>很多网友在问有没有 PyQt5 的文档之类的问题,在 PyQt4 的时候 PyQt 官网有了英文版的文档,随后有网友翻译成了中文。不过现在 PyQt5 官方的文档都指向了 C<ins> 的 Qt 文档,其实 C</ins> 的 Qt API 文档结构很清晰,翻阅很容易的,需要注意几点。</p> + + + + + + + + +
+ + + 推荐编辑器LiClipse + + https://pyqt5.com/suggesteditor.html + 2019-05-04T10:04:08.000Z + 2024-04-30T01:46:52.396Z + + 关于 Python 的开发编辑器有很多,每个人有每个人的喜好,经常看到很多在问什么编辑器好用,有人推荐 Sublime,有人推荐 Pycharm 等等,这里就不去比较其它编辑器的优缺点了,只谈谈关于 LiClipse 这个编辑器在初级使用阶段的智能提示功能等。开箱即用,支持多种语言,RST,Markdown 和 HTML 编辑器的 HTML 预览。

其实 LiClipse 这个编辑器就是以前的 PyDev 插件的独立版本,基于 Eclipse 编辑器开发,去掉了 Java 的相关开发功能,关于软件的详细说明可以去官网查看: http://www.liclipse.com/

编辑器只需要少量的配置,打开即可使用,快速自动 import,也可以根据需要安装自己所需的插件,比如 json、svn、主题插件等。个人推荐:适合刚入门的新手使用

由于新版的 PyQt 和 PyDev 去掉了详细的函数提示,所以 PyQt 的智能提示只有函数和返回值,并没有英文注释,但是以前的比如 PyQt4 的智能提示应该是有详细的英文注释提示。

# 界面预览

  1. 主界面
    editor1
  2. 鼠标悬停提示
    editor2
  3. 输入提示
    editor3
  4. Git 面板
    editor4
  5. 全局搜索(Ctrl + H)
    editor5
    editor6

# 自动导包

其实这个功能我是非常喜欢的,通过按下快捷键即可自动寻找包名导入,快捷键 Ctrl + Shift + O

editor_import

也可以在标红的代码上按下 Ctrl + F1 进行导入

editor_import2

# 配置

打开编辑器后首先要配置【Window -> Preferences】的就是 Python 的环境变量,可以同时添加多个 Python 版本

editor_env

# Tab 等设置

  1. Insert spaces for tabs tab 转空格
  2. Show line numbers 显示行号

editor_tab

# 模版

这个功能可以快速插入自己定义好的模版代码,比如 if __name__ == '__main__': 等等,比如我这里配置的创建文件的模版

editor_tpl

# 常用快捷键

格式化对齐Ctrl + Shift + F
自动导包Ctrl + Shift + O
快捷提示Alt + /
]]>
+ + + <p>关于 Python 的开发编辑器有很多,每个人有每个人的喜好,经常看到很多在问什么编辑器好用,有人推荐 Sublime,有人推荐 Pycharm 等等,这里就不去比较其它编辑器的优缺点了,只谈谈关于 LiClipse 这个编辑器在初级使用阶段的智能提示功能等。开箱即用,支持多种语言,RST,Markdown 和 HTML 编辑器的 HTML 预览。</p> + + + + + + + + +
+ + + 三种方式绑定信号槽 + + https://pyqt5.com/bindsignals.html + 2019-05-04T08:07:06.000Z + 2024-04-30T01:46:52.392Z + + 网上关于 PyQt5 的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在 PyQt5 中无法使用,这里归纳总结下已有的几种绑定信号槽的方式,
这几种方式各有各的优点和缺点。

# 方式一

这个方式是最开始接触设计师的时候知道的,主要是通过控件的 objectNameQtCore.QMetaObject.connectSlotsByName(Form) 提供的连接函数来自动完成注册,
比如带有按钮的界面 ui 文件转成 py 文件后会发现如下代码:

self.pushButton = QtWidgets.QPushButton(Form)self.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28))self.pushButton.setObjectName("pushButton")# 通过这里自动完成连接信号槽QtCore.QMetaObject.connectSlotsByName(Form)

此时只需要继承该 UI 文件类然后增加如下方法:

@pyqtSlot()def on_pushButton_clicked(self):    print('button clicked')

这里解释一下, @pyqtSlot() 装饰器把函数 on_pushButton_clicked 包装为一个槽函数,
QtCore.QMetaObject.connectSlotsByName(Form) 这句代码的意思就是自动去寻找满足的槽函数

注意:这里有个规范(on_xxxx_clicked),这里必须要满足 on_控件的objectName_控件的信号 这样下划线连接起来的函数名才能被识别,
比如按钮的点击: on_pushButton_clicked 、勾选框的选中: on_checkbox_toggled(self, checked)

# 方式二

这种方式则直接通过代码里调用控件的信号的 connect 方法来进行绑定,比如:

# 按钮点击函数def doClicked(self):    print(self.sender(), 'clicked')# 绑定点击信号self.pushButton.clicked.connect(self.doClicked)

注意: connect 的是函数名字self.sender() 这句代码是获取信号发送者(比如这里就是得到这个按钮对象),
用处在于有时候要循环创建一堆按钮

# 方式三

通过参数这种方式其实比较特殊,在 PyQt 中大部分存在,但是在 PySide 中则很少,原因是两者的封装方式不同。

同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如:

# 按钮点击函数def doClicked(self):    print(self.sender(), 'clicked')pushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40)

这里可以通过参数(信号名字) = 函数来绑定信号

同时也可以设置其它参数,比如
button.setMinimumHeight(40) 也可以像参数里那样设置 minimumHeight=40

]]>
+ + + <p>网上关于 PyQt5 的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在 PyQt5 中无法使用,这里归纳总结下已有的几种绑定信号槽的方式,<br> +这几种方式各有各的优点和缺点。</p> + + + + + + + + + + +
+ + + python 拷贝虚拟环境(一)   + + https://pyqt5.com/virtualenvpy_625781186.html + 2019-05-02T07:21:01.000Z + 2024-04-30T01:46:52.396Z + + 通常来说,刚开始使用 python 的时候都是把包装到全局路径,随着各个项目安装的包越来越多,之后每开始一个项目,pycharm 创建索引的时间都越来越漫长,所以不可避免得开始使用虚拟环境。
经过一番了解 ,虚拟环境的优点有这些:

  • 改善 pycharm 索引时间;
  • 各个项目的库不会冲突;
  • 理论上虚拟环境可以给同版本的操作系统使用 (未试验过);
  • pip freeze > requestment.txt 导出的依赖清晰;
  • 各个版本的 python 共存;

python 虚拟环境库除了自带的 venv , 还有三方库 virtualenv , 此外 在 virtualenv 基础上又开发了 virtualenvwrapper(virtualenvwrapper_win) 来管理

本文基于 virtualenvwrapper 创建的虚拟环境来讲解.

以下是收集的一些virtualenvwrapper配置教程:# linux平台https://www.cnblogs.com/netfoxman/p/5994697.html# window平台https://blog.csdn.net/shaququ/article/details/54292043   https://blog.csdn.net/iaau0908/article/details/54021518

虚拟环境创建多了我们就会发现,
有时候使用相同版本的环境,一些常用的库是需要重新安装的,
那么能不能创建一个基础环境,默认拥有这些库,然后在这个基础环境上继续安装三方库呢?

本文经过试验发现是可行的:

  1. 创建基础虚拟环境 mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径] . 例如 mkvirtualenv py34 -p c:\Python34\python.exe

  2. 切换到虚拟环境 workon py34 , 然后安装一下三方库,然后复制 py34 这个文件夹备份一下;

  3. 接着复制这个 py34 文件夹,把复制后的文件夹改名为我们需要需要的文件夹例如 new34

  4. 进入 new34文件夹 ,用任意编辑器全路径搜索 py34 (替换虚拟环境的路径)

  5. 删除 new34/Scripts 下的 pip.exe, pip3.exe, pip3.x.exe, easy_install.exe (因为安装路径硬编码到这里面了,改不了,需要重新安装)

  6. https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二,源码安装 setuptools 后再用 easy_install pip 安装 pip 后,完成;
    如果有问题,就继续按照方式一的源码安装 pip;

  7. new34 环境下 用 pip show 三方库 来看一些库的位置,确保正确.

]]>
+ + + <p>通常来说,刚开始使用 python 的时候都是把包装到全局路径,随着各个项目安装的包越来越多,之后每开始一个项目,pycharm 创建索引的时间都越来越漫长,所以不可避免得开始使用虚拟环境。<br> +经过一番了解 ,虚拟环境的优点有这些:</p> +<ul> +<li>改善 pycharm 索引时间;</li> +<li>各个项目的库不会冲突;</li> +<li>理论上虚拟环境可以给同版本的操作系统使用 (未试验过);</li> +<li>pip freeze &gt; requestment.txt 导出的依赖清晰;</li> +<li>各个版本的 python 共存;</li> +<li>…</li> +</ul> + + + + + + + + + + + + +
+ + + QRunnable线程池发信号 + + https://pyqt5.com/runnablesignal_625781186.html + 2019-04-30T07:58:09.000Z + 2024-04-30T01:46:52.392Z + + 因为只有继承 QObject 的类才能有信号和自定义信号,而 QRunnable 并不是继承自 QObject ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。

pools 是 QThreadPool 实例

# 看图说话

  1. runnablesignal1
  2. 定义一个全局信号类
    runnablesignal2
  3. 在 QRunnable 中发送
    runnablesignal3
]]>
+ + + <p>因为只有继承 <code>QObject</code> 的类才能有信号和自定义信号,而 <code>QRunnable</code> 并不是继承自 <code>QObject</code> ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。</p> + + + + + + + + + + + + +
+ + + 如何和设计师中查看ui转换的py代码 + + https://pyqt5.com/viewpyindesigner_625781186.html + 2019-04-30T05:11:09.000Z + 2024-04-30T01:46:52.396Z + + 通过 设计师  查看 ui 转换的 py 代码

当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。

后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。

再来后发现了官方文档,查一查函数就行了 .

但是有些 api 文档找起来麻烦,用设计师点几下就行了,然后把转换出来的代码拷贝一下就完事了.

可是需要单独把 ui 转为 py 文件,之后再删除这个文件也是很烦的一件事 .

好,话不多说,接下来手把手教你如何快速在 ui 中查看 py 代码 .

官方也考虑过这种情况,所以 设计师中 是有这个功能的,但是 qt 的是没问题的,pyqt 的毕竟是绑定过来的,所以正常来说 你点击之后会弹出一个找不到应用程序的提示 .

看到这个东西是不是很眼熟,我们用的命令 pyuic5 和这个东西应该是一样的 .

viewpyindesigner1

所以接下来,我们找找电脑上有没有这个东西

viewpyindesigner2

果然在 pyqt5-toos 文件夹下有这个东西,

我们根据第一张图的提示,把这个东西拷贝到相应的目录 (如果没有那个 bin 文件夹,手动创建),

viewpyindesigner3

好了,大功告成!

]]>
+ + + <p>通过 设计师  查看 ui 转换的 py 代码</p> +<p>当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。</p> +<p>后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。</p> + + + + + + + + + + + + +
+ + + PyQt5调整窗口显示边框 + + https://pyqt5.com/showframe.html + 2019-04-26T14:19:26.000Z + 2024-04-30T01:46:52.392Z + + windows 某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 windows 在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -> 调整 Windows 的外观和性能 -> 去掉勾选 拖动时显示窗口内容。

由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。

  1. 一种是自己绘制一个边框效果,放开鼠标时才操作真正的窗口。
  2. 二是替换窗口的处理过程函数 wndproc 处理 WM_NCLBUTTONDOWN 消息事件。

今天讲第二种方法:

  1. 需要了解 SystemParametersInfo  API 函数
  2. SPI_GETDRAGFULLWINDOWS :确定是否允许拖拉到最大窗口
  3. SPI_SETDRAGFULLWINDOWS :设置是否允许拖至最大窗口

效果就是这样的:

ShowFrameWhenDrag

正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。

# 代码

https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年4月23日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: ShowFrameWhenDrag@description: 调整窗口显示边框"""from ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\    c_ulonglong, WINFUNCTYPE, c_uintfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel__Author__ = 'Irony'__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0if sizeof(c_long) == sizeof(c_void_p):    WPARAM = c_ulong    LPARAM = c_longelif sizeof(c_longlong) == sizeof(c_void_p):    WPARAM = c_ulonglong    LPARAM = c_longlongWM_NCLBUTTONDOWN = 0x00a1GWL_WNDPROC = -4SPI_GETDRAGFULLWINDOWS = 38SPI_SETDRAGFULLWINDOWS = 37WNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM)try:    CallWindowProc = windll.user32.CallWindowProcW    SetWindowLong = windll.user32.SetWindowLongW    SystemParametersInfo = windll.user32.SystemParametersInfoWexcept:    CallWindowProc = windll.user32.CallWindowProcA    SetWindowLong = windll.user32.SetWindowLongA    SystemParametersInfo = windll.user32.SystemParametersInfoAdef GetDragFullwindows():    rv = c_int()    SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0)    return rv.valuedef SetDragFullwindows(value):    SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0)class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        layout.addWidget(QLabel('拖动或者调整窗口试试看'))        # 重点替换窗口处理过程        self._newwndproc = WNDPROC(self._wndproc)        self._oldwndproc = SetWindowLong(            int(self.winId()), GWL_WNDPROC, self._newwndproc)    def _wndproc(self, hwnd, msg, wparam, lparam):        if msg == WM_NCLBUTTONDOWN:            # 获取系统本身是否已经开启            isDragFullWindow = GetDragFullwindows()            if isDragFullWindow != 0:                # 开启虚线框                SetDragFullwindows(0)                # 系统本身处理                ret = CallWindowProc(                    self._oldwndproc, hwnd, msg, wparam, lparam)                # 关闭虚线框                SetDragFullwindows(1)                return ret        return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

# 片尾

替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。

]]>
+ + + <p>在 <code>windows</code> 某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 <code>windows</code> 在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -&gt; 调整 <code>Windows</code> 的外观和性能 -&gt; 去掉勾选 拖动时显示窗口内容。</p> + + + + + + + + + + +
+ + + PyQt5判断信号是否连接 + + https://pyqt5.com/issignalconnected.html + 2019-04-26T14:06:26.000Z + 2024-04-30T01:46:52.392Z + + PyQt 中某些情况下需要取消原来的信号连接,此时需要使用 disconnect 方法,但是在逻辑不严谨的情况下可能会导致多次调用 disconnect 方法而导致报错,当然可以通过 try except 来包裹代码。这里通过  isSignalConnected  来判断信号是否连接。

在 QOjbect 文档中这样写到:

static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged);if (isSignalConnected(valueChangedSignal)) {    QByteArray data;    data = get_the_value();       // expensive operation    emit valueChanged(data);}

通过直接传入信号就行了,但是这在 PyQt 中不可行。需要这么做

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年2月24日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: IsSignalConnected@description: 判断信号是否连接"""from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        self.button1 = QPushButton('已连接', self, clicked=self.doTest)        self.button2 = QPushButton('未连接', self)        self.retView = QTextBrowser(self)        layout.addWidget(self.button1)        layout.addWidget(self.button2)        layout.addWidget(self.retView)    def doTest(self):        self.retView.append("""        # button1 clicked 是否连接: %s        # button2 clicked 是否连接: %s        """ % (            self.isSignalConnected(self.button1, 'clicked()'),            self.isSignalConnected(self.button2, 'clicked()')        ))    def isSignalConnected(self, obj, name):        """判断信号是否连接        :param obj:        对象        :param name:       信号名,如 clicked()        """        index = obj.metaObject().indexOfMethod(name)        if index > -1:            method = obj.metaObject().method(index)            if method:                return obj.isSignalConnected(method)        return Falseif __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

# 效果图

IsSignalConnected

]]>
+ + + <p>在 <code>PyQt</code> 中某些情况下需要取消原来的信号连接,此时需要使用 <code>disconnect</code> 方法,但是在逻辑不严谨的情况下可能会导致多次调用 <code>disconnect</code> 方法而导致报错,当然可以通过 try except 来包裹代码。这里通过  <code>isSignalConnected</code>  来判断信号是否连接。</p> + + + + + + + + + + +
+ +
diff --git a/baidusitemap.xml b/baidusitemap.xml new file mode 100644 index 00000000..57b2f74e --- /dev/null +++ b/baidusitemap.xml @@ -0,0 +1,167 @@ + + + + https://pyqt5.com/suggesteditor.html + 2024-04-30 + + + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + 2024-04-30 + + + https://pyqt5.com/viewapi.html + 2024-04-30 + + + https://pyqt5.com/viewpyindesigner_625781186.html + 2024-04-30 + + + https://pyqt5.com/webviewnew.html + 2024-04-30 + + + https://pyqt5.com/virtualenvpy_625781186.html + 2024-04-30 + + + https://pyqt5.com/QPropertyAnimation.html + 2024-04-30 + + + https://pyqt5.com/animateshadow.html + 2024-04-30 + + + https://pyqt5.com/bindsignals.html + 2024-04-30 + + + https://pyqt5.com/calljava.html + 2024-04-30 + + + https://pyqt5.com/cgitb.html + 2024-04-30 + + + https://pyqt5.com/circleimage.html + 2024-04-30 + + + https://pyqt5.com/daemonthread.html + 2024-04-30 + + + https://pyqt5.com/datawidgetmapper_625781186.html + 2024-04-30 + + + https://pyqt5.com/equal_str_width_625781186.html + 2024-04-30 + + + https://pyqt5.com/ffmpeghls.html + 2024-04-30 + + + https://pyqt5.com/flipwidgetanimation.html + 2024-04-30 + + + https://pyqt5.com/followwindow.html + 2024-04-30 + + + https://pyqt5.com/issignalconnected.html + 2024-04-30 + + + https://pyqt5.com/jumpslider.html + 2024-04-30 + + + https://pyqt5.com/likehtmleffect.html + 2024-04-30 + + + https://pyqt5.com/macm1pyqt.html + 2024-04-30 + + + https://pyqt5.com/mselectmenu.html + 2024-04-30 + + + https://pyqt5.com/pageswitching.html + 2024-04-30 + + + https://pyqt5.com/processinclass_625781186.html + 2024-04-30 + + + https://pyqt5.com/pyqt5_hook_key_625781186.html + 2024-04-30 + + + https://pyqt5.com/pyqt5asyncio.html + 2024-04-30 + + + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + 2024-04-30 + + + https://pyqt5.com/pyqtclient.html + 2024-04-30 + + + https://pyqt5.com/pyqtclientmac.html + 2024-04-30 + + + https://pyqt5.com/pytest_qt_modal_625781186.html + 2024-04-30 + + + https://pyqt5.com/python_statemachine_625781186.html + 2024-04-30 + + + https://pyqt5.com/qtninepatch.html + 2024-04-30 + + + https://pyqt5.com/qtwebjs.html + 2024-04-30 + + + https://pyqt5.com/read_open_source.html + 2024-04-30 + + + https://pyqt5.com/rlatticeeffect.html + 2024-04-30 + + + https://pyqt5.com/runnablesignal_625781186.html + 2024-04-30 + + + https://pyqt5.com/shadowradius.html + 2024-04-30 + + + https://pyqt5.com/showframe.html + 2024-04-30 + + + https://pyqt5.com/speedgithub.html + 2024-04-30 + + + https://pyqt5.com/studynotes.html + 2024-04-30 + + \ No newline at end of file diff --git a/bindsignals.html b/bindsignals.html new file mode 100644 index 00000000..d1cdba8b --- /dev/null +++ b/bindsignals.html @@ -0,0 +1,314 @@ +三种方式绑定信号槽 | PyQt + + + + + + + + + + + + + + +

三种方式绑定信号槽

网上关于 PyQt5 的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在 PyQt5 中无法使用,这里归纳总结下已有的几种绑定信号槽的方式,
+这几种方式各有各的优点和缺点。

+ +

# 方式一

+

这个方式是最开始接触设计师的时候知道的,主要是通过控件的 objectNameQtCore.QMetaObject.connectSlotsByName(Form) 提供的连接函数来自动完成注册,
+比如带有按钮的界面 ui 文件转成 py 文件后会发现如下代码:

+
self.pushButton = QtWidgets.QPushButton(Form)
+self.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28))
+self.pushButton.setObjectName("pushButton")
+
+# 通过这里自动完成连接信号槽
+QtCore.QMetaObject.connectSlotsByName(Form)
+

此时只需要继承该 UI 文件类然后增加如下方法:

+

+@pyqtSlot()
+def on_pushButton_clicked(self):
+    print('button clicked')
+

这里解释一下, @pyqtSlot() 装饰器把函数 on_pushButton_clicked 包装为一个槽函数,
+而 QtCore.QMetaObject.connectSlotsByName(Form) 这句代码的意思就是自动去寻找满足的槽函数

+

注意:这里有个规范(on_xxxx_clicked),这里必须要满足 on_控件的objectName_控件的信号 这样下划线连接起来的函数名才能被识别,
+比如按钮的点击: on_pushButton_clicked 、勾选框的选中: on_checkbox_toggled(self, checked)

+

# 方式二

+

这种方式则直接通过代码里调用控件的信号的 connect 方法来进行绑定,比如:

+
# 按钮点击函数
+def doClicked(self):
+    print(self.sender(), 'clicked')
+
+# 绑定点击信号
+self.pushButton.clicked.connect(self.doClicked)
+

注意: connect 的是函数名字self.sender() 这句代码是获取信号发送者(比如这里就是得到这个按钮对象),
+用处在于有时候要循环创建一堆按钮

+

# 方式三

+

通过参数这种方式其实比较特殊,在 PyQt 中大部分存在,但是在 PySide 中则很少,原因是两者的封装方式不同。

+

同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如:

+

+# 按钮点击函数
+def doClicked(self):
+    print(self.sender(), 'clicked')
+
+pushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40)
+

这里可以通过参数(信号名字) = 函数来绑定信号

+

同时也可以设置其它参数,比如
+ button.setMinimumHeight(40) 也可以像参数里那样设置 minimumHeight=40

+
文章作者: Irony
文章链接: https://pyqt5.com/bindsignals.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/calljava.html b/calljava.html new file mode 100644 index 00000000..2cc67f1a --- /dev/null +++ b/calljava.html @@ -0,0 +1,372 @@ +Python调用Java对Excel截图 | PyQt + + + + + + + + + + + + + + +

Python调用Java对Excel截图

有的时候会遇到一些奇葩的需求,就是用 Excel 做报表,但是需要对里面的数据进行填充并生成报表图片,发送出去。这里记录用 python 调用 jar 包对 excel 文件进行公式计算和截图,数据填充可以用 xlrd 或者 openpyxl

+ +

利用 jpype 模块初始化 java 虚拟机加载 jar 包然后执行其中的功能。

+

# 代码

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2019年3月12日
+@author: Irony
+@site: https://pyqt5.com https://github.com/892768447
+@email: 892768447@qq.com
+@file: CallJava
+@description: 
+"""
+import os
+
+import jpype
+
+
+__Author__ = 'Irony'
+__Copyright__ = 'Copyright (c) 2019'
+
+
+def convertToImage():
+    Workbook = jpype.JClass('com.aspose.cells.Workbook')
+    ImageFormat = jpype.JClass('com.aspose.cells.ImageFormat')
+    ImageOrPrintOptions = jpype.JClass(
+        'com.aspose.cells.ImageOrPrintOptions')
+    SheetRender = jpype.JClass('com.aspose.cells.SheetRender')
+
+    book = Workbook(os.path.abspath('data/test.xlsx').replace('\\', '/'))
+    # 保存为html
+    book.save('data/index.html', 12)
+    # 保存为pdf
+    book.save('data/test.pdf')
+
+    # 截图
+    imgOptions = ImageOrPrintOptions()
+    # imgOptions.setQuality(100)
+    imgOptions.setOnePagePerSheet(True)
+
+    # 输出图片格式
+#     imgOptions.setImageFormat(ImageFormat.getJpeg())
+    imgOptions.setImageFormat(ImageFormat.getPng())
+
+    # 计算
+    CalculationOptions = jpype.JClass(
+        'com.aspose.cells.CalculationOptions')
+    opt = CalculationOptions()
+    # 对Sheet1中的公式进行计算
+    sheet = book.getWorksheets().get('Sheet1')
+    sheet.calculateFormula(opt, True)
+
+    # 设置区域
+    pageSetup = sheet.getPageSetup()
+    # 去掉边距
+    pageSetup.setBottomMargin(0.)
+    pageSetup.setLeftMargin(0.)
+    pageSetup.setRightMargin(0.)
+    pageSetup.setTopMargin(0.)
+    # 设置要截图的区域(对角线)
+    pageSetup.setPrintArea('A0:C2')
+    # Create a SheetRender object for the target sheet
+    sr = SheetRender(sheet, imgOptions)
+    for page in range(sr.getPageCount()):
+        # Generate an image for the worksheet
+        sr.toImage(
+            page, os.path.join('data', '%d.png' % (page + 1)))
+
+
+def test():
+    # emm这里不知道什么用绝对路径就报错
+    libs = '{};{}'.format(
+        'libs/bcprov-jdk16-146.jar',
+        'libs/aspose-cells-19.2.jar'
+    )
+    command = (jpype.getDefaultJVMPath(),
+                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',
+                   '-Djava.class.path={0}'.format(libs))
+    print(command)
+    jpype.startJVM(jpype.getDefaultJVMPath(),
+                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',
+                   '-Djava.class.path={0}'.format(libs)
+                   )
+    # 解决多线程问题
+    jpype.attachThreadToJVM()
+    # 对excel截图
+    convertToImage()
+    # 关闭虚拟机
+    jpype.shutdownJVM()
+    print('截图完成')
+
+
+if __name__ == '__main__':
+    test()
+

# 附件

+

调用 java 生成报表.7z

+

解压后进入 whls 文件夹安装对应版本的 jpype 包

+

# 效果图

+

calljava

+
文章作者: Irony
文章链接: https://pyqt5.com/calljava.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 00000000..b9cb7da0 --- /dev/null +++ b/categories/index.html @@ -0,0 +1,278 @@ +分类 | PyQt + + + + + + + + + + + + + +
\ No newline at end of file diff --git "a/categories/\344\276\213\345\255\220/index.html" "b/categories/\344\276\213\345\255\220/index.html" new file mode 100644 index 00000000..05dab9d7 --- /dev/null +++ "b/categories/\344\276\213\345\255\220/index.html" @@ -0,0 +1,443 @@ +分类: 例子 | PyQt + + + + + + + + + + + +
QtWebkit和QWebEngineView与Javascript交互
PyQt5窗口翻转动画
PyQt5无边框圆角阴影
PyQt5之图片轮播
PyQt5之QSlider滑动条点击定位
PyQt5仿网页鼠标移动点阵特效
QDataWidgetMapper 数据库绑定 QLineEdit控件
PyQt5显示.9格式的PNG图片
PyQt5菜单之多选功能
PyQt5结合Asyncio异步
\ No newline at end of file diff --git "a/categories/\344\276\213\345\255\220/page/2/index.html" "b/categories/\344\276\213\345\255\220/page/2/index.html" new file mode 100644 index 00000000..a8b6c129 --- /dev/null +++ "b/categories/\344\276\213\345\255\220/page/2/index.html" @@ -0,0 +1,362 @@ +分类: 例子 | PyQt + + + + + + + + + + + +
PyQt5仿网页图片鼠标移动特效
PyQt5窗口跟随其它窗口
PyQt5动画边框阴影
PyQt5圆形图片
\ No newline at end of file diff --git "a/categories/\346\225\231\347\250\213/index.html" "b/categories/\346\225\231\347\250\213/index.html" new file mode 100644 index 00000000..2c7569d0 --- /dev/null +++ "b/categories/\346\225\231\347\250\213/index.html" @@ -0,0 +1,426 @@ +分类: 教程 | PyQt + + + + + + + + + + + +
pytest-qt 测试模态窗体.
三种方式绑定信号槽
QRunnable线程池发信号
如何和设计师中查看ui转换的py代码
PyQt5调整窗口显示边框
PyQt5判断信号是否连接
在Mac上以正确的姿势使用PyQtClient看Demo
Python调用Java对Excel截图
PyQt5编译QWebView与QWebEngineView共存
\ No newline at end of file diff --git "a/categories/\347\254\224\350\256\260/index.html" "b/categories/\347\254\224\350\256\260/index.html" new file mode 100644 index 00000000..e3d667b3 --- /dev/null +++ "b/categories/\347\254\224\350\256\260/index.html" @@ -0,0 +1,437 @@ +分类: 笔记 | PyQt + + + + + + + + + + + +
如何在Mac M1上快速安装PyQt5
PyQt学习心得
PyQt属性动画(QPropertyAnimation)
如何查阅Qt文档
解决GitHub下载速度缓慢的问题
FFmpeg合成加密HLS记录
python 在类里使用进程池
多线程之守护线程和阻塞线程
异常捕获之cgitb模块
\ No newline at end of file diff --git "a/categories/\351\232\217\347\254\224/index.html" "b/categories/\351\232\217\347\254\224/index.html" new file mode 100644 index 00000000..59d804bc --- /dev/null +++ "b/categories/\351\232\217\347\254\224/index.html" @@ -0,0 +1,417 @@ +分类: 随笔 | PyQt + + + + + + + + + + + +
python 判断屏幕等宽字符串的长度  
修改pyuic代替pyside2-uic.
python 状态机模块  
在pyqt中使用python全局钩子模块
像读文章一样读源码
python 获取子进程print信息  
推荐编辑器LiClipse
python 拷贝虚拟环境(一)  
PyQtClient例子客户端
\ No newline at end of file diff --git "a/category/\344\276\213\345\255\220/feed.json" "b/category/\344\276\213\345\255\220/feed.json" new file mode 100644 index 00000000..8daf7b97 --- /dev/null +++ "b/category/\344\276\213\345\255\220/feed.json" @@ -0,0 +1,174 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"例子\" category", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/qtwebjs.html", + "url": "https://pyqt5.com/qtwebjs.html", + "title": "QtWebkit和QWebEngineView与Javascript交互", + "date_published": "2019-05-22T03:30:36.000Z", + "content_html": "

以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

\n\n

# 说明

\n
    \n
  1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
  2. \n
  3. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
  4. \n
  5. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
  6. \n
\n
@pyqtSlot(str)\ndef callFromJs(self, text):\n    QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))
\n
    \n
  1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
  2. \n
\n
// 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\nBridge.windowTitleChanged.connect({fun: function(title) {\n    showLog(\"标题被修改为:\" + title);\n}}, \"fun\");\n\n// 绑定自定义的信号customSignal\nBridge.customSignal.connect({fun: function(text) {\n    showLog(\"收到自定义信号内容:\" + text);\n}}, \"fun\");
\n
    \n
  1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
  2. \n
\n
new QWebChannel(qt.webChannelTransport,\n    function(channel) {\n        window.Bridge = channel.objects.Bridge;\n        \n        // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\n        Bridge.windowTitleChanged.connect(function(title) {\n            showLog(\"标题被修改为:\" + title);\n        });\n        \n        // 绑定自定义的信号customSignal\n        Bridge.customSignal.connect(function(text) {\n           showLog(\"收到自定义信号内容:\" + text);\n        });\n    }\n);
\n

# 代码

\n

QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

\n

QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

\n
    \n
  1. 针对 QWebView 的核心实现
  2. \n
\n
class WebView(QWebView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebView, self).__init__(*args, **kwargs)\n        self.initSettings()\n        # 暴露接口对象\n        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)\n\n    def _exposeInterface(self):\n        \"\"\"向Js暴露调用本地方法接口\n        \"\"\"\n        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
\n
    \n
  1. 针对 QWebEngineView 的核心实现
  2. \n
\n
class WebEngineView(QWebEngineView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebEngineView, self).__init__(*args, **kwargs)\n        self.channel = QWebChannel(self)\n        # 把自身对象传递进去\n        self.channel.registerObject('Bridge', self)\n        # 设置交互接口\n        self.page().setWebChannel(self.channel)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
\n

# 效果图

\n

\"JsSignals\"

\n", + "tags": [ + "PyQt", + "QWebView", + "QWebEngineView", + "浏览器" + ] + }, + { + "id": "https://pyqt5.com/flipwidgetanimation.html", + "url": "https://pyqt5.com/flipwidgetanimation.html", + "title": "PyQt5窗口翻转动画", + "date_published": "2019-05-15T14:48:00.000Z", + "content_html": "

QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 QStackedWidget 包含两个控件做切换,同时单独使用一个窗口做动画绘制。

\n\n

# 原理说明

\n
    \n
  1. 用了两个 QLabel 来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
  2. \n
  3. 用了 QStackedWidget 来存放上面的两个界面 QLabel
  4. \n
  5. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口
  6. \n
  7. 通过 setWindowOpacity 控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
  8. \n
  9. 动画窗口 FlipWidget.py 主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画
  10. \n
\n

# 核心实现

\n
    \n
  1. 主要是在 paintEvent 方法中使用 QTransformQPainter 进行圆心变换以及 rotate 设置翻转角度
  2. \n
  3. 同时根据翻转的角度范围对图片进行切换和缩放
  4. \n
\n
def paintEvent(self, event):\n    super(FlipWidget, self).paintEvent(event)\n\n    if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible():\n\n        painter = QPainter(self)\n        painter.setRenderHint(QPainter.Antialiasing, True)\n        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n        # 变换\n        transform = QTransform()\n        # 把圆心设置为矩形中心\n        transform.translate(self.width() / 2, self.height() / 2)\n\n        if self._angle >= -90 and self._angle <= 90:\n            # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程\n            painter.save()\n            # 设置翻转角度\n            transform.rotate(self._angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image1.width() / 2\n            height = int(self.image1.height() *\n                         (1 - abs(self._angle / self.Scale) / 100))\n            image = self.image1.scaled(\n                self.image1.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()\n        else:\n            # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程\n            painter.save()\n            if self._angle > 0:\n                angle = 180 + self._angle\n            else:\n                angle = self._angle - 180\n            # 设置翻转角度, 注意这里角度有差异\n            transform.rotate(angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image2.width() / 2\n            height = int(self.image2.height() *\n                         (1 - ((360 - abs(angle)) / self.Scale / 100)))\n            image = self.image2.scaled(\n                self.image2.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()
\n

# 代码

\n

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py

\n

# 效果图

\n

\"FlipWidgetAnimation\"

\n", + "tags": [ + "PyQt", + "动画", + "翻转" + ] + }, + { + "id": "https://pyqt5.com/shadowradius.html", + "url": "https://pyqt5.com/shadowradius.html", + "title": "PyQt5无边框圆角阴影", + "date_published": "2019-04-25T16:06:26.000Z", + "content_html": "

在做 PyQt 窗口开发中经常会遇到要做一些无边框不规则的窗口,可能还会带有阴影效果,这里演示做一个简单的无边框圆角的窗口,原理就在于背景窗口的透明和一层有色背景控件的叠加。

\n\n

# 原理说明

\n
    \n
  1. 黑色(方便说明)的 QDialog 或者 QWidget 作为全透明无边框窗口。
  2. \n
  3. 其中白色的 QWidget 才是主要显示圆角和阴影的窗口,用于承载其它控件的显示。
  4. \n
  5. 注意红色和紫色的方框内的层次。
  6. \n
  7. 另:如果要熟悉纯代码编写请看 FramelessDialog.py
  8. \n
\n

如图:

\n

\"FramelessDialog1\"

\n

# 代码

\n

https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月25日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: FramelessWidget\n@description: 无边框圆角带阴影窗口 \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect\nfrom frameless import Ui_Dialog\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019'\n\n\nclass Window(QDialog, Ui_Dialog):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.mPos = None\n        self.setupUi(self)\n        self.closeButton.clicked.connect(self.close)\n        # 重点\n        # 无边框\n        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)\n        # 背景透明(就是ui中黑色背景的那个控件)\n        self.setAttribute(Qt.WA_TranslucentBackground, True)\n\n        # 添加阴影\n        effect = QGraphicsDropShadowEffect(self)\n        effect.setBlurRadius(12)\n        effect.setOffset(0, 0)\n        effect.setColor(Qt.gray)\n        self.setGraphicsEffect(effect)\n\n    # 加上简单的移动功能\n\n    def mousePressEvent(self, event):\n        \"\"\"鼠标点击事件\"\"\"\n        if event.button() == Qt.LeftButton:\n            self.mPos = event.pos()\n        event.accept()\n\n    def mouseReleaseEvent(self, event):\n        '''鼠标弹起事件'''\n        self.mPos = None\n        event.accept()\n\n    def mouseMoveEvent(self, event):\n        if event.buttons() == Qt.LeftButton and self.mPos:\n            self.move(self.mapToGlobal(event.pos() - self.mPos))\n        event.accept()\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"FramelessDialog\"

\n

# 下载

\n

无边框圆角阴影.zip

\n", + "tags": [ + "PyQt", + "阴影", + "无边框", + "圆角" + ] + }, + { + "id": "https://pyqt5.com/pageswitching.html", + "url": "https://pyqt5.com/pageswitching.html", + "title": "PyQt5之图片轮播", + "date_published": "2018-11-24T13:45:06.000Z", + "content_html": "

之前看到了 QStackedWidget 做切换动画,让界面不那么生硬,于是参考了 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt 做了一个 QStackedWidget 的切换动画,然后利用 QStackedWidget 结合多个 QLabel 显示图片来做一个轮播效果。

\n

其实在写之前也在网上找了很多例子,参看过后发现大多例子都是利用到了 paintEvent 去绘制,这样其实还是比较麻烦,个人觉得更好的方式是使用 QPropertyAnimation 属性动画修改控件中 QLabel 图片控件的 pos 位置属性就可以达到移动效果了。

\n\n
    \n
  1. 比较核心的算法就是要计算当前页面和下一个页面的位置偏移量,比如:
  2. \n
\n
# 计算偏移量\noffsetX = self.frameRect().width()\noffsetY = self.frameRect().height()\nw_next.setGeometry(0, 0, offsetX, offsetY)\n\nif direction == self.BOTTOM2TOP:\n    offsetX = 0\n    offsetY = -offsetY\nelif direction == self.TOP2BOTTOM:\n    offsetX = 0\nelif direction == self.RIGHT2LEFT:\n    offsetX = -offsetX\n    offsetY = 0\nelif direction == self.LEFT2RIGHT:\n    offsetY = 0\n\n# 重新定位显示区域外部/旁边的下一个窗口小部件\npnext = w_next.pos()\npnow = w_now.pos()\nself._pnow = pnow\n\n# 移动到指定位置并显示\nw_next.move(pnext.x() - offsetX, pnext.y() - offsetY)\nw_next.show()\nw_next.raise_()
\n
    \n
  1. \n

    其次是对这两个页面增加关联 pos 属性的 QPropertyAnimation 动画,然后加入到并行动画组 QParallelAnimationGroup 中再启动即可。

    \n
  2. \n
  3. \n

    QStackedWidgetsetCurrentIndexsetCurrentWidget 这两个函数进行了覆盖重写达到及时手动调用这两个函数也会产生动画效果的目的。

    \n
  4. \n
\n

# 代码

\n

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/PageSwitching.py

\n

# 效果图

\n

\"PageSwitching\"

\n", + "tags": [ + "PyQt", + "动画", + "轮播" + ] + }, + { + "id": "https://pyqt5.com/jumpslider.html", + "url": "https://pyqt5.com/jumpslider.html", + "title": "PyQt5之QSlider滑动条点击定位", + "date_published": "2018-11-05T15:12:26.000Z", + "content_html": "

QSlider 在通常情况下支持鼠标点击可以任意拖动,或者鼠标点击则往鼠标点击的方向移动一小格,这种移动一小格通常情况下用起来很不方便,比如我要做一个播放器的播放进度条,肯定是点击某个位置就直接跳到该位置,为此需要对 QSlider 的鼠标事件 mousePressEvent 进行重写。

\n\n

# 实现方法

\n

一般的想法就是重写 mousePressEvent 后,得到鼠标点击的 x 和 y 点然后进行比例换算,再通过 setValue 来设置值,其实 QSliderstyle 里面是有一个 sliderValueFromPosition 方法来计算值的。直接调用这个方法即可。

\n
    \n
  1. 首先通过 QSlider.style().subControlRect 方法计算得到滑块的区域,当鼠标点击区域在此次时则交给系统自己处理(比如按住不放拖动)
  2. \n
  3. 通过 orientation 判断滑动条的方向(横竖)
  4. \n
  5. 通过 invertedAppearance 判断滑动条是否反向(左右、上下)
  6. \n
  7. 通过 QSlider.style().sliderValueFromPosition(最小值, 最大值, x或者y坐标, 宽度或者高度) 来计算得到值
  8. \n
  9. 最后通过 setValue 来设置值
  10. \n
\n

# 代码

\n

https://github.com/PyQt5/PyQt/blob/master/QSlider/ClickJumpSlider.py

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年11月5日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: JumpSlider\n@description: \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle, QWidget,\\\n    QFormLayout, QLabel\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass JumpSlider(QSlider):\n\n    def mousePressEvent(self, event):\n        # 获取上面的拉动块位置\n        option = QStyleOptionSlider()\n        self.initStyleOption(option)\n        rect = self.style().subControlRect(\n            QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)\n        if rect.contains(event.pos()):\n            # 如果鼠标点击的位置在滑块上则交给Qt自行处理\n            super(JumpSlider, self).mousePressEvent(event)\n            return\n        if self.orientation() == Qt.Horizontal:\n            # 横向,要考虑invertedAppearance是否反向显示的问题\n            self.setValue(self.style().sliderValueFromPosition(\n                self.minimum(), self.maximum(),\n                event.x() if not self.invertedAppearance() else (self.width(\n                ) - event.x()), self.width()))\n        else:\n            # 纵向\n            self.setValue(self.style().sliderValueFromPosition(\n                self.minimum(), self.maximum(),\n                (self.height() - event.y()) if not self.invertedAppearance(\n                ) else event.y(), self.height()))\n\n\nclass TestWindow(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(TestWindow, self).__init__(*args, **kwargs)\n        layout = QFormLayout(self)\n\n        self.label1 = QLabel('0', self)\n        layout.addRow(self.label1, JumpSlider(\n            Qt.Horizontal, valueChanged=lambda v: self.label1.setText(str(v))))\n\n        # 横向-反向显示\n        self.label2 = QLabel('0', self)\n        layout.addRow(self.label2, JumpSlider(\n            Qt.Horizontal, invertedAppearance=True,\n            valueChanged=lambda v: self.label2.setText(str(v))))\n\n        self.label3 = QLabel('0', self)\n        layout.addRow(self.label3, JumpSlider(\n            Qt.Vertical, minimumHeight=200, valueChanged=lambda v: self.label3.setText(str(v))))\n\n        # 纵向反向显示\n        self.label4 = QLabel('0', self)\n        layout.addRow(self.label4, JumpSlider(\n            Qt.Vertical, invertedAppearance=True,\n            minimumHeight=200, valueChanged=lambda v: self.label4.setText(str(v))))\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, '')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = TestWindow()\n    w.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"ClickJumpSlider\"

\n", + "tags": [ + "PyQt", + "滑动条" + ] + }, + { + "id": "https://pyqt5.com/rlatticeeffect.html", + "url": "https://pyqt5.com/rlatticeeffect.html", + "title": "PyQt5仿网页鼠标移动点阵特效", + "date_published": "2018-10-29T08:49:10.000Z", + "content_html": "

Orz,前段时间有个 zz 需求,就是要做一个类似网页上很多个多点连线、鼠标移动跟随的那种炫酷特效,然后花了点时间在网上找了 js 做的,刚开始打算是嵌入 QWebView 来显示网页,后来研究了下 js 的算法代码,遂改用 QWidgetpaintEvent 直接绘制。

\n\n

# 大概思路

\n
    \n
  1. 先根据窗口大小随机创建一些点
  2. \n
  3. 遍历这些点并找到与之相关联的点
  4. \n
  5. 在动画过程中绘制圆点和画两点之间的连线
  6. \n
  7. 属性动画 QPropertyAnimation 改变颜色的透明度
  8. \n
\n

# 题外

\n
    \n
  1. 这里没有仔细去研究 js 里的算法优化,在浏览器里嗖嗖的就生成了,在 py 里好慢…
  2. \n
  3. 尽量在 py 里优化了循环操作,也简单的做了个 cython 加速也才提高了 1s ? 1 倍?..
  4. \n
  5. 不要只是为了好看用这玩意儿,和网页的效果一样,占 CPU !!! 没有任何意义
  6. \n
  7. 如果有更好的优化算法请告知,3Q
  8. \n
  9. pyd 是 python3.4 生成的,删掉 pyd 也能运行
  10. \n
\n

# 代码

\n

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/RlatticeEffect.py

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年11月22日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\nfrom random import random\nfrom time import time\n\nfrom PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\\\n    Qt, QRectF, pyqtSignal\nfrom PyQt5.QtGui import QColor, QPainterPath, QPainter\nfrom PyQt5.QtWidgets import QWidget\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\ntry:\n    import pointtool  # @UnusedImport @UnresolvedImport\n    getDistance = pointtool.getDistance\n    findClose = pointtool.findClose\nexcept:\n    import math\n\n    def getDistance(p1, p2):\n        return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2)\n\n    def findClose(points):\n        plen = len(points)\n        for i in range(plen):\n            closest = [None, None, None, None, None]\n            p1 = points[i]\n            for j in range(plen):\n                p2 = points[j]\n                dte1 = getDistance(p1, p2)\n                if p1 != p2:\n                    placed = False\n                    for k in range(5):\n                        if not placed:\n                            if not closest[k]:\n                                closest[k] = p2\n                                placed = True\n                    for k in range(5):\n                        if not placed:\n                            if dte1 < getDistance(p1, closest[k]):\n                                closest[k] = p2\n                                placed = True\n            p1.closest = closest\n\n\nclass Target:\n\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n\nclass Point(QObject):\n\n    valueChanged = pyqtSignal()\n\n    def __init__(self, x, ox, y, oy, *args, **kwargs):\n        super(Point, self).__init__(*args, **kwargs)\n        self.__x = x\n        self._x = x\n        self.originX = ox\n        self._y = y\n        self.__y = y\n        self.originY = oy\n        # 5个闭合点\n        self.closest = [0, 0, 0, 0, 0]\n        # 圆半径\n        self.radius = 2 + random() * 2\n        # 连线颜色\n        self.lineColor = QColor(156, 217, 249)\n        # 圆颜色\n        self.circleColor = QColor(156, 217, 249)\n\n    def initAnimation(self):\n        # 属性动画\n        if not hasattr(self, 'xanimation'):\n            self.xanimation = QPropertyAnimation(\n                self, b'x', self, valueChanged=self.valueChanged.emit,\n                easingCurve=QEasingCurve.InOutSine)\n            self.yanimation = QPropertyAnimation(\n                self, b'y', self, valueChanged=self.valueChanged.emit,\n                easingCurve=QEasingCurve.InOutSine,\n                finished=self.updateAnimation)\n            self.updateAnimation()\n\n    def updateAnimation(self):\n        self.xanimation.stop()\n        self.yanimation.stop()\n        duration = (1 + random()) * 1000\n        self.xanimation.setDuration(duration)\n        self.yanimation.setDuration(duration)\n        self.xanimation.setStartValue(self.__x)\n        self.xanimation.setEndValue(self.originX - 50 + random() * 100)\n        self.yanimation.setStartValue(self.__y)\n        self.yanimation.setEndValue(self.originY - 50 + random() * 100)\n        self.xanimation.start()\n        self.yanimation.start()\n\n    @pyqtProperty(float)\n    def x(self):\n        return self._x\n\n    @x.setter\n    def x(self, x):\n        self._x = x\n\n    @pyqtProperty(float)\n    def y(self):\n        return self._y\n\n    @y.setter\n    def y(self, y):\n        self._y = y\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.setMouseTracking(True)\n        self.resize(800, 600)\n        self.points = []\n        self.target = Target(self.width() / 2, self.height() / 2)\n        self.initPoints()\n\n    def paintEvent(self, event):\n        super(Window, self).paintEvent(event)\n        painter = QPainter()\n        painter.begin(self)\n        painter.setRenderHint(QPainter.Antialiasing)\n        painter.fillRect(self.rect(), Qt.black)\n        self.animate(painter)\n        painter.end()\n\n    def mouseMoveEvent(self, event):\n        super(Window, self).mouseMoveEvent(event)\n        # 鼠标移动时更新xy坐标\n        self.target.x = event.x()\n        self.target.y = event.y()\n        self.update()\n\n    def initPoints(self):\n        t = time()\n        self.points.clear()\n        # 创建点\n        stepX = self.width() / 20\n        stepY = self.height() / 20\n        for x in range(0, self.width(), int(stepX)):\n            for y in range(0, self.height(), int(stepY)):\n                ox = x + random() * stepX\n                oy = y + random() * stepY\n                point = Point(ox, ox, oy, oy)\n                point.valueChanged.connect(self.update)\n                self.points.append(point)\n        print(time() - t)\n\n        t = time()\n        # 每个点寻找5个闭合点\n        findClose(self.points)\n        print(time() - t)\n\n    def animate(self, painter):\n        for p in self.points:\n            # 检测点的范围\n            value = abs(getDistance(self.target, p))\n            if value < 4000:\n                # 其实就是修改颜色透明度\n                p.lineColor.setAlphaF(0.3)\n                p.circleColor.setAlphaF(0.6)\n            elif value < 20000:\n                p.lineColor.setAlphaF(0.1)\n                p.circleColor.setAlphaF(0.3)\n            elif value < 40000:\n                p.lineColor.setAlphaF(0.02)\n                p.circleColor.setAlphaF(0.1)\n            else:\n                p.lineColor.setAlphaF(0)\n                p.circleColor.setAlphaF(0)\n\n            # 画线条\n            if p.lineColor.alpha():\n                for pc in p.closest:\n                    if not pc:\n                        continue\n                    path = QPainterPath()\n                    path.moveTo(p.x, p.y)\n                    path.lineTo(pc.x, pc.y)\n                    painter.save()\n                    painter.setPen(p.lineColor)\n                    painter.drawPath(path)\n                    painter.restore()\n\n            # 画圆\n            painter.save()\n            painter.setPen(Qt.NoPen)\n            painter.setBrush(p.circleColor)\n            painter.drawRoundedRect(QRectF(\n                p.x - p.radius, p.y - p.radius, 2 * p.radius, 2 * p.radius), p.radius, p.radius)\n            painter.restore()\n\n            # 开启动画\n            p.initAnimation()\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, '')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"RlatticeEffect\"

\n", + "tags": [ + "PyQt", + "动画", + "特效" + ] + }, + { + "id": "https://pyqt5.com/datawidgetmapper_625781186.html", + "url": "https://pyqt5.com/datawidgetmapper_625781186.html", + "title": "QDataWidgetMapper 数据库绑定 QLineEdit控件", + "date_published": "2018-10-29T08:17:59.000Z", + "content_html": "

qt 为操作数据库提供了一个 model+view 的模式,这样简单的出入库逻辑就不需要自己编写。

\n

QDataWidgetMapper 可以 将数据库的数据 映射到其他控件 。

\n

注意:表格里的数据修改 还没有提交到数据库,需要点击提交按钮才生效。

\n\n

https://github.com/PyQt5/PyQt/tree/master/Test/partner_625781186/16_sqlModel/01_mapper

\n

# 代码

\n
#-*- coding: utf-8 -*-\n\nfrom PyQt5 import  QtWidgets, QtGui, QtCore\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nfrom PyQt5.QtSql import *\n\nimport sys\n\nsys.path.append('./ui')\nfrom Ui_MainWindow import Ui_MainWindow\n\n\nclass MainWindow(QMainWindow, Ui_MainWindow):\n    def __init__(self, parent=None, *args):\n\n        super(MainWindow, self).__init__(parent,  *args)\n        self.setupUi(self)\n        self.resize(800,600)\n        \n        #===============================   db   ======================================#\n        # self.db = QSqlDatabase.addDatabase('QMYSQL')\n        # self.db.setHostName(\"127.0.0.1\")  # set address\n        # self.db.setUserName(\"root\")  # set user name\n        # self.db.setPassword('123456')  # set user pwd   \n        # self.db.setDatabaseName(\"database\")\n        \n        self.db = QSqlDatabase.addDatabase('QSQLITE')\n        self.db.setDatabaseName('./db/database.db')\n\n        #================================= codemodel =====================================#\n        # 实例化model\n        self.codeModel = QSqlRelationalTableModel()\n        # model设置表\n        self.initializeModel(self.codeModel, 'Mongo')\n        # 设置编辑策略\n        # self.codeModel.setEditStrategy(QSqlTableModel.OnFieldChange)\n        # !!! 这里要注意 , 只能用这个策略 , 才可以实现自动提交\n        self.codeModel.setEditStrategy(QSqlTableModel.OnManualSubmit)\n\n        self.codeView = self.createView(\"code_View\", self.codeModel)\n        self.verticalLayout.addWidget(self.codeView)  \n\n        #================================ initData ==================================#\n        # 数据映射\n        self.mapper = QDataWidgetMapper()\n        # 提交策略\n        self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)\n        # 映射的模型源\n        self.mapper.setModel(self.codeModel)\n        self.mapper.addMapping(self.l1,0)\n        self.mapper.addMapping(self.l2,1)\n        self.mapper.addMapping(self.l3,2)\n        self.mapper.addMapping(self.l4,3)\n        self.mapper.addMapping(self.l5,4)\n\n        self.mapper.toFirst()\n        \n        #================================ pushButton ==================================#\n        self.sub_btn.clicked.connect(self.mapper.submit)\n        self.sub_btn.clicked.connect(self.codeModel.submitAll)\n        self.pre_btn.clicked.connect(self.mapper.toPrevious)\n        self.next_btn.clicked.connect(self.mapper.toNext)\n        \n    def initializeModel(self, model, tablename):\n        '''重关联。'''\n        model.setTable(tablename)\n#        model.setEditStrategy(QSqlTableModel.OnRowChange)\n        model.select()\n        \n    def createView(self, title, model):\n        '''创建TableView视图'''\n        view =  QTableView()\n        view.setModel(model)\n        view.setWindowTitle(title)\n        #列宽设置\n        view.horizontalHeader().setSectionResizeMode(3)\n        #行高设置\n        view.verticalHeader().setSectionResizeMode(1)\n        #充满列宽\n        view.horizontalHeader().setStretchLastSection(True) \n#        view.verticalHeader().setVisible(False)#隐藏行标题\n        #标题左对齐\n        view.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)\n        #标题左对齐\n        view.verticalHeader().setDefaultAlignment(Qt.AlignLeft)\n        \n        return view  \n    \n\nif __name__ == \"__main__\":\n    import sys\n    \n    app = QApplication(sys.argv)\n    app.setStyle(QStyleFactory.create(\"Fusion\"))\n    ui = MainWindow()\n    ui.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"datawidgetmapper\"

\n", + "tags": [ + "PyQt", + "Model" + ] + }, + { + "id": "https://pyqt5.com/qtninepatch.html", + "url": "https://pyqt5.com/qtninepatch.html", + "title": "PyQt5显示.9格式的PNG图片", + "date_published": "2018-10-26T02:00:08.000Z", + "content_html": "

做过安卓开发的和使用过 QQ 的都知道 .9.png 这种图片格式,效果就如 QQ 的聊天气泡一样可以拉伸,这种格式的图片允许开发人员定义可扩展区域,当需要延伸图片以填充比图片本身更大区域时,可扩展区的内容被延展;允许开发人员定义内容显示区,用于显示文字或其他内容。目前在 Github 上有两个 C++ 版本的,在这里我把它们都用 Python 实现了一遍。另外一个我也为 PyQt 提供了编译好的 pyd 文件。

\n\n

# C++ 版本

\n

在 Github 开源库中搜索到两个 C++ 版本的

\n
    \n
  1. 一个是 NinePatchQt
  2. \n
  3. 一个是 QtNinePatch
  4. \n
\n

# PyQt5 版本

\n

这里也分为两个版本,都是基于上面的 C++ 源码翻译改写过来的,具体的例子见项目里面的测试代码吧。

\n
    \n
  1. QtNinePatch 是参考第一个源码编写,用法是在 paintEvent 中调用
  2. \n
  3. QtNinePatch2 是参考第二个源码编写,用法是 pixmap = QtNinePatch.createPixmapFromNinePatchImage(self.image, self.width(), self.height()) 直接得到一个处理好的 QPixmap 对象来使用
  4. \n
\n

# 说明

\n
    \n
  1. 建议优先使用 pyd 版本的(后续提供 Python3.4 3.5 3.6 3.7 编译好的 32 为库文件),也可以自行编译,编译步骤见下文。
  2. \n
  3. 其次可以使用纯 python 版本 2 的(个人觉得方便调用)
  4. \n
  5. 最后再考虑纯 python 版本 1 的吧
  6. \n
  7. 以上为个人意见,两个 C++ 版本的写法不一样,但是核心算法应该是类似的。
  8. \n
\n

# 自行编译

\n
    \n
  1. 首先要安装好 Qt、PyQt5、编译安装对应的 sip、对应的 VC++ 编译工具
  2. \n
  3. 用 Qt Creator 打开 pro 文件进行编译
  4. \n
  5. 进入源码中的 sip 文件夹修改 configure.py 文件
  6. \n
\n
# 这里是你的VC版本和对应的Qt目录中的文件夹\nconfig.platform = "win32-msvc2010"\nqt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010'
\n
    \n
  1. 最后执行 python configure.py 来编译
  2. \n
\n

# 下载

\n

https://github.com/PyQt5/PyQt/tree/master/QLabel

\n

# 效果图

\n

\"NinePatchImage\"

\n", + "tags": [ + "PyQt", + "图片", + "气泡", + ".9png" + ] + }, + { + "id": "https://pyqt5.com/mselectmenu.html", + "url": "https://pyqt5.com/mselectmenu.html", + "title": "PyQt5菜单之多选功能", + "date_published": "2018-10-25T01:53:34.000Z", + "content_html": "

有时候会遇到这种需求:在界面某个位置弹出一个菜单,其中里面的菜单项可以多选(类似配置选项),此时用 QMenu 会遇到点击一个菜单项就会自动关闭,当然可以通过其他方式实现该功能,不过这里就采用 QMenu 通过特殊的方式来实现该需求。

\n\n

# 需求

\n

要实现的效果:

\n
    \n
  1. 菜单 1
  2. \n
  3. 菜单 2
  4. \n
  5. 菜单 3
  6. \n
  7. 菜单 4
  8. \n
\n

点击菜单 1、2、3 可以多选不关闭菜单

\n

点击菜单 4 可以勾选,并且关闭菜单

\n

# 原理

\n
    \n
  1. 设置菜单项可勾选:通过 QAction.setCheckable(True) 方法实现
  2. \n
  3. 设置菜单不可关闭:通过覆盖 QMenu 的鼠标释放 mouseReleaseEvent 方法(可直接替换或者通过 installEventFilter 安装事件过滤器实现)
  4. \n
  5. 在菜单的鼠标释放事件中,当点击菜单项后是通过点击点坐标来查找是否有 QAction ,然后触发对应的 QAction
  6. \n
  7. 故在没有 QAction 的地方则直接交还给 QMenu 自行处理逻辑,在有 QAction 的地方可以根据自己的需求进行处理(如上所提)
  8. \n
\n

# 代码

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月24日\n@author: Irony\n@site: https://github.com/892768447\n@email: 892768447@qq.com\n@file: 菜单多选不关闭\n@description: \n\"\"\"\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QMenu,\\\n    QAction\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.labelInfo = QLabel(self)\n        self.button = QPushButton('带按钮的菜单', self)\n        layout.addWidget(self.labelInfo)\n        layout.addWidget(self.button)\n\n        # 添加菜单\n        self._initMenu()\n\n    def _initMenu(self):\n        # 创建菜单\n        self._menu = QMenu(self.button)\n        # 替换menu的鼠标释放事件达到选择性不关闭菜单\n        self._menu.mouseReleaseEvent = self._menu_mouseReleaseEvent\n        self._menu.addAction('菜单1', self._checkAction)\n        self._menu.addAction('菜单2', self._checkAction)\n        self._menu.addAction(\n            QAction('菜单3', self._menu, triggered=self._checkAction))\n        action = QAction('菜单4', self._menu, triggered=self._checkAction)\n        # 添加自定义的属性,判断该属性可以关闭菜单\n        action.setProperty('canHide', True)\n        self._menu.addAction(action)\n        for action in self._menu.actions():\n            # 循环设置可勾选\n            action.setCheckable(True)\n        self.button.setMenu(self._menu)\n\n    def _menu_mouseReleaseEvent(self, event):\n        action = self._menu.actionAt(event.pos())\n        if not action:\n            # 没有找到action就交给QMenu自己处理\n            return QMenu.mouseReleaseEvent(self._menu, event)\n        if action.property('canHide'):  # 如果有该属性则给菜单自己处理\n            return QMenu.mouseReleaseEvent(self._menu, event)\n        # 找到了QAction则只触发Action\n        action.activate(action.Trigger)\n\n    def _checkAction(self):\n        # 三个action都响应该函数\n        self.labelInfo.setText('\\n'.join(['{}\\t选中:{}'.format(\n            action.text(), action.isChecked()) for action in self._menu.actions()]))\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, 'text')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.resize(400, 400)\n    w.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"MultiSelect\"

\n", + "tags": [ + "PyQt", + "菜单" + ] + }, + { + "id": "https://pyqt5.com/pyqt5asyncio.html", + "url": "https://pyqt5.com/pyqt5asyncio.html", + "title": "PyQt5结合Asyncio异步", + "date_published": "2018-10-24T06:32:26.000Z", + "content_html": "

今天尝试了下 quamash 框架,该框架是一个 PyQt 的异步事件循环封装库,使用 Python3+ 的 asyncio 这个异步库。在看了该项目的内容后发现只有一个简单的进度条例子,故尝试用其来下载网络图片并显示。

\n\n

# 安装依赖

\n
    \n
  1. pip install quamash
  2. \n
  3. pip install aiohttp
  4. \n
  5. Python3.5+ 和 PyQt5
  6. \n
\n

这里使用 aiohttp 是因为它基于 asyncio 封装的网络操作库,常见的 getpost 等方法,不过它只支持 Python3.5 及以上的版本,主要是它使用了 async def 这样的语法。

\n

# 说明

\n
    \n
  1. 在创建 QApplication 后随即设置替换事件循环 loop
  2. \n
\n
app = QApplication(sys.argv)\nloop = QEventLoop(app)\nasyncio.set_event_loop(loop)\nw = Window()
\n
    \n
  1. 通过 asyncio.ensure_future(func(), loop=loop) 来执行某个异步函数
  2. \n
\n

# 流程

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
WindowinitSession(初始化 session)
下载按钮doDownload(执行_doDownload 方法)
session.get(下载 json 数据进行解析)
添加到界面_doDownloadImage(对单张图片进行下载)
\n

# 源码

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月24日\n@author: Irony\n@site: https://github.com/892768447\n@email: 892768447@qq.com\n@file: AsyncioUiClient\n@description: \n\"\"\"\nimport asyncio\n\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QMovie\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton,\\\n    QApplication, QListWidget, QListWidgetItem, QLabel, QMessageBox\nimport aiohttp\nfrom quamash import QEventLoop\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\nUrl = 'https://www.doutula.com/api/search?keyword=%E6%9C%80%E6%96%B0%E8%A1%A8%E6%83%85&mime=0&page={}'\nHeaders = {\n    ':authority': 'www.doutula.com',\n    ':method': 'GET',\n    ':scheme': 'https',\n    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',\n    'accept-language': 'zh-CN,zh;q=0.9',\n    'cache-control': 'max-age=0',\n    'dnt': '1',\n    'upgrade-insecure-requests': '1',\n    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6756.400 QQBrowser/10.2.2498.400'\n}\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.listWidget = QListWidget(self)\n        self.listWidget.setSpacing(2)  # item直接的间隔\n        # 隐藏横向滚动条\n        self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        # 让list 从左到右排列\n        self.listWidget.setFlow(self.listWidget.LeftToRight)\n        # 自动换行\n        self.listWidget.setWrapping(True)\n        self.listWidget.setResizeMode(self.listWidget.Adjust)\n\n        self.buttonMsg = QPushButton('弹出提示框', self, clicked=self.showMessage)\n        self.buttonDown = QPushButton('下载图片', self, clicked=self.doDownload)\n        layout.addWidget(self.listWidget)\n        layout.addWidget(self.buttonMsg)\n        layout.addWidget(self.buttonDown)\n        self.currentPage = 0\n        self.initSession()  # 其实没必要,session主要用在需要登录的网站。缓存cookie用\n\n    def initSession(self):\n        async def _initSession():\n            # 初始化session\n            self.session = aiohttp.ClientSession(loop=loop)\n            print(self.session)\n        asyncio.ensure_future(_initSession(), loop=loop)\n\n    async def _doDownloadImage(self, url):\n        # 下载图片并添加到界面\n        async with self.session.get(url) as resp:\n            data = await resp.read()\n            if not data:\n                print('下载失败: ', url)\n                return\n            path = os.path.join('tmp', os.path.basename(url))\n            with open(path, 'wb') as fp:\n                fp.write(data)\n            item = QListWidgetItem(url, self.listWidget)\n            image = QPixmap(path)\n            item.setSizeHint(image.size())\n            label = QLabel(self.listWidget)\n            label.setPixmap(image)\n            if path.endswith('.gif'):  # 可能是动态图\n                label.setMovie(QMovie(path))\n            self.listWidget.setItemWidget(item, label)\n            self.listWidget.scrollToBottom()\n\n    async def _doDownload(self):\n        # 下载工作\n        if self.currentPage == -1:\n            QMessageBox.information(self, '提示', '已经没有更多了')\n            return\n        self.currentPage += 1\n        url = Url.format(self.currentPage)\n        print('get url: ', url)\n        async with self.session.get(url, headers=Headers) as resp:\n            data = await resp.json()\n            if not data:\n                return\n            data = data.get('data', None)\n            if not data:\n                self.currentPage = -1\n                print('已经是最后一页了')\n                return\n            # 解析json并生成item添加到界面中\n            for entity in data.get('list', []):\n                url = entity.get('image_url', None)\n                if not url:\n                    continue\n                await self._doDownloadImage(url)  # 下载图片\n\n    def doDownload(self):\n        # 响应按钮点击调用\n        asyncio.ensure_future(self._doDownload(), loop=loop)\n\n    def showMessage(self):\n        # 显示对话框\n        app.aboutQt()\n\n    def closeEvent(self, event):\n        if not self.session.closed:\n            asyncio.ensure_future(self.session.close(), loop=loop)\n        super(Window, self).closeEvent(event)\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    import os\n    os.makedirs('tmp', exist_ok=True)\n    sys.excepthook = cgitb.enable(1, None, 5, 'text')\n    app = QApplication(sys.argv)\n    loop = QEventLoop(app)\n    asyncio.set_event_loop(loop)\n    w = Window()\n    w.show()\n    with loop:\n        loop.run_forever()
\n

# 效果图

\n

\"pyqt5asyncio\"

\n", + "tags": [ + "PyQt", + "Asyncio", + "异步" + ] + }, + { + "id": "https://pyqt5.com/likehtmleffect.html", + "url": "https://pyqt5.com/likehtmleffect.html", + "title": "PyQt5仿网页图片鼠标移动特效", + "date_published": "2018-10-23T09:57:03.000Z", + "content_html": "

em,就是类似于那种游戏官网首页的图片,鼠标放上去后来回移动,图片的前景和背景错位移动。

\n\n

# 原理分析

\n
    \n
  1. 2 张一样大小的透明图片,1 张作为背景,一张作为前景(比如说人物)。
  2. \n
  3. 当鼠标往左移动时,前景人物跟着往左移动,背景往右移动
  4. \n
  5. 计算好偏移量(见代码中)
  6. \n
\n

https://github.com/PyQt5/PyQt/blob/master/QLabel/ImageSlipped.py

\n

# 关键代码

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月18日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: ImageSlipped\n@description: \n\"\"\"\nfrom PyQt5.QtGui import QPixmap, QPainter\nfrom PyQt5.QtWidgets import QWidget\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass SlippedImgWidget(QWidget):\n\n    def __init__(self, bg, fg, *args, **kwargs):\n        super(SlippedImgWidget, self).__init__(*args, **kwargs)\n        # 开启鼠标跟踪\n        self.setMouseTracking(True)\n        # 背景\n        self.bgPixmap = QPixmap(bg)\n        # 前景\n        self.pePixmap = QPixmap(fg)\n        # 最小尺寸(背景右边和下方隐藏10个像素)\n        size = self.bgPixmap.size()\n        self.setMinimumSize(size.width() - 10, size.height() - 10)\n        self.setMaximumSize(size.width() - 10, size.height() - 10)\n        # 分成10份用于鼠标移动判断\n        self.stepX = size.width() / 10\n        self.stepY = size.height() / 10\n        # 偏移量\n        self._offsets = [-4, -4, -4, -4]  # 背景(-4,-4),前景(-4,-4)\n\n    def mouseMoveEvent(self, event):\n        super(SlippedImgWidget, self).mouseMoveEvent(event)\n        pos = event.pos()\n\n        # 偏移量\n        offsetX = 5 - int(pos.x() / self.stepX)\n        offsetY = 5 - int(pos.y() / self.stepY)\n        self._offsets[0] = offsetX\n        self._offsets[1] = offsetY\n        self._offsets[2] = offsetX\n        self._offsets[3] = offsetY\n        # 刷新\n        self.update()\n\n    def paintEvent(self, event):\n        super(SlippedImgWidget, self).paintEvent(event)\n        # 绘制图形\n        painter = QPainter(self)\n        painter.setRenderHint(QPainter.Antialiasing)\n        # 左上角偏移5个像素画背景图片\n        painter.drawPixmap(\n            -5 + self._offsets[0],\n            -5 + self._offsets[1], self.bgPixmap)\n        # 右下角偏移5个像素画前景图片\n        painter.drawPixmap(\n            self.width() - self.pePixmap.width() + 5 - self._offsets[2],\n            self.height() - self.pePixmap.height() + 5 - self._offsets[3],\n            self.pePixmap\n        )\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = SlippedImgWidget('images/bg.png', 'images/fg.png')\n    w.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"ImageSlipped\"

\n", + "tags": [ + "PyQt", + "特效" + ] + }, + { + "id": "https://pyqt5.com/followwindow.html", + "url": "https://pyqt5.com/followwindow.html", + "title": "PyQt5窗口跟随其它窗口", + "date_published": "2018-10-23T07:08:56.000Z", + "content_html": "

要实现 PyQt 窗口跟随其它外部的窗口,能想到两点办法,一个是 hook 系统事件得到目标窗口的位置和大小以及是否关闭等,二是通过循环检测窗口的位置来实现。

\n\n

# 基于 Windows 定时检测目标窗口

\n
    \n
  1. 利用 win32gui 模块获取目标窗口的句柄
  2. \n
  3. 通过句柄获取目标窗口的大小位置,并设置自己的位置
  4. \n
  5. 主要是检测时间,在 10 毫秒以下很流畅
  6. \n
  7. 窗口关闭是根据目标句柄无效来判断
  8. \n
\n

https://github.com/PyQt5/PyQt/blob/master/Demo/FollowWindow.py

\n

# 代码

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月22日\n@author: Irony\n@site: https://github.com/892768447\n@email: 892768447@qq.com\n@file: FollowWindow\n@description: \n\"\"\"\nimport os\n\nfrom PyQt5.QtCore import QTimer\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton\nimport win32gui\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        layout.addWidget(QPushButton('test', self))\n        self.tmpHwnd = None\n        # 启动定时器检测记事本的位置大小和是否关闭\n        self.checkTimer = QTimer(self, timeout=self.checkWindow)\n        self.checkTimer.start(10)  # 10毫秒比较流畅\n\n    def checkWindow(self):\n        # 查找\n        hwnd = win32gui.FindWindow('Notepad', None)\n        if self.tmpHwnd and not hwnd:\n            # 表示记事本关闭了\n            self.checkTimer.stop()\n            self.close()  # 关闭自己\n            return\n        if not hwnd:\n            return\n        self.tmpHwnd = hwnd\n        # 获取位置\n        rect = win32gui.GetWindowRect(hwnd)\n        print(rect)\n        self.move(rect[2], rect[1])\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    # 先检测是否已有记事本打开\n    hwnd = win32gui.FindWindow('Notepad', None)\n    print('hwnd', hwnd)\n    if not hwnd:\n        # 启动记事本\n        os.startfile('notepad')\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"FollowWindow\"

\n", + "tags": [ + "PyQt", + "窗口" + ] + }, + { + "id": "https://pyqt5.com/animateshadow.html", + "url": "https://pyqt5.com/animateshadow.html", + "title": "PyQt5动画边框阴影", + "date_published": "2018-09-25T15:38:12.000Z", + "content_html": "

为子控件增加动画阴影效果,结合 QGraphicsDropShadowEffectQPropertyAnimation 动态改变阴影半径达到效果,在旧版本的 Qt 中 QGraphicsDropShadowEffect 可能会有点问题(父控件会影响子控件)

\n\n

# 原理

\n

原理是利用 QGraphicsDropShadowEffect 添加边框阴影,然后使用动画不停改变阴影的模糊半径来达到效果,如图:

\n

\"ShadowEffect\"

\n

# 简单说明

\n
    \n
  1. 继承 QGraphicsDropShadowEffect 增加动态属性 radius
  2. \n
  3. 通过 setGraphicsEffect 方法设置控件的边框阴影
  4. \n
  5. 通过 QPropertyAnimation 属性动画不断改变 radius 的值并调用 setBlurRadius 更新半径值
  6. \n
\n

https://github.com/PyQt5/PyQt/blob/master/QGraphicsDropShadowEffect/ShadowEffect.py

\n

# 自定义类

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年9月25日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: AnimationShadowEffect\n@description: 边框动画阴影动画\n\"\"\"\nfrom PyQt5.QtCore import QPropertyAnimation, pyqtProperty\nfrom PyQt5.QtWidgets import QGraphicsDropShadowEffect\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\nclass AnimationShadowEffect(QGraphicsDropShadowEffect):\n\n    def __init__(self, color, *args, **kwargs):\n        super(AnimationShadowEffect, self).__init__(*args, **kwargs)\n        self.setColor(color)\n        self.setOffset(0, 0)\n        self.setBlurRadius(0)\n        self._radius = 0\n        self.animation = QPropertyAnimation(self)\n        self.animation.setTargetObject(self)\n        self.animation.setDuration(2000)  # 一次循环时间\n        self.animation.setLoopCount(-1)  # 永久循环\n        self.animation.setPropertyName(b'radius')\n        # 插入线行值\n        self.animation.setKeyValueAt(0, 1)\n        self.animation.setKeyValueAt(0.5, 30)\n        self.animation.setKeyValueAt(1, 1)\n\n    def start(self):\n        self.animation.start()\n\n    @pyqtProperty(int)\n    def radius(self):\n        return self._radius\n\n    @radius.setter\n    def radius(self, r):\n        self._radius = r\n        self.setBlurRadius(r)
\n

# 测试代码

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年9月25日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: Test\n@description: \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap\nfrom PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit\n\nfrom AnimationShadowEffect import AnimationShadowEffect  # @UnresolvedImport\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n\n        # 绿色边框\n        labelGreen = QLabel(self, pixmap=QPixmap('1.jpg').scaled(100, 100))\n        layout.addWidget(labelGreen)\n        aniGreen = AnimationShadowEffect(Qt.darkGreen, labelGreen)\n        labelGreen.setGraphicsEffect(aniGreen)\n        aniGreen.start()\n\n        # 红色边框,圆形图片\n        labelRed = QLabel(self)\n        labelRed.setMinimumSize(100, 100)\n        labelRed.setMaximumSize(100, 100)\n        labelRed.setStyleSheet('border-image: url(1.jpg);border-radius: 50px;')\n        layout.addWidget(labelRed)\n        aniRed = AnimationShadowEffect(Qt.red, labelGreen)\n        labelRed.setGraphicsEffect(aniRed)\n        aniRed.start()\n\n        # 蓝色边框按钮\n        button = QPushButton('按钮', self)\n        aniButton = AnimationShadowEffect(Qt.blue, button)\n        layout.addWidget(button)\n        button.setGraphicsEffect(aniButton)\n        aniButton.start()\n\n        # 青色边框输入框\n        lineedit = QLineEdit(self)\n        aniEdit = AnimationShadowEffect(Qt.cyan, lineedit)\n        layout.addWidget(lineedit)\n        lineedit.setGraphicsEffect(aniEdit)\n        aniEdit.start()\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
", + "tags": [ + "PyQt", + "动画", + "阴影" + ] + }, + { + "id": "https://pyqt5.com/circleimage.html", + "url": "https://pyqt5.com/circleimage.html", + "title": "PyQt5圆形图片", + "date_published": "2018-09-25T14:13:26.000Z", + "content_html": "

实现圆形图片的方法有很多,比如用遮罩(mask), 裁切等等。这里比较几种实现方式,选出个人认为最优的方案。

\n\n

https://github.com/PyQt5/PyQt/blob/master/QLabel/CircleImage.py

\n

# 采用 mask 方式

\n

具体参考 【Qt】QLabel 实现的圆形图像 - 米罗西 - 博客园

\n

# 画圆形遮盖(适合纯色背景)

\n

原理是在原图片上画一个 4 角有颜色,中间圆形镂空的图片。

\n

\"circleimage1\"

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n'''\nCreated on 2017年8月25日\n@author: Irony.\"[讽刺]\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@description: \n'''\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QPainter, QPainterPath\nfrom PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout\n\n\n__Author__ = \"By: Irony.\\\"[讽刺]\\nQQ: 892768447\\nEmail: 892768447@qq.com\"\n__Copyright__ = \"Copyright (c) 2017 Irony.\\\"[讽刺]\"\n__Version__ = \"Version 1.0\"\n\n\nclass Label(QLabel):\n\n    def __init__(self, *args, antialiasing=True, **kwargs):\n        super(Label, self).__init__(*args, **kwargs)\n        self.Antialiasing = antialiasing\n        self.setMaximumSize(200, 200)\n        self.setMinimumSize(200, 200)\n        self.radius = 100\n\n        #加载图片并缩放\n        self.image = QPixmap(\"head.jpg\").scaled(\n            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)\n\n        painter = QPainter(self.image)\n        if self.Antialiasing:\n            painter.setRenderHint(QPainter.Antialiasing, True)\n            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)\n            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n        path = QPainterPath()\n        path.addRoundedRect(\n            0, 0, self.width(), self.height(), self.radius, self.radius)\n        path.addRect(0,0,self.width(),self.height())\n        painter.setPen(Qt.NoPen)\n        painter.setBrush(Qt.green)\n        painter.drawPath(path)\n        self.setPixmap(self.image)\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n        layout.addWidget(Label(self))\n        layout.addWidget(Label(self, antialiasing=False))\n        self.setStyleSheet(\"background: black;\")\n\nif __name__ == \"__main__\":\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

# 使用 QPainter 的 setCompositionMode

\n

具体参考 Qt 圆形头像制作工具 抗锯齿 可缩放编辑

\n
//result_avatar_size 是我们最后生成的图片的长宽,可以是QSize(200, 200)的正圆\ndestination_image = QImage(result_avatar_size, QImage::Format_ARGB32_Premultiplied);\n//在黑色的正方形中间画一个透明的圆,作为头像遮罩\nQPainter painter(&destination_image);\npainter.setRenderHint(QPainter::Antialiasing);\n//全涂黑\npainter.fillRect(destination_image.rect(), QBrush(Qt::black, Qt::SolidPattern));\npainter.setCompositionMode(QPainter::CompositionMode_SourceOut);\npainter.setPen(Qt::NoPen);\npainter.setBrush(QBrush(Qt::transparent, Qt::SolidPattern));\n//画透明区域\npainter.drawEllipse(destination_image.rect());
\n

# 使用 QPainter 的切割方法(推荐)

\n

利用 QPainter.setClipPath 方法切割一个圆形的 QPainterPath

\n

\"circleimage2\"

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n'''\nCreated on 2017年8月25日\n@author: Irony.\"[讽刺]\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n'''\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QPainter, QPainterPath, QPen\nfrom PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout\n\n\n__Author__ = \"By: Irony.\\\"[讽刺]\\nQQ: 892768447\\nEmail: 892768447@qq.com\"\n__Copyright__ = \"Copyright (c) 2017 Irony.\\\"[讽刺]\"\n__Version__ = \"Version 1.0\"\n\n\nclass Label(QLabel):\n\n    def __init__(self, *args, antialiasing=True, **kwargs):\n        super(Label, self).__init__(*args, **kwargs)\n        self.Antialiasing = antialiasing\n        self.setMaximumSize(200, 200)\n        self.setMinimumSize(200, 200)\n        self.radius = 100\n\n        #####################核心实现#########################\n        self.target = QPixmap(self.size())  # 大小和控件一样\n        self.target.fill(Qt.transparent)  # 填充背景为透明\n\n        p = QPixmap(\"head.jpg\").scaled(  # 加载图片并缩放和控件一样大\n            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)\n\n        painter = QPainter(self.target)\n        if self.Antialiasing:\n            # 抗锯齿\n            painter.setRenderHint(QPainter.Antialiasing, True)\n            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)\n            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n#         painter.setPen(# 测试黑色圆圈\n#             QPen(Qt.black, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))\n        path = QPainterPath()\n        path.addRoundedRect(\n            0, 0, self.width(), self.height(), self.radius, self.radius)\n        #**** 切割为圆形 ****#\n        painter.setClipPath(path)\n#         painter.drawPath(path)  # 测试黑色圆圈\n\n        painter.drawPixmap(0, 0, p)\n        self.setPixmap(self.target)\n        #####################核心实现#########################\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n        layout.addWidget(Label(self))\n        layout.addWidget(Label(self, antialiasing=False))\n        self.setStyleSheet(\"background: black;\")\n\nif __name__ == \"__main__\":\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
", + "tags": [ + "PyQt", + "圆形", + "图片" + ] + } + ] +} \ No newline at end of file diff --git "a/category/\344\276\213\345\255\220/rss.xml" "b/category/\344\276\213\345\255\220/rss.xml" new file mode 100644 index 00000000..5a18bcfc --- /dev/null +++ "b/category/\344\276\213\345\255\220/rss.xml" @@ -0,0 +1,1758 @@ + + + + PyQt • Posts by "例子" category + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 22 May 2019 03:30:36 +0000 + Wed, 22 May 2019 03:30:36 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/qtwebjs.html + QtWebkit和QWebEngineView与Javascript交互 + https://pyqt5.com/qtwebjs.html + PyQt + QWebView + QWebEngineView + 浏览器 + Wed, 22 May 2019 03:30:36 +0000 + + + + https://pyqt5.com/flipwidgetanimation.html + PyQt5窗口翻转动画 + https://pyqt5.com/flipwidgetanimation.html + PyQt + 动画 + 翻转 + Wed, 15 May 2019 14:48:00 +0000 + + + + https://pyqt5.com/shadowradius.html + PyQt5无边框圆角阴影 + https://pyqt5.com/shadowradius.html + PyQt + 阴影 + 无边框 + 圆角 + Thu, 25 Apr 2019 16:06:26 +0000 + + + + https://pyqt5.com/pageswitching.html + PyQt5之图片轮播 + https://pyqt5.com/pageswitching.html + PyQt + 动画 + 轮播 + Sat, 24 Nov 2018 13:45:06 +0000 + + + + https://pyqt5.com/jumpslider.html + PyQt5之QSlider滑动条点击定位 + https://pyqt5.com/jumpslider.html + PyQt + 滑动条 + Mon, 05 Nov 2018 15:12:26 +0000 + + + + https://pyqt5.com/rlatticeeffect.html + PyQt5仿网页鼠标移动点阵特效 + https://pyqt5.com/rlatticeeffect.html + PyQt + 动画 + 特效 + Mon, 29 Oct 2018 08:49:10 +0000 + + + + https://pyqt5.com/datawidgetmapper_625781186.html + QDataWidgetMapper 数据库绑定 QLineEdit控件 + https://pyqt5.com/datawidgetmapper_625781186.html + PyQt + Model + Mon, 29 Oct 2018 08:17:59 +0000 + + + + https://pyqt5.com/qtninepatch.html + PyQt5显示.9格式的PNG图片 + https://pyqt5.com/qtninepatch.html + PyQt + 图片 + 气泡 + .9png + Fri, 26 Oct 2018 02:00:08 +0000 + + + + https://pyqt5.com/mselectmenu.html + PyQt5菜单之多选功能 + https://pyqt5.com/mselectmenu.html + PyQt + 菜单 + Thu, 25 Oct 2018 01:53:34 +0000 + + + + https://pyqt5.com/pyqt5asyncio.html + PyQt5结合Asyncio异步 + https://pyqt5.com/pyqt5asyncio.html + PyQt + Asyncio + 异步 + Wed, 24 Oct 2018 06:32:26 +0000 + + + + https://pyqt5.com/likehtmleffect.html + PyQt5仿网页图片鼠标移动特效 + https://pyqt5.com/likehtmleffect.html + PyQt + 特效 + Tue, 23 Oct 2018 09:57:03 +0000 + + + + https://pyqt5.com/followwindow.html + PyQt5窗口跟随其它窗口 + https://pyqt5.com/followwindow.html + PyQt + 窗口 + Tue, 23 Oct 2018 07:08:56 +0000 + + + + https://pyqt5.com/animateshadow.html + PyQt5动画边框阴影 + https://pyqt5.com/animateshadow.html + PyQt + 动画 + 阴影 + Tue, 25 Sep 2018 15:38:12 +0000 + + + + https://pyqt5.com/circleimage.html + PyQt5圆形图片 + https://pyqt5.com/circleimage.html + PyQt + 圆形 + 图片 + Tue, 25 Sep 2018 14:13:26 +0000 + + + + diff --git "a/category/\346\225\231\347\250\213/feed.json" "b/category/\346\225\231\347\250\213/feed.json" new file mode 100644 index 00000000..529da55a --- /dev/null +++ "b/category/\346\225\231\347\250\213/feed.json" @@ -0,0 +1,108 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"教程\" category", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pytest_qt_modal_625781186.html", + "url": "https://pyqt5.com/pytest_qt_modal_625781186.html", + "title": "pytest-qt 测试模态窗体.", + "date_published": "2024-04-30T01:46:52.392Z", + "content_html": "

步骤分别是 :

\n
    \n
  1. \n

    点击 开始扫描 弹出 选择路径窗口;

    \n
  2. \n
  3. \n

    勾选路基;

    \n
  4. \n
\n

3. 点击确定;

\n\n

大概想测一下这个界面 :

\n

\"image.png\"

\n

步骤分别是 :

\n
    \n
  1. \n

    点击 开始扫描 弹出 选择路径窗口;

    \n
  2. \n
  3. \n

    勾选路基;

    \n
  4. \n
\n

3. 点击确定;

\n

需要测试的函数 :

\n

\"image.png\"

\n

测试函数 :

\n

\"image.png\"

\n

可以发现断言失败 .

\n

\"image.png\"

\n

官方文档:测试模态窗体.

\n

https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html

\n

用的是官方的 monkeypatch 方式 .

\n

大致意思就是替换 FileSelectPathDialog 类的 exec 函数.

\n", + "tags": [ + "pytest-qt" + ] + }, + { + "id": "https://pyqt5.com/bindsignals.html", + "url": "https://pyqt5.com/bindsignals.html", + "title": "三种方式绑定信号槽", + "date_published": "2019-05-04T08:07:06.000Z", + "content_html": "

网上关于 PyQt5 的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在 PyQt5 中无法使用,这里归纳总结下已有的几种绑定信号槽的方式,
\n这几种方式各有各的优点和缺点。

\n\n

# 方式一

\n

这个方式是最开始接触设计师的时候知道的,主要是通过控件的 objectNameQtCore.QMetaObject.connectSlotsByName(Form) 提供的连接函数来自动完成注册,
\n比如带有按钮的界面 ui 文件转成 py 文件后会发现如下代码:

\n
self.pushButton = QtWidgets.QPushButton(Form)\nself.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28))\nself.pushButton.setObjectName(\"pushButton\")\n\n# 通过这里自动完成连接信号槽\nQtCore.QMetaObject.connectSlotsByName(Form)
\n

此时只需要继承该 UI 文件类然后增加如下方法:

\n
\n@pyqtSlot()\ndef on_pushButton_clicked(self):\n    print('button clicked')
\n

这里解释一下, @pyqtSlot() 装饰器把函数 on_pushButton_clicked 包装为一个槽函数,
\n而 QtCore.QMetaObject.connectSlotsByName(Form) 这句代码的意思就是自动去寻找满足的槽函数

\n

注意:这里有个规范(on_xxxx_clicked),这里必须要满足 on_控件的objectName_控件的信号 这样下划线连接起来的函数名才能被识别,
\n比如按钮的点击: on_pushButton_clicked 、勾选框的选中: on_checkbox_toggled(self, checked)

\n

# 方式二

\n

这种方式则直接通过代码里调用控件的信号的 connect 方法来进行绑定,比如:

\n
# 按钮点击函数\ndef doClicked(self):\n    print(self.sender(), 'clicked')\n\n# 绑定点击信号\nself.pushButton.clicked.connect(self.doClicked)
\n

注意: connect 的是函数名字self.sender() 这句代码是获取信号发送者(比如这里就是得到这个按钮对象),
\n用处在于有时候要循环创建一堆按钮

\n

# 方式三

\n

通过参数这种方式其实比较特殊,在 PyQt 中大部分存在,但是在 PySide 中则很少,原因是两者的封装方式不同。

\n

同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如:

\n
\n# 按钮点击函数\ndef doClicked(self):\n    print(self.sender(), 'clicked')\n\npushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40)
\n

这里可以通过参数(信号名字) = 函数来绑定信号

\n

同时也可以设置其它参数,比如
\n button.setMinimumHeight(40) 也可以像参数里那样设置 minimumHeight=40

\n", + "tags": [ + "PyQt", + "信号" + ] + }, + { + "id": "https://pyqt5.com/runnablesignal_625781186.html", + "url": "https://pyqt5.com/runnablesignal_625781186.html", + "title": "QRunnable线程池发信号", + "date_published": "2019-04-30T07:58:09.000Z", + "content_html": "

因为只有继承 QObject 的类才能有信号和自定义信号,而 QRunnable 并不是继承自 QObject ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。

\n\n

pools 是 QThreadPool 实例

\n

# 看图说话

\n
    \n
  1. \"runnablesignal1\"
  2. \n
  3. 定义一个全局信号类
    \n\"runnablesignal2\"
  4. \n
  5. 在 QRunnable 中发送
    \n\"runnablesignal3\"
  6. \n
\n", + "tags": [ + "PyQt", + "信号", + "线程" + ] + }, + { + "id": "https://pyqt5.com/viewpyindesigner_625781186.html", + "url": "https://pyqt5.com/viewpyindesigner_625781186.html", + "title": "如何和设计师中查看ui转换的py代码", + "date_published": "2019-04-30T05:11:09.000Z", + "content_html": "

通过 设计师  查看 ui 转换的 py 代码

\n

当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。

\n

后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。

\n\n

再来后发现了官方文档,查一查函数就行了 .

\n

但是有些 api 文档找起来麻烦,用设计师点几下就行了,然后把转换出来的代码拷贝一下就完事了.

\n

可是需要单独把 ui 转为 py 文件,之后再删除这个文件也是很烦的一件事 .

\n

好,话不多说,接下来手把手教你如何快速在 ui 中查看 py 代码 .

\n

官方也考虑过这种情况,所以 设计师中 是有这个功能的,但是 qt 的是没问题的,pyqt 的毕竟是绑定过来的,所以正常来说 你点击之后会弹出一个找不到应用程序的提示 .

\n

看到这个东西是不是很眼熟,我们用的命令 pyuic5 和这个东西应该是一样的 .

\n

\"viewpyindesigner1\"

\n

所以接下来,我们找找电脑上有没有这个东西

\n

\"viewpyindesigner2\"

\n

果然在 pyqt5-toos 文件夹下有这个东西,

\n

我们根据第一张图的提示,把这个东西拷贝到相应的目录 (如果没有那个 bin 文件夹,手动创建),

\n

\"viewpyindesigner3\"

\n

好了,大功告成!

\n", + "tags": [ + "PyQt", + "Designer", + "设计师" + ] + }, + { + "id": "https://pyqt5.com/showframe.html", + "url": "https://pyqt5.com/showframe.html", + "title": "PyQt5调整窗口显示边框", + "date_published": "2019-04-26T14:19:26.000Z", + "content_html": "

windows 某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 windows 在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -> 调整 Windows 的外观和性能 -> 去掉勾选 拖动时显示窗口内容。

\n\n

由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。

\n
    \n
  1. 一种是自己绘制一个边框效果,放开鼠标时才操作真正的窗口。
  2. \n
  3. 二是替换窗口的处理过程函数 wndproc 处理 WM_NCLBUTTONDOWN 消息事件。
  4. \n
\n

今天讲第二种方法:

\n
    \n
  1. 需要了解 SystemParametersInfo  API 函数
  2. \n
  3. SPI_GETDRAGFULLWINDOWS :确定是否允许拖拉到最大窗口
  4. \n
  5. SPI_SETDRAGFULLWINDOWS :设置是否允许拖至最大窗口
  6. \n
\n

效果就是这样的:

\n

\"ShowFrameWhenDrag\"

\n

正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。

\n

# 代码

\n

https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月23日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: ShowFrameWhenDrag\n@description: 调整窗口显示边框\n\"\"\"\nfrom ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\\\n    c_ulonglong, WINFUNCTYPE, c_uint\n\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\nif sizeof(c_long) == sizeof(c_void_p):\n    WPARAM = c_ulong\n    LPARAM = c_long\nelif sizeof(c_longlong) == sizeof(c_void_p):\n    WPARAM = c_ulonglong\n    LPARAM = c_longlong\n\nWM_NCLBUTTONDOWN = 0x00a1\nGWL_WNDPROC = -4\nSPI_GETDRAGFULLWINDOWS = 38\nSPI_SETDRAGFULLWINDOWS = 37\nWNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM)\n\ntry:\n    CallWindowProc = windll.user32.CallWindowProcW\n    SetWindowLong = windll.user32.SetWindowLongW\n    SystemParametersInfo = windll.user32.SystemParametersInfoW\nexcept:\n    CallWindowProc = windll.user32.CallWindowProcA\n    SetWindowLong = windll.user32.SetWindowLongA\n    SystemParametersInfo = windll.user32.SystemParametersInfoA\n\n\ndef GetDragFullwindows():\n    rv = c_int()\n    SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0)\n    return rv.value\n\n\ndef SetDragFullwindows(value):\n    SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0)\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        layout.addWidget(QLabel('拖动或者调整窗口试试看'))\n\n        # 重点替换窗口处理过程\n        self._newwndproc = WNDPROC(self._wndproc)\n        self._oldwndproc = SetWindowLong(\n            int(self.winId()), GWL_WNDPROC, self._newwndproc)\n\n    def _wndproc(self, hwnd, msg, wparam, lparam):\n        if msg == WM_NCLBUTTONDOWN:\n            # 获取系统本身是否已经开启\n            isDragFullWindow = GetDragFullwindows()\n            if isDragFullWindow != 0:\n                # 开启虚线框\n                SetDragFullwindows(0)\n                # 系统本身处理\n                ret = CallWindowProc(\n                    self._oldwndproc, hwnd, msg, wparam, lparam)\n                # 关闭虚线框\n                SetDragFullwindows(1)\n                return ret\n        return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

# 片尾

\n

替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。

\n", + "tags": [ + "PyQt", + "边框" + ] + }, + { + "id": "https://pyqt5.com/issignalconnected.html", + "url": "https://pyqt5.com/issignalconnected.html", + "title": "PyQt5判断信号是否连接", + "date_published": "2019-04-26T14:06:26.000Z", + "content_html": "

PyQt 中某些情况下需要取消原来的信号连接,此时需要使用 disconnect 方法,但是在逻辑不严谨的情况下可能会导致多次调用 disconnect 方法而导致报错,当然可以通过 try except 来包裹代码。这里通过  isSignalConnected  来判断信号是否连接。

\n\n

在 QOjbect 文档中这样写到:

\n
static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged);\nif (isSignalConnected(valueChangedSignal)) {\n    QByteArray data;\n    data = get_the_value();       // expensive operation\n    emit valueChanged(data);\n}
\n

通过直接传入信号就行了,但是这在 PyQt 中不可行。需要这么做

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年2月24日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: IsSignalConnected\n@description: 判断信号是否连接\n\"\"\"\n\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.button1 = QPushButton('已连接', self, clicked=self.doTest)\n        self.button2 = QPushButton('未连接', self)\n        self.retView = QTextBrowser(self)\n        layout.addWidget(self.button1)\n        layout.addWidget(self.button2)\n        layout.addWidget(self.retView)\n\n    def doTest(self):\n        self.retView.append(\"\"\"\n        # button1 clicked 是否连接: %s\n        # button2 clicked 是否连接: %s\n        \"\"\" % (\n            self.isSignalConnected(self.button1, 'clicked()'),\n            self.isSignalConnected(self.button2, 'clicked()')\n        ))\n\n    def isSignalConnected(self, obj, name):\n        \"\"\"判断信号是否连接\n        :param obj:        对象\n        :param name:       信号名,如 clicked()\n        \"\"\"\n        index = obj.metaObject().indexOfMethod(name)\n        if index > -1:\n            method = obj.metaObject().method(index)\n            if method:\n                return obj.isSignalConnected(method)\n        return False\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"IsSignalConnected\"

\n", + "tags": [ + "PyQt", + "信号" + ] + }, + { + "id": "https://pyqt5.com/pyqtclientmac.html", + "url": "https://pyqt5.com/pyqtclientmac.html", + "title": "在Mac上以正确的姿势使用PyQtClient看Demo", + "date_published": "2019-04-02T09:18:43.000Z", + "content_html": "

由于 PyQtClient 只提供了 Windows 的版本,这里记录下编译后在 Mac 上运行。

\n\n

# 下载项目

\n

安装 git 略。没有的东西可以都先去试试 brew install xxx。没安装 homebrew 的建议使用搜索引擎

\n

git clone https://github.com/PyQt5/PyQtClient.git

\n

# 配置环境

\n
    \n
  1. 打开 IDE 配置 python 环境,使用 anaconda 比较方便
  2. \n
  3. 推荐用 pycharm,我是习惯了用 idea。anaconda 安装可以去官网下载。
  4. \n
  5. 环境原因,选择新建一个 python 3.6 p.s. 我取的环境名字就是 3.6 所以后面的 3.6 其实是这个原因
  6. \n
\n

# conda 源

\n

最好是加环境变量,不加也可以,就是以后用到的都需要指定路径,不太常用,我就没加

\n
~/anaconda3/bin/conda config --add channels conda-forge\n~/anaconda3/bin/conda config --add channels defaults\n~/anaconda3/bin/conda config --add channels r\n~/anaconda3/bin/conda config --add channels bioconda\n~/anaconda3/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ 
\n

# pip 源

\n
mkdir ~/.pip && vim ~/.pip/pip.conf
\n
[global]\nindex-url = http://mirrors.aliyun.com/pypi/simple/\n[install]\ntrusted-host = mirrors.aliyun.com
\n

# 安装编译依赖

\n
~/.conda/envs/3.6/bin/pip install -r PyQtClient/requirements.txt
\n

运行提示没有 webkit,开始手动编译

\n
    \n
  1. wget http://download.qt.io/archive/qt/5.9/5.9.0/qt-opensource-mac-x64-5.9.0.dmg
  2. \n
  3. wget https://github.com/annulen/webkit/releases/download/qtwebkit-5.212.0-alpha2/qtwebkit-5.212.0_alpha2-qt59-darwin-x64.tar.xz
  4. \n
  5. wget https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.10.1/PyQt5_gpl-5.10.1.zip
  6. \n
  7. wget https://www.riverbankcomputing.com/static/Downloads/sip/4.19.8/sip-4.19.8.tar.gz
  8. \n
  9. 编译 sip: ~/.conda/envs/3.6/bin/python configure.py --platform macx-g++ && make && sudo make install
  10. \n
  11. 编译 Webkit.so 没有 qmake 和 sip 的环境变量, 所以后面都是手动指定的
  12. \n
\n
~/.conda/envs/3.6/bin/python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc --qmake=~/Qt5.9.0/5.9/clang_64/bin/qmake --sip=~/.conda/3.6/bin/sip && make && sudo make install
\n

# 插曲

\n
    \n
  1. libcurl 版本要求 10.0,而我的是 9.0,原因是前面我自己摸索,乱装依赖,所以遇到了
  2. \n
\n
~/anaconda3/bin/conda install -n 3.6 -c conda-forge libcurl
\n
    \n
  1. 结果这个 libcurl 10.0.0 是装上了,可是 pygit2 版本不对了,conda 给升级了,PyQtClient 里 requirements.txt 要求这个包的版本(pygit2==0.27.2)几乎决定了其他的环境版本。后来还是老实的用 conda 去装了。这个连 python 版本什么的都会跟着变的。最后降级的结果是 python 3.6.7
  2. \n
\n
~/anaconda3/bin/conda install -n 3.6 -c conda-forge libgit2==0.27.2
\n

至此总算是启动正常了。

\n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/calljava.html", + "url": "https://pyqt5.com/calljava.html", + "title": "Python调用Java对Excel截图", + "date_published": "2019-03-12T13:15:06.000Z", + "content_html": "

有的时候会遇到一些奇葩的需求,就是用 Excel 做报表,但是需要对里面的数据进行填充并生成报表图片,发送出去。这里记录用 python 调用 jar 包对 excel 文件进行公式计算和截图,数据填充可以用 xlrd 或者 openpyxl

\n\n

利用 jpype 模块初始化 java 虚拟机加载 jar 包然后执行其中的功能。

\n

# 代码

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年3月12日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: CallJava\n@description: \n\"\"\"\nimport os\n\nimport jpype\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019'\n\n\ndef convertToImage():\n    Workbook = jpype.JClass('com.aspose.cells.Workbook')\n    ImageFormat = jpype.JClass('com.aspose.cells.ImageFormat')\n    ImageOrPrintOptions = jpype.JClass(\n        'com.aspose.cells.ImageOrPrintOptions')\n    SheetRender = jpype.JClass('com.aspose.cells.SheetRender')\n\n    book = Workbook(os.path.abspath('data/test.xlsx').replace('\\\\', '/'))\n    # 保存为html\n    book.save('data/index.html', 12)\n    # 保存为pdf\n    book.save('data/test.pdf')\n\n    # 截图\n    imgOptions = ImageOrPrintOptions()\n    # imgOptions.setQuality(100)\n    imgOptions.setOnePagePerSheet(True)\n\n    # 输出图片格式\n#     imgOptions.setImageFormat(ImageFormat.getJpeg())\n    imgOptions.setImageFormat(ImageFormat.getPng())\n\n    # 计算\n    CalculationOptions = jpype.JClass(\n        'com.aspose.cells.CalculationOptions')\n    opt = CalculationOptions()\n    # 对Sheet1中的公式进行计算\n    sheet = book.getWorksheets().get('Sheet1')\n    sheet.calculateFormula(opt, True)\n\n    # 设置区域\n    pageSetup = sheet.getPageSetup()\n    # 去掉边距\n    pageSetup.setBottomMargin(0.)\n    pageSetup.setLeftMargin(0.)\n    pageSetup.setRightMargin(0.)\n    pageSetup.setTopMargin(0.)\n    # 设置要截图的区域(对角线)\n    pageSetup.setPrintArea('A0:C2')\n    # Create a SheetRender object for the target sheet\n    sr = SheetRender(sheet, imgOptions)\n    for page in range(sr.getPageCount()):\n        # Generate an image for the worksheet\n        sr.toImage(\n            page, os.path.join('data', '%d.png' % (page + 1)))\n\n\ndef test():\n    # emm这里不知道什么用绝对路径就报错\n    libs = '{};{}'.format(\n        'libs/bcprov-jdk16-146.jar',\n        'libs/aspose-cells-19.2.jar'\n    )\n    command = (jpype.getDefaultJVMPath(),\n                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',\n                   '-Djava.class.path={0}'.format(libs))\n    print(command)\n    jpype.startJVM(jpype.getDefaultJVMPath(),\n                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',\n                   '-Djava.class.path={0}'.format(libs)\n                   )\n    # 解决多线程问题\n    jpype.attachThreadToJVM()\n    # 对excel截图\n    convertToImage()\n    # 关闭虚拟机\n    jpype.shutdownJVM()\n    print('截图完成')\n\n\nif __name__ == '__main__':\n    test()
\n

# 附件

\n

调用 java 生成报表.7z

\n

解压后进入 whls 文件夹安装对应版本的 jpype 包

\n

# 效果图

\n

\"calljava\"

\n", + "tags": [ + "Python", + "截图" + ] + }, + { + "id": "https://pyqt5.com/webviewnew.html", + "url": "https://pyqt5.com/webviewnew.html", + "title": "PyQt5编译QWebView与QWebEngineView共存", + "date_published": "2019-01-12T11:28:06.000Z", + "content_html": "

在 PyQt5.5 过后移除了 QWebView 控件,改用 QWebEngineView ,但是这个刚开始用起来不是很方便,最近在整理一些例子的时候需要同时使用 QWebViewQWebEngineView ,故希望把 QWebView 重新加入到后面的 PyQt5 版本中,查看 PyQt5.10.1 的源码发现里面其实是有 QWebView 的,只是因为 Qt5.10.1 中没有编译好的 dll 等导致无法编译。

\n\n

# 准备工作

\n
    \n
  1. 安装 VS2015
  2. \n
  3. 安装 Qt5.10.1
  4. \n
  5. 前往 https://github.com/annulen/webkit/releases 下载对应的文件,比如:qtwebkit-5.212.0_alpha2-qt59-msvc2015-x86.zip
  6. \n
  7. 下载 PyQt5.10.1 源码
  8. \n
  9. 下载对应版本的 sip 源码
  10. \n
\n

# 编译

\n
    \n
  1. 设置环境变量 set PATH=D:\\soft\\Qt\\Qt5.10.1\\5.10.1\\msvc2015\\bin;%PATH%
  2. \n
  3. 首先进入 vs2015 命令行编译 sip 并安装, python configure.py && nmake && nmake install
  4. \n
  5. 进入 PyQt5.10.1 源码编译安装即可
  6. \n
  7. 如果要减少 PyQt5.10.1 的编译可以试试以下代码
  8. \n
\n
D:\\soft\\Python35\\python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc
", + "tags": [ + "PyQt", + "QWebView", + "浏览器" + ] + } + ] +} \ No newline at end of file diff --git "a/category/\346\225\231\347\250\213/rss.xml" "b/category/\346\225\231\347\250\213/rss.xml" new file mode 100644 index 00000000..fa80ed8c --- /dev/null +++ "b/category/\346\225\231\347\250\213/rss.xml" @@ -0,0 +1,591 @@ + + + + PyQt • Posts by "教程" category + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 30 Apr 2024 01:46:52 +0000 + Tue, 30 Apr 2024 01:46:52 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pytest_qt_modal_625781186.html + pytest-qt 测试模态窗体. + https://pyqt5.com/pytest_qt_modal_625781186.html + pytest-qt + Tue, 30 Apr 2024 01:46:52 +0000 + + + + https://pyqt5.com/bindsignals.html + 三种方式绑定信号槽 + https://pyqt5.com/bindsignals.html + PyQt + 信号 + Sat, 04 May 2019 08:07:06 +0000 + + + + https://pyqt5.com/runnablesignal_625781186.html + QRunnable线程池发信号 + https://pyqt5.com/runnablesignal_625781186.html + PyQt + 信号 + 线程 + Tue, 30 Apr 2019 07:58:09 +0000 + + + + https://pyqt5.com/viewpyindesigner_625781186.html + 如何和设计师中查看ui转换的py代码 + https://pyqt5.com/viewpyindesigner_625781186.html + PyQt + Designer + 设计师 + Tue, 30 Apr 2019 05:11:09 +0000 + + + + https://pyqt5.com/showframe.html + PyQt5调整窗口显示边框 + https://pyqt5.com/showframe.html + PyQt + 边框 + Fri, 26 Apr 2019 14:19:26 +0000 + + + + https://pyqt5.com/issignalconnected.html + PyQt5判断信号是否连接 + https://pyqt5.com/issignalconnected.html + PyQt + 信号 + Fri, 26 Apr 2019 14:06:26 +0000 + + + + https://pyqt5.com/pyqtclientmac.html + 在Mac上以正确的姿势使用PyQtClient看Demo + https://pyqt5.com/pyqtclientmac.html + PyQt + Tue, 02 Apr 2019 09:18:43 +0000 + + + + https://pyqt5.com/calljava.html + Python调用Java对Excel截图 + https://pyqt5.com/calljava.html + Python + 截图 + Tue, 12 Mar 2019 13:15:06 +0000 + + + + https://pyqt5.com/webviewnew.html + PyQt5编译QWebView与QWebEngineView共存 + https://pyqt5.com/webviewnew.html + PyQt + QWebView + 浏览器 + Sat, 12 Jan 2019 11:28:06 +0000 + + + + diff --git "a/category/\347\254\224\350\256\260/feed.json" "b/category/\347\254\224\350\256\260/feed.json" new file mode 100644 index 00000000..f4e11a34 --- /dev/null +++ "b/category/\347\254\224\350\256\260/feed.json" @@ -0,0 +1,105 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"笔记\" category", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/macm1pyqt.html", + "url": "https://pyqt5.com/macm1pyqt.html", + "title": "如何在Mac M1上快速安装PyQt5", + "date_published": "2023-10-07T06:08:06.000Z", + "content_html": "

由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。
\nM1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。

\n\n

1. 安装 Python
\npython-3.9.13-macosx10.9.pkg

\n

2. 勾选自定义同时只勾选安装 pip

\n

\"step1.png\"

\n

\"step1.png\"

\n

3. 设置 pip 源

\n
/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi\n/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna
\n

4. 安装 PyQt5

\n
/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5
\n

5. 测试

\n
/Library/Frameworks/Python.framework/Versions/3.9/bin/python3
\n

\"step3.png\"

\n

📢📢📢

\n

也可以直接安装 Miniconda

\n

然后:conda install -c conda-forge pyqt

\n", + "tags": [ + "PyQt", + "Mac", + "M1" + ] + }, + { + "id": "https://pyqt5.com/studynotes.html", + "url": "https://pyqt5.com/studynotes.html", + "title": "PyQt学习心得", + "date_published": "2019-08-26T01:00:00.000Z", + "content_html": "

在学习 PyQt 的过程中由于资料的缺乏或者没有中文导致大多数人感叹资料太少,学习困难,又或者急于求进,赶鸭子上架的情况,此时有系统的学习方法很重要。每个人都需要有自己的学习方法,别人的学习方法并不一定适合自己但可以采纳一些。笔者在这里列举了一些当初自己自学的一些心得和方法,希望帮助大家建立一套自己的学习 PyQt 的方法,提高自身的学习能力。

\n\n

# Python 基础

\n

在学习和使用 PyQt 之前需要熟练使用 Python,经过对 QQ 群里经常提问的问题的分析,发现大部分人对 Python 中的基础知识掌握不牢固导致很多基础问题,如果要想更好的使用 Python 以及它的扩展必需要进行系统的学习。这里列举一下常用的知识点。

\n
    \n
  1. 类         参考资料
  2. \n
  3. 类的继承
  4. \n
  5. 类的多继承
  6. \n
  7. 类方法重写     参考资料
  8. \n
  9. 类中的 super 函数  参考资料
  10. \n
  11. 函数调用 / 参数类型
  12. \n
  13. 对象调用 (参考第 1 点)
  14. \n
\n

必须熟练掌握上面的知识点后入门 PyQt 才比较容易,如果初学者对上面的知识点还不是很了解,本文不适合继续往下阅读。

\n

# 设计师

\n

Qt 设计师除了方便快速设计一些简单的界面外,其实笔者觉得更大的作用在于帮助用户熟悉各类控件、属性、信号等

\n
    \n
  1. 这里建议初学者不要急于求成,打开设计师新建一个 Widget 的窗口,比如
  2. \n
\n

\"desiger_create\"

\n
    \n
  1. 然后把左侧的所有控件挨个拖动到中间的窗口中,比如这里拖动一个 Push Button 按钮
  2. \n
\n

\"desiger_drag\"

\n
    \n
  1. 在设计师右下角的属性编辑器中列举了该控件的所有父类,意味着可以调用和重写父类的所有方法,建议初学者把这个属性编辑器的所有属性挨个调整看看效果,部分控件可能需要 Ctrl+R 预览界面才能看到,同时像 QListWidget,QTreeWidget,QTableWidget 等某些控件需要在控件上右键增加数据才可以
  2. \n
\n

\"desiger_property\"
\n\"desiger_property2\"

\n
    \n
  1. 两个控件之间简单的信号槽关联可以通过设计师快速的设置
  2. \n
\n

\"desiger_signal\"
\n\"desiger_signal2\"

\n
    \n
  1. 提高进阶的方法,当你需要手动写代码实现界面的时候,不妨把 UI 文件转出 PY 文件,看看是如何构造的(这里涉及到布局等知识见后文)
  2. \n
\n

# 布局

\n

Qt 界面提供了方便的 4 种基本布局,QVboxLayout,QHboxLayout,QFormLayout,QGridLayout,初学者需要数量掌握这 4 种布局外加 2 种拉伸器(占位挤压)

\n

首先需要知道 Qt 界面的中控件的层级顺序以及 parent,parent 的作用既作为子控件的父元素也可以自动管理 Qt 的对象(具体可以搜索下关于 Qt parent 的资料)

\n
    \n
  1. 在没有布局的情况下,在设计师中拖动摆放的控件是一层一层的叠加覆盖,此时每个添加的子控件的 parent 都是最外层的控件
  2. \n
\n

\"desiger_stack\"

\n
    \n
  1. 如果需要界面中的控件自动适应高度宽度,此时则需要使用 4 种布局来包裹里面的子控件,注意的是:布局不是控件不能设置高度宽度和样式等,是一个抽象的东西,就好比是一根橡皮筋包裹几个矩形的物品;布局也可以设置一些属性(在设计师属性编辑器中),比如设置两者直接的间距,设置距离上下左右的间距,设置比例等
  2. \n
\n

\"desiger_layout\"

\n
    \n
  1. 在没有布局或者有布局的时候。可以添加容器控件(QWidget,QFrame,QGroupBox,QScrollArea,QToolBox,QTabWidget,QStackedWidget,QMidArea,QDockWidget)这些容器可以放置子控件,从而循环嵌套。
  2. \n
\n

# 例子

\n

在 PyQt5.5 的时候自带了一个例子文件夹(后面的版本没有的话可以下载 PyQt5 源码,里面有个 examples 文件夹),想要熟练的掌握 PyQt 还需要从自带的例子中学习,必须要每个例子都运行一次然后看看这个例子实现了什么,这样才能记忆深刻。
\n同时很多开发者在 https://github.com/PyQt5/PyQt 分享了各类进阶例子,同时也欢迎大家共同完善该项目,提供更多更好的例子。另外也可以下载该项目的客户端 PyQtClient 软件,支持运行其中的例子

\n

建议在更深入的学习 PyQt 之前多看看一些例子。

\n

# 文档

\n

接下来要说的就是 Qt 的 api 文档,官网文档,这里其实不要害怕是英文就不想看,觉得看不懂了,其实官网的文档还是比较简洁的,而且函数名也比较直观就能知道意思。也可以用谷歌浏览器打开右键翻译,基本上都能看懂。笔者前期写过一篇如何查阅 Qt 文档的文档可以阅读学习一番。

\n

这里就拿 QWebEngineView 举一个例子,首先初学者在使用这个浏览器控件时候,会有诸多的问题比如:Cookie,拦截器等就不知道如何去调用函数来设置

\n
    \n
  1. 首先打开官网文档 https://doc.qt.io/qt-5/qwebengineview.html,可以看到只有少量的函数可以调用,寻找一番并没有发现和 Cookie 相关的东西,这个时候就需要把重点放在有特俗返回值的函数上,比如:
  2. \n
\n
QWebEngineHistory *\t          history() const\nQWebEnginePage *\t          page() const\nQWebEngineSettings *\t      settings() const
\n

这三个函数返回了一个类实例,就意味着可以调用其中的方法。

\n
    \n
  1. \n

    点击 page () 打开 https://doc.qt.io/qt-5/qwebenginepage.html,发现没有 cookie 相关的东西,只有 QWebEngineProfile *\tprofile () const 这个函数比较可疑。

    \n
  2. \n
  3. \n

    点击 **profile ()** 打开 https://doc.qt.io/qt-5/qwebengineprofile.html,在浏览器中搜索 cookie 发现这个类中包含大量和 cookie 相关的东西,比如:**QWebEngineCookieStore *\tcookieStore ()`** 从名字上可以猜测大概意思为 cookie 储存

    \n
  4. \n
  5. \n

    点击 **cookieStore ()** 打开 https://doc.qt.io/qt-5/qwebenginecookiestore.html,此时就会发现这个类里面包含了删除和设置 cookie 的方法。

    \n
  6. \n
  7. \n

    但是找到了这些方法后,面对初学者又一个问题来了,该如何去用?根据上面 4 点整理一下,把他们当做简单的 Python 对象,方法和操作方法和 class 一样的。

    \n
  8. \n
\n
self.webview = QWebEngineView()\n# 得到page\npage = self.webview.page()\n# 得到profile\nprofile = page.profile()\n# 得到cookieStore\ncookieStore = profile.cookieStore()\n# 清空cookie\ncookieStore.deleteAllCookies()\n\n# 用简短代码来表达就是\ncookieStore = self.webview.page().profile().cookieStore()\ncookieStore.deleteAllCookies()
\n

# 异常调试

\n

可能有时候由于粗心,或者调用了一些非法函数,参数错误等会导致程序出现一些异常,首先第一步复制最后一行的错误去百度或者谷歌搜索,大多时候能找到问题所在。其次如果搜索不到或者自己的异常可能是由于某个变量的值不对引起的,就需要在编辑器中打断点使用 DEBUG 模式调试变量值(如果不会可以采用麻烦一点的办法:用 print 打印出变量值)

\n

遇到问题后首先需要自己多调试排查问题,不要一遇到问题就去问,自己多尝试一个一个排查直到找到问题所在并解决,这也是一种提高自身能力的地方。

\n

# 检索资料

\n

作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。信息检索是每个程序猿必备的能力之一,其好处在于可以更快更准确的在茫茫网络海洋中找到自己所需要的东西,这个过程需要长期不断积累和练习。

\n
    \n
  1. 中文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt 拖拽
  2. \n
  3. 英文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt Drag Drop
  4. \n
\n

# 片尾

\n

好了,笔者基本上的学习过程就整理如上,这并不是说每个人都适合这样的方法,但至少笔者是这样一步一步走过来的。当你养成了一个学习、发现和解决问题的好习惯时就会慢慢得心应手。

\n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/QPropertyAnimation.html", + "url": "https://pyqt5.com/QPropertyAnimation.html", + "title": "PyQt属性动画(QPropertyAnimation)", + "date_published": "2019-05-08T07:43:06.000Z", + "content_html": "

QPropertyAnimation 继承自 QVariantAnimation ,其作为 Qt 的属性动画用于针对控件的属性或者继承自 QObject 的对象中定义的属性做修改,
\n简单来说就是基类是 QObject 且定义了属性变量,就可以用 QPropertyAnimation 来做属性动画。同时也可以通过 pyqtProperty 来增加自定义属性。

\n\n

首先,通过构造函数 QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None) 创建一个对象,其中

\n
    \n
  1. 第一个参数是动画作用的对象,也可以通过 setTargetObject 设置
  2. \n
  3. 第二个参数是属性名,在 py3 中类型是 bytes,也可以通过 setPropertyName 设置
  4. \n
\n

# 函数

\n

一些常见的设置函数

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
setPropertyName设置属性名
setTargetObject设置动画作用对象
setDuration设置动画持续时间(毫秒)
setStartValue设置开始值
setEndValue设置结束值
setEasingCurve设置动画曲线
setKeyValueAt插入线性值
setLoopCount设置循环次数(-1 为永久)
\n

# 示例

\n

比如这个例子:

\n
    \n
  1. 修改控件的 geometry 大小
  2. \n
  3. 修改自定义属性
  4. \n
  5. 修改进度条的 value 值
  6. \n
\n

\"QPropertyAnimation\"

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年5月8日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\nfrom PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurve\nfrom PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\\\n    QLabel, QProgressBar, QSpacerItem, QSizePolicy\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.resize(400, 400)\n        self._value = 0\n        self.button = QPushButton('属性动画测试', self)\n        self.button.clicked.connect(self.doStart)\n        self.button.setGeometry(0, 0, 80, 40)\n\n        self.buttonc = QPushButton('自定义属性 测试', self)\n        self.buttonc.clicked.connect(self.doStartCustom)\n\n        self.label = QLabel('', self)\n\n        self.progressbar = QProgressBar(self)\n        self.progressbar.setRange(0, 99)\n\n        layout = QVBoxLayout(self)\n        layout.addItem(QSpacerItem(\n            20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed))\n        layout.addWidget(self.buttonc)\n        layout.addWidget(self.label)\n        layout.addWidget(self.progressbar)\n\n        # 进度条动画\n        self.progressStart()\n\n    # 此处是自定义属性,并通过动画修改后,设置QLabel的值\n    @pyqtProperty(int)\n    def value(self):\n        return self._value\n\n    @value.setter\n    def value(self, v):\n        self._value = v\n        self.label.setText('当前值:{}'.format(v))\n\n    def doStart(self):\n        # 第一个参数是要执行的对象\n        animation = QPropertyAnimation(self.button, b'geometry', self)\n        animation.setDuration(2000)  # 持续时间\n        # 缓和曲线风格,加了曲线动画会很大程度影响\n        animation.setEasingCurve(QEasingCurve.OutBounce)\n        animation.setStartValue(QRect(0, 0, 40, 40))\n        animation.setEndValue(QRect(250, 250, 80, 80))\n        animation.start(animation.DeleteWhenStopped)\n\n    def doStartCustom(self):\n        # 自定义属性动画\n        # 由于定义的属性是在继承的QWidget, 所以第一个参数是self\n        # 第二个参数就是 value\n        animation = QPropertyAnimation(self, b'value', self)\n        animation.setDuration(2000)  # 持续时间\n        animation.setStartValue(0)\n        animation.setEndValue(100)\n        animation.start(animation.DeleteWhenStopped)\n\n    def progressStart(self):\n        # 进度条动画\n        # 这里 value是QProgressBar自带的属性,具体可以看文档\n        # https://doc.qt.io/qt-5/qprogressbar.html#properties\n        animation = QPropertyAnimation(self.progressbar, b'value', self)\n        animation.setDuration(2000)  # 持续时间\n        animation.setLoopCount(-1)\n        # 这里采用插入线性值,第一个参数的范围是(0-1)\n        # 第二个参数的范围是进度(最小值-最大值)\n        animation.setKeyValueAt(0, self.progressbar.minimum())\n        animation.setKeyValueAt(0.1, 10)\n        animation.setKeyValueAt(0.2, 30)\n        animation.setKeyValueAt(0.5, 60)\n        animation.setKeyValueAt(0.7, 80)\n        animation.setKeyValueAt(1, self.progressbar.maximum())\n        animation.start(animation.DeleteWhenStopped)\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())\n
", + "tags": [ + "PyQt", + "动画" + ] + }, + { + "id": "https://pyqt5.com/viewapi.html", + "url": "https://pyqt5.com/viewapi.html", + "title": "如何查阅Qt文档", + "date_published": "2019-05-04T12:50:20.000Z", + "content_html": "

很多网友在问有没有 PyQt5 的文档之类的问题,在 PyQt4 的时候 PyQt 官网有了英文版的文档,随后有网友翻译成了中文。不过现在 PyQt5 官方的文档都指向了 C 的 Qt 文档,其实 C 的 Qt API 文档结构很清晰,翻阅很容易的,需要注意几点。

\n\n

作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。

\n

拿 Qt C++ 文档来说,官网地址是:https://doc.qt.io/qt-5/qtwidgets-module.html 这里面记录了所有控件的详细函数文档。

\n

比如拿 输入框 QLineEdit 来说,怎么去查询它的用法和信号槽等资料?

\n

https://doc.qt.io/qt-5/qlineedit.html

\n

# 左侧目录

\n

在文档左侧目录中有如下几个:

\n

Properties - 控件里的属性(比如宽高等,通常需要当作函数调用)

\n

Public Slots - 这个是控件自己的槽函数(当作普通函数就行)

\n

Signals - 这个是输入框的包含的信号

\n

Public Functions、Reimplemented Public Functions、Static Public Members、Protected Functions、Reimplemented Protected Functions - 这几个都是函数列表

\n

\"howtoviewapi1\"

\n

# 类说明

\n

\"howtoviewapi2\"

\n

这里有两个注意点

\n
    \n
  1. 红色方框内的表示该控件(输入框)继承于 QWidget ,所以该控件(输入框)拥有父类的所有方法和信号,当当前文档找不到相关资料和函数时,可以去父类找找看。
  2. \n
  3. 紫色方框内表示列举所有的方法(包括父类)
  4. \n
\n

# 函数列表

\n

\"howtoviewapi3\"

\n

这里列举的就是该控件(输入框)的函数,同理点击上面的紫色方框是查看所有方法,一般这里主要用来查询你需要的功能函数,Qt 的函数名比较容易理解,比如:只读 ReadOnly,选择文字:setSelection。

\n

所以再查下这部分资料的时候建议在浏览器中 Ctrl + F 打开浏览器的搜索框,并输入英文关键词来检索你所需要的函数在哪里。

\n

\"howtoviewapi8\"

\n

# 槽函数

\n

\"howtoviewapi4\"

\n

这部分列举的是槽函数,其实在 PyQt 中槽函数可以当作普通的函数。普通的函数也可以作为槽函数,直接通过信号连接即可,注意方框所示,还有很多函数是在父类里面。

\n

# 信号

\n

\"howtoviewapi5\"

\n

这部分列举了该控件(输入框)所定义的信号,主要还是看名字,大多都能知道是做什么的,比如:

\n
    \n
  1. editingFinished - 编辑完成信号
  2. \n
  3. returnPressed - 回车键信号
  4. \n
  5. textChanged (const QString &text) - 内容改变信号
  6. \n
\n

这里还有个问题就是参数问题,一般 & 后面的 text 作为参数传递到槽函数中

\n

# 函数详细说明

\n

当不明确这个函数是做什么的,可以点击该函数跳转到下面的说明,比如回车键信号 returnPressed

\n

\"howtoviewapi6\"

\n

如图上所示,用翻译插件翻译,大部分就明白了,如下:

\n

\"howtoviewapi7\"

\n

# 关于如何搜索资料

\n

比如当你要搜索输入框内容改变事件,一般建议两种搜索,且搜索的时候用空格把关键词分开搜索,而且直接用控件名

\n
    \n
  1. 中文搜索引擎:QLineEdit 内容 改变
  2. \n
  3. 英文搜索引擎:QLineEdit text change
  4. \n
\n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/speedgithub.html", + "url": "https://pyqt5.com/speedgithub.html", + "title": "解决GitHub下载速度缓慢的问题", + "date_published": "2019-04-18T00:59:06.000Z", + "content_html": "

由于 Github 的下载走的是 AWS - 亚马逊的路线,,so slow,跟乌龟一样慢。。照着一些方法改了 hosts 文件,偶尔能提提速度。

\n\n

# Windows

\n

Hosts 文件的路径是:

\n

C:\\Windows\\System32\\drivers\\etc

\n

# Mac

\n

终端内输入:

\n

sudo vim /etc/hosts

\n

# 追加域名的 IP 地址

\n

利用 https://www.ipaddress.com/ 来获得以下两个 GitHub 域名的 IP 地址:

\n

(1) github.com

\n

(2) github.global.ssl.fastly.net

\n

打开网页后,利用输入框内分别查询两个域名

\n

将以上两段 IP 写入 Hosts 文件中:

\n
192.30.253.112               github.com\n151.101.185.194              github.global.ssl.fastly.net
\n

保存。

\n

刷新 DNS 缓存

\n

在终端或 CMD 中,执行以下命令:

\n

ipconfig /flushdns

\n", + "tags": [ + "Github" + ] + }, + { + "id": "https://pyqt5.com/ffmpeghls.html", + "url": "https://pyqt5.com/ffmpeghls.html", + "title": "FFmpeg合成加密HLS记录", + "date_published": "2019-01-12T11:28:06.000Z", + "content_html": "

记录在某个需求中要求截图并合成加密视频文件,这里采用 FFmpeg 的管道流来实现生成 HLS 加密文件。

\n\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年3月4日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\n\nfrom pathlib import Path\nfrom subprocess import Popen, PIPE\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\n# p = Popen([r'D:\\soft\\ffmpeg\\bin\\ffmpeg.exe', '-y',\n#            '-threads', '2',\n#            '-f', 'image2pipe',\n#            '-vcodec', 'mjpeg', '-r', '24', '-i', '-',\n#            '-vcodec', 'h264', '-r', '24',\n#            #            '-encryption_scheme', 'cenc-aes-ctr',\n#            #            '-encryption_key', '617D8A125A284DF48E3C6B1866348A3F',\n#            #            '-encryption_kid', 'B326F895B6A24CC5A4DC70995728059C',\n#            r'F:\\Workspace\\Test\\videos\\video.mp4'], stdin=PIPE)\n\np = Popen([r'D:\\soft\\ffmpeg\\bin\\ffmpeg.exe',\n           '-re',   # 按照实际帧率读取输入文件\n           '-y',        # 覆盖已存在文件\n           '-threads', '2',  # 线程数量\n           '-f', 'image2pipe',  # PIPE图片流\n           '-vcodec', 'mjpeg',  # 图片编码\n           '-r', '24',  # 帧率\n           '-i', '-',  # 指定输入流为PIPE\n           '-vcodec', 'h264',  # 输出编码\n           '-r', '24',  # 帧率\n           '-map', '0',\n#            '-crf','20',     # 降低质量\n           '-b', '720k',        # 码率\n           '-f', 'hls',\n           '-codec:v', 'libx264',\n           '-vbsf', 'h264_mp4toannexb',\n           # 指定加密密匙文件\n           '-hls_key_info_file', r'F:\\Workspace\\Test\\videokey.info',\n           '-hls_time', '20',\n           '-hls_list_size', '0',\n           '-hls_wrap', '0',\n#            '-hls_flags', 'single_file',  # 生成单个文件(有bug)\n           r'F:\\Workspace\\Test\\videos\\playlist.m3u8'], stdin=PIPE)\nprint(p)\n\nt = 1 / 24\nfor i, path in enumerate(Path('frames').rglob('*.jpg')):\n    #     print(i, path)\n    p.stdin.write(open(str(path), 'rb').read())\n\np.stdin.close()\np.wait()\nprint('ok')
", + "tags": [ + "Python", + "FFmpeg", + "HLS" + ] + }, + { + "id": "https://pyqt5.com/processinclass_625781186.html", + "url": "https://pyqt5.com/processinclass_625781186.html", + "title": "python 在类里使用进程池", + "date_published": "2018-11-16T13:37:31.000Z", + "content_html": "
    \n
  1. 首先, 进程池的作用就是减少进程的创建和释放 开销的, 所以在类中作为局部变量是不合适的;
  2. \n
  3. 其次, 进程池必须在 if __name__ == "__main__" 里 ,否则会报 frozen_ 什么什么的错误;(这一点可能解释有误);
  4. \n
\n\n
    \n
  1. 然后, 线程池的 apply_async 中如果传入 self.xxx 方法,会报 multiprocessing.Pool pickling error 什么的错误, 具体解释见 https://blog.csdn.net/dutsoft/article/details/70336462, 里面有解决方法,但是我没有成功(最开始测试没有现在理解的透彻, 不过应该是可以的); 由于第 1 点 不合理, 所以有什么办法在类 函数中获取 进程池对象 po 的地址:
  2. \n
\n

\"processinclass1\"

\n

我的解决思路和方法是:

\n
    \n
  1. 通过 globals () 取得全局变量 , 测试证明 :不同文件的 globals() 是不同的: 如 Tab2.py globals() main_extra_func_file.py 中的 globals() 是不同的 , 所以 这样在 Tab2.py 中取不到 po 对象;
  2. \n
  3. 通过 __main__.po 来获取 (为什么会想到这个呢, 因为有时候导包 import .xxx 和 import xxx 会报 __main__ 没有什么属性什么的):
  4. \n
\n
def getPoolObject():\n# po 的名字在main函数中定义\n# __main__ 模块在sys.modules 的键是\"__mp_main__\"\n    return sys.modules[\"__mp_main__\"].po
\n

ps : (图没截好 , rglob_worker 是外部函数 , 非类内函数 ,po = getPoolBojcet () 这一行是类内函数 ,红色箭头 2. 在的那条白色分割线 是 2 个函数。)

\n

\"processinclass2\"

\n

len(po._cache) == 1 : po._cache 是当前有任务的进程数, ==1 表示所有任务结束;利用回调 , 可以更轻松地进行进程通信。

\n", + "tags": [ + "进程" + ] + }, + { + "id": "https://pyqt5.com/daemonthread.html", + "url": "https://pyqt5.com/daemonthread.html", + "title": "多线程之守护线程和阻塞线程", + "date_published": "2018-10-24T07:51:15.000Z", + "content_html": "

如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的 daemon 属性。

\n\n

即在线程开始(thread.start ())之前,调用 setDeamon()函数,设定线程的 daemon 标志。

\n

(thread.setDaemon (True))就表示这个线程 “不重要”。

\n

如果你想等待子线程完成再退出,那就什么都不用做,或者显示地调用 thread.setDaemon (False),设置 daemon 的值为 false。新的子线程会继承父线程的 daemon 标志。

\n

整个 Python 会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

\n

setDaemon () 函数要放在 start 之前设置才行。

\n
import threading\nimport time\n\ndef func():\n    print(\"子线程开启:\", time.localtime())\n    time.sleep(2)\n    print(\"子线程结束:\", time.localtime())\n\n\nprint(\"主线程开启:\", time.localtime())\nt = threading.Thread(target=func, args=())\n# t.setDaemon(True)\nt.start()\nprint(\"主线程关闭:\", time.localtime())
\n

在 Python 的多线程编程中,在实例代码中经常有 thread1.join () 这样的代码。那么今天咱们用实际代码来解释一下 join 函数的作用。

\n

join 的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的 join 函数。

\n

先看看这个:

\n
    \n
  1. 阻塞主进程,专注于执行多线程中的程序。
  2. \n
  3. 多线程多 join 的情况下,依次执行各线程的 join 方法,前头一个结束了才能执行后面一个。
  4. \n
  5. 无参数,则等待到该线程结束,才开始执行下一个线程的 join。
  6. \n
  7. 参数 timeout 为线程的阻塞时间,如 timeout=2 就是罩着这个线程 2s 以后,就不管他了,继续执行下面的代码。
  8. \n
  9. 下面的例子是一次阻塞子线程,每个子线程都会等上个子线程 join 结束才会执行,如果注释掉 t.join 则会同时执行 5 个子线程,多线程在做网络访问的时候可以减少等待时间,那么在一个工作流程中可以将访问网络接口的情况做成多线程。
  10. \n
\n
import threading, time\n\ndef func():\n    print(\"hello world!\")\n    time.sleep(1)\n\nprint(\"hello main start\")\nfor i in range(5):\n    t = threading.Thread(target=func, args=())\n    print(t.getName())\n    t.start()\n    t.join()
", + "tags": [ + "Python", + "线程" + ] + }, + { + "id": "https://pyqt5.com/cgitb.html", + "url": "https://pyqt5.com/cgitb.html", + "title": "异常捕获之cgitb模块", + "date_published": "2018-09-17T15:17:06.000Z", + "content_html": "

cgitb 模块为 Python 脚本提供了一个特殊的异常管理器。名字有点误导人,它最初设计是为了以 HTML 格式展示 cgi 脚本的大量异常信息。后来,他扩展为也可以展示纯文本信息。该模块激活后,如果发生了未捕获的异常,将会展示格式化的输出报告。该报告包括源代码每一层的回溯,以及当前执行程序的参数和局部变量。以及,你可以选择将这些信息存到一个文件里,而不是发送到浏览器。

\n\n

# 用途

\n

当编辑器中无法显示错误信息时,尤其是 PyQt ,可以尝试在 cmd 中运行代码,或者使用此模块来得到错误信息。

\n

# 介绍

\n

# cgitb.enable

\n
cgitb.enable(display=1, logdir=None, context=5, format=\"html\")
\n

参数说明

\n
    \n
  1. display 1,发送至浏览器;0, 不发送
  2. \n
  3. logdir 如果有的话,写到该目录下
  4. \n
  5. context 显示错误代码周围的代码行数
  6. \n
  7. format 是否显示为 HTML,除了’html’之外的所有值,都会显示为纯文本
  8. \n
\n

# cgitb.handle

\n
cgitb.handle(info=None)
\n

参数说明

\n
    \n
  1. 如果你想用 cgitb 处理异常,你可以调用这个函数。
  2. \n
  3. info 应当是含有异常类型、异常值和 traceback 对象的三元组
  4. \n
  5. 如同 sys.exc_info () 返回的那样。如果不提供 info,则从 sys.exc_info 中获取。
  6. \n
\n

# 如何使用

\n

以下代码放在最开始执行

\n
import cgitb\nimport sys\nsys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
\n", + "tags": [ + "Python", + "异常" + ] + } + ] +} \ No newline at end of file diff --git "a/category/\347\254\224\350\256\260/rss.xml" "b/category/\347\254\224\350\256\260/rss.xml" new file mode 100644 index 00000000..29ce4501 --- /dev/null +++ "b/category/\347\254\224\350\256\260/rss.xml" @@ -0,0 +1,647 @@ + + + + PyQt • Posts by "笔记" category + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 07 Oct 2023 06:08:06 +0000 + Sat, 07 Oct 2023 06:08:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/macm1pyqt.html + 如何在Mac M1上快速安装PyQt5 + https://pyqt5.com/macm1pyqt.html + PyQt + Mac + M1 + Sat, 07 Oct 2023 06:08:06 +0000 + + + + https://pyqt5.com/studynotes.html + PyQt学习心得 + https://pyqt5.com/studynotes.html + PyQt + Mon, 26 Aug 2019 01:00:00 +0000 + + + + https://pyqt5.com/QPropertyAnimation.html + PyQt属性动画(QPropertyAnimation) + https://pyqt5.com/QPropertyAnimation.html + PyQt + 动画 + Wed, 08 May 2019 07:43:06 +0000 + + + + https://pyqt5.com/viewapi.html + 如何查阅Qt文档 + https://pyqt5.com/viewapi.html + PyQt + Sat, 04 May 2019 12:50:20 +0000 + + + + https://pyqt5.com/speedgithub.html + 解决GitHub下载速度缓慢的问题 + https://pyqt5.com/speedgithub.html + Github + Thu, 18 Apr 2019 00:59:06 +0000 + + + + https://pyqt5.com/ffmpeghls.html + FFmpeg合成加密HLS记录 + https://pyqt5.com/ffmpeghls.html + Python + FFmpeg + HLS + Sat, 12 Jan 2019 11:28:06 +0000 + + + + https://pyqt5.com/processinclass_625781186.html + python 在类里使用进程池 + https://pyqt5.com/processinclass_625781186.html + 进程 + Fri, 16 Nov 2018 13:37:31 +0000 + + + + https://pyqt5.com/daemonthread.html + 多线程之守护线程和阻塞线程 + https://pyqt5.com/daemonthread.html + Python + 线程 + Wed, 24 Oct 2018 07:51:15 +0000 + + + + https://pyqt5.com/cgitb.html + 异常捕获之cgitb模块 + https://pyqt5.com/cgitb.html + Python + 异常 + Mon, 17 Sep 2018 15:17:06 +0000 + + + + diff --git "a/category/\351\232\217\347\254\224/feed.json" "b/category/\351\232\217\347\254\224/feed.json" new file mode 100644 index 00000000..e71db226 --- /dev/null +++ "b/category/\351\232\217\347\254\224/feed.json" @@ -0,0 +1,106 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"随笔\" category", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/equal_str_width_625781186.html", + "url": "https://pyqt5.com/equal_str_width_625781186.html", + "title": "python 判断屏幕等宽字符串的长度  ", + "date_published": "2019-12-26T11:49:41.000Z", + "content_html": "

判断屏幕等宽字符串的长度?

\n\n

判断屏幕等宽字符串的长度?

\n

\"image.png\"

\n

【新手】重庆 - 搬砖 - NoWait 22:41:50 @北京 - BUG 开发 - 黑择明 求指点
\n【专家】北京 - BUG 开发 - 黑择明 22:43:04 fontMetrics
\n【专家】 https://pyqt.site (892768447) 22:43:54 QFontMetrics
\n【专家】 https://pyqt.site (892768447) 22:44:09 通过 QLabel.font ().fontMetrics () 得到

\n

【新手】重庆 - 搬砖 - NoWait 22:52:00
\nhttps://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size
\n\"image.png\"

\n

【新手】重庆 - 搬砖 - NoWait 22:53:15 感觉和 fontMetrics 应该是差不多的

\n

\"image.png\"

\n
\n

【专家】北京 - BUG 开发 - 黑择明 (996742224) 11:29:04
\nfm = QFontMetrics(QFont())
\nfm.width(“qweqwe”)

\n", + "tags": [ + "Python" + ] + }, + { + "id": "https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html", + "url": "https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html", + "title": "修改pyuic代替pyside2-uic.", + "date_published": "2019-12-26T11:49:41.000Z", + "content_html": "

修改 pyuic 代替 pyside2-uic

\n\n

修改 pyuic 代替 pyside2-uic.

\n

最近看到挺多人用 pyside2 的 uic 编译 ui 文件有问题 .
\n 写个解决办法.

\n

首先,
\n pip install qtpy ,
\n 这个是兼容 pyqt5 和 pyside2 的,无缝转换 .

\n

然后,
\n 修改 pyqt5 的 uic ,

\n

\"image.png\"

\n

最后用 pyuic5 , 生成 Ui_XXX.py 文件 .

\n", + "tags": [ + "PyQt5 PySide2" + ] + }, + { + "id": "https://pyqt5.com/python_statemachine_625781186.html", + "url": "https://pyqt5.com/python_statemachine_625781186.html", + "title": "python 状态机模块  ", + "date_published": "2019-07-17T09:03:33.000Z", + "content_html": "

用状态来取代 if…else 判断。

\n\n

GUI 涉及到挺多的状态改变,以前一直用 if…else 来判断,最近读了设计模式,发现有个状态模式,随后发现了状态机这个东西 .

\n

python 的状态机模块挺多的,不过好像很多都不更新了.
\n 推荐 2 个状态机模块,但是也没有太深入的使用经验,就跑跑例子,以后有更详细的 pyqt 例子再补上 .

\n

1: pip install python-statemachine

\n

官方例子 : https://github.com/fgmacedo/python-statemachine

\n

2. pip install state_machine

\n

官方例子 : https://github.com/jtushman/state_machine

\n

1 的 最近一次更新在 6 个月以前,使用 类继承mixin 方式,不过有些地方不如 2 个人性化;

\n

2 的设计更人性化一些,包括状态改变 beforeafter , 不过由于是装饰器实现的动态增加属性,有些地方编辑器智能提示可能就靠不上了.

\n

两者实现实现方式不一样,有兴趣可以读读源码 .

\n
    \n
  1. qt 内置状态机框架
  2. \n
\n

https://blog.csdn.net/amnes1a/article/details/62418196

\n

https://blog.csdn.net/dongfenghuojian/article/details/78187131

\n

http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程)

\n", + "tags": [ + "Python", + "python 状态机" + ] + }, + { + "id": "https://pyqt5.com/pyqt5_hook_key_625781186.html", + "url": "https://pyqt5.com/pyqt5_hook_key_625781186.html", + "title": "在pyqt中使用python全局钩子模块", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像 QQ 一样可以全局热键截图。这里介绍几个方法实现在 PyQt 中使用 Python 全局钩子模块实现全局热键功能。

\n\n
    \n
  1. pyHook3
  2. \n
\n

安装命令 : pip install pyhook3

\n

https://blog.csdn.net/q871063970/article/details/86648386

\n

似乎将 pyhook 支持 py3 版本的了?没有太多研究.

\n

缺点:只支持 win 平台.

\n

2. keyboard & mouse

\n

安装命令: pip install keyboard mouse

\n
\nfrom PyQt5 import  QtGui, QtWidgets, QtCore\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nimport keyboard\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.testBtn = QPushButton(self)\n        layout.addWidget(self.testBtn)\n\n        keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey'))\n        keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc"))\n\n    def abc(self,a,b,c):\n        print(a,b,c)\n        \nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

更详细例子 : pyqt 中使用 keyboard 全局热键

\n

优点:跨平台;

\n

缺点:模块名字取得太差,不容易被发现.

\n", + "tags": [ + "Python", + "pyqt hook key" + ] + }, + { + "id": "https://pyqt5.com/read_open_source.html", + "url": "https://pyqt5.com/read_open_source.html", + "title": "像读文章一样读源码", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

使用 snoop, 像读文章一样读源码。

\n\n

不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
\n\"image.png\"

\n

没有目的地概览 , 不知不觉就追究到细节里面去了。

\n

\"image.png\"

\n

所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。

\n

开始 :

\n
    \n
  1. python DEBUG 模块介绍 :
    \n 前段时间看过挺多文章提到 pysoonper 这个调试模块,有兴趣的可以百度一下.
    \n 个人尝试了一下,篇幅过大的 DEBUG 不适合用 pysoonper , 因为没有缩进!
    \n 这几天偶然遇到一个二次封装的模块 snoop, 完美地解决了这个问题.
  2. \n
  3. 操作步骤 :
  4. \n
\n\n

发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。

\n\n

callreturn 给加进去.

\n\n

\"eric6启动阶段\"

\n

\"image.png\"

\n

#000 是为了方便搜索 。
\n需要自己手动折叠 。
\n可以发现 每个 splash.showMessage() 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。

\n
\n

# ps: vscode 阅读 log 文件还是有一些不方便的地方,除了在 2. 中提到的,还有包括关闭文件再打开,折叠状态不会保留,有其他更好的方式 请留言告诉我,谢谢.

\n", + "tags": [ + "Python", + "debug", + "snoop" + ] + }, + { + "id": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "url": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "title": "python 获取子进程print信息  ", + "date_published": "2019-05-24T06:39:44.000Z", + "content_html": "

在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。

\n\n

问题所在:

\n

\"image.png\"

\n

如果模块都由自己开发, 正常操作

\n

\"image.png\"

\n

但是因为不能改,所以只能拦截:
\n代码:

\n
pythonPath = self.pythonPath_cb.currentText()\n\nif suffix == \"py\":\n    # 首次\n    self.pyCommand = [pythonPath, path]\n    self.modifiedReloadPython(path)\ndef modifiedReloadPython(self, path_):\n    os.chdir(os.path.dirname(path_))\n    # 子进程调用\n    self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n    # self.stdoutWorker.p = self.p\n    self.stdoutWorker = Worker(self.p)\n    self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append(\"PYDEBUG:\\n\" + x))\n    self.stdoutWorker.start()\nclass Worker(QThread):\n    stdout_signal = pyqtSignal(str)\n\n    def __init__(self, p, parent=None):\n        super().__init__(parent)\n        self.p = p\n\n    def run(self):\n        while True:\n            QApplication.processEvents()\n            if self.p is not None:\n                line = self.p.stdout.readline()\n                # line = line.strip()\n                if line != b'':\n                    try:\n                        info = line.decode()\n                        self.stdout_signal.emit(info)\n                    except:\n                        self.stdout_signal.emit(repr(line))\n    
\n", + "tags": [ + "Python", + "subprocess.Popen", + "拦截print" + ] + }, + { + "id": "https://pyqt5.com/suggesteditor.html", + "url": "https://pyqt5.com/suggesteditor.html", + "title": "推荐编辑器LiClipse", + "date_published": "2019-05-04T10:04:08.000Z", + "content_html": "

关于 Python 的开发编辑器有很多,每个人有每个人的喜好,经常看到很多在问什么编辑器好用,有人推荐 Sublime,有人推荐 Pycharm 等等,这里就不去比较其它编辑器的优缺点了,只谈谈关于 LiClipse 这个编辑器在初级使用阶段的智能提示功能等。开箱即用,支持多种语言,RST,Markdown 和 HTML 编辑器的 HTML 预览。

\n\n

其实 LiClipse 这个编辑器就是以前的 PyDev 插件的独立版本,基于 Eclipse 编辑器开发,去掉了 Java 的相关开发功能,关于软件的详细说明可以去官网查看: http://www.liclipse.com/

\n

编辑器只需要少量的配置,打开即可使用,快速自动 import,也可以根据需要安装自己所需的插件,比如 json、svn、主题插件等。个人推荐:适合刚入门的新手使用

\n

由于新版的 PyQt 和 PyDev 去掉了详细的函数提示,所以 PyQt 的智能提示只有函数和返回值,并没有英文注释,但是以前的比如 PyQt4 的智能提示应该是有详细的英文注释提示。

\n

# 界面预览

\n
    \n
  1. 主界面
    \n\"editor1\"
  2. \n
  3. 鼠标悬停提示
    \n\"editor2\"
  4. \n
  5. 输入提示
    \n\"editor3\"
  6. \n
  7. Git 面板
    \n\"editor4\"
  8. \n
  9. 全局搜索(Ctrl + H)
    \n\"editor5\"
    \n\"editor6\"
  10. \n
\n

# 自动导包

\n

其实这个功能我是非常喜欢的,通过按下快捷键即可自动寻找包名导入,快捷键 Ctrl + Shift + O

\n

\"editor_import\"

\n

也可以在标红的代码上按下 Ctrl + F1 进行导入

\n

\"editor_import2\"

\n

# 配置

\n

打开编辑器后首先要配置【Window -> Preferences】的就是 Python 的环境变量,可以同时添加多个 Python 版本

\n

\"editor_env\"

\n

# Tab 等设置

\n
    \n
  1. Insert spaces for tabs tab 转空格
  2. \n
  3. Show line numbers 显示行号
  4. \n
\n

\"editor_tab\"

\n

# 模版

\n

这个功能可以快速插入自己定义好的模版代码,比如 if __name__ == '__main__': 等等,比如我这里配置的创建文件的模版

\n

\"editor_tpl\"

\n

# 常用快捷键

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
格式化对齐Ctrl + Shift + F
自动导包Ctrl + Shift + O
快捷提示Alt + /
\n", + "tags": [ + "编辑器" + ] + }, + { + "id": "https://pyqt5.com/virtualenvpy_625781186.html", + "url": "https://pyqt5.com/virtualenvpy_625781186.html", + "title": "python 拷贝虚拟环境(一)  ", + "date_published": "2019-05-02T07:21:01.000Z", + "content_html": "

通常来说,刚开始使用 python 的时候都是把包装到全局路径,随着各个项目安装的包越来越多,之后每开始一个项目,pycharm 创建索引的时间都越来越漫长,所以不可避免得开始使用虚拟环境。
\n经过一番了解 ,虚拟环境的优点有这些:

\n\n\n

python 虚拟环境库除了自带的 venv , 还有三方库 virtualenv , 此外 在 virtualenv 基础上又开发了 virtualenvwrapper(virtualenvwrapper_win) 来管理

\n

本文基于 virtualenvwrapper 创建的虚拟环境来讲解.

\n
以下是收集的一些virtualenvwrapper配置教程:\n# linux平台\nhttps://www.cnblogs.com/netfoxman/p/5994697.html\n# window平台\nhttps://blog.csdn.net/shaququ/article/details/54292043   \nhttps://blog.csdn.net/iaau0908/article/details/54021518\n
\n

虚拟环境创建多了我们就会发现,
\n 有时候使用相同版本的环境,一些常用的库是需要重新安装的,
\n 那么能不能创建一个基础环境,默认拥有这些库,然后在这个基础环境上继续安装三方库呢?

\n

本文经过试验发现是可行的:

\n
    \n
  1. \n

    创建基础虚拟环境 mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径] . 例如 mkvirtualenv py34 -p c:\\Python34\\python.exe

    \n
  2. \n
  3. \n

    切换到虚拟环境 workon py34 , 然后安装一下三方库,然后复制 py34 这个文件夹备份一下;

    \n
  4. \n
  5. \n

    接着复制这个 py34 文件夹,把复制后的文件夹改名为我们需要需要的文件夹例如 new34

    \n
  6. \n
  7. \n

    进入 new34文件夹 ,用任意编辑器全路径搜索 py34 (替换虚拟环境的路径)

    \n
  8. \n
  9. \n

    删除 new34/Scripts 下的 pip.exe, pip3.exe, pip3.x.exe, easy_install.exe (因为安装路径硬编码到这里面了,改不了,需要重新安装)

    \n
  10. \n
  11. \n

    https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二,源码安装 setuptools 后再用 easy_install pip 安装 pip 后,完成;
    \n 如果有问题,就继续按照方式一的源码安装 pip;

    \n
  12. \n
  13. \n

    new34 环境下 用 pip show 三方库 来看一些库的位置,确保正确.

    \n
  14. \n
\n", + "tags": [ + "Python", + "virtualenvwrapper", + "virtualenv" + ] + }, + { + "id": "https://pyqt5.com/pyqtclient.html", + "url": "https://pyqt5.com/pyqtclient.html", + "title": "PyQtClient例子客户端", + "date_published": "2019-02-02T07:15:06.000Z", + "content_html": "

\"1.gif\" 对本博客所写的项目 PyQt 例子进行一个客户端的编写,客户端主要实现一些动画效果,更换皮肤,运行例子等功能。\"2.gif\"

\n\n

# 项目地址

\n
\n

# Windows 客户端下载

\n
    \n
  1. 包含部分例子
  2. \n
  3. 不包含例子
  4. \n
  5. 百度网盘 提取码: nadv
  6. \n
\n

# 效果图

\n

\"PyQtClient\"

\n", + "tags": [ + "PyQt" + ] + } + ] +} \ No newline at end of file diff --git "a/category/\351\232\217\347\254\224/rss.xml" "b/category/\351\232\217\347\254\224/rss.xml" new file mode 100644 index 00000000..89014314 --- /dev/null +++ "b/category/\351\232\217\347\254\224/rss.xml" @@ -0,0 +1,426 @@ + + + + PyQt • Posts by "随笔" category + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 26 Dec 2019 11:49:41 +0000 + Thu, 26 Dec 2019 11:49:41 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/equal_str_width_625781186.html + python 判断屏幕等宽字符串的长度   + https://pyqt5.com/equal_str_width_625781186.html + Python + Thu, 26 Dec 2019 11:49:41 +0000 + + + + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + 修改pyuic代替pyside2-uic. + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + PyQt5 PySide2 + Thu, 26 Dec 2019 11:49:41 +0000 + + + + https://pyqt5.com/python_statemachine_625781186.html + python 状态机模块   + https://pyqt5.com/python_statemachine_625781186.html + Python + python 状态机 + Wed, 17 Jul 2019 09:03:33 +0000 + + + + https://pyqt5.com/pyqt5_hook_key_625781186.html + 在pyqt中使用python全局钩子模块 + https://pyqt5.com/pyqt5_hook_key_625781186.html + Python + pyqt hook key + Sat, 06 Jul 2019 17:37:22 +0000 + + + + https://pyqt5.com/read_open_source.html + 像读文章一样读源码 + https://pyqt5.com/read_open_source.html + Python + debug + snoop + Sat, 06 Jul 2019 17:37:22 +0000 + + + + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + python 获取子进程print信息   + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + Python + subprocess.Popen + 拦截print + Fri, 24 May 2019 06:39:44 +0000 + + + + https://pyqt5.com/suggesteditor.html + 推荐编辑器LiClipse + https://pyqt5.com/suggesteditor.html + 编辑器 + Sat, 04 May 2019 10:04:08 +0000 + + + + https://pyqt5.com/virtualenvpy_625781186.html + python 拷贝虚拟环境(一)   + https://pyqt5.com/virtualenvpy_625781186.html + Python + virtualenvwrapper + virtualenv + Thu, 02 May 2019 07:21:01 +0000 + + + + https://pyqt5.com/pyqtclient.html + PyQtClient例子客户端 + https://pyqt5.com/pyqtclient.html + PyQt + Sat, 02 Feb 2019 07:15:06 +0000 + + + + diff --git a/cgitb.html b/cgitb.html new file mode 100644 index 00000000..53ed7cd6 --- /dev/null +++ b/cgitb.html @@ -0,0 +1,298 @@ +异常捕获之cgitb模块 | PyQt + + + + + + + + + + + + + + +

异常捕获之cgitb模块

cgitb 模块为 Python 脚本提供了一个特殊的异常管理器。名字有点误导人,它最初设计是为了以 HTML 格式展示 cgi 脚本的大量异常信息。后来,他扩展为也可以展示纯文本信息。该模块激活后,如果发生了未捕获的异常,将会展示格式化的输出报告。该报告包括源代码每一层的回溯,以及当前执行程序的参数和局部变量。以及,你可以选择将这些信息存到一个文件里,而不是发送到浏览器。

+ +

# 用途

+

当编辑器中无法显示错误信息时,尤其是 PyQt ,可以尝试在 cmd 中运行代码,或者使用此模块来得到错误信息。

+

# 介绍

+

# cgitb.enable

+
cgitb.enable(display=1, logdir=None, context=5, format="html")
+

参数说明

+
    +
  1. display 1,发送至浏览器;0, 不发送
  2. +
  3. logdir 如果有的话,写到该目录下
  4. +
  5. context 显示错误代码周围的代码行数
  6. +
  7. format 是否显示为 HTML,除了’html’之外的所有值,都会显示为纯文本
  8. +
+

# cgitb.handle

+
cgitb.handle(info=None)
+

参数说明

+
    +
  1. 如果你想用 cgitb 处理异常,你可以调用这个函数。
  2. +
  3. info 应当是含有异常类型、异常值和 traceback 对象的三元组
  4. +
  5. 如同 sys.exc_info () 返回的那样。如果不提供 info,则从 sys.exc_info 中获取。
  6. +
+

# 如何使用

+

以下代码放在最开始执行

+
import cgitb
+import sys
+sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
+
文章作者: Irony
文章链接: https://pyqt5.com/cgitb.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/circleimage.html b/circleimage.html new file mode 100644 index 00000000..406c6cf0 --- /dev/null +++ b/circleimage.html @@ -0,0 +1,434 @@ +PyQt5圆形图片 | PyQt + + + + + + + + + + + + + + + +

PyQt5圆形图片

实现圆形图片的方法有很多,比如用遮罩(mask), 裁切等等。这里比较几种实现方式,选出个人认为最优的方案。

+ +

https://github.com/PyQt5/PyQt/blob/master/QLabel/CircleImage.py

+

# 采用 mask 方式

+

具体参考 【Qt】QLabel 实现的圆形图像 - 米罗西 - 博客园

+

# 画圆形遮盖(适合纯色背景)

+

原理是在原图片上画一个 4 角有颜色,中间圆形镂空的图片。

+

circleimage1

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+'''
+Created on 2017年8月25日
+@author: Irony."[讽刺]
+@site: https://pyqt5.com, https://github.com/892768447
+@email: 892768447@qq.com
+@description: 
+'''
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QPainter, QPainterPath
+from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout
+
+
+__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
+__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
+__Version__ = "Version 1.0"
+
+
+class Label(QLabel):
+
+    def __init__(self, *args, antialiasing=True, **kwargs):
+        super(Label, self).__init__(*args, **kwargs)
+        self.Antialiasing = antialiasing
+        self.setMaximumSize(200, 200)
+        self.setMinimumSize(200, 200)
+        self.radius = 100
+
+        #加载图片并缩放
+        self.image = QPixmap("head.jpg").scaled(
+            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
+
+        painter = QPainter(self.image)
+        if self.Antialiasing:
+            painter.setRenderHint(QPainter.Antialiasing, True)
+            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
+            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
+
+        path = QPainterPath()
+        path.addRoundedRect(
+            0, 0, self.width(), self.height(), self.radius, self.radius)
+        path.addRect(0,0,self.width(),self.height())
+        painter.setPen(Qt.NoPen)
+        painter.setBrush(Qt.green)
+        painter.drawPath(path)
+        self.setPixmap(self.image)
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+        layout.addWidget(Label(self))
+        layout.addWidget(Label(self, antialiasing=False))
+        self.setStyleSheet("background: black;")
+
+if __name__ == "__main__":
+    import sys
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
+

# 使用 QPainter 的 setCompositionMode

+

具体参考 Qt 圆形头像制作工具 抗锯齿 可缩放编辑

+
//result_avatar_size 是我们最后生成的图片的长宽,可以是QSize(200, 200)的正圆
+destination_image = QImage(result_avatar_size, QImage::Format_ARGB32_Premultiplied);
+//在黑色的正方形中间画一个透明的圆,作为头像遮罩
+QPainter painter(&destination_image);
+painter.setRenderHint(QPainter::Antialiasing);
+//全涂黑
+painter.fillRect(destination_image.rect(), QBrush(Qt::black, Qt::SolidPattern));
+painter.setCompositionMode(QPainter::CompositionMode_SourceOut);
+painter.setPen(Qt::NoPen);
+painter.setBrush(QBrush(Qt::transparent, Qt::SolidPattern));
+//画透明区域
+painter.drawEllipse(destination_image.rect());
+

# 使用 QPainter 的切割方法(推荐)

+

利用 QPainter.setClipPath 方法切割一个圆形的 QPainterPath

+

circleimage2

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+'''
+Created on 2017年8月25日
+@author: Irony."[讽刺]
+@site: https://pyqt5.com, https://github.com/892768447
+@email: 892768447@qq.com
+@file: 
+@description: 
+'''
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QPainter, QPainterPath, QPen
+from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout
+
+
+__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
+__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
+__Version__ = "Version 1.0"
+
+
+class Label(QLabel):
+
+    def __init__(self, *args, antialiasing=True, **kwargs):
+        super(Label, self).__init__(*args, **kwargs)
+        self.Antialiasing = antialiasing
+        self.setMaximumSize(200, 200)
+        self.setMinimumSize(200, 200)
+        self.radius = 100
+
+        #####################核心实现#########################
+        self.target = QPixmap(self.size())  # 大小和控件一样
+        self.target.fill(Qt.transparent)  # 填充背景为透明
+
+        p = QPixmap("head.jpg").scaled(  # 加载图片并缩放和控件一样大
+            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
+
+        painter = QPainter(self.target)
+        if self.Antialiasing:
+            # 抗锯齿
+            painter.setRenderHint(QPainter.Antialiasing, True)
+            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
+            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
+
+#         painter.setPen(# 测试黑色圆圈
+#             QPen(Qt.black, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
+        path = QPainterPath()
+        path.addRoundedRect(
+            0, 0, self.width(), self.height(), self.radius, self.radius)
+        #**** 切割为圆形 ****#
+        painter.setClipPath(path)
+#         painter.drawPath(path)  # 测试黑色圆圈
+
+        painter.drawPixmap(0, 0, p)
+        self.setPixmap(self.target)
+        #####################核心实现#########################
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QHBoxLayout(self)
+        layout.addWidget(Label(self))
+        layout.addWidget(Label(self, antialiasing=False))
+        self.setStyleSheet("background: black;")
+
+if __name__ == "__main__":
+    import sys
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
文章作者: Irony
文章链接: https://pyqt5.com/circleimage.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/createThumb.py b/createThumb.py deleted file mode 100644 index c2247cd5..00000000 --- a/createThumb.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Created on 2019年6月17日 -@author: Irony -@site: -@email: 892768447@qq.com -@file: -@description: 生成缩略图 -""" -import os -import sys -import traceback - -import requests -from PIL import Image - -try: - os.mkdir('tmp') -except Exception: - pass - - -def download(url, src, dst): - try: - resp = requests.get(url) - open(src, 'wb').write(resp.content) - thumbnail(src, dst) - except Exception: - traceback.print_exc() - - -def thumbnail(src, dst): - try: - img = Image.open(src) - img = img.convert('RGB') - img.thumbnail((80, 80), Image.ANTIALIAS) - img.save(dst) - except Exception: - traceback.print_exc() - - -if __name__ == '__main__': - length = len(sys.argv) - if length == 3: - # 本地文件生成缩略图 - _, src, dst = sys.argv - thumbnail(src, dst) - elif length == 4: - # 远程文件生成缩略图 - _, url, src, dst = sys.argv - if url.find('jsdelivr') > -1 or url.find('jsd.') > -1: - print('ignore cdn file') - else: - download(url, src, dst) diff --git a/css/index.css b/css/index.css new file mode 100644 index 00000000..f3a52233 --- /dev/null +++ b/css/index.css @@ -0,0 +1,4645 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ +html { + line-height: 1.15; + -webkit-text-size-adjust: 100% +} + +body { + margin: 0 +} + +main { + display: block +} + +h1 { + font-size: 2em; + margin: .67em 0 +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} + +pre { + font-family: monospace, monospace; + font-size: 1em +} + +a { + background-color: transparent +} + +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted +} + +b, +strong { + font-weight: bolder +} + +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em +} + +small { + font-size: 80% +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sub { + bottom: -.25em +} + +sup { + top: -.5em +} + +img { + border-style: none +} + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0 +} + +button, +input { + overflow: visible +} + +button, +select { + text-transform: none +} + +[type=button], +[type=reset], +[type=submit], +button { + -webkit-appearance: button +} + +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner, +button::-moz-focus-inner { + border-style: none; + padding: 0 +} + +[type=button]:-moz-focusring, +[type=reset]:-moz-focusring, +[type=submit]:-moz-focusring, +button:-moz-focusring { + outline: 1px dotted ButtonText +} + +fieldset { + padding: .35em .75em .625em +} + +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal +} + +progress { + vertical-align: baseline +} + +textarea { + overflow: auto +} + +[type=checkbox], +[type=radio] { + box-sizing: border-box; + padding: 0 +} + +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { + height: auto +} + +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px +} + +[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit +} + +details { + display: block +} + +summary { + display: list-item +} + +template { + display: none +} + +[hidden] { + display: none +} +.limit-one-line, +#article-container .flink .flink-item-name, +#article-container .flink .flink-item-desc, +#aside-content .card-archives ul.card-archive-list > .card-archive-list-item a span, +#aside-content .card-categories ul.card-category-list > .card-category-list-item a span, +.site-data > a .headline, +#nav #blog-info, +#pagination .prev_info, +#pagination .next_info, +#sidebar #sidebar-menus .menus_items .site-page { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.limit-more-line, +.error404 #error-wrap .error-content .error-info .error_subtitle, +.article-sort-item-title, +#recent-posts > .recent-post-item >.recent-post-info > .article-title, +#recent-posts > .recent-post-item >.recent-post-info > .content, +#aside-content .aside-list > .aside-list-item .content > .name, +#aside-content .aside-list > .aside-list-item .content > .title, +#aside-content .aside-list > .aside-list-item .content > .comment, +#post-info .post-title, +.relatedPosts > .relatedPosts-list .content .title, +#article-container figure.gallery-group p, +#article-container figure.gallery-group .gallery-group-name { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; +} +.fontawesomeIcon, +.custom-hr:before, +#article-container.post-content h1:before, +#article-container.post-content h2:before, +#article-container.post-content h3:before, +#article-container.post-content h4:before, +#article-container.post-content h5:before, +#article-container.post-content h6:before, +#article-container.post-content hr:before, +#post .post-copyright:before, +#post .post-outdate-notice:before, +.note:not(.no-icon)::before, +.search-dialog hr:before { + display: inline-block; + font-weight: 600; + font-family: 'Font Awesome 6 Free'; + text-rendering: auto; + -webkit-font-smoothing: antialiased; +} +.cardHover, +.error404 #error-wrap .error-content, +.layout > div:first-child:not(.recent-posts), +#recent-posts > .recent-post-item, +#aside-content .card-widget, +.layout > .recent-posts .pagination > *:not(.space) { + border-radius: 8px; + background: var(--card-bg); + box-shadow: var(--card-box-shadow); + transition: all 0.3s; +} +.cardHover:hover, +.error404 #error-wrap .error-content:hover, +.layout > div:first-child:not(.recent-posts):hover, +#recent-posts > .recent-post-item:hover, +#aside-content .card-widget:hover, +.layout > .recent-posts .pagination > *:not(.space):hover { + box-shadow: var(--card-hover-box-shadow); +} +.imgHover, +.error404 #error-wrap .error-content .error-img img, +.article-sort-item-img :first-child, +#recent-posts > .recent-post-item .post_cover .post-bg, +#aside-content .aside-list > .aside-list-item .thumbnail :first-child { + width: 100%; + height: 100%; + transition: filter 375ms ease-in 0.2s, transform 0.6s; + -o-object-fit: cover; + object-fit: cover; +} +.imgHover:hover, +.error404 #error-wrap .error-content .error-img img:hover, +.article-sort-item-img :first-child:hover, +#recent-posts > .recent-post-item .post_cover .post-bg:hover, +#aside-content .aside-list > .aside-list-item .thumbnail :first-child:hover { + transform: scale(1.1); +} +.postImgHover:hover .cover, +#pagination .prev-post:hover .cover, +#pagination .next-post:hover .cover, +.relatedPosts > .relatedPosts-list > div:hover .cover { + opacity: 0.8; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; + filter: alpha(opacity=80); + transform: scale(1.1); +} +.postImgHover .cover, +#pagination .prev-post .cover, +#pagination .next-post .cover, +.relatedPosts > .relatedPosts-list > div .cover { + position: absolute; + width: 100%; + height: 100%; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + transition: all 0.6s, filter 375ms ease-in 0.2s; + -o-object-fit: cover; + object-fit: cover; +} +.list-beauty, +.category-lists ul { + list-style: none; +} +.list-beauty li, +.category-lists ul li { + position: relative; + padding: 0.12em 0.4em 0.12em 1.4em; +} +.list-beauty li:hover:before, +.category-lists ul li:hover:before { + border-color: var(--pseudo-hover); +} +.list-beauty li:before, +.category-lists ul li:before { + position: absolute; + top: 0.67em; + left: 0; + width: 0.43em; + height: 0.43em; + border: 0.215em solid #49b1f5; + border-radius: 0.43em; + background: transparent; + content: ''; + cursor: pointer; + transition: all 0.3s ease-out; +} +.custom-hr, +#article-container.post-content hr, +.search-dialog hr { + position: relative; + margin: 40px auto; + border: 2px dashed var(--hr-border); + width: calc(100% - 4px); +} +.custom-hr:hover:before, +#article-container.post-content hr:hover:before, +.search-dialog hr:hover:before { + left: calc(95% - 20px); +} +.custom-hr:before, +#article-container.post-content hr:before, +.search-dialog hr:before { + position: absolute; + top: -10px; + left: 5%; + z-index: 1; + color: var(--hr-before-color); + content: '\f0c4'; + font-size: 20px; + line-height: 1; + transition: all 1s ease-in-out; +} +#content-inner, +#footer { + animation: bottom-top 1s; +} +#page-header:not(.full_page) { + animation: header-effect 1s; +} +#site-title, +#site-subtitle { + animation: titleScale 1s; +} +#nav.show { + animation: headerNoOpacity 1s; +} +canvas:not(#ribbon-canvas), +#web_bg { + animation: to_show 4s; +} +#ribbon-canvas { + animation: ribbon_to_show 4s; +} +#sidebar-menus.open > :nth-child(1) { + animation: sidebarItem 0.2s; +} +#sidebar-menus.open > :nth-child(2) { + animation: sidebarItem 0.4s; +} +#sidebar-menus.open > :nth-child(3) { + animation: sidebarItem 0.6s; +} +#sidebar-menus.open > :nth-child(4) { + animation: sidebarItem 0.8s; +} +.scroll-down-effects { + animation: scroll-down-effect 1.5s infinite; +} +.reward-main { + animation: donate_effcet 0.3s 0.1s ease both; +} +@keyframes scroll-down-effect { + 0% { + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + transform: translate(0, 0); + } + 50% { + opacity: 1; + -ms-filter: none; + filter: none; + transform: translate(0, -16px); + } + 100% { + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + transform: translate(0, 0); + } +} +@keyframes header-effect { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transform: translateY(-50px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + transform: translateY(0); + } +} +@keyframes headerNoOpacity { + 0% { + transform: translateY(-50px); + } + 100% { + transform: translateY(0); + } +} +@keyframes bottom-top { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transform: translateY(50px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + transform: translateY(0); + } +} +@keyframes titleScale { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transform: scale(0.7); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + transform: scale(1); + } +} +@keyframes search_close { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + transform: scale(1); + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transform: scale(0.7); + } +} +@keyframes to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@keyframes to_hide { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } +} +@keyframes ribbon_to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +@keyframes avatar_turn_around { + from { + transform: rotate(0); + } + to { + transform: rotate(360deg); + } +} +@keyframes sub_menus { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transform: translateY(10px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + transform: translateY(0); + } +} +@keyframes donate_effcet { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transform: translateY(-20px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + transform: translateY(0); + } +} +@keyframes sidebarItem { + 0% { + transform: translateX(200px); + } + 100% { + transform: translateX(0); + } +} +:root { + --global-font-size: 14px; + --global-bg: #fff; + --font-color: #4c4948; + --hr-border: #a4d8fa; + --hr-before-color: #80c8f8; + --search-bg: #f6f8fa; + --search-input-color: #4c4948; + --search-a-color: #4c4948; + --preloader-bg: #37474f; + --preloader-color: #fff; + --tab-border-color: #f0f0f0; + --tab-botton-bg: #f0f0f0; + --tab-botton-color: #1f2d3d; + --tab-button-hover-bg: #dcdcdc; + --tab-button-active-bg: #fff; + --card-bg: #fff; + --sidebar-bg: #f6f8fa; + --btn-hover-color: #ff7242; + --btn-color: #fff; + --btn-bg: #49b1f5; + --text-bg-hover: rgba(73,177,245,0.7); + --light-grey: #eee; + --dark-grey: #cacaca; + --white: #fff; + --text-highlight-color: #1f2d3d; + --blockquote-color: #6a737d; + --blockquote-bg: rgba(73,177,245,0.1); + --reward-pop: #f5f5f5; + --toc-link-color: #666261; + --card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.05); + --card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09); + --pseudo-hover: #ff7242; + --headline-presudo: #a0a0a0; + --scrollbar-color: #49b1f5; + --default-bg-color: #49b1f5; + --zoom-bg: #fff; + --mark-bg: rgba(0,0,0,0.3); +} +body { + position: relative; + min-height: 100%; + background: var(--global-bg); + color: var(--font-color); + font-size: var(--global-font-size); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Lato, Roboto, "PingFang SC", "Microsoft JhengHei", "Microsoft YaHei", sans-serif; + line-height: 2; + -webkit-tap-highlight-color: rgba(0,0,0,0); +} +*::-webkit-scrollbar { + width: 5px; + height: 5px; +} +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-color); +} +*::-webkit-scrollbar-track { + background-color: transparent; +} +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-color) transparent; +} +input::-moz-placeholder { + color: var(--font-color); +} +input:-ms-input-placeholder { + color: var(--font-color); +} +input::placeholder { + color: var(--font-color); +} +#web_bg { + position: fixed; + z-index: -999; + width: 100%; + height: 100%; + background: /images/bg-2.jpg; + background-attachment: local; + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} +h1, +h2, +h3, +h4, +h5, +h6 { + position: relative; + margin: 20px 0 14px; + color: var(--text-highlight-color); + font-weight: bold; +} +h1 code, +h2 code, +h3 code, +h4 code, +h5 code, +h6 code { + font-size: inherit !important; +} +* { + box-sizing: border-box; +} +.table-wrap { + overflow-x: scroll; + margin: 0 0 20px; +} +table { + display: table; + width: 100%; + border-spacing: 0; + border-collapse: collapse; + empty-cells: show; +} +table thead { + background: rgba(153,169,191,0.1); +} +table th, +table td { + padding: 6px 12px; + border: 1px solid var(--light-grey); + vertical-align: middle; +} +*::-moz-selection { + background: #00c4b6; + color: #f7f7f7; +} +*::selection { + background: #00c4b6; + color: #f7f7f7; +} +button { + padding: 0; + outline: 0; + border: none; + background: none; + cursor: pointer; + -ms-touch-action: manipulation; + touch-action: manipulation; +} +a { + color: #99a9bf; + text-decoration: none; + word-wrap: break-word; + transition: all 0.2s; + overflow-wrap: break-word; +} +a:hover { + color: #49b1f5; +} +#site-title, +#site-subtitle, +.site-name, +#aside-content .author-info__name, +#aside-content .author-info__description { + font-family: Titillium Web, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft JhengHei', 'Microsoft YaHei', sans-serif; +} +.is-center { + text-align: center; +} +.pull-left { + float: left; +} +.pull-right { + float: right; +} +img[src=''], +img:not([src]) { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +.img-alt { + margin: -10px 0 10px; + color: #858585; +} +.img-alt:hover { + text-decoration: none !important; +} +blockquote { + margin: 0 0 20px; + padding: 12px 15px; + border-left: 3px solid #49b1f5; + background-color: var(--blockquote-bg); + color: var(--blockquote-color); +} +blockquote footer cite:before { + padding: 0 5px; + content: '—'; +} +blockquote > :last-child { + margin-bottom: 0 !important; +} +:root { + --hl-color: #90a4ae; + --hl-bg: #f6f8fa; + --hltools-bg: #e6ebf1; + --hltools-color: #90a4ae; + --hlnumber-bg: #f6f8fa; + --hlnumber-color: rgba(144,164,174,0.5); + --hlscrollbar-bg: #dce4eb; + --hlexpand-bg: linear-gradient(180deg, rgba(246,248,250,0.6), rgba(246,248,250,0.9)); +} +[data-theme='dark'] { + --hl-color: rgba(255,255,255,0.7); + --hl-bg: #171717; + --hltools-bg: #1a1a1a; + --hltools-color: #90a4ae; + --hlnumber-bg: #171717; + --hlnumber-color: rgba(255,255,255,0.4); + --hlscrollbar-bg: #1f1f1f; + --hlexpand-bg: linear-gradient(180deg, rgba(23,23,23,0.6), rgba(23,23,23,0.9)); +} +#article-container pre[class*='language-'].line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; + line-height: 1.6; +} +#article-container pre[class*='language-'].line-numbers > code { + position: relative; + line-height: 1.6; + white-space: pre-wrap; +} +#article-container pre[class*='language-'].line-numbers .line-numbers-rows { + position: absolute; + top: 0; + left: -3.8em; + width: 3em; + letter-spacing: -1px; + font-size: 100%; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-select: none; +} +#article-container pre[class*='language-'].line-numbers .line-numbers-rows > span { + display: block; + counter-increment: linenumber; + pointer-events: none; +} +#article-container pre[class*='language-'].line-numbers .line-numbers-rows > span:before { + display: block; + padding-right: 0.8em; + color: var(--hlnumber-color); + content: counter(linenumber); + text-align: right; +} +pre[class*='language-'] .token.function { + color: #ffb62c; +} +pre[class*='language-'] .token.comment, +pre[class*='language-'] .token.prolog, +pre[class*='language-'] .token.doctype, +pre[class*='language-'] .token.cdata { + color: rgba(149,165,166,0.8); +} +pre[class*='language-'] .token.punctuation { + color: #5e6687; +} +pre[class*='language-'] .token.namespace { + opacity: 0.7; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; + filter: alpha(opacity=70); +} +pre[class*='language-'] .token.operator, +pre[class*='language-'] .token.boolean, +pre[class*='language-'] .token.number { + color: #c76b29; +} +pre[class*='language-'] .token.property { + color: #c08b30; +} +pre[class*='language-'] .token.tag { + color: #3d8fd1; +} +pre[class*='language-'] .token.string { + color: #22a2c9; +} +pre[class*='language-'] .token.selector { + color: #6679cc; +} +pre[class*='language-'] .token.attr-name { + color: #c76b29; +} +pre[class*='language-'] .token.entity, +pre[class*='language-'] .token.url, +pre[class*='language-'] .language-css .token.string, +pre[class*='language-'] .style .token.string { + color: #22a2c9; +} +pre[class*='language-'] .token.attr-value, +pre[class*='language-'] .token.keyword, +pre[class*='language-'] .token.control, +pre[class*='language-'] .token.directive, +pre[class*='language-'] .token.unit { + color: #ac9739; +} +pre[class*='language-'] .token.statement, +pre[class*='language-'] .token.regex, +pre[class*='language-'] .token.atrule { + color: #22a2c9; +} +pre[class*='language-'] .token.placeholder, +pre[class*='language-'] .token.variable { + color: #3d8fd1; +} +pre[class*='language-'] .token.deleted { + text-decoration: line-through; +} +pre[class*='language-'] .token.inserted { + border-bottom: 1px dotted #202746; + text-decoration: none; +} +pre[class*='language-'] .token.italic { + font-style: italic; +} +pre[class*='language-'] .token.important, +pre[class*='language-'] .token.bold { + font-weight: bold; +} +pre[class*='language-'] .token.important { + color: #c94922; +} +pre[class*='language-'] .token.entity { + cursor: help; +} +pre[class*='language-'] pre > code.highlight { + outline: 0.4em solid #c94922; + outline-offset: 0.4em; +} +#article-container pre[class*='language-'] { + scrollbar-color: var(--hlscrollbar-bg) transparent; +} +#article-container pre[class*='language-']::-webkit-scrollbar-thumb { + background: var(--hlscrollbar-bg); +} +#article-container pre[class*='language-']:not(.line-numbers) { + padding: 10px 20px; +} +#article-container pre[class*='language-'] .caption { + margin-left: -3.8em; + padding: 4px 16px !important; +} +#article-container pre[class*='language-'] .caption a { + padding: 0 !important; +} +#article-container pre, +#article-container figure.highlight { + overflow: auto; + margin: 0 0 20px; + padding: 0; + background: var(--hl-bg); + color: var(--hl-color); + line-height: 1.6; +} +#article-container pre, +#article-container code { + font-size: var(--global-font-size); + font-family: consolas, Menlo, "PingFang SC", "Microsoft JhengHei", "Microsoft YaHei", sans-serif !important; +} +#article-container code { + padding: 2px 4px; + background: rgba(27,31,35,0.05); + color: #f47466; +} +#article-container pre { + padding: 10px 20px; +} +#article-container pre code { + padding: 0; + background: none; + color: var(--hl-color); + text-shadow: none; +} +#article-container figure.highlight { + position: relative; +} +#article-container figure.highlight pre { + margin: 0; + padding: 8px 0; + border: none; +} +#article-container figure.highlight figcaption, +#article-container figure.highlight .caption { + padding: 6px 0 2px 14px; + font-size: var(--global-font-size); + line-height: 1em; +} +#article-container figure.highlight figcaption a, +#article-container figure.highlight .caption a { + float: right; + padding-right: 10px; + color: var(--hl-color); +} +#article-container figure.highlight figcaption a:hover, +#article-container figure.highlight .caption a:hover { + border-bottom-color: var(--hl-color); +} +#article-container figure.highlight.copy-true { + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + -webkit-user-select: all; +} +#article-container figure.highlight.copy-true > table, +#article-container figure.highlight.copy-true > pre { + display: block !important; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +#article-container .highlight-tools { + position: relative; + display: -ms-flexbox; + display: box; + display: flex; + -o-box-align: center; + -ms-flex-align: center; + align-items: center; + overflow: hidden; + min-height: 24px; + height: 2.15em; + background: var(--hltools-bg); + color: var(--hltools-color); + font-size: var(--global-font-size); +} +#article-container .highlight-tools.closed ~ * { + display: none; +} +#article-container .highlight-tools.closed .expand { + transition: all 0.3s; + transform: rotate(-90deg) !important; +} +#article-container .highlight-tools .expand { + position: absolute; + padding: 0.57em 0.7em; + cursor: pointer; + transition: transform 0.3s; +} +#article-container .highlight-tools .expand + .code-lang { + left: 1.7em; +} +#article-container .highlight-tools .code-lang { + position: absolute; + left: 14px; + text-transform: uppercase; + font-weight: bold; + font-size: 1.15em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-select: none; +} +#article-container .highlight-tools .copy-notice { + position: absolute; + right: 2.4em; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transition: opacity 0.4s; +} +#article-container .highlight-tools .copy-button { + position: absolute; + right: 14px; + cursor: pointer; + transition: color 0.2s; +} +#article-container .highlight-tools .copy-button:hover { + color: #49b1f5; +} +#article-container .gutter { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-select: none; +} +#article-container .gist table { + width: auto; +} +#article-container .gist table td { + border: none; +} +#article-container figure.highlight { + margin: 0 0 24px; + border-radius: 7px; + box-shadow: 0 5px 10px 0 rgba(144,164,174,0.4); + -webkit-transform: translateZ(0); +} +#article-container figure.highlight .highlight-tools:after { + position: absolute; + left: 14px; + width: 12px; + height: 12px; + border-radius: 50%; + background: #fc625d; + box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b; + content: ' '; +} +#article-container figure.highlight .highlight-tools .expand { + right: 0; +} +#article-container figure.highlight .highlight-tools .expand.closed { + transition: all 0.3s; + transform: rotate(90deg) !important; +} +#article-container figure.highlight .highlight-tools .expand ~ .copy-notice { + right: 3.45em; +} +#article-container figure.highlight .highlight-tools .expand ~ .copy-button { + right: 2.1em; +} +#article-container figure.highlight .highlight-tools .code-lang { + left: 75px; +} +#article-container .code-expand-btn { + position: absolute; + bottom: 0; + z-index: 10; + width: 100%; + background: var(--hlexpand-bg); + text-align: center; + font-size: var(--global-font-size); + cursor: pointer; +} +#article-container .code-expand-btn i { + padding: 6px 0; + color: var(--hlnumber-color); + animation: code-expand-key 1.2s infinite; +} +#article-container .code-expand-btn.expand-done > i { + transform: rotate(180deg); +} +#article-container .code-expand-btn.expand-done + table, +#article-container .code-expand-btn.expand-done + pre { + margin-bottom: 1.8em; +} +#article-container .code-expand-btn:not(.expand-done) ~ table, +#article-container .code-expand-btn:not(.expand-done) ~ pre { + overflow: hidden; + height: 220px; +} +@keyframes code-expand-key { + 0% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } + 50% { + opacity: 0.1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)"; + filter: alpha(opacity=10); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +.error404 #error-wrap { + position: absolute; + top: 50%; + right: 0; + left: 0; + margin: 0 auto; + padding: 60px 20px 0; + max-width: 1000px; + transform: translate(0, -50%); +} +.error404 #error-wrap .error-content { + overflow: hidden; + margin: 0 20px; + height: 360px; +} +@media screen and (max-width: 768px) { + .error404 #error-wrap .error-content { + margin: 0; + height: 500px; + } +} +.error404 #error-wrap .error-content .error-img { + display: inline-block; + overflow: hidden; + width: 50%; + height: 100%; +} +@media screen and (max-width: 768px) { + .error404 #error-wrap .error-content .error-img { + width: 100%; + height: 45%; + } +} +.error404 #error-wrap .error-content .error-img img { + background-color: #49b1f5; +} +.error404 #error-wrap .error-content .error-info { + display: -ms-inline-flexbox; + display: inline-box; + display: inline-flex; + -o-box-orient: vertical; + -ms-flex-direction: column; + flex-direction: column; + -o-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -ms-flex-line-pack: center; + align-content: center; + width: 50%; + height: 100%; + vertical-align: top; + text-align: center; + font-family: Titillium Web, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft JhengHei', 'Microsoft YaHei', sans-serif; +} +@media screen and (max-width: 768px) { + .error404 #error-wrap .error-content .error-info { + width: 100%; + height: 55%; + } +} +.error404 #error-wrap .error-content .error-info .error_title { + margin-top: -0.6em; + font-size: 9em; +} +@media screen and (max-width: 768px) { + .error404 #error-wrap .error-content .error-info .error_title { + font-size: 8em; + } +} +.error404 #error-wrap .error-content .error-info .error_subtitle { + margin-top: -3em; + word-break: break-word; + font-size: 1.6em; + -webkit-line-clamp: 2; +} +.error404 + #rightside { + display: none; +} +.article-sort { + margin-left: 10px; + padding-left: 20px; + border-left: 2px solid #aadafa; +} +.article-sort-title { + position: relative; + margin-left: 10px; + padding-bottom: 20px; + padding-left: 20px; + font-size: 1.72em; +} +.article-sort-title:hover:before { + border-color: var(--pseudo-hover); +} +.article-sort-title:before { + position: absolute; + top: calc(((100% - 36px) / 2)); + left: -9px; + z-index: 1; + width: 10px; + height: 10px; + border: 5px solid #49b1f5; + border-radius: 10px; + background: var(--card-bg); + content: ''; + line-height: 10px; + transition: all 0.2s ease-in-out; +} +.article-sort-title:after { + position: absolute; + bottom: 0; + left: 0; + z-index: 0; + width: 2px; + height: 1.5em; + background: #aadafa; + content: ''; +} +.article-sort-item { + position: relative; + display: -ms-flexbox; + display: box; + display: flex; + -o-box-align: center; + -ms-flex-align: center; + align-items: center; + margin: 0 0 20px 10px; + transition: all 0.2s ease-in-out; +} +.article-sort-item:hover:before { + border-color: var(--pseudo-hover); +} +.article-sort-item:before { + position: absolute; + left: calc(-20px - 17px); + width: 6px; + height: 6px; + border: 3px solid #49b1f5; + border-radius: 6px; + background: var(--card-bg); + content: ''; + transition: all 0.2s ease-in-out; +} +.article-sort-item.no-article-cover { + height: 80px; +} +.article-sort-item.no-article-cover .article-sort-item-info { + padding: 0; +} +.article-sort-item.year { + font-size: 1.43em; +} +.article-sort-item.year:hover:before { + border-color: #49b1f5; +} +.article-sort-item.year:before { + border-color: var(--pseudo-hover); +} +.article-sort-item-time { + color: #858585; + font-size: 95%; +} +.article-sort-item-time time { + padding-left: 6px; + cursor: default; +} +.article-sort-item-title { + color: var(--font-color); + font-size: 1.1em; + transition: all 0.3s; + -webkit-line-clamp: 2; +} +.article-sort-item-title:hover { + color: #49b1f5; + transform: translateX(10px); +} +.article-sort-item-img { + overflow: hidden; + width: 80px; + height: 80px; +} +.article-sort-item-info { + -o-box-flex: 1; + box-flex: 1; + -ms-flex: 1; + flex: 1; + padding: 0 16px; +} +.category-lists .category-title { + font-size: 2.57em; +} +@media screen and (max-width: 768px) { + .category-lists .category-title { + font-size: 2em; + } +} +.category-lists .category-list { + margin-bottom: 0; +} +.category-lists .category-list a { + color: var(--font-color); +} +.category-lists .category-list a:hover { + color: #49b1f5; +} +.category-lists .category-list .category-list-count { + margin-left: 8px; + color: #858585; +} +.category-lists .category-list .category-list-count:before { + content: '('; +} +.category-lists .category-list .category-list-count:after { + content: ')'; +} +.category-lists ul { + padding: 0 0 0 20px; +} +.category-lists ul ul { + padding-left: 4px; +} +.category-lists ul li { + position: relative; + margin: 6px 0; + padding: 0.12em 0.4em 0.12em 1.4em; +} +#body-wrap { + display: -ms-flexbox; + display: box; + display: flex; + -o-box-orient: vertical; + -ms-flex-direction: column; + flex-direction: column; + min-height: 100vh; +} +.layout { + display: -ms-flexbox; + display: box; + display: flex; + -o-box-flex: 1; + box-flex: 1; + -ms-flex: 1 auto; + flex: 1 auto; + margin: 0 auto; + padding: 40px 15px; + max-width: 1200px; + width: 100%; +} +@media screen and (max-width: 900px) { + .layout { + -o-box-orient: vertical; + -ms-flex-direction: column; + flex-direction: column; + } +} +@media screen and (max-width: 768px) { + .layout { + padding: 20px 5px; + } +} +@media screen and (min-width: 2000px) { + .layout { + max-width: 70%; + } +} +.layout > div:first-child:not(.recent-posts) { + align-self: flex-start; + -ms-flex-item-align: start; + padding: 50px 40px; +} +@media screen and (max-width: 768px) { + .layout > div:first-child:not(.recent-posts) { + padding: 36px 14px; + } +} +.layout > div:first-child { + width: 74%; + transition: all 0.3s; +} +@media screen and (max-width: 900px) { + .layout > div:first-child { + width: 100% !important; + } +} +@media screen and (min-width: 900px) { + .layout > div:first-child { + -o-box-ordinal-group: 2; + -ms-flex-order: 2; + order: 2; + } +} +.layout.hide-aside { + max-width: 1000px; +} +@media screen and (min-width: 2000px) { + .layout.hide-aside { + max-width: 1300px; + } +} +.layout.hide-aside > div { + width: 100% !important; +} +.apple #page-header.full_page { + background-attachment: scroll !important; +} +.apple .recent-post-item, +.apple .avatar-img, +.apple .flink-item-icon { + transform: translateZ(0); +} +#article-container .flink { + margin-bottom: 20px; +} +#article-container .flink .flink-list { + overflow: auto; + padding: 10px 10px 0; + text-align: center; +} +#article-container .flink .flink-list > .flink-list-item { + position: relative; + float: left; + overflow: hidden; + margin: 15px 7px; + width: calc(100% / 3 - 15px); + height: 90px; + border-radius: 8px; + line-height: 17px; + -webkit-transform: translateZ(0); +} +@media screen and (max-width: 1024px) { + #article-container .flink .flink-list > .flink-list-item { + width: calc(50% - 15px) !important; + } +} +@media screen and (max-width: 600px) { + #article-container .flink .flink-list > .flink-list-item { + width: calc(100% - 15px) !important; + } +} +#article-container .flink .flink-list > .flink-list-item:hover .flink-item-icon { + margin-left: -10px; + width: 0; +} +#article-container .flink .flink-list > .flink-list-item:before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -1; + background: var(--text-bg-hover); + content: ''; + transition: transform 0.3s ease-out; + transform: scale(0); +} +#article-container .flink .flink-list > .flink-list-item:hover:before, +#article-container .flink .flink-list > .flink-list-item:focus:before, +#article-container .flink .flink-list > .flink-list-item:active:before { + transform: scale(1); +} +#article-container .flink .flink-list > .flink-list-item a { + color: var(--font-color); + text-decoration: none; +} +#article-container .flink .flink-list > .flink-list-item a .flink-item-icon { + float: left; + overflow: hidden; + margin: 15px 10px; + width: 60px; + height: 60px; + border-radius: 35px; + transition: width 0.3s ease-out; +} +#article-container .flink .flink-list > .flink-list-item a .flink-item-icon img { + width: 100%; + height: 100%; + transition: filter 375ms ease-in 0.2s, transform 0.3s; + -o-object-fit: cover; + object-fit: cover; +} +#article-container .flink .flink-list > .flink-list-item a .img-alt { + display: none; +} +#article-container .flink .flink-item-name { + padding: 16px 10px 0 0; + height: 40px; + font-weight: bold; + font-size: 1.43em; +} +#article-container .flink .flink-item-desc { + padding: 16px 10px 16px 0; + height: 50px; + font-size: 0.93em; +} +#article-container .flink .flink-name { + margin-bottom: 5px; + font-weight: bold; + font-size: 1.5em; +} +#recent-posts > .recent-post-item:not(:first-child) { + margin-top: 20px; +} +#recent-posts > .recent-post-item { + display: -ms-flexbox; + display: box; + display: flex; + -o-box-orient: horizontal; + -ms-flex-direction: row; + flex-direction: row; + -o-box-align: center; + -ms-flex-align: center; + align-items: center; + overflow: hidden; + height: 16.8em; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item { + -o-box-orient: vertical; + -ms-flex-direction: column; + flex-direction: column; + height: auto; + } +} +#recent-posts > .recent-post-item:hover img.post-bg { + transform: scale(1.1); +} +#recent-posts > .recent-post-item.ads-wrap { + display: block !important; + height: auto !important; +} +#recent-posts > .recent-post-item .post_cover { + overflow: hidden; + width: 42%; + height: 100%; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item .post_cover { + width: 100%; + height: 230px; + } +} +#recent-posts > .recent-post-item .post_cover.right { + -o-box-ordinal-group: 1; + -ms-flex-order: 1; + order: 1; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item .post_cover.right { + -o-box-ordinal-group: 0; + -ms-flex-order: 0; + order: 0; + } +} +#recent-posts > .recent-post-item >.recent-post-info { + padding: 0 40px; + width: 58%; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item >.recent-post-info { + padding: 20px 20px 30px; + width: 100%; + } +} +#recent-posts > .recent-post-item >.recent-post-info.no-cover { + width: 100%; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item >.recent-post-info.no-cover { + padding: 30px 20px; + } +} +#recent-posts > .recent-post-item >.recent-post-info > .article-title { + color: var(--text-highlight-color); + font-size: 1.55em; + line-height: 1.4; + transition: all 0.2s ease-in-out; + -webkit-line-clamp: 2; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-title .sticky { + margin-right: 10px; + color: #ff7242; + transform: rotate(45deg); +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item >.recent-post-info > .article-title { + font-size: 1.43em; + } +} +#recent-posts > .recent-post-item >.recent-post-info > .article-title:hover { + color: #49b1f5; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap { + margin: 6px 0; + color: #858585; + font-size: 0.9em; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap > .post-meta-date { + cursor: default; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap i { + margin: 0 4px 0 0; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .fa-spinner { + margin: 0; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .article-meta-label { + padding-right: 4px; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .article-meta-separator { + margin: 0 6px; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .article-meta-link { + margin: 0 4px; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap a { + color: #858585; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap a:hover { + color: #49b1f5; + text-decoration: underline; +} +#recent-posts > .recent-post-item >.recent-post-info > .content { + -webkit-line-clamp: 2; +} +.tag-cloud-list a { + display: inline-block; + padding: 0 8px; + transition: all 0.3s; +} +.tag-cloud-list a:hover { + color: #49b1f5 !important; + transform: scale(1.1); +} +@media screen and (max-width: 768px) { + .tag-cloud-list a { + zoom: 0.85; + } +} +.tag-cloud-title { + font-size: 2.57em; +} +@media screen and (max-width: 768px) { + .tag-cloud-title { + font-size: 2em; + } +} +h1.page-title + .tag-cloud-list { + text-align: left; +} +#aside-content { + width: 26%; +} +@media screen and (min-width: 900px) { + #aside-content { + padding-right: 15px; + } +} +@media screen and (max-width: 900px) { + #aside-content { + width: 100%; + } +} +#aside-content > .card-widget:first-child { + margin-top: 0; +} +@media screen and (max-width: 900px) { + #aside-content > .card-widget:first-child { + margin-top: 20px; + } +} +#aside-content .card-widget { + position: relative; + overflow: hidden; + margin-top: 20px; + padding: 20px 24px; +} +#aside-content .card-info .author-info__name { + font-weight: 500; + font-size: 1.57em; +} +#aside-content .card-info .author-info__description { + margin-top: -0.42em; +} +#aside-content .card-info .card-info-data { + margin: 14px 0 4px; +} +#aside-content .card-info .card-info-social-icons { + margin: 6px 0 -6px; +} +#aside-content .card-info .card-info-social-icons .social-icon { + margin: 0 10px; + color: var(--font-color); + font-size: 1.4em; +} +#aside-content .card-info .card-info-social-icons i { + transition: all 0.3s; +} +#aside-content .card-info .card-info-social-icons i:hover { + transform: rotate(360deg); +} +#aside-content .card-info #card-info-btn { + display: block; + margin-top: 14px; + background-color: var(--btn-bg); + color: var(--btn-color); + text-align: center; + line-height: 2.4; +} +#aside-content .card-info #card-info-btn:hover { + background-color: var(--btn-hover-color); +} +#aside-content .card-info #card-info-btn span { + padding-left: 10px; +} +#aside-content .item-headline { + padding-bottom: 6px; + font-size: 1.2em; +} +#aside-content .item-headline span { + margin-left: 6px; +} +@media screen and (min-width: 900px) { + #aside-content .sticky_layout { + position: sticky; + position: -webkit-sticky; + top: 20px; + transition: top 0.3s; + } +} +#aside-content .card-tag-cloud a { + display: inline-block; + padding: 0 4px; +} +#aside-content .card-tag-cloud a:hover { + color: #49b1f5 !important; +} +#aside-content .aside-list > span { + display: block; + margin-bottom: 10px; + text-align: center; +} +#aside-content .aside-list > .aside-list-item { + display: -ms-flexbox; + display: box; + display: flex; + -o-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 6px 0; +} +#aside-content .aside-list > .aside-list-item:first-child { + padding-top: 0; +} +#aside-content .aside-list > .aside-list-item:not(:last-child) { + border-bottom: 1px dashed #f5f5f5; +} +#aside-content .aside-list > .aside-list-item:last-child { + padding-bottom: 0; +} +#aside-content .aside-list > .aside-list-item .thumbnail { + overflow: hidden; + width: 4.2em; + height: 4.2em; +} +#aside-content .aside-list > .aside-list-item .content { + -o-box-flex: 1; + box-flex: 1; + -ms-flex: 1; + flex: 1; + padding-left: 10px; + word-break: break-all; +} +#aside-content .aside-list > .aside-list-item .content > .name { + -webkit-line-clamp: 1; +} +#aside-content .aside-list > .aside-list-item .content > time, +#aside-content .aside-list > .aside-list-item .content > .name { + display: block; + color: #858585; + font-size: 85%; +} +#aside-content .aside-list > .aside-list-item .content > .title, +#aside-content .aside-list > .aside-list-item .content > .comment { + color: var(--font-color); + font-size: 95%; + line-height: 1.5; + -webkit-line-clamp: 2; +} +#aside-content .aside-list > .aside-list-item .content > .title:hover, +#aside-content .aside-list > .aside-list-item .content > .comment:hover { + color: #49b1f5; +} +#aside-content .aside-list > .aside-list-item.no-cover { + min-height: 4.4em; +} +#aside-content .card-archives ul.card-archive-list, +#aside-content .card-categories ul.card-category-list { + margin: 0; + padding: 0; + list-style: none; +} +#aside-content .card-archives ul.card-archive-list > .card-archive-list-item a, +#aside-content .card-categories ul.card-category-list > .card-category-list-item a { + display: -ms-flexbox; + display: box; + display: flex; + -o-box-orient: horizontal; + -ms-flex-direction: row; + flex-direction: row; + padding: 3px 10px; + color: var(--font-color); + transition: all 0.4s; +} +#aside-content .card-archives ul.card-archive-list > .card-archive-list-item a:hover, +#aside-content .card-categories ul.card-category-list > .card-category-list-item a:hover { + padding: 3px 17px; + background-color: var(--text-bg-hover); +} +#aside-content .card-archives ul.card-archive-list > .card-archive-list-item a span:first-child, +#aside-content .card-categories ul.card-category-list > .card-category-list-item a span:first-child { + -o-box-flex: 1; + box-flex: 1; + -ms-flex: 1; + flex: 1; +} +#aside-content .card-categories .card-category-list.child { + padding: 0 0 0 16px; +} +#aside-content .card-categories .card-category-list > .parent > a.expand i { + transform: rotate(-90deg); +} +#aside-content .card-categories .card-category-list > .parent > a.expand + .child { + display: block; +} +#aside-content .card-categories .card-category-list > .parent > a .card-category-list-name { + width: 70% !important; +} +#aside-content .card-categories .card-category-list > .parent > a .card-category-list-count { + width: calc(100% - 70% - 20px); + text-align: right; +} +#aside-content .card-categories .card-category-list > .parent > a i { + float: right; + margin-right: -0.5em; + padding: 0.5em; + transition: transform 0.3s; + transform: rotate(0); +} +#aside-content .card-categories .card-category-list > .parent > .child { + display: none; +} +#aside-content .card-webinfo .webinfo .webinfo-item { + display: -ms-flexbox; + display: box; + display: flex; + -o-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 2px 10px 0; +} +#aside-content .card-webinfo .webinfo .webinfo-item div:first-child { + -o-box-flex: 1; + box-flex: 1; + -ms-flex: 1; + flex: 1; + padding-right: 20px; +} +@media screen and (min-width: 901px) { + #aside-content #card-toc { + right: 0 !important; + } +} +@media screen and (max-width: 900px) { + #aside-content #card-toc { + position: fixed; + right: 55px; + bottom: 30px; + z-index: 100; + max-width: 380px; + max-height: calc(100% - 60px); + width: calc(100% - 80px); + transition: none; + transform: scale(0); + transform-origin: right bottom; + } + #aside-content #card-toc.open { + transform: scale(1); + } +} +#aside-content #card-toc .toc-percentage { + float: right; + margin-top: -9px; + color: #a9a9a9; + font-style: italic; + font-size: 140%; +} +#aside-content #card-toc .toc-content { + overflow-y: scroll; + overflow-y: overlay; + margin: 0 -24px; + max-height: calc(100vh - 120px); + width: calc(100% + 48px); +} +@media screen and (max-width: 900px) { + #aside-content #card-toc .toc-content { + max-height: calc(100vh - 140px); + } +} +#aside-content #card-toc .toc-content > * { + margin: 0 20px !important; +} +#aside-content #card-toc .toc-content > * > .toc-item > .toc-child { + margin-left: 10px; + padding-left: 10px; + border-left: 1px solid var(--dark-grey); +} +#aside-content #card-toc .toc-content:not(.is-expand) .toc-child { + display: none; +} +@media screen and (max-width: 900px) { + #aside-content #card-toc .toc-content:not(.is-expand) .toc-child { + display: block !important; + } +} +#aside-content #card-toc .toc-content:not(.is-expand) .toc-item.active .toc-child { + display: block; +} +#aside-content #card-toc .toc-content ol, +#aside-content #card-toc .toc-content li { + list-style: none; +} +#aside-content #card-toc .toc-content > ol { + padding: 0 !important; +} +#aside-content #card-toc .toc-content ol { + margin: 0; + padding-left: 18px; +} +#aside-content #card-toc .toc-content .toc-link { + display: block; + margin: 4px 0; + padding: 1px 6px; + color: var(--toc-link-color); + transition: all 0.2s ease-in-out; +} +#aside-content #card-toc .toc-content .toc-link:hover { + color: #49b1f5; +} +#aside-content #card-toc .toc-content .toc-link.active { + background: #00c4b6; + color: #fff; +} +#aside-content .sticky_layout:only-child > :first-child { + margin-top: 0; +} +#aside-content .card-more-btn { + float: right; + color: inherit; +} +#aside-content .card-more-btn:hover { + animation: more-btn-move 1s infinite; +} +#aside-content .card-announcement .item-headline i { + color: #f00; +} +.avatar-img { + overflow: hidden; + margin: 0 auto; + width: 110px; + height: 110px; + border-radius: 70px; +} +.avatar-img img { + width: 100%; + height: 100%; + transition: filter 375ms ease-in 0.2s, transform 0.3s; + -o-object-fit: cover; + object-fit: cover; +} +.avatar-img img:hover { + transform: rotate(360deg); +} +.site-data { + display: table; + width: 100%; + table-layout: fixed; +} +.site-data > a { + display: table-cell; +} +.site-data > a div { + transition: all 0.3s; +} +.site-data > a:hover div { + color: #49b1f5 !important; +} +.site-data > a .headline { + color: var(--font-color); +} +.site-data > a .length-num { + margin-top: -0.32em; + color: var(--text-highlight-color); + font-size: 1.4em; +} +@media screen and (min-width: 900px) { + html.hide-aside .layout { + -o-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + } + html.hide-aside .layout > .aside-content { + display: none; + } + html.hide-aside .layout > div:first-child { + width: 80%; + } +} +.page .sticky_layout { + display: -ms-flexbox; + display: box; + display: flex; + -o-box-orient: vertical; + -ms-flex-direction: column; + flex-direction: column; +} +@keyframes more-btn-move { + 0%, 100% { + transform: translateX(0); + } + 50% { + transform: translateX(3px); + } +} +@keyframes toc-open { + 0% { + transform: scale(0.7); + } + 100% { + transform: scale(1); + } +} +@keyframes toc-close { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0.7); + } +} +#post-comment .comment-head { + margin-bottom: 20px; +} +#post-comment .comment-head:after { + display: block; + clear: both; + content: ''; +} +#post-comment .comment-head .comment-headline { + display: inline-block; + vertical-align: middle; + font-weight: 700; + font-size: 1.43em; +} +#post-comment .comment-head .comment-switch { + display: inline-block; + float: right; + margin: 2px auto 0; + padding: 4px 16px; + width: -moz-max-content; + width: max-content; + border-radius: 8px; + background: #f6f8fa; +} +#post-comment .comment-head .comment-switch .first-comment { + color: #49b1f5; +} +#post-comment .comment-head .comment-switch .second-comment { + color: #ff7242; +} +#post-comment .comment-head .comment-switch #switch-btn { + position: relative; + display: inline-block; + margin: -4px 8px 0; + width: 42px; + height: 22px; + border-radius: 34px; + background-color: #49b1f5; + vertical-align: middle; + cursor: pointer; + transition: 0.4s; +} +#post-comment .comment-head .comment-switch #switch-btn:before { + position: absolute; + bottom: 4px; + left: 4px; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: #fff; + content: ''; + transition: 0.4s; +} +#post-comment .comment-wrap > div { + animation: tabshow 0.5s; +} +#post-comment .comment-wrap > div:nth-child(2) { + display: none; +} +#post-comment.move #switch-btn { + background-color: #ff7242; +} +#post-comment.move #switch-btn:before { + transform: translateX(20px); +} +#post-comment.move .comment-wrap > div:first-child { + display: none; +} +#post-comment.move .comment-wrap > div:last-child { + display: block; +} +#footer { + position: relative; + background-color: #49b1f5; + background-attachment: scroll; + background-position: bottom; + background-size: cover; +} +#footer:before { + position: absolute; + width: 100%; + height: 100%; + background-color: var(--mark-bg); + content: ''; +} +#footer-wrap { + position: relative; + padding: 40px 20px; + color: var(--light-grey); + text-align: center; +} +#footer-wrap a { + color: var(--light-grey); +} +#footer-wrap a:hover { + text-decoration: underline; +} +#footer-wrap .footer-separator { + margin: 0 4px; +} +#footer-wrap .icp-icon { + padding: 0 4px; + max-height: 1.4em; + width: auto; + vertical-align: text-bottom; +} +#page-header { + position: relative; + width: 100%; + background-color: #49b1f5; + background-position: center center; + background-size: cover; + background-repeat: no-repeat; + transition: all 0.5s; +} +#page-header:not(.not-top-img):before { + position: absolute; + width: 100%; + height: 100%; + background-color: var(--mark-bg); + content: ''; +} +#page-header.full_page { + height: 100vh; + background-attachment: fixed; +} +#page-header.full_page #site-info { + position: absolute; + top: 43%; + padding: 0 10px; + width: 100%; +} +#page-header #site-title, +#page-header #site-subtitle, +#page-header #scroll-down .scroll-down-effects { + text-align: center; + text-shadow: 2px 2px 4px rgba(0,0,0,0.15); + line-height: 1.5; +} +#page-header #site-title { + margin: 0; + color: var(--white); + font-size: 1.85em; +} +@media screen and (min-width: 768px) { + #page-header #site-title { + font-size: 2.85em; + } +} +#page-header #site-subtitle { + color: var(--light-grey); + font-size: 1.15em; +} +@media screen and (min-width: 768px) { + #page-header #site-subtitle { + font-size: 1.72em; + } +} +#page-header #site_social_icons { + display: none; + margin: 0 auto; + text-align: center; +} +@media screen and (max-width: 768px) { + #page-header #site_social_icons { + display: block; + } +} +#page-header #site_social_icons .social-icon { + margin: 0 10px; + color: var(--light-grey); + text-shadow: 2px 2px 4px rgba(0,0,0,0.15); + font-size: 1.43em; +} +#page-header #scroll-down { + position: absolute; + bottom: 10px; + width: 100%; + cursor: pointer; +} +#page-header #scroll-down .scroll-down-effects { + position: relative; + width: 100%; + color: var(--light-grey); + font-size: 20px; +} +#page-header.not-home-page { + height: 400px; +} +@media screen and (max-width: 768px) { + #page-header.not-home-page { + height: 280px; + } +} +#page-header #page-site-info { + position: absolute; + top: 200px; + padding: 0 10px; + width: 100%; +} +@media screen and (max-width: 768px) { + #page-header #page-site-info { + top: 140px; + } +} +#page-header.post-bg { + height: 400px; +} +@media screen and (max-width: 768px) { + #page-header.post-bg { + height: 360px; + } +} +#page-header #post-info { + position: absolute; + bottom: 100px; + padding: 0 8%; + width: 100%; + text-align: center; +} +@media screen and (max-width: 900px) { + #page-header #post-info { + bottom: 30px; + text-align: left; + } +} +@media screen and (max-width: 768px) { + #page-header #post-info { + bottom: 22px; + padding: 0 22px; + } +} +#page-header.not-top-img { + margin-bottom: 10px; + height: 60px; + background: 0; +} +#page-header.not-top-img #nav { + background: rgba(255,255,255,0.8); + box-shadow: 0 5px 6px -5px rgba(133,133,133,0.6); +} +#page-header.not-top-img #nav a, +#page-header.not-top-img #nav .site-name { + color: var(--font-color); + text-shadow: none; +} +#page-header.nav-fixed #nav { + position: fixed; + top: -60px; + z-index: 91; + background: rgba(255,255,255,0.8); + box-shadow: 0 5px 6px -5px rgba(133,133,133,0.6); + transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out; +} +#page-header.nav-fixed #nav #blog-info { + color: var(--font-color); +} +#page-header.nav-fixed #nav #blog-info:hover { + color: #49b1f5; +} +#page-header.nav-fixed #nav #blog-info .site-name { + text-shadow: none; +} +#page-header.nav-fixed #nav a, +#page-header.nav-fixed #nav #toggle-menu { + color: var(--font-color); + text-shadow: none; +} +#page-header.nav-fixed #nav a:hover, +#page-header.nav-fixed #nav #toggle-menu:hover { + color: #49b1f5; +} +#page-header.nav-fixed.fixed #nav { + top: 0; + transition: all 0.5s; +} +#page-header.nav-visible:not(.fixed) #nav { + transition: all 0.5s; + transform: translate3d(0, 100%, 0); +} +#page-header.nav-visible:not(.fixed) + .layout > .aside-content > .sticky_layout { + top: 70px; + transition: top 0.5s; +} +#page-header.fixed #nav { + position: fixed; +} +#page-header.fixed + .layout > .aside-content > .sticky_layout { + top: 70px; + transition: top 0.5s; +} +#page-header.fixed + .layout #card-toc .toc-content { + max-height: calc(100vh - 170px); +} +#page h1.page-title { + margin: 8px 0 20px; +} +#post > #post-info { + margin-bottom: 30px; +} +#post > #post-info .post-title { + padding-bottom: 4px; + border-bottom: 1px solid var(--light-grey); + color: var(--text-highlight-color); +} +#post > #post-info .post-title .post-edit-link { + float: right; +} +#post > #post-info #post-meta, +#post > #post-info #post-meta a { + color: #78818a; +} +#post-info .post-title { + margin-bottom: 8px; + color: var(--white); + font-weight: normal; + font-size: 2.5em; + line-height: 1.5; + -webkit-line-clamp: 3; +} +@media screen and (max-width: 768px) { + #post-info .post-title { + font-size: 2.1em; + } +} +#post-info .post-title .post-edit-link { + padding-left: 10px; +} +#post-info #post-meta { + color: var(--light-grey); + font-size: 95%; +} +@media screen and (min-width: 768px) { + #post-info #post-meta > .meta-secondline > span:first-child { + display: none; + } +} +@media screen and (max-width: 768px) { + #post-info #post-meta { + font-size: 90%; + } + #post-info #post-meta > .meta-firstline, + #post-info #post-meta > .meta-secondline { + display: inline; + } +} +#post-info #post-meta .post-meta-separator { + margin: 0 5px; +} +#post-info #post-meta .post-meta-icon { + margin-right: 4px; +} +#post-info #post-meta .post-meta-label { + margin-right: 4px; +} +#post-info #post-meta a { + color: var(--light-grey); + transition: all 0.3s ease-out; +} +#post-info #post-meta a:hover { + color: #49b1f5; + text-decoration: underline; +} +#nav { + position: absolute; + top: 0; + z-index: 90; + display: -ms-flexbox; + display: box; + display: flex; + -o-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0 36px; + width: 100%; + height: 60px; + font-size: 1.3em; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transition: all 0.5s; +} +@media screen and (max-width: 768px) { + #nav { + padding: 0 16px; + } +} +#nav.show { + opacity: 1; + -ms-filter: none; + filter: none; +} +#nav #blog-info { + -o-box-flex: 1; + box-flex: 1; + -ms-flex: 1; + flex: 1; + color: var(--light-grey); +} +#nav #blog-info .site-icon { + margin-right: 6px; + height: 36px; + vertical-align: middle; +} +#nav #toggle-menu { + display: none; + padding: 2px 0 0 6px; + vertical-align: top; +} +#nav #toggle-menu:hover { + color: var(--white); +} +#nav a { + color: var(--light-grey); +} +#nav a:hover { + color: var(--white); +} +#nav .site-name { + text-shadow: 2px 2px 4px rgba(0,0,0,0.15); + font-weight: bold; +} +#nav .menus_items { + display: inline; +} +#nav .menus_items .menus_item { + position: relative; + display: inline-block; + padding: 0 0 0 14px; +} +#nav .menus_items .menus_item:hover .menus_item_child { + display: block; +} +#nav .menus_items .menus_item:hover > a > i:last-child { + transform: rotate(180deg); +} +#nav .menus_items .menus_item > a > i:last-child { + padding: 4px; + transition: transform 0.3s; +} +#nav .menus_items .menus_item .menus_item_child { + position: absolute; + right: 0; + display: none; + margin-top: 8px; + padding: 0; + width: -moz-max-content; + width: max-content; + border-radius: 5px; + background-color: var(--sidebar-bg); + box-shadow: 0 5px 20px -4px rgba(0,0,0,0.5); + animation: sub_menus 0.3s 0.1s ease both; +} +#nav .menus_items .menus_item .menus_item_child:before { + position: absolute; + top: -8px; + left: 0; + width: 100%; + height: 20px; + content: ''; +} +#nav .menus_items .menus_item .menus_item_child li { + list-style: none; +} +#nav .menus_items .menus_item .menus_item_child li:hover { + background: var(--text-bg-hover); +} +#nav .menus_items .menus_item .menus_item_child li:first-child { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} +#nav .menus_items .menus_item .menus_item_child li:last-child { + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} +#nav .menus_items .menus_item .menus_item_child li a { + display: inline-block; + padding: 8px 16px; + width: 100%; + color: var(--font-color) !important; + text-shadow: none !important; +} +#nav.hide-menu #toggle-menu { + display: inline-block !important; +} +#nav.hide-menu #toggle-menu .site-page { + font-size: inherit; +} +#nav.hide-menu .menus_items { + display: none; +} +#nav.hide-menu #search-button span { + display: none; +} +#nav #search-button { + display: inline; + padding: 0 0 0 14px; +} +#nav .site-page { + position: relative; + padding-bottom: 6px; + text-shadow: 1px 1px 2px rgba(0,0,0,0.3); + font-size: 0.78em; + cursor: pointer; +} +#nav .site-page:not(.child):after { + position: absolute; + bottom: 0; + left: 0; + z-index: -1; + width: 0; + height: 3px; + background-color: #80c8f8; + content: ''; + transition: all 0.3s ease-in-out; +} +#nav .site-page:not(.child):hover:after { + width: 100%; +} +#pagination .pagination { + margin-top: 20px; + text-align: center; +} +#pagination .page-number.current { + background: #00c4b6; + color: var(--white); +} +#pagination .pagination-info { + position: absolute; + top: 50%; + padding: 20px 40px; + width: 100%; + transform: translate(0, -50%); +} +#pagination .prev_info, +#pagination .next_info { + color: var(--white); + font-weight: 500; +} +#pagination .next-post .pagination-info { + text-align: right; +} +#pagination .pull-full { + width: 100% !important; +} +#pagination .prev-post .label, +#pagination .next-post .label { + color: var(--light-grey); + text-transform: uppercase; + font-size: 90%; +} +#pagination .prev-post, +#pagination .next-post { + width: 50%; +} +@media screen and (max-width: 768px) { + #pagination .prev-post, + #pagination .next-post { + width: 100%; + } +} +#pagination .prev-post a, +#pagination .next-post a { + position: relative; + display: block; + overflow: hidden; + height: 150px; +} +#pagination.pagination-post { + overflow: hidden; + margin-top: 40px; + width: 100%; + background: #000; +} +.layout > .recent-posts .pagination > * { + display: inline-block; + margin: 0 6px; + width: 2.5em; + height: 2.5em; + line-height: 2.5em; +} +.layout > .recent-posts .pagination > *:not(.space):hover { + background: var(--btn-hover-color); + color: var(--btn-color); +} +.layout > div:not(.recent-posts) .pagination .page-number { + display: inline-block; + margin: 0 4px; + min-width: 24px; + height: 24px; + text-align: center; + line-height: 24px; + cursor: pointer; +} +#article-container { + word-wrap: break-word; + overflow-wrap: break-word; +} +#article-container a { + color: #49b1f5; +} +#article-container a:hover { + text-decoration: underline; +} +#article-container img { + display: block; + margin: 0 auto 20px; + max-width: 100%; + transition: filter 375ms ease-in 0.2s; +} +#article-container p { + margin: 0 0 16px; +} +#article-container iframe { + margin: 0 0 20px; +} +#article-container kbd { + margin: 0 3px; + padding: 3px 5px; + border: 1px solid #b4b4b4; + border-radius: 3px; + background-color: #f8f8f8; + box-shadow: 0 1px 3px rgba(0,0,0,0.25), 0 2px 1px 0 rgba(255,255,255,0.6) inset; + color: #34495e; + white-space: nowrap; + font-weight: 600; + font-size: 0.9em; + font-family: Monaco, 'Ubuntu Mono', monospace; + line-height: 1em; +} +#article-container a.headerlink { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + width: 100%; + height: 100%; +} +#article-container ol ol, +#article-container ul ol, +#article-container ol ul, +#article-container ul ul { + padding-left: 20px; +} +#article-container ol li, +#article-container ul li { + margin: 4px 0; +} +#article-container ol p, +#article-container ul p { + margin: 0 0 8px; +} +#article-container > :last-child { + margin-bottom: 0 !important; +} +#article-container hr { + margin: 20px 0; +} +#article-container.post-content h1, +#article-container.post-content h2, +#article-container.post-content h3, +#article-container.post-content h4, +#article-container.post-content h5, +#article-container.post-content h6 { + transition: all 0.2s ease-out; +} +#article-container.post-content h1:before, +#article-container.post-content h2:before, +#article-container.post-content h3:before, +#article-container.post-content h4:before, +#article-container.post-content h5:before, +#article-container.post-content h6:before { + position: absolute; + top: calc(50% - 7px); + color: #f47466; + content: '\f0c1'; + left: 0; + line-height: 1; + transition: all 0.2s ease-out; +} +#article-container.post-content h1:hover:before, +#article-container.post-content h2:hover:before, +#article-container.post-content h3:hover:before, +#article-container.post-content h4:hover:before, +#article-container.post-content h5:hover:before, +#article-container.post-content h6:hover:before { + color: #49b1f5; +} +#article-container.post-content h1 { + padding-left: 28px; +} +#article-container.post-content h1:before { + font-size: 18px; +} +#article-container.post-content h1:hover { + padding-left: 32px; +} +#article-container.post-content h2 { + padding-left: 26px; +} +#article-container.post-content h2:before { + font-size: 16px; +} +#article-container.post-content h2:hover { + padding-left: 30px; +} +#article-container.post-content h3 { + padding-left: 24px; +} +#article-container.post-content h3:before { + font-size: 14px; +} +#article-container.post-content h3:hover { + padding-left: 28px; +} +#article-container.post-content h4 { + padding-left: 22px; +} +#article-container.post-content h4:before { + font-size: 12px; +} +#article-container.post-content h4:hover { + padding-left: 26px; +} +#article-container.post-content h5 { + padding-left: 20px; +} +#article-container.post-content h5:before { + font-size: 10px; +} +#article-container.post-content h5:hover { + padding-left: 24px; +} +#article-container.post-content h6 { + padding-left: 20px; +} +#article-container.post-content h6:before { + font-size: 10px; +} +#article-container.post-content h6:hover { + padding-left: 24px; +} +#article-container.post-content ol p, +#article-container.post-content ul p { + margin: 0 0 8px; +} +#article-container.post-content li::marker { + color: #49b1f5; + font-weight: 600; + font-size: 1.05em; +} +#article-container.post-content li:hover::marker { + color: var(--pseudo-hover); +} +#article-container.post-content ul > li { + list-style-type: circle; +} +#post .tag_share:after { + display: block; + clear: both; + content: ''; +} +#post .tag_share .post-meta__tag-list { + display: inline-block; +} +#post .tag_share .post-meta__tags { + display: inline-block; + margin: 8px 8px 8px 0; + padding: 0 12px; + width: -moz-fit-content; + width: fit-content; + border: 1px solid #49b1f5; + border-radius: 12px; + color: #49b1f5; + font-size: 0.85em; + transition: all 0.2s ease-in-out; +} +#post .tag_share .post-meta__tags:hover { + background: #49b1f5; + color: var(--white); +} +#post .tag_share .post_share { + display: inline-block; + float: right; + margin: 8px 0 0; + width: -moz-fit-content; + width: fit-content; +} +#post .tag_share .post_share .social-share { + font-size: 0.85em; +} +#post .tag_share .post_share .social-share .social-share-icon { + margin: 0 4px; + width: 1.85em; + height: 1.85em; + font-size: 1.2em; + line-height: 1.85em; +} +#post .post-copyright { + position: relative; + margin: 40px 0 10px; + padding: 10px 16px; + border: 1px solid var(--light-grey); + transition: box-shadow 0.3s ease-in-out; +} +#post .post-copyright:before { + position: absolute; + top: 2px; + right: 12px; + color: #49b1f5; + content: '\f1f9'; + font-size: 1.3em; +} +#post .post-copyright:hover { + box-shadow: 0 0 8px 0 rgba(232,237,250,0.6), 0 2px 4px 0 rgba(232,237,250,0.5); +} +#post .post-copyright .post-copyright-meta { + color: #49b1f5; + font-weight: bold; +} +#post .post-copyright .post-copyright-meta i { + margin-right: 3px; +} +#post .post-copyright .post-copyright-info { + padding-left: 6px; +} +#post .post-copyright .post-copyright-info a { + text-decoration: underline; + word-break: break-word; +} +#post .post-copyright .post-copyright-info a:hover { + text-decoration: none; +} +#post .post-outdate-notice { + position: relative; + margin: 0 0 20px; + padding: 0.5em 1.2em; + border-radius: 3px; + background-color: #ffe6e6; + color: #f66; + padding: 0.5em 1em 0.5em 2.6em; + border-left: 5px solid #ff8080; +} +#post .post-outdate-notice:before { + position: absolute; + top: 50%; + left: 0.9em; + color: #ff8080; + content: '\f071'; + transform: translateY(-50%); +} +#post .ads-wrap { + margin: 40px 0; +} +.relatedPosts { + margin-top: 40px; +} +.relatedPosts > .headline { + margin-bottom: 5px; + font-weight: 700; + font-size: 1.43em; +} +.relatedPosts > .relatedPosts-list > div { + position: relative; + display: inline-block; + overflow: hidden; + margin: 3px; + width: calc(33.333% - 6px); + height: 200px; + background: #000; + vertical-align: bottom; +} +@media screen and (max-width: 768px) { + .relatedPosts > .relatedPosts-list > div { + margin: 2px; + width: calc(50% - 4px); + height: 150px; + } +} +@media screen and (max-width: 600px) { + .relatedPosts > .relatedPosts-list > div { + width: calc(100% - 4px); + } +} +.relatedPosts > .relatedPosts-list .content { + position: absolute; + top: 50%; + padding: 0 20px; + width: 100%; + transform: translate(0, -50%); +} +.relatedPosts > .relatedPosts-list .content .date { + color: var(--light-grey); + font-size: 90%; +} +.relatedPosts > .relatedPosts-list .content .title { + color: var(--white); + -webkit-line-clamp: 2; +} +.post-reward { + position: relative; + margin-top: 80px; + width: 100%; + text-align: center; + pointer-events: none; +} +.post-reward > * { + pointer-events: auto; +} +.post-reward .reward-button { + display: inline-block; + padding: 4px 24px; + background: var(--btn-bg); + color: var(--btn-color); + cursor: pointer; +} +.post-reward .reward-button i { + margin-right: 5px; +} +.post-reward:hover .reward-button { + background: var(--btn-hover-color); +} +.post-reward:hover > .reward-main { + display: block; +} +.post-reward .reward-main { + position: absolute; + bottom: 40px; + left: 0; + z-index: 100; + display: none; + padding: 0 0 15px; + width: 100%; +} +.post-reward .reward-main .reward-all { + display: inline-block; + margin: 0; + padding: 20px 10px; + border-radius: 4px; + background: var(--reward-pop); +} +.post-reward .reward-main .reward-all:before { + position: absolute; + bottom: -10px; + left: 0; + width: 100%; + height: 20px; + content: ''; +} +.post-reward .reward-main .reward-all:after { + position: absolute; + right: 0; + bottom: 2px; + left: 0; + margin: 0 auto; + width: 0; + height: 0; + border-top: 13px solid var(--reward-pop); + border-right: 13px solid transparent; + border-left: 13px solid transparent; + content: ''; +} +.post-reward .reward-main .reward-all .reward-item { + display: inline-block; + padding: 0 8px; + list-style-type: none; + vertical-align: top; +} +.post-reward .reward-main .reward-all .reward-item img { + width: 130px; + height: 130px; +} +.post-reward .reward-main .reward-all .reward-item .post-qr-code-desc { + width: 130px; + color: #858585; +} +#rightside { + position: fixed; + right: -48px; + bottom: 40px; + z-index: 100; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transition: all 0.5s; +} +#rightside.rightside-show { + opacity: 0.8; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; + filter: alpha(opacity=80); + transform: translate(-58px, 0); +} +#rightside #rightside-config-hide { + height: 0; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transition: transform 0.4s; + transform: translate(45px, 0); +} +#rightside #rightside-config-hide.show { + height: auto; + opacity: 1; + -ms-filter: none; + filter: none; + transform: translate(0, 0); +} +#rightside #rightside-config-hide.status { + height: auto; + opacity: 1; + -ms-filter: none; + filter: none; +} +#rightside > div > button, +#rightside > div > a { + display: block; + margin-bottom: 5px; + width: 35px; + height: 35px; + border-radius: 5px; + background-color: var(--btn-bg); + color: var(--btn-color); + text-align: center; + font-size: 16px; + line-height: 35px; +} +#rightside > div > button:hover, +#rightside > div > a:hover { + background-color: var(--btn-hover-color); +} +#rightside #mobile-toc-button { + display: none; +} +@media screen and (max-width: 900px) { + #rightside #mobile-toc-button { + display: block; + } +} +@media screen and (max-width: 900px) { + #rightside #hide-aside-btn { + display: none; + } +} +#rightside #go-up .scroll-percent { + display: none; +} +#rightside #go-up.show-percent .scroll-percent { + display: block; +} +#rightside #go-up.show-percent .scroll-percent + i { + display: none; +} +#rightside #go-up:hover .scroll-percent { + display: none; +} +#rightside #go-up:hover .scroll-percent + i { + display: block; +} +#sidebar #menu-mask { + position: fixed; + z-index: 102; + display: none; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.8); +} +#sidebar #sidebar-menus { + position: fixed; + top: 0; + right: -300px; + z-index: 103; + overflow-x: hidden; + overflow-y: scroll; + padding-left: 5px; + width: 300px; + height: 100%; + background: var(--sidebar-bg); + transition: all 0.5s; +} +#sidebar #sidebar-menus.open { + transform: translate3d(-100%, 0, 0); +} +#sidebar #sidebar-menus > .avatar-img { + margin: 20px auto; +} +#sidebar #sidebar-menus .sidebar-site-data { + padding: 0 10px; +} +#sidebar #sidebar-menus hr { + margin: 20px auto; +} +#sidebar #sidebar-menus .menus_items { + padding: 0 5px; +} +#sidebar #sidebar-menus .menus_items .site-page { + position: relative; + display: block; + padding: 6px 30px 6px 22px; + color: var(--font-color); + font-size: 1.15em; +} +#sidebar #sidebar-menus .menus_items .site-page:hover { + background: var(--text-bg-hover); +} +#sidebar #sidebar-menus .menus_items .site-page i:first-child { + width: 15%; + text-align: left; +} +#sidebar #sidebar-menus .menus_items .site-page.group > i:last-child { + position: absolute; + top: 0.78em; + right: 13px; + transition: transform 0.3s; +} +#sidebar #sidebar-menus .menus_items .site-page.group.hide > i:last-child { + transform: rotate(90deg); +} +#sidebar #sidebar-menus .menus_items .site-page.group.hide + .menus_item_child { + display: none; +} +#sidebar #sidebar-menus .menus_items .menus_item_child { + margin: 0; + padding-left: 25px; + list-style: none; +} +#vcomment { + font-size: 1.1em; +} +#vcomment .vbtn { + border: none; + background: var(--btn-bg); + color: var(--btn-color); +} +#vcomment .vbtn:hover { + background: var(--btn-hover-color); +} +#vcomment .vimg { + transition: all 0.3s; +} +#vcomment .vimg:hover { + transform: rotate(360deg); +} +#vcomment .vcards .vcard .vcontent.expand:before, +#vcomment .vcards .vcard .vcontent.expand:after { + z-index: 22; +} +#waline-wrap { + --waline-font-size: 1.1em; + --waline-theme-color: #49b1f5; + --waline-active-color: #ff7242; +} +#waline-wrap .wl-comment-actions > button:not(last-child) { + padding-right: 4px; +} +.fireworks { + position: fixed; + top: 0; + left: 0; + z-index: 9999; + pointer-events: none; +} +.medium-zoom-image--opened { + z-index: 99999 !important; + margin: 0 !important; +} +.medium-zoom-overlay { + z-index: 99999 !important; +} +.mermaid-wrap { + margin: 0 0 20px; + text-align: center; +} +.mermaid-wrap > svg { + height: 100%; +} +.utterances, +.fb-comments iframe { + width: 100% !important; +} +#gitalk-container .gt-meta { + margin: 0 0 0.8em; + padding: 6px 0 16px; +} +.katex-wrap { + overflow: auto; +} +.katex-wrap::-webkit-scrollbar { + display: none; +} +mjx-container { + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 4px; + max-width: 100%; +} +mjx-container[display] { + display: block !important; + min-width: auto !important; +} +mjx-container:not([display]) { + display: inline-grid !important; +} +mjx-assistive-mml { + right: 0; + bottom: 0; +} +.aplayer { + color: #4c4948; +} +#article-container .aplayer { + margin: 0 0 20px; +} +#article-container .aplayer ol, +#article-container .aplayer ul { + margin: 0; + padding: 0; +} +#article-container .aplayer ol li, +#article-container .aplayer ul li { + margin: 0; + padding: 0 15px; +} +#article-container .aplayer ol li:before, +#article-container .aplayer ul li:before { + content: none; +} +.snackbar-css { + border-radius: 5px !important; +} +.abc-music-sheet { + margin: 0 0 20px; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transition: opacity 0.3s; +} +.abc-music-sheet.abcjs-container { + opacity: 1; + -ms-filter: none; + filter: none; +} +@media screen and (max-width: 768px) { + .fancybox__toolbar__column.is-middle { + display: none; + } +} +#article-container .btn-center { + margin: 0 0 20px; + text-align: center; +} +#article-container .btn-beautify { + display: inline-block; + margin: 0 4px 6px; + padding: 0 15px; + background-color: var(--btn-beautify-color, #777); + color: #fff; + line-height: 2; +} +#article-container .btn-beautify.blue { + --btn-beautify-color: #428bca; +} +#article-container .btn-beautify.pink { + --btn-beautify-color: #ff69b4; +} +#article-container .btn-beautify.red { + --btn-beautify-color: #f00; +} +#article-container .btn-beautify.purple { + --btn-beautify-color: #6f42c1; +} +#article-container .btn-beautify.orange { + --btn-beautify-color: #ff8c00; +} +#article-container .btn-beautify.green { + --btn-beautify-color: #5cb85c; +} +#article-container .btn-beautify:hover { + background-color: var(--btn-hover-color); +} +#article-container .btn-beautify i + span { + margin-left: 6px; +} +#article-container .btn-beautify:not(.block) + .btn-beautify:not(.block) { + margin: 0 4px 20px; +} +#article-container .btn-beautify.block { + display: block; + margin: 0 0 20px; + width: fit-content; + width: -moz-fit-content; +} +#article-container .btn-beautify.block.center { + margin: 0 auto 20px; +} +#article-container .btn-beautify.block.right { + margin: 0 0 20px auto; +} +#article-container .btn-beautify.larger { + padding: 6px 15px; +} +#article-container .btn-beautify:hover { + text-decoration: none; +} +#article-container .btn-beautify.outline { + border: 1px solid transparent; + border-color: var(--btn-beautify-color, #777); + background-color: transparent; + color: var(--btn-beautify-color, #777); +} +#article-container .btn-beautify.outline:hover { + background-color: var(--btn-beautify-color, #777); +} +#article-container .btn-beautify.outline:hover { + color: #fff !important; +} +#article-container figure.gallery-group { + position: relative; + float: left; + overflow: hidden; + margin: 6px 4px; + width: calc(50% - 8px); + height: 250px; + border-radius: 8px; + background: #000; + -webkit-transform: translate3d(0, 0, 0); +} +@media screen and (max-width: 600px) { + #article-container figure.gallery-group { + width: calc(100% - 8px); + } +} +#article-container figure.gallery-group:hover img { + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + transform: translate3d(0, 0, 0); +} +#article-container figure.gallery-group:hover .gallery-group-name::after { + transform: translate3d(0, 0, 0); +} +#article-container figure.gallery-group:hover p { + opacity: 1; + -ms-filter: none; + filter: none; + transform: translate3d(0, 0, 0); +} +#article-container figure.gallery-group img { + position: relative; + margin: 0; + max-width: none; + width: calc(100% + 20px); + height: 250px; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + opacity: 0.8; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; + filter: alpha(opacity=80); + transition: all 0.3s, filter 375ms ease-in 0.2s; + transform: translate3d(-10px, 0, 0); + -o-object-fit: cover; + object-fit: cover; +} +#article-container figure.gallery-group figcaption { + position: absolute; + top: 0; + left: 0; + padding: 30px; + width: 100%; + height: 100%; + color: #fff; + text-transform: uppercase; + -ms-backface-visibility: hidden; + backface-visibility: hidden; +} +#article-container figure.gallery-group figcaption > a { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1000; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +#article-container figure.gallery-group p { + margin: 0; + padding: 8px 0 0; + letter-spacing: 1px; + font-size: 1.1em; + line-height: 1.5; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + transition: opacity 0.35s, transform 0.35s; + transform: translate3d(100%, 0, 0); + -webkit-line-clamp: 4; +} +#article-container figure.gallery-group .gallery-group-name { + position: relative; + margin: 0; + padding: 8px 0; + font-weight: bold; + font-size: 1.65em; + line-height: 1.5; + -webkit-line-clamp: 2; +} +#article-container figure.gallery-group .gallery-group-name:after { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background: #fff; + content: ''; + transition: transform 0.35s; + transform: translate3d(-100%, 0, 0); +} +#article-container .gallery-group-main { + overflow: auto; + padding: 0 0 16px; +} +#article-container .gallery-container { + margin: 0 0 16px; + text-align: center; +} +#article-container .gallery-container img { + display: initial; + margin: 0; + width: 100%; + height: 100%; +} +#article-container .gallery-container .gallery-data { + display: none; +} +#article-container .gallery-container button { + margin-top: 25px; + padding: 10px; + width: 9em; + border-radius: 5px; + background: var(--btn-bg); + color: var(--btn-color); + font-weight: bold; + font-size: 1.1em; + transition: all 0.3s; +} +#article-container .gallery-container button:hover { + background: var(--btn-hover-color); +} +#article-container .loading-container { + display: inline-block; + overflow: hidden; + width: 154px; + height: 154px; +} +#article-container .loading-container .loading-item { + position: relative; + width: 100%; + height: 100%; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + transform: translateZ(0) scale(1); + transform-origin: 0 0; +} +#article-container .loading-container .loading-item div { + position: absolute; + width: 30.8px; + height: 30.8px; + border-radius: 50%; + background: #e15b64; + transform: translate(61.6px, 61.6px) scale(1); + animation: loading-ball 1.92s infinite cubic-bezier(0, 0.5, 0.5, 1); +} +#article-container .loading-container .loading-item div:nth-child(1) { + background: #f47e60; + transform: translate(113.96px, 61.6px) scale(1); + animation: loading-ball-r 0.48s infinite cubic-bezier(0, 0.5, 0.5, 1), loading-ball-c 1.92s infinite step-start; +} +#article-container .loading-container .loading-item div:nth-child(2) { + background: #e15b64; + animation-delay: -0.48s; +} +#article-container .loading-container .loading-item div:nth-child(3) { + background: #f47e60; + animation-delay: -0.96s; +} +#article-container .loading-container .loading-item div:nth-child(4) { + background: #f8b26a; + animation-delay: -1.44s; +} +#article-container .loading-container .loading-item div:nth-child(5) { + background: #abbd81; + animation-delay: -1.92s; +} +@keyframes loading-ball { + 0% { + transform: translate(9.24px, 61.6px) scale(0); + } + 25% { + transform: translate(9.24px, 61.6px) scale(0); + } + 50% { + transform: translate(9.24px, 61.6px) scale(1); + } + 75% { + transform: translate(61.6px, 61.6px) scale(1); + } + 100% { + transform: translate(113.96px, 61.6px) scale(1); + } +} +@keyframes loading-ball-r { + 0% { + transform: translate(113.96px, 61.6px) scale(1); + } + 100% { + transform: translate(113.96px, 61.6px) scale(0); + } +} +@keyframes loading-ball-c { + 0% { + background: #e15b64; + } + 25% { + background: #abbd81; + } + 50% { + background: #f8b26a; + } + 75% { + background: #f47e60; + } + 100% { + background: #e15b64; + } +} +blockquote.pullquote { + position: relative; + max-width: 45%; + font-size: 110%; +} +blockquote.pullquote.left { + float: left; + margin: 1em 0.5em 0 0; +} +blockquote.pullquote.right { + float: right; + margin: 1em 0 0 0.5em; +} +.video-container { + position: relative; + overflow: hidden; + margin-bottom: 16px; + padding-top: 56.25%; + height: 0; +} +.video-container iframe { + position: absolute; + top: 0; + left: 0; + margin-top: 0; + width: 100%; + height: 100%; +} +.hide-inline > .hide-button, +.hide-block > .hide-button { + display: inline-block; + padding: 5px 18px; + background: #49b1f5; + color: var(--white); +} +.hide-inline > .hide-button:hover, +.hide-block > .hide-button:hover { + background-color: var(--btn-hover-color); +} +.hide-inline > .hide-button.open, +.hide-block > .hide-button.open { + display: none; +} +.hide-inline > .hide-button.open + div, +.hide-block > .hide-button.open + div { + display: block; +} +.hide-inline > .hide-button.open + span, +.hide-block > .hide-button.open + span { + display: inline; +} +.hide-inline > .hide-content, +.hide-block > .hide-content { + display: none; +} +.hide-inline > .hide-button { + margin: 0 6px; +} +.hide-inline > .hide-content { + margin: 0 6px; +} +.hide-block { + margin: 0 0 16px; +} +.toggle { + margin-bottom: 20px; + border: 1px solid #f0f0f0; +} +.toggle > .toggle-button { + padding: 6px 15px; + background: #f0f0f0; + color: #1f2d3d; + cursor: pointer; +} +.toggle > .toggle-content { + margin: 30px 24px; +} +#article-container .inline-img { + display: inline; + margin: 0 3px; + height: 1.1em; + vertical-align: text-bottom; +} +.hl-label { + padding: 2px 4px; + border-radius: 3px; + color: #fff; +} +.hl-label.default { + background-color: #777; +} +.hl-label.blue { + background-color: #428bca; +} +.hl-label.pink { + background-color: #ff69b4; +} +.hl-label.red { + background-color: #f00; +} +.hl-label.purple { + background-color: #6f42c1; +} +.hl-label.orange { + background-color: #ff8c00; +} +.hl-label.green { + background-color: #5cb85c; +} +.note { + position: relative; + margin: 0 0 20px; + padding: 15px; + border-radius: 3px; +} +.note.icon-padding { + padding-left: 3em; +} +.note > .note-icon { + position: absolute; + top: calc(50% - 0.5em); + left: 0.8em; + font-size: larger; +} +.note.blue:not(.disabled) { + border-left-color: #428bca !important; +} +.note.blue:not(.disabled).modern { + border-left-color: transparent !important; + color: #428bca; +} +.note.blue:not(.disabled):not(.simple) { + background: #e3eef7 !important; +} +.note.blue > .note-icon { + color: #428bca; +} +.note.pink:not(.disabled) { + border-left-color: #ff69b4 !important; +} +.note.pink:not(.disabled).modern { + border-left-color: transparent !important; + color: #ff69b4; +} +.note.pink:not(.disabled):not(.simple) { + background: #ffe9f4 !important; +} +.note.pink > .note-icon { + color: #ff69b4; +} +.note.red:not(.disabled) { + border-left-color: #f00 !important; +} +.note.red:not(.disabled).modern { + border-left-color: transparent !important; + color: #f00; +} +.note.red:not(.disabled):not(.simple) { + background: #ffd9d9 !important; +} +.note.red > .note-icon { + color: #f00; +} +.note.purple:not(.disabled) { + border-left-color: #6f42c1 !important; +} +.note.purple:not(.disabled).modern { + border-left-color: transparent !important; + color: #6f42c1; +} +.note.purple:not(.disabled):not(.simple) { + background: #e9e3f6 !important; +} +.note.purple > .note-icon { + color: #6f42c1; +} +.note.orange:not(.disabled) { + border-left-color: #ff8c00 !important; +} +.note.orange:not(.disabled).modern { + border-left-color: transparent !important; + color: #ff8c00; +} +.note.orange:not(.disabled):not(.simple) { + background: #ffeed9 !important; +} +.note.orange > .note-icon { + color: #ff8c00; +} +.note.green:not(.disabled) { + border-left-color: #5cb85c !important; +} +.note.green:not(.disabled).modern { + border-left-color: transparent !important; + color: #5cb85c; +} +.note.green:not(.disabled):not(.simple) { + background: #e7f4e7 !important; +} +.note.green > .note-icon { + color: #5cb85c; +} +.note.simple { + border: 1px solid #eee; + border-left-width: 5px; +} +.note.modern { + border: 1px solid transparent !important; + background-color: #f5f5f5; + color: #4c4948; +} +.note.flat { + border: initial; + border-left: 5px solid #eee; + background-color: #f9f9f9; + color: #4c4948; +} +.note h2, +.note h3, +.note h4, +.note h5, +.note h6 { + margin-top: 3px; + margin-bottom: 0; + padding-top: 0 !important; + border-bottom: initial; +} +.note p:first-child, +.note ul:first-child, +.note ol:first-child, +.note table:first-child, +.note pre:first-child, +.note blockquote:first-child, +.note img:first-child { + margin-top: 0 !important; +} +.note p:last-child, +.note ul:last-child, +.note ol:last-child, +.note table:last-child, +.note pre:last-child, +.note blockquote:last-child, +.note img:last-child { + margin-bottom: 0 !important; +} +.note .img-alt { + margin: 5px 0 10px; +} +.note:not(.no-icon) { + padding-left: 3em; +} +.note:not(.no-icon)::before { + position: absolute; + top: calc(50% - 0.95em); + left: 0.8em; + font-size: larger; +} +.note.default.flat { + background: #f7f7f7; +} +.note.default.modern { + border-color: #e1e1e1; + background: #f3f3f3; + color: #666; +} +.note.default.modern a:not(.btn) { + color: #666; +} +.note.default.modern a:not(.btn):hover { + color: #454545; +} +.note.default:not(.modern) { + border-left-color: #777; +} +.note.default:not(.modern) h2, +.note.default:not(.modern) h3, +.note.default:not(.modern) h4, +.note.default:not(.modern) h5, +.note.default:not(.modern) h6 { + color: #777; +} +.note.default:not(.no-icon)::before { + content: '\f0a9'; +} +.note.default:not(.no-icon):not(.modern)::before { + color: #777; +} +.note.primary.flat { + background: #f5f0fa; +} +.note.primary.modern { + border-color: #e1c2ff; + background: #f3daff; + color: #6f42c1; +} +.note.primary.modern a:not(.btn) { + color: #6f42c1; +} +.note.primary.modern a:not(.btn):hover { + color: #453298; +} +.note.primary:not(.modern) { + border-left-color: #6f42c1; +} +.note.primary:not(.modern) h2, +.note.primary:not(.modern) h3, +.note.primary:not(.modern) h4, +.note.primary:not(.modern) h5, +.note.primary:not(.modern) h6 { + color: #6f42c1; +} +.note.primary:not(.no-icon)::before { + content: '\f055'; +} +.note.primary:not(.no-icon):not(.modern)::before { + color: #6f42c1; +} +.note.info.flat { + background: #eef7fa; +} +.note.info.modern { + border-color: #b3e5ef; + background: #d9edf7; + color: #31708f; +} +.note.info.modern a:not(.btn) { + color: #31708f; +} +.note.info.modern a:not(.btn):hover { + color: #215761; +} +.note.info:not(.modern) { + border-left-color: #428bca; +} +.note.info:not(.modern) h2, +.note.info:not(.modern) h3, +.note.info:not(.modern) h4, +.note.info:not(.modern) h5, +.note.info:not(.modern) h6 { + color: #428bca; +} +.note.info:not(.no-icon)::before { + content: '\f05a'; +} +.note.info:not(.no-icon):not(.modern)::before { + color: #428bca; +} +.note.success.flat { + background: #eff8f0; +} +.note.success.modern { + border-color: #d0e6be; + background: #dff0d8; + color: #3c763d; +} +.note.success.modern a:not(.btn) { + color: #3c763d; +} +.note.success.modern a:not(.btn):hover { + color: #32562c; +} +.note.success:not(.modern) { + border-left-color: #5cb85c; +} +.note.success:not(.modern) h2, +.note.success:not(.modern) h3, +.note.success:not(.modern) h4, +.note.success:not(.modern) h5, +.note.success:not(.modern) h6 { + color: #5cb85c; +} +.note.success:not(.no-icon)::before { + content: '\f058'; +} +.note.success:not(.no-icon):not(.modern)::before { + color: #5cb85c; +} +.note.warning.flat { + background: #fdf8ea; +} +.note.warning.modern { + border-color: #fae4cd; + background: #fcf4e3; + color: #8a6d3b; +} +.note.warning.modern a:not(.btn) { + color: #8a6d3b; +} +.note.warning.modern a:not(.btn):hover { + color: #714f30; +} +.note.warning:not(.modern) { + border-left-color: #f0ad4e; +} +.note.warning:not(.modern) h2, +.note.warning:not(.modern) h3, +.note.warning:not(.modern) h4, +.note.warning:not(.modern) h5, +.note.warning:not(.modern) h6 { + color: #f0ad4e; +} +.note.warning:not(.no-icon)::before { + content: '\f06a'; +} +.note.warning:not(.no-icon):not(.modern)::before { + color: #f0ad4e; +} +.note.danger.flat { + background: #fcf1f2; +} +.note.danger.modern { + border-color: #ebcdd2; + background: #f2dfdf; + color: #a94442; +} +.note.danger.modern a:not(.btn) { + color: #a94442; +} +.note.danger.modern a:not(.btn):hover { + color: #84333f; +} +.note.danger:not(.modern) { + border-left-color: #d9534f; +} +.note.danger:not(.modern) h2, +.note.danger:not(.modern) h3, +.note.danger:not(.modern) h4, +.note.danger:not(.modern) h5, +.note.danger:not(.modern) h6 { + color: #d9534f; +} +.note.danger:not(.no-icon)::before { + content: '\f056'; +} +.note.danger:not(.no-icon):not(.modern)::before { + color: #d9534f; +} +#article-container .tabs { + position: relative; + margin: 0 0 20px; + border-right: 1px solid var(--tab-border-color); + border-bottom: 1px solid var(--tab-border-color); + border-left: 1px solid var(--tab-border-color); +} +#article-container .tabs > .nav-tabs { + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-lines: multiple; + -moz-box-lines: multiple; + -o-box-lines: multiple; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin: 0; + padding: 0; + background: var(--tab-botton-bg); +} +#article-container .tabs > .nav-tabs > .tab { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -o-box-flex: 1; + -ms-box-flex: 1; + box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + padding: 8px 18px; + border-top: 2px solid var(--tab-border-color); + background: var(--tab-botton-bg); + color: var(--tab-botton-color); + line-height: 2; + transition: all 0.4s; +} +#article-container .tabs > .nav-tabs > .tab i { + width: 1.5em; +} +#article-container .tabs > .nav-tabs > .tab.active { + border-top: 2px solid #49b1f5; + background: var(--tab-button-active-bg); + cursor: default; +} +#article-container .tabs > .nav-tabs > .tab:not(.active):hover { + border-top: 2px solid var(--tab-button-hover-bg); + background: var(--tab-button-hover-bg); +} +#article-container .tabs > .nav-tabs.no-default ~ .tab-to-top { + display: none; +} +#article-container .tabs > .tab-contents .tab-item-content { + position: relative; + display: none; + padding: 36px 24px 10px; +} +@media screen and (max-width: 768px) { + #article-container .tabs > .tab-contents .tab-item-content { + padding: 24px 14px; + } +} +#article-container .tabs > .tab-contents .tab-item-content.active { + display: block; + animation: tabshow 0.5s; +} +#article-container .tabs > .tab-contents .tab-item-content > :last-child { + margin-bottom: 0; +} +#article-container .tabs > .tab-to-top { + padding: 0 16px 10px 0; + width: 100%; + text-align: right; +} +#article-container .tabs > .tab-to-top button { + color: #99a9bf; +} +#article-container .tabs > .tab-to-top button:hover { + color: #49b1f5; +} +@keyframes tabshow { + 0% { + transform: translateY(15px); + } + 100% { + transform: translateY(0); + } +} +#article-container .timeline { + margin: 0 0 20px 10px; + padding: 14px 20px 5px; + border-left: 2px solid var(--timeline-color, #49b1f5); +} +#article-container .timeline.blue { + --timeline-color: #428bca; + --timeline-bg: rgba(66,139,202, 0.2); +} +#article-container .timeline.pink { + --timeline-color: #ff69b4; + --timeline-bg: rgba(255,105,180, 0.2); +} +#article-container .timeline.red { + --timeline-color: #f00; + --timeline-bg: rgba(255,0,0, 0.2); +} +#article-container .timeline.purple { + --timeline-color: #6f42c1; + --timeline-bg: rgba(111,66,193, 0.2); +} +#article-container .timeline.orange { + --timeline-color: #ff8c00; + --timeline-bg: rgba(255,140,0, 0.2); +} +#article-container .timeline.green { + --timeline-color: #5cb85c; + --timeline-bg: rgba(92,184,92, 0.2); +} +#article-container .timeline .timeline-item { + margin: 0 0 15px; +} +#article-container .timeline .timeline-item:hover .item-circle:before { + border-color: var(--timeline-color, #49b1f5); +} +#article-container .timeline .timeline-item.headline .timeline-item-title .item-circle > p { + font-weight: 600; + font-size: 1.2em; +} +#article-container .timeline .timeline-item.headline .timeline-item-title .item-circle:before { + left: -28px; + border: 4px solid var(--timeline-color, #49b1f5); +} +#article-container .timeline .timeline-item.headline:hover .item-circle:before { + border-color: var(--pseudo-hover); +} +#article-container .timeline .timeline-item .timeline-item-title { + position: relative; +} +#article-container .timeline .timeline-item .item-circle:before { + position: absolute; + top: 50%; + left: -27px; + width: 6px; + height: 6px; + border: 3px solid var(--pseudo-hover); + border-radius: 50%; + background: var(--card-bg); + content: ''; + transition: all 0.3s; + transform: translate(0, -50%); +} +#article-container .timeline .timeline-item .item-circle > p { + margin: 0 0 8px; + font-weight: 500; +} +#article-container .timeline .timeline-item .timeline-item-content { + position: relative; + padding: 12px 15px; + border-radius: 8px; + background: var(--timeline-bg, #e4f3fd); + font-size: 0.93em; +} +#article-container .timeline .timeline-item .timeline-item-content > :last-child { + margin-bottom: 0; +} +#article-container .timeline + .timeline { + margin-top: -20px; +} +[data-theme='dark'] { + --global-bg: #0d0d0d; + --font-color: rgba(255,255,255,0.7); + --hr-border: rgba(255,255,255,0.4); + --hr-before-color: rgba(255,255,255,0.7); + --search-bg: #121212; + --search-input-color: rgba(255,255,255,0.7); + --search-a-color: rgba(255,255,255,0.7); + --preloader-bg: #0d0d0d; + --preloader-color: rgba(255,255,255,0.7); + --tab-border-color: #2c2c2c; + --tab-botton-bg: #2c2c2c; + --tab-botton-color: rgba(255,255,255,0.7); + --tab-button-hover-bg: #383838; + --tab-button-active-bg: #121212; + --card-bg: #121212; + --sidebar-bg: #121212; + --btn-hover-color: #787878; + --btn-color: rgba(255,255,255,0.7); + --btn-bg: #1f1f1f; + --text-bg-hover: #383838; + --light-grey: rgba(255,255,255,0.7); + --dark-grey: rgba(255,255,255,0.2); + --white: rgba(255,255,255,0.9); + --text-highlight-color: rgba(255,255,255,0.9); + --blockquote-color: rgba(255,255,255,0.7); + --blockquote-bg: #2c2c2c; + --reward-pop: #2c2c2c; + --toc-link-color: rgba(255,255,255,0.6); + --scrollbar-color: #525252; + --timeline-bg: #1f1f1f; + --zoom-bg: #121212; + --mark-bg: rgba(0,0,0,0.6); +} +[data-theme='dark'] #web_bg:before { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.7); + content: ''; +} +[data-theme='dark'] #article-container code { + background: #2c2c2c; +} +[data-theme='dark'] #article-container pre > code { + background: #171717; +} +[data-theme='dark'] #article-container figure.highlight { + box-shadow: none; +} +[data-theme='dark'] #article-container .note code { + background: rgba(27,31,35,0.05); +} +[data-theme='dark'] #article-container .aplayer { + filter: brightness(0.8); +} +[data-theme='dark'] #article-container kbd { + border-color: #696969; + background-color: #525252; + color: #e2f1ff; +} +[data-theme='dark'] #page-header.nav-fixed > #nav, +[data-theme='dark'] #page-header.not-top-img > #nav { + background: rgba(18,18,18,0.8); + box-shadow: 0 5px 6px -5px rgba(133,133,133,0); +} +[data-theme='dark'] #post-comment .comment-switch { + background: #2c2c2c !important; +} +[data-theme='dark'] #post-comment .comment-switch #switch-btn { + filter: brightness(0.8); +} +[data-theme='dark'] .note { + filter: brightness(0.8); +} +[data-theme='dark'] .hide-button, +[data-theme='dark'] .btn-beautify, +[data-theme='dark'] .hl-label, +[data-theme='dark'] .post-outdate-notice, +[data-theme='dark'] .error-img, +[data-theme='dark'] #article-container iframe, +[data-theme='dark'] .gist, +[data-theme='dark'] .ads-wrap { + filter: brightness(0.8); +} +[data-theme='dark'] img { + filter: brightness(0.8); +} +[data-theme='dark'] #aside-content .aside-list > .aside-list-item:not(:last-child) { + border-bottom: 1px dashed rgba(255,255,255,0.1); +} +[data-theme='dark'] #gitalk-container { + filter: brightness(0.8); +} +[data-theme='dark'] #gitalk-container svg { + fill: rgba(255,255,255,0.9) !important; +} +[data-theme='dark'] #disqusjs #dsqjs:hover, +[data-theme='dark'] #disqusjs #dsqjs:focus, +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-tab-active, +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-no-comment { + color: rgba(255,255,255,0.7); +} +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-order-label { + background-color: #1f1f1f; +} +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-post-body { + color: rgba(255,255,255,0.7); +} +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-post-body code, +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-post-body pre { + background: #2c2c2c; +} +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-post-body blockquote { + color: rgba(255,255,255,0.7); +} +[data-theme='dark'] #artitalk_main #lazy { + background: #121212; +} +[data-theme='dark'] #operare_artitalk .c2 { + background: #121212; +} +@media screen and (max-width: 900px) { + [data-theme='dark'] #card-toc { + background: #1f1f1f; + } +} +.read-mode { + --font-color: #4c4948; + --readmode-light-color: #fff; + --white: #4c4948; + --light-grey: #4c4948; + --gray: #d6dbdf; + --hr-border: #d6dbdf; + --hr-before-color: #b9c2c9; + --highlight-bg: #f7f7f7; + --exit-btn-bg: #c0c0c0; + --exit-btn-color: #fff; + --exit-btn-hover: #8d8d8d; + --pseudo-hover: none; +} +[data-theme='dark'] .read-mode { + --font-color: rgba(255,255,255,0.7); + --readmode-light-color: #0d0d0d; + --white: rgba(255,255,255,0.9); + --light-grey: rgba(255,255,255,0.7); + --gray: rgba(255,255,255,0.7); + --hr-border: rgba(255,255,255,0.5); + --hr-before-color: rgba(255,255,255,0.7); + --highlight-bg: #171717; + --exit-btn-bg: #1f1f1f; + --exit-btn-color: rgba(255,255,255,0.9); + --exit-btn-hover: #525252; +} +.read-mode { + background: var(--readmode-light-color); +} +.read-mode .exit-readmode { + position: fixed; + top: 30px; + right: 30px; + z-index: 100; + width: 40px; + height: 40px; + border-radius: 8px; + background: var(--exit-btn-bg); + color: var(--exit-btn-color); + font-size: 16px; + transition: background 0.3s; +} +@media screen and (max-width: 768px) { + .read-mode .exit-readmode { + top: initial; + bottom: 30px; + } +} +.read-mode .exit-readmode:hover { + background: var(--exit-btn-hover); +} +.read-mode #aside-content { + display: none; +} +.read-mode #page-header.post-bg { + background: none !important; +} +.read-mode #page-header.post-bg:before { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +.read-mode #page-header.post-bg > #post-info { + text-align: center; +} +.read-mode #post { + margin: 0 auto; + background: transparent; + box-shadow: none; +} +.read-mode #post:hover { + box-shadow: none; +} +.read-mode > canvas { + display: none !important; +} +.read-mode .highlight-tools, +.read-mode #footer, +.read-mode #post > *:not(#post-info):not(.post-content), +.read-mode #nav, +.read-mode .post-outdate-notice, +.read-mode #web_bg, +.read-mode #rightside, +.read-mode .not-top-img { + display: none !important; +} +.read-mode #article-container a { + color: #99a9bf; +} +.read-mode #article-container pre, +.read-mode #article-container .highlight:not(.js-file-line-container) { + background: var(--highlight-bg) !important; +} +.read-mode #article-container pre *, +.read-mode #article-container .highlight:not(.js-file-line-container) * { + color: var(--font-color) !important; +} +.read-mode #article-container figure.highlight { + border-radius: 0 !important; + box-shadow: none !important; +} +.read-mode #article-container figure.highlight > :not(.highlight-tools) { + display: block !important; +} +.read-mode #article-container figure.highlight .line:before { + color: var(--font-color) !important; +} +.read-mode #article-container figure.highlight .hljs { + background: var(--highlight-bg) !important; +} +.read-mode #article-container h1, +.read-mode #article-container h2, +.read-mode #article-container h3, +.read-mode #article-container h4, +.read-mode #article-container h5, +.read-mode #article-container h6 { + padding: 0; +} +.read-mode #article-container h1:before, +.read-mode #article-container h2:before, +.read-mode #article-container h3:before, +.read-mode #article-container h4:before, +.read-mode #article-container h5:before, +.read-mode #article-container h6:before { + content: ''; +} +.read-mode #article-container h1:hover, +.read-mode #article-container h2:hover, +.read-mode #article-container h3:hover, +.read-mode #article-container h4:hover, +.read-mode #article-container h5:hover, +.read-mode #article-container h6:hover { + padding: 0; +} +.read-mode #article-container ul:hover:before, +.read-mode #article-container li:hover:before, +.read-mode #article-container ol:hover:before { + transform: none !important; +} +.read-mode #article-container ol:before, +.read-mode #article-container li:before { + background: transparent !important; + color: var(--font-color) !important; +} +.read-mode #article-container ul >li:before { + border-color: var(--gray) !important; +} +.read-mode #article-container .tabs { + border: 2px solid var(--tab-border-color); +} +.read-mode #article-container .tabs > .nav-tabs { + background: transparent; +} +.read-mode #article-container .tabs > .nav-tabs > .tab { + border-top: none !important; +} +.read-mode #article-container .tabs > .tab-contents .tab-item-content.active { + animation: none; +} +.read-mode #article-container code { + color: var(--font-color); +} +.read-mode #article-container blockquote { + border-color: var(--gray); + background-color: var(--readmode-light-color); +} +.read-mode #article-container kbd { + border: 1px solid var(--gray); + background-color: transparent; + box-shadow: none; + color: var(--font-color); +} +.read-mode #article-container .hide-toggle { + border: 1px solid var(--gray) !important; +} +.read-mode #article-container .hide-button, +.read-mode #article-container .btn-beautify, +.read-mode #article-container .hl-label { + border: 1px solid var(--gray) !important; + background: var(--readmode-light-color) !important; + color: var(--font-color) !important; +} +.read-mode #article-container .note { + border: 2px solid var(--gray); + border-left-color: var(--gray) !important; + filter: none; + background-color: var(--readmode-light-color) !important; + color: var(--font-color); +} +.read-mode #article-container .note:before, +.read-mode #article-container .note .note-icon { + color: var(--font-color); +} +.search-dialog { + position: fixed; + top: 10%; + left: 50%; + z-index: 1001; + display: none; + margin-left: -300px; + padding: 20px; + width: 600px; + border-radius: 8px; + background: var(--search-bg); + --search-height: 100vh; +} +@media screen and (max-width: 768px) { + .search-dialog { + top: 0; + left: 0; + margin: 0; + width: 100%; + height: 100%; + border-radius: 0; + } +} +.search-dialog hr { + margin: 20px auto; +} +.search-dialog .search-nav { + margin: 0 0 14px; + color: #49b1f5; + font-size: 1.4em; + line-height: 1; +} +.search-dialog .search-nav .search-dialog-title { + margin-right: 10px; +} +.search-dialog .search-nav .search-close-button { + float: right; + color: #858585; + transition: color 0.2s ease-in-out; +} +.search-dialog .search-nav .search-close-button:hover { + color: #49b1f5; +} +.search-dialog hr { + margin: 20px auto; +} +#search-mask { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1000; + display: none; + background: rgba(0,0,0,0.6); +} +#local-search .search-dialog .local-search-box { + margin: 0 auto; + max-width: 100%; + width: 100%; +} +#local-search .search-dialog .local-search-box input { + padding: 5px 14px; + width: 100%; + outline: none; + border: 2px solid #49b1f5; + border-radius: 40px; + background: var(--search-bg); + color: var(--search-input-color); + -webkit-appearance: none; +} +#local-search .search-dialog .search-wrap { + display: none; +} +#local-search .search-dialog .local-search-hit-item { + position: relative; + padding-left: 24px; + line-height: 1.7; +} +#local-search .search-dialog .local-search-hit-item:hover:before { + border-color: var(--pseudo-hover); +} +#local-search .search-dialog .local-search-hit-item:before { + position: absolute; + top: 0.45em; + left: 0; + width: 0.5em; + height: 0.5em; + border: 3px solid #49b1f5; + border-radius: 0.5em; + background: transparent; + content: ''; + line-height: 0.5em; + transition: all 0.2s ease-in-out; +} +#local-search .search-dialog .local-search-hit-item a { + display: block; + color: var(--search-a-color); +} +#local-search .search-dialog .local-search-hit-item a:hover { + color: #49b1f5; +} +#local-search .search-dialog .local-search-hit-item .search-result-title { + font-weight: 600; +} +#local-search .search-dialog .local-search-hit-item .search-result { + margin: 0 0 8px; + word-break: break-word; +} +#local-search .search-dialog .search-result-list { + overflow-y: overlay; + margin: 0 -20px; + padding: 0 22px; + max-height: calc(80vh - 200px); +} +@media screen and (max-width: 768px) { + #local-search .search-dialog .search-result-list { + max-height: calc(var(--search-height) - 220px) !important; + } +} +.search-keyword { + background: transparent; + color: #f47466; + font-weight: bold; +} diff --git a/source/static/api/js/trans/logger.js b/css/var.css similarity index 100% rename from source/static/api/js/trans/logger.js rename to css/var.css diff --git a/daemonthread.html b/daemonthread.html new file mode 100644 index 00000000..4f014d7f --- /dev/null +++ b/daemonthread.html @@ -0,0 +1,313 @@ +多线程之守护线程和阻塞线程 | PyQt + + + + + + + + + + + + + + +

多线程之守护线程和阻塞线程

如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的 daemon 属性。

+ +

即在线程开始(thread.start ())之前,调用 setDeamon()函数,设定线程的 daemon 标志。

+

(thread.setDaemon (True))就表示这个线程 “不重要”。

+

如果你想等待子线程完成再退出,那就什么都不用做,或者显示地调用 thread.setDaemon (False),设置 daemon 的值为 false。新的子线程会继承父线程的 daemon 标志。

+

整个 Python 会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

+

setDaemon () 函数要放在 start 之前设置才行。

+
import threading
+import time
+
+def func():
+    print("子线程开启:", time.localtime())
+    time.sleep(2)
+    print("子线程结束:", time.localtime())
+
+
+print("主线程开启:", time.localtime())
+t = threading.Thread(target=func, args=())
+# t.setDaemon(True)
+t.start()
+print("主线程关闭:", time.localtime())
+

在 Python 的多线程编程中,在实例代码中经常有 thread1.join () 这样的代码。那么今天咱们用实际代码来解释一下 join 函数的作用。

+

join 的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的 join 函数。

+

先看看这个:

+
    +
  1. 阻塞主进程,专注于执行多线程中的程序。
  2. +
  3. 多线程多 join 的情况下,依次执行各线程的 join 方法,前头一个结束了才能执行后面一个。
  4. +
  5. 无参数,则等待到该线程结束,才开始执行下一个线程的 join。
  6. +
  7. 参数 timeout 为线程的阻塞时间,如 timeout=2 就是罩着这个线程 2s 以后,就不管他了,继续执行下面的代码。
  8. +
  9. 下面的例子是一次阻塞子线程,每个子线程都会等上个子线程 join 结束才会执行,如果注释掉 t.join 则会同时执行 5 个子线程,多线程在做网络访问的时候可以减少等待时间,那么在一个工作流程中可以将访问网络接口的情况做成多线程。
  10. +
+
import threading, time
+
+def func():
+    print("hello world!")
+    time.sleep(1)
+
+print("hello main start")
+for i in range(5):
+    t = threading.Thread(target=func, args=())
+    print(t.getName())
+    t.start()
+    t.join()
文章作者: Irony
文章链接: https://pyqt5.com/daemonthread.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/datawidgetmapper_625781186.html b/datawidgetmapper_625781186.html new file mode 100644 index 00000000..27813ae0 --- /dev/null +++ b/datawidgetmapper_625781186.html @@ -0,0 +1,378 @@ +QDataWidgetMapper 数据库绑定 QLineEdit控件 | PyQt + + + + + + + + + + + + + + +

QDataWidgetMapper 数据库绑定 QLineEdit控件

qt 为操作数据库提供了一个 model+view 的模式,这样简单的出入库逻辑就不需要自己编写。

+

QDataWidgetMapper 可以 将数据库的数据 映射到其他控件 。

+

注意:表格里的数据修改 还没有提交到数据库,需要点击提交按钮才生效。

+ +

https://github.com/PyQt5/PyQt/tree/master/Test/partner_625781186/16_sqlModel/01_mapper

+

# 代码

+
#-*- coding: utf-8 -*-
+
+from PyQt5 import  QtWidgets, QtGui, QtCore
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtSql import *
+
+import sys
+
+sys.path.append('./ui')
+from Ui_MainWindow import Ui_MainWindow
+
+
+class MainWindow(QMainWindow, Ui_MainWindow):
+    def __init__(self, parent=None, *args):
+
+        super(MainWindow, self).__init__(parent,  *args)
+        self.setupUi(self)
+        self.resize(800,600)
+        
+        #===============================   db   ======================================#
+        # self.db = QSqlDatabase.addDatabase('QMYSQL')
+        # self.db.setHostName("127.0.0.1")  # set address
+        # self.db.setUserName("root")  # set user name
+        # self.db.setPassword('123456')  # set user pwd   
+        # self.db.setDatabaseName("database")
+        
+        self.db = QSqlDatabase.addDatabase('QSQLITE')
+        self.db.setDatabaseName('./db/database.db')
+
+        #================================= codemodel =====================================#
+        # 实例化model
+        self.codeModel = QSqlRelationalTableModel()
+        # model设置表
+        self.initializeModel(self.codeModel, 'Mongo')
+        # 设置编辑策略
+        # self.codeModel.setEditStrategy(QSqlTableModel.OnFieldChange)
+        # !!! 这里要注意 , 只能用这个策略 , 才可以实现自动提交
+        self.codeModel.setEditStrategy(QSqlTableModel.OnManualSubmit)
+
+        self.codeView = self.createView("code_View", self.codeModel)
+        self.verticalLayout.addWidget(self.codeView)  
+
+        #================================ initData ==================================#
+        # 数据映射
+        self.mapper = QDataWidgetMapper()
+        # 提交策略
+        self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)
+        # 映射的模型源
+        self.mapper.setModel(self.codeModel)
+        self.mapper.addMapping(self.l1,0)
+        self.mapper.addMapping(self.l2,1)
+        self.mapper.addMapping(self.l3,2)
+        self.mapper.addMapping(self.l4,3)
+        self.mapper.addMapping(self.l5,4)
+
+        self.mapper.toFirst()
+        
+        #================================ pushButton ==================================#
+        self.sub_btn.clicked.connect(self.mapper.submit)
+        self.sub_btn.clicked.connect(self.codeModel.submitAll)
+        self.pre_btn.clicked.connect(self.mapper.toPrevious)
+        self.next_btn.clicked.connect(self.mapper.toNext)
+        
+    def initializeModel(self, model, tablename):
+        '''重关联。'''
+        model.setTable(tablename)
+#        model.setEditStrategy(QSqlTableModel.OnRowChange)
+        model.select()
+        
+    def createView(self, title, model):
+        '''创建TableView视图'''
+        view =  QTableView()
+        view.setModel(model)
+        view.setWindowTitle(title)
+        #列宽设置
+        view.horizontalHeader().setSectionResizeMode(3)
+        #行高设置
+        view.verticalHeader().setSectionResizeMode(1)
+        #充满列宽
+        view.horizontalHeader().setStretchLastSection(True) 
+#        view.verticalHeader().setVisible(False)#隐藏行标题
+        #标题左对齐
+        view.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
+        #标题左对齐
+        view.verticalHeader().setDefaultAlignment(Qt.AlignLeft)
+        
+        return view  
+    
+
+if __name__ == "__main__":
+    import sys
+    
+    app = QApplication(sys.argv)
+    app.setStyle(QStyleFactory.create("Fusion"))
+    ui = MainWindow()
+    ui.show()
+    sys.exit(app.exec_())
+

# 效果图

+

datawidgetmapper

+
文章作者: Irony
文章链接: https://pyqt5.com/datawidgetmapper_625781186.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/equal_str_width_625781186.html b/equal_str_width_625781186.html new file mode 100644 index 00000000..d381aa02 --- /dev/null +++ b/equal_str_width_625781186.html @@ -0,0 +1,287 @@ +python 判断屏幕等宽字符串的长度   | PyQt + + + + + + + + + + + + + +

python 判断屏幕等宽字符串的长度  

判断屏幕等宽字符串的长度?

+ +

判断屏幕等宽字符串的长度?

+

image.png

+

【新手】重庆 - 搬砖 - NoWait 22:41:50 @北京 - BUG 开发 - 黑择明 求指点
+【专家】北京 - BUG 开发 - 黑择明 22:43:04 fontMetrics
+【专家】 https://pyqt.site (892768447) 22:43:54 QFontMetrics
+【专家】 https://pyqt.site (892768447) 22:44:09 通过 QLabel.font ().fontMetrics () 得到

+

【新手】重庆 - 搬砖 - NoWait 22:52:00
+https://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size
+image.png

+

【新手】重庆 - 搬砖 - NoWait 22:53:15 感觉和 fontMetrics 应该是差不多的

+

image.png

+
+

【专家】北京 - BUG 开发 - 黑择明 (996742224) 11:29:04
+fm = QFontMetrics(QFont())
+fm.width(“qweqwe”)

+
文章作者: Irony
文章链接: https://pyqt5.com/equal_str_width_625781186.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/faq/index.html b/faq/index.html new file mode 100644 index 00000000..e0e77705 --- /dev/null +++ b/faq/index.html @@ -0,0 +1,356 @@ +常见问题 | PyQt + + + + + + + + + + + + + +

# 常见问题整理

+

这里会收集和整理各种常见问题,包括但不限于编辑器问题、编译打包问题、常见犯错。希望大家可以共同来完善此文档,编辑地址:https://github.com/PyQt5/blog/blob/dev/source/faq/index.md

+

# 案例库和提问

+

有专门的项目收集例子、提交例子、和回答问题,同时也建议大家都在上面多多提问和提交例子,这样可以留下记录方便遇到同样问题的人快速找到答案。

+

项目地址:https://github.com/PyQt5/PyQt/issues

+

如果你想写一写文章来帮助其他人少走弯路,也可以到 https://github.com/PyQt5/blog/tree/dev 去提交博客文章,访问地址是:https://pyqt5.com

+

# 文档查阅

+ +

# 界面美化

+ +

# Pyinstaller

+
    +
  1. 如果安装 anaconda, 请别用这个环境的 python
  2. +
  3. 设置 pyqt5 的环境变量
  4. +
  5. 如果在 pycharm 中把文件夹设置为了根路径,请在终端 (cmd) 中 使用命令 python xxx.py 运行脚本来确认 模块导入无错误后在打包
  6. +
  7. 这点很重要!!如果需要打包成单文件,先别用 - w 命令,最后打包无错误后再加上 - w
  8. +
  9. 如果打包后的窗体一闪而过,请在 cmd 中运行你的 exe 文件
  10. +
+

错误处理:

+
    +
  1. module PyQt5.sip not found: 确保在 cmd 模式下可以 import 这个模块后,再在程序中手动 import PyQt5.sip , 然后再打包尝试运行
  2. +
  3. Failed to load platform plugin “windows”…: 百度有解决方法,拷贝 python 目录下的 \PyQt5\Qt\plugins\platforms 到 exe 目录
  4. +
  5. QPixmap 处理 / 样式 问题 都是同 5. 一样都是 dll 丢失,到目录下找对应的文件件拷贝到 exe 目录
  6. +
  7. –add-data 打包非 python 模块文件,可能出现的问题及办法: https://github.com/pyinstaller/pyinstaller/issues/3749 +
      +
    • 还是失败的话检查 电脑用户名是否是中文,如果是中文
    • +
    • 对那个路径名进行编码
    • +
    • 则改变 spec 中 exe= EXE (…) 里的 runtime_tmpdir 指定为英文路径
    • +
    +
  8. +
  9. 如果需要打包成单文件,先别用 - w 命令,最后打包无错误后再加上 - w
  10. +
+

# Pycharm

+
    +
  1. PyQt5 环境配置 https://blog.csdn.net/px41834/article/details/79383985
  2. +
  3. 调试 PyQt 没有错误信息提示,原因以及解决办法: https://www.jianshu.com/p/47b6e7ce4639
  4. +
  5. 不识别 PyQt5 模块: +
      +
    • 新建的项目使用了新建的虚拟环境的 python.exe 解释器,更换已经安装过 pyqt5 的解释器再更新索引即可,设置 python 解释器路径在 pycharm 的菜单 File->Settings->Project:->Project Interpreter
    • +
    • 在尝试网上搜索的办法都没解决的情况下,一般就是 pycharm 的配置出问题了,找到 C:\Users\XXX\.PyCharm2018.1 路径,删除之后重启 pycharm , 重新配置
    • +
    +
  6. +
+

# Eric6

+
    +
  1. 环境配置 请参考 第一讲 https://space.bilibili.com/1863103/#/
  2. +
  3. 汉化:eric6 汉化包只到 17.12 版本,但是可以兼容高版本,自行百度或群文件下载
  4. +
  5. 双击无法打开设计师 //pyuic5|pyrcc5 无法编译 ui|qrc 文件 +
      +
    • 检查是否安装 pyqt-tools 或者 PyQt5Designer
    • +
    • 检查是否加入环境变量
    • +
    • eric6 菜单栏 设置 (settings) - 首选项 (preference) - Qt
    • +
    • Qt -> Tools -> Tools Directory: 配置 designer.exe 路径
    • +
    • PyQt -> Tools -> Tools Directory: 配置 pyuic5.exe/pyrcc5.exe 路径
    • +
    • !! 检查 qrc 路径中是否含有中文!!如果有则重命名
    • +
    +
  6. +
+

# 设计师

+
    +
  1. 通过 pip install pyqt5-tools 或者 pip install PyQt5Designer 安装
  2. +
  3. PyQt5Designer 自带汉化包,执行在 site-packages\PyQt5\Qt\bin\designer.exe
  4. +
+

# Matplotlib

+
    +
  1. PyQt5 结合 matplotlib 时,如何显示其 NavigationToolbar:http://www.cnblogs.com/hhh5460/p/5189843.html
  2. +
  3. matplotlib 绑定到 PyQt5:http://www.cnblogs.com/hhh5460/p/4322652.html
  4. +
+
\ No newline at end of file diff --git a/source/favicon.ico b/favicon.ico similarity index 100% rename from source/favicon.ico rename to favicon.ico diff --git a/feed.json b/feed.json new file mode 100644 index 00000000..bf6edeb9 --- /dev/null +++ b/feed.json @@ -0,0 +1,231 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pytest_qt_modal_625781186.html", + "url": "https://pyqt5.com/pytest_qt_modal_625781186.html", + "title": "pytest-qt 测试模态窗体.", + "date_published": "2024-04-30T01:46:52.392Z", + "content_html": "

步骤分别是 :

\n
    \n
  1. \n

    点击 开始扫描 弹出 选择路径窗口;

    \n
  2. \n
  3. \n

    勾选路基;

    \n
  4. \n
\n

3. 点击确定;

\n\n

大概想测一下这个界面 :

\n

\"image.png\"

\n

步骤分别是 :

\n
    \n
  1. \n

    点击 开始扫描 弹出 选择路径窗口;

    \n
  2. \n
  3. \n

    勾选路基;

    \n
  4. \n
\n

3. 点击确定;

\n

需要测试的函数 :

\n

\"image.png\"

\n

测试函数 :

\n

\"image.png\"

\n

可以发现断言失败 .

\n

\"image.png\"

\n

官方文档:测试模态窗体.

\n

https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html

\n

用的是官方的 monkeypatch 方式 .

\n

大致意思就是替换 FileSelectPathDialog 类的 exec 函数.

\n", + "tags": [ + "pytest-qt" + ] + }, + { + "id": "https://pyqt5.com/macm1pyqt.html", + "url": "https://pyqt5.com/macm1pyqt.html", + "title": "如何在Mac M1上快速安装PyQt5", + "date_published": "2023-10-07T06:08:06.000Z", + "content_html": "

由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。
\nM1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。

\n\n

1. 安装 Python
\npython-3.9.13-macosx10.9.pkg

\n

2. 勾选自定义同时只勾选安装 pip

\n

\"step1.png\"

\n

\"step1.png\"

\n

3. 设置 pip 源

\n
/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi\n/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna
\n

4. 安装 PyQt5

\n
/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5
\n

5. 测试

\n
/Library/Frameworks/Python.framework/Versions/3.9/bin/python3
\n

\"step3.png\"

\n

📢📢📢

\n

也可以直接安装 Miniconda

\n

然后:conda install -c conda-forge pyqt

\n", + "tags": [ + "PyQt", + "Mac", + "M1" + ] + }, + { + "id": "https://pyqt5.com/equal_str_width_625781186.html", + "url": "https://pyqt5.com/equal_str_width_625781186.html", + "title": "python 判断屏幕等宽字符串的长度  ", + "date_published": "2019-12-26T11:49:41.000Z", + "content_html": "

判断屏幕等宽字符串的长度?

\n\n

判断屏幕等宽字符串的长度?

\n

\"image.png\"

\n

【新手】重庆 - 搬砖 - NoWait 22:41:50 @北京 - BUG 开发 - 黑择明 求指点
\n【专家】北京 - BUG 开发 - 黑择明 22:43:04 fontMetrics
\n【专家】 https://pyqt.site (892768447) 22:43:54 QFontMetrics
\n【专家】 https://pyqt.site (892768447) 22:44:09 通过 QLabel.font ().fontMetrics () 得到

\n

【新手】重庆 - 搬砖 - NoWait 22:52:00
\nhttps://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size
\n\"image.png\"

\n

【新手】重庆 - 搬砖 - NoWait 22:53:15 感觉和 fontMetrics 应该是差不多的

\n

\"image.png\"

\n
\n

【专家】北京 - BUG 开发 - 黑择明 (996742224) 11:29:04
\nfm = QFontMetrics(QFont())
\nfm.width(“qweqwe”)

\n", + "tags": [ + "Python" + ] + }, + { + "id": "https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html", + "url": "https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html", + "title": "修改pyuic代替pyside2-uic.", + "date_published": "2019-12-26T11:49:41.000Z", + "content_html": "

修改 pyuic 代替 pyside2-uic

\n\n

修改 pyuic 代替 pyside2-uic.

\n

最近看到挺多人用 pyside2 的 uic 编译 ui 文件有问题 .
\n 写个解决办法.

\n

首先,
\n pip install qtpy ,
\n 这个是兼容 pyqt5 和 pyside2 的,无缝转换 .

\n

然后,
\n 修改 pyqt5 的 uic ,

\n

\"image.png\"

\n

最后用 pyuic5 , 生成 Ui_XXX.py 文件 .

\n", + "tags": [ + "PyQt5 PySide2" + ] + }, + { + "id": "https://pyqt5.com/studynotes.html", + "url": "https://pyqt5.com/studynotes.html", + "title": "PyQt学习心得", + "date_published": "2019-08-26T01:00:00.000Z", + "content_html": "

在学习 PyQt 的过程中由于资料的缺乏或者没有中文导致大多数人感叹资料太少,学习困难,又或者急于求进,赶鸭子上架的情况,此时有系统的学习方法很重要。每个人都需要有自己的学习方法,别人的学习方法并不一定适合自己但可以采纳一些。笔者在这里列举了一些当初自己自学的一些心得和方法,希望帮助大家建立一套自己的学习 PyQt 的方法,提高自身的学习能力。

\n\n

# Python 基础

\n

在学习和使用 PyQt 之前需要熟练使用 Python,经过对 QQ 群里经常提问的问题的分析,发现大部分人对 Python 中的基础知识掌握不牢固导致很多基础问题,如果要想更好的使用 Python 以及它的扩展必需要进行系统的学习。这里列举一下常用的知识点。

\n
    \n
  1. 类         参考资料
  2. \n
  3. 类的继承
  4. \n
  5. 类的多继承
  6. \n
  7. 类方法重写     参考资料
  8. \n
  9. 类中的 super 函数  参考资料
  10. \n
  11. 函数调用 / 参数类型
  12. \n
  13. 对象调用 (参考第 1 点)
  14. \n
\n

必须熟练掌握上面的知识点后入门 PyQt 才比较容易,如果初学者对上面的知识点还不是很了解,本文不适合继续往下阅读。

\n

# 设计师

\n

Qt 设计师除了方便快速设计一些简单的界面外,其实笔者觉得更大的作用在于帮助用户熟悉各类控件、属性、信号等

\n
    \n
  1. 这里建议初学者不要急于求成,打开设计师新建一个 Widget 的窗口,比如
  2. \n
\n

\"desiger_create\"

\n
    \n
  1. 然后把左侧的所有控件挨个拖动到中间的窗口中,比如这里拖动一个 Push Button 按钮
  2. \n
\n

\"desiger_drag\"

\n
    \n
  1. 在设计师右下角的属性编辑器中列举了该控件的所有父类,意味着可以调用和重写父类的所有方法,建议初学者把这个属性编辑器的所有属性挨个调整看看效果,部分控件可能需要 Ctrl+R 预览界面才能看到,同时像 QListWidget,QTreeWidget,QTableWidget 等某些控件需要在控件上右键增加数据才可以
  2. \n
\n

\"desiger_property\"
\n\"desiger_property2\"

\n
    \n
  1. 两个控件之间简单的信号槽关联可以通过设计师快速的设置
  2. \n
\n

\"desiger_signal\"
\n\"desiger_signal2\"

\n
    \n
  1. 提高进阶的方法,当你需要手动写代码实现界面的时候,不妨把 UI 文件转出 PY 文件,看看是如何构造的(这里涉及到布局等知识见后文)
  2. \n
\n

# 布局

\n

Qt 界面提供了方便的 4 种基本布局,QVboxLayout,QHboxLayout,QFormLayout,QGridLayout,初学者需要数量掌握这 4 种布局外加 2 种拉伸器(占位挤压)

\n

首先需要知道 Qt 界面的中控件的层级顺序以及 parent,parent 的作用既作为子控件的父元素也可以自动管理 Qt 的对象(具体可以搜索下关于 Qt parent 的资料)

\n
    \n
  1. 在没有布局的情况下,在设计师中拖动摆放的控件是一层一层的叠加覆盖,此时每个添加的子控件的 parent 都是最外层的控件
  2. \n
\n

\"desiger_stack\"

\n
    \n
  1. 如果需要界面中的控件自动适应高度宽度,此时则需要使用 4 种布局来包裹里面的子控件,注意的是:布局不是控件不能设置高度宽度和样式等,是一个抽象的东西,就好比是一根橡皮筋包裹几个矩形的物品;布局也可以设置一些属性(在设计师属性编辑器中),比如设置两者直接的间距,设置距离上下左右的间距,设置比例等
  2. \n
\n

\"desiger_layout\"

\n
    \n
  1. 在没有布局或者有布局的时候。可以添加容器控件(QWidget,QFrame,QGroupBox,QScrollArea,QToolBox,QTabWidget,QStackedWidget,QMidArea,QDockWidget)这些容器可以放置子控件,从而循环嵌套。
  2. \n
\n

# 例子

\n

在 PyQt5.5 的时候自带了一个例子文件夹(后面的版本没有的话可以下载 PyQt5 源码,里面有个 examples 文件夹),想要熟练的掌握 PyQt 还需要从自带的例子中学习,必须要每个例子都运行一次然后看看这个例子实现了什么,这样才能记忆深刻。
\n同时很多开发者在 https://github.com/PyQt5/PyQt 分享了各类进阶例子,同时也欢迎大家共同完善该项目,提供更多更好的例子。另外也可以下载该项目的客户端 PyQtClient 软件,支持运行其中的例子

\n

建议在更深入的学习 PyQt 之前多看看一些例子。

\n

# 文档

\n

接下来要说的就是 Qt 的 api 文档,官网文档,这里其实不要害怕是英文就不想看,觉得看不懂了,其实官网的文档还是比较简洁的,而且函数名也比较直观就能知道意思。也可以用谷歌浏览器打开右键翻译,基本上都能看懂。笔者前期写过一篇如何查阅 Qt 文档的文档可以阅读学习一番。

\n

这里就拿 QWebEngineView 举一个例子,首先初学者在使用这个浏览器控件时候,会有诸多的问题比如:Cookie,拦截器等就不知道如何去调用函数来设置

\n
    \n
  1. 首先打开官网文档 https://doc.qt.io/qt-5/qwebengineview.html,可以看到只有少量的函数可以调用,寻找一番并没有发现和 Cookie 相关的东西,这个时候就需要把重点放在有特俗返回值的函数上,比如:
  2. \n
\n
QWebEngineHistory *\t          history() const\nQWebEnginePage *\t          page() const\nQWebEngineSettings *\t      settings() const
\n

这三个函数返回了一个类实例,就意味着可以调用其中的方法。

\n
    \n
  1. \n

    点击 page () 打开 https://doc.qt.io/qt-5/qwebenginepage.html,发现没有 cookie 相关的东西,只有 QWebEngineProfile *\tprofile () const 这个函数比较可疑。

    \n
  2. \n
  3. \n

    点击 **profile ()** 打开 https://doc.qt.io/qt-5/qwebengineprofile.html,在浏览器中搜索 cookie 发现这个类中包含大量和 cookie 相关的东西,比如:**QWebEngineCookieStore *\tcookieStore ()`** 从名字上可以猜测大概意思为 cookie 储存

    \n
  4. \n
  5. \n

    点击 **cookieStore ()** 打开 https://doc.qt.io/qt-5/qwebenginecookiestore.html,此时就会发现这个类里面包含了删除和设置 cookie 的方法。

    \n
  6. \n
  7. \n

    但是找到了这些方法后,面对初学者又一个问题来了,该如何去用?根据上面 4 点整理一下,把他们当做简单的 Python 对象,方法和操作方法和 class 一样的。

    \n
  8. \n
\n
self.webview = QWebEngineView()\n# 得到page\npage = self.webview.page()\n# 得到profile\nprofile = page.profile()\n# 得到cookieStore\ncookieStore = profile.cookieStore()\n# 清空cookie\ncookieStore.deleteAllCookies()\n\n# 用简短代码来表达就是\ncookieStore = self.webview.page().profile().cookieStore()\ncookieStore.deleteAllCookies()
\n

# 异常调试

\n

可能有时候由于粗心,或者调用了一些非法函数,参数错误等会导致程序出现一些异常,首先第一步复制最后一行的错误去百度或者谷歌搜索,大多时候能找到问题所在。其次如果搜索不到或者自己的异常可能是由于某个变量的值不对引起的,就需要在编辑器中打断点使用 DEBUG 模式调试变量值(如果不会可以采用麻烦一点的办法:用 print 打印出变量值)

\n

遇到问题后首先需要自己多调试排查问题,不要一遇到问题就去问,自己多尝试一个一个排查直到找到问题所在并解决,这也是一种提高自身能力的地方。

\n

# 检索资料

\n

作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。信息检索是每个程序猿必备的能力之一,其好处在于可以更快更准确的在茫茫网络海洋中找到自己所需要的东西,这个过程需要长期不断积累和练习。

\n
    \n
  1. 中文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt 拖拽
  2. \n
  3. 英文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt Drag Drop
  4. \n
\n

# 片尾

\n

好了,笔者基本上的学习过程就整理如上,这并不是说每个人都适合这样的方法,但至少笔者是这样一步一步走过来的。当你养成了一个学习、发现和解决问题的好习惯时就会慢慢得心应手。

\n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/python_statemachine_625781186.html", + "url": "https://pyqt5.com/python_statemachine_625781186.html", + "title": "python 状态机模块  ", + "date_published": "2019-07-17T09:03:33.000Z", + "content_html": "

用状态来取代 if…else 判断。

\n\n

GUI 涉及到挺多的状态改变,以前一直用 if…else 来判断,最近读了设计模式,发现有个状态模式,随后发现了状态机这个东西 .

\n

python 的状态机模块挺多的,不过好像很多都不更新了.
\n 推荐 2 个状态机模块,但是也没有太深入的使用经验,就跑跑例子,以后有更详细的 pyqt 例子再补上 .

\n

1: pip install python-statemachine

\n

官方例子 : https://github.com/fgmacedo/python-statemachine

\n

2. pip install state_machine

\n

官方例子 : https://github.com/jtushman/state_machine

\n

1 的 最近一次更新在 6 个月以前,使用 类继承mixin 方式,不过有些地方不如 2 个人性化;

\n

2 的设计更人性化一些,包括状态改变 beforeafter , 不过由于是装饰器实现的动态增加属性,有些地方编辑器智能提示可能就靠不上了.

\n

两者实现实现方式不一样,有兴趣可以读读源码 .

\n
    \n
  1. qt 内置状态机框架
  2. \n
\n

https://blog.csdn.net/amnes1a/article/details/62418196

\n

https://blog.csdn.net/dongfenghuojian/article/details/78187131

\n

http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程)

\n", + "tags": [ + "Python", + "python 状态机" + ] + }, + { + "id": "https://pyqt5.com/pyqt5_hook_key_625781186.html", + "url": "https://pyqt5.com/pyqt5_hook_key_625781186.html", + "title": "在pyqt中使用python全局钩子模块", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像 QQ 一样可以全局热键截图。这里介绍几个方法实现在 PyQt 中使用 Python 全局钩子模块实现全局热键功能。

\n\n
    \n
  1. pyHook3
  2. \n
\n

安装命令 : pip install pyhook3

\n

https://blog.csdn.net/q871063970/article/details/86648386

\n

似乎将 pyhook 支持 py3 版本的了?没有太多研究.

\n

缺点:只支持 win 平台.

\n

2. keyboard & mouse

\n

安装命令: pip install keyboard mouse

\n
\nfrom PyQt5 import  QtGui, QtWidgets, QtCore\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nimport keyboard\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.testBtn = QPushButton(self)\n        layout.addWidget(self.testBtn)\n\n        keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey'))\n        keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc"))\n\n    def abc(self,a,b,c):\n        print(a,b,c)\n        \nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

更详细例子 : pyqt 中使用 keyboard 全局热键

\n

优点:跨平台;

\n

缺点:模块名字取得太差,不容易被发现.

\n", + "tags": [ + "Python", + "pyqt hook key" + ] + }, + { + "id": "https://pyqt5.com/read_open_source.html", + "url": "https://pyqt5.com/read_open_source.html", + "title": "像读文章一样读源码", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

使用 snoop, 像读文章一样读源码。

\n\n

不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
\n\"image.png\"

\n

没有目的地概览 , 不知不觉就追究到细节里面去了。

\n

\"image.png\"

\n

所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。

\n

开始 :

\n
    \n
  1. python DEBUG 模块介绍 :
    \n 前段时间看过挺多文章提到 pysoonper 这个调试模块,有兴趣的可以百度一下.
    \n 个人尝试了一下,篇幅过大的 DEBUG 不适合用 pysoonper , 因为没有缩进!
    \n 这几天偶然遇到一个二次封装的模块 snoop, 完美地解决了这个问题.
  2. \n
  3. 操作步骤 :
  4. \n
\n\n

发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。

\n\n

callreturn 给加进去.

\n\n

\"eric6启动阶段\"

\n

\"image.png\"

\n

#000 是为了方便搜索 。
\n需要自己手动折叠 。
\n可以发现 每个 splash.showMessage() 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。

\n
\n

# ps: vscode 阅读 log 文件还是有一些不方便的地方,除了在 2. 中提到的,还有包括关闭文件再打开,折叠状态不会保留,有其他更好的方式 请留言告诉我,谢谢.

\n", + "tags": [ + "Python", + "debug", + "snoop" + ] + }, + { + "id": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "url": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "title": "python 获取子进程print信息  ", + "date_published": "2019-05-24T06:39:44.000Z", + "content_html": "

在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。

\n\n

问题所在:

\n

\"image.png\"

\n

如果模块都由自己开发, 正常操作

\n

\"image.png\"

\n

但是因为不能改,所以只能拦截:
\n代码:

\n
pythonPath = self.pythonPath_cb.currentText()\n\nif suffix == \"py\":\n    # 首次\n    self.pyCommand = [pythonPath, path]\n    self.modifiedReloadPython(path)\ndef modifiedReloadPython(self, path_):\n    os.chdir(os.path.dirname(path_))\n    # 子进程调用\n    self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n    # self.stdoutWorker.p = self.p\n    self.stdoutWorker = Worker(self.p)\n    self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append(\"PYDEBUG:\\n\" + x))\n    self.stdoutWorker.start()\nclass Worker(QThread):\n    stdout_signal = pyqtSignal(str)\n\n    def __init__(self, p, parent=None):\n        super().__init__(parent)\n        self.p = p\n\n    def run(self):\n        while True:\n            QApplication.processEvents()\n            if self.p is not None:\n                line = self.p.stdout.readline()\n                # line = line.strip()\n                if line != b'':\n                    try:\n                        info = line.decode()\n                        self.stdout_signal.emit(info)\n                    except:\n                        self.stdout_signal.emit(repr(line))\n    
\n", + "tags": [ + "Python", + "subprocess.Popen", + "拦截print" + ] + }, + { + "id": "https://pyqt5.com/qtwebjs.html", + "url": "https://pyqt5.com/qtwebjs.html", + "title": "QtWebkit和QWebEngineView与Javascript交互", + "date_published": "2019-05-22T03:30:36.000Z", + "content_html": "

以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

\n\n

# 说明

\n
    \n
  1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
  2. \n
  3. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
  4. \n
  5. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
  6. \n
\n
@pyqtSlot(str)\ndef callFromJs(self, text):\n    QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))
\n
    \n
  1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
  2. \n
\n
// 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\nBridge.windowTitleChanged.connect({fun: function(title) {\n    showLog(\"标题被修改为:\" + title);\n}}, \"fun\");\n\n// 绑定自定义的信号customSignal\nBridge.customSignal.connect({fun: function(text) {\n    showLog(\"收到自定义信号内容:\" + text);\n}}, \"fun\");
\n
    \n
  1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
  2. \n
\n
new QWebChannel(qt.webChannelTransport,\n    function(channel) {\n        window.Bridge = channel.objects.Bridge;\n        \n        // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\n        Bridge.windowTitleChanged.connect(function(title) {\n            showLog(\"标题被修改为:\" + title);\n        });\n        \n        // 绑定自定义的信号customSignal\n        Bridge.customSignal.connect(function(text) {\n           showLog(\"收到自定义信号内容:\" + text);\n        });\n    }\n);
\n

# 代码

\n

QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

\n

QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

\n
    \n
  1. 针对 QWebView 的核心实现
  2. \n
\n
class WebView(QWebView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebView, self).__init__(*args, **kwargs)\n        self.initSettings()\n        # 暴露接口对象\n        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)\n\n    def _exposeInterface(self):\n        \"\"\"向Js暴露调用本地方法接口\n        \"\"\"\n        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
\n
    \n
  1. 针对 QWebEngineView 的核心实现
  2. \n
\n
class WebEngineView(QWebEngineView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebEngineView, self).__init__(*args, **kwargs)\n        self.channel = QWebChannel(self)\n        # 把自身对象传递进去\n        self.channel.registerObject('Bridge', self)\n        # 设置交互接口\n        self.page().setWebChannel(self.channel)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
\n

# 效果图

\n

\"JsSignals\"

\n", + "tags": [ + "PyQt", + "QWebView", + "QWebEngineView", + "浏览器" + ] + }, + { + "id": "https://pyqt5.com/flipwidgetanimation.html", + "url": "https://pyqt5.com/flipwidgetanimation.html", + "title": "PyQt5窗口翻转动画", + "date_published": "2019-05-15T14:48:00.000Z", + "content_html": "

QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 QStackedWidget 包含两个控件做切换,同时单独使用一个窗口做动画绘制。

\n\n

# 原理说明

\n
    \n
  1. 用了两个 QLabel 来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
  2. \n
  3. 用了 QStackedWidget 来存放上面的两个界面 QLabel
  4. \n
  5. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口
  6. \n
  7. 通过 setWindowOpacity 控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
  8. \n
  9. 动画窗口 FlipWidget.py 主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画
  10. \n
\n

# 核心实现

\n
    \n
  1. 主要是在 paintEvent 方法中使用 QTransformQPainter 进行圆心变换以及 rotate 设置翻转角度
  2. \n
  3. 同时根据翻转的角度范围对图片进行切换和缩放
  4. \n
\n
def paintEvent(self, event):\n    super(FlipWidget, self).paintEvent(event)\n\n    if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible():\n\n        painter = QPainter(self)\n        painter.setRenderHint(QPainter.Antialiasing, True)\n        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n        # 变换\n        transform = QTransform()\n        # 把圆心设置为矩形中心\n        transform.translate(self.width() / 2, self.height() / 2)\n\n        if self._angle >= -90 and self._angle <= 90:\n            # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程\n            painter.save()\n            # 设置翻转角度\n            transform.rotate(self._angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image1.width() / 2\n            height = int(self.image1.height() *\n                         (1 - abs(self._angle / self.Scale) / 100))\n            image = self.image1.scaled(\n                self.image1.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()\n        else:\n            # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程\n            painter.save()\n            if self._angle > 0:\n                angle = 180 + self._angle\n            else:\n                angle = self._angle - 180\n            # 设置翻转角度, 注意这里角度有差异\n            transform.rotate(angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image2.width() / 2\n            height = int(self.image2.height() *\n                         (1 - ((360 - abs(angle)) / self.Scale / 100)))\n            image = self.image2.scaled(\n                self.image2.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()
\n

# 代码

\n

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py

\n

# 效果图

\n

\"FlipWidgetAnimation\"

\n", + "tags": [ + "PyQt", + "动画", + "翻转" + ] + }, + { + "id": "https://pyqt5.com/QPropertyAnimation.html", + "url": "https://pyqt5.com/QPropertyAnimation.html", + "title": "PyQt属性动画(QPropertyAnimation)", + "date_published": "2019-05-08T07:43:06.000Z", + "content_html": "

QPropertyAnimation 继承自 QVariantAnimation ,其作为 Qt 的属性动画用于针对控件的属性或者继承自 QObject 的对象中定义的属性做修改,
\n简单来说就是基类是 QObject 且定义了属性变量,就可以用 QPropertyAnimation 来做属性动画。同时也可以通过 pyqtProperty 来增加自定义属性。

\n\n

首先,通过构造函数 QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None) 创建一个对象,其中

\n
    \n
  1. 第一个参数是动画作用的对象,也可以通过 setTargetObject 设置
  2. \n
  3. 第二个参数是属性名,在 py3 中类型是 bytes,也可以通过 setPropertyName 设置
  4. \n
\n

# 函数

\n

一些常见的设置函数

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
setPropertyName设置属性名
setTargetObject设置动画作用对象
setDuration设置动画持续时间(毫秒)
setStartValue设置开始值
setEndValue设置结束值
setEasingCurve设置动画曲线
setKeyValueAt插入线性值
setLoopCount设置循环次数(-1 为永久)
\n

# 示例

\n

比如这个例子:

\n
    \n
  1. 修改控件的 geometry 大小
  2. \n
  3. 修改自定义属性
  4. \n
  5. 修改进度条的 value 值
  6. \n
\n

\"QPropertyAnimation\"

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年5月8日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\nfrom PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurve\nfrom PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\\\n    QLabel, QProgressBar, QSpacerItem, QSizePolicy\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.resize(400, 400)\n        self._value = 0\n        self.button = QPushButton('属性动画测试', self)\n        self.button.clicked.connect(self.doStart)\n        self.button.setGeometry(0, 0, 80, 40)\n\n        self.buttonc = QPushButton('自定义属性 测试', self)\n        self.buttonc.clicked.connect(self.doStartCustom)\n\n        self.label = QLabel('', self)\n\n        self.progressbar = QProgressBar(self)\n        self.progressbar.setRange(0, 99)\n\n        layout = QVBoxLayout(self)\n        layout.addItem(QSpacerItem(\n            20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed))\n        layout.addWidget(self.buttonc)\n        layout.addWidget(self.label)\n        layout.addWidget(self.progressbar)\n\n        # 进度条动画\n        self.progressStart()\n\n    # 此处是自定义属性,并通过动画修改后,设置QLabel的值\n    @pyqtProperty(int)\n    def value(self):\n        return self._value\n\n    @value.setter\n    def value(self, v):\n        self._value = v\n        self.label.setText('当前值:{}'.format(v))\n\n    def doStart(self):\n        # 第一个参数是要执行的对象\n        animation = QPropertyAnimation(self.button, b'geometry', self)\n        animation.setDuration(2000)  # 持续时间\n        # 缓和曲线风格,加了曲线动画会很大程度影响\n        animation.setEasingCurve(QEasingCurve.OutBounce)\n        animation.setStartValue(QRect(0, 0, 40, 40))\n        animation.setEndValue(QRect(250, 250, 80, 80))\n        animation.start(animation.DeleteWhenStopped)\n\n    def doStartCustom(self):\n        # 自定义属性动画\n        # 由于定义的属性是在继承的QWidget, 所以第一个参数是self\n        # 第二个参数就是 value\n        animation = QPropertyAnimation(self, b'value', self)\n        animation.setDuration(2000)  # 持续时间\n        animation.setStartValue(0)\n        animation.setEndValue(100)\n        animation.start(animation.DeleteWhenStopped)\n\n    def progressStart(self):\n        # 进度条动画\n        # 这里 value是QProgressBar自带的属性,具体可以看文档\n        # https://doc.qt.io/qt-5/qprogressbar.html#properties\n        animation = QPropertyAnimation(self.progressbar, b'value', self)\n        animation.setDuration(2000)  # 持续时间\n        animation.setLoopCount(-1)\n        # 这里采用插入线性值,第一个参数的范围是(0-1)\n        # 第二个参数的范围是进度(最小值-最大值)\n        animation.setKeyValueAt(0, self.progressbar.minimum())\n        animation.setKeyValueAt(0.1, 10)\n        animation.setKeyValueAt(0.2, 30)\n        animation.setKeyValueAt(0.5, 60)\n        animation.setKeyValueAt(0.7, 80)\n        animation.setKeyValueAt(1, self.progressbar.maximum())\n        animation.start(animation.DeleteWhenStopped)\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())\n
", + "tags": [ + "PyQt", + "动画" + ] + }, + { + "id": "https://pyqt5.com/viewapi.html", + "url": "https://pyqt5.com/viewapi.html", + "title": "如何查阅Qt文档", + "date_published": "2019-05-04T12:50:20.000Z", + "content_html": "

很多网友在问有没有 PyQt5 的文档之类的问题,在 PyQt4 的时候 PyQt 官网有了英文版的文档,随后有网友翻译成了中文。不过现在 PyQt5 官方的文档都指向了 C 的 Qt 文档,其实 C 的 Qt API 文档结构很清晰,翻阅很容易的,需要注意几点。

\n\n

作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。

\n

拿 Qt C++ 文档来说,官网地址是:https://doc.qt.io/qt-5/qtwidgets-module.html 这里面记录了所有控件的详细函数文档。

\n

比如拿 输入框 QLineEdit 来说,怎么去查询它的用法和信号槽等资料?

\n

https://doc.qt.io/qt-5/qlineedit.html

\n

# 左侧目录

\n

在文档左侧目录中有如下几个:

\n

Properties - 控件里的属性(比如宽高等,通常需要当作函数调用)

\n

Public Slots - 这个是控件自己的槽函数(当作普通函数就行)

\n

Signals - 这个是输入框的包含的信号

\n

Public Functions、Reimplemented Public Functions、Static Public Members、Protected Functions、Reimplemented Protected Functions - 这几个都是函数列表

\n

\"howtoviewapi1\"

\n

# 类说明

\n

\"howtoviewapi2\"

\n

这里有两个注意点

\n
    \n
  1. 红色方框内的表示该控件(输入框)继承于 QWidget ,所以该控件(输入框)拥有父类的所有方法和信号,当当前文档找不到相关资料和函数时,可以去父类找找看。
  2. \n
  3. 紫色方框内表示列举所有的方法(包括父类)
  4. \n
\n

# 函数列表

\n

\"howtoviewapi3\"

\n

这里列举的就是该控件(输入框)的函数,同理点击上面的紫色方框是查看所有方法,一般这里主要用来查询你需要的功能函数,Qt 的函数名比较容易理解,比如:只读 ReadOnly,选择文字:setSelection。

\n

所以再查下这部分资料的时候建议在浏览器中 Ctrl + F 打开浏览器的搜索框,并输入英文关键词来检索你所需要的函数在哪里。

\n

\"howtoviewapi8\"

\n

# 槽函数

\n

\"howtoviewapi4\"

\n

这部分列举的是槽函数,其实在 PyQt 中槽函数可以当作普通的函数。普通的函数也可以作为槽函数,直接通过信号连接即可,注意方框所示,还有很多函数是在父类里面。

\n

# 信号

\n

\"howtoviewapi5\"

\n

这部分列举了该控件(输入框)所定义的信号,主要还是看名字,大多都能知道是做什么的,比如:

\n
    \n
  1. editingFinished - 编辑完成信号
  2. \n
  3. returnPressed - 回车键信号
  4. \n
  5. textChanged (const QString &text) - 内容改变信号
  6. \n
\n

这里还有个问题就是参数问题,一般 & 后面的 text 作为参数传递到槽函数中

\n

# 函数详细说明

\n

当不明确这个函数是做什么的,可以点击该函数跳转到下面的说明,比如回车键信号 returnPressed

\n

\"howtoviewapi6\"

\n

如图上所示,用翻译插件翻译,大部分就明白了,如下:

\n

\"howtoviewapi7\"

\n

# 关于如何搜索资料

\n

比如当你要搜索输入框内容改变事件,一般建议两种搜索,且搜索的时候用空格把关键词分开搜索,而且直接用控件名

\n
    \n
  1. 中文搜索引擎:QLineEdit 内容 改变
  2. \n
  3. 英文搜索引擎:QLineEdit text change
  4. \n
\n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/suggesteditor.html", + "url": "https://pyqt5.com/suggesteditor.html", + "title": "推荐编辑器LiClipse", + "date_published": "2019-05-04T10:04:08.000Z", + "content_html": "

关于 Python 的开发编辑器有很多,每个人有每个人的喜好,经常看到很多在问什么编辑器好用,有人推荐 Sublime,有人推荐 Pycharm 等等,这里就不去比较其它编辑器的优缺点了,只谈谈关于 LiClipse 这个编辑器在初级使用阶段的智能提示功能等。开箱即用,支持多种语言,RST,Markdown 和 HTML 编辑器的 HTML 预览。

\n\n

其实 LiClipse 这个编辑器就是以前的 PyDev 插件的独立版本,基于 Eclipse 编辑器开发,去掉了 Java 的相关开发功能,关于软件的详细说明可以去官网查看: http://www.liclipse.com/

\n

编辑器只需要少量的配置,打开即可使用,快速自动 import,也可以根据需要安装自己所需的插件,比如 json、svn、主题插件等。个人推荐:适合刚入门的新手使用

\n

由于新版的 PyQt 和 PyDev 去掉了详细的函数提示,所以 PyQt 的智能提示只有函数和返回值,并没有英文注释,但是以前的比如 PyQt4 的智能提示应该是有详细的英文注释提示。

\n

# 界面预览

\n
    \n
  1. 主界面
    \n\"editor1\"
  2. \n
  3. 鼠标悬停提示
    \n\"editor2\"
  4. \n
  5. 输入提示
    \n\"editor3\"
  6. \n
  7. Git 面板
    \n\"editor4\"
  8. \n
  9. 全局搜索(Ctrl + H)
    \n\"editor5\"
    \n\"editor6\"
  10. \n
\n

# 自动导包

\n

其实这个功能我是非常喜欢的,通过按下快捷键即可自动寻找包名导入,快捷键 Ctrl + Shift + O

\n

\"editor_import\"

\n

也可以在标红的代码上按下 Ctrl + F1 进行导入

\n

\"editor_import2\"

\n

# 配置

\n

打开编辑器后首先要配置【Window -> Preferences】的就是 Python 的环境变量,可以同时添加多个 Python 版本

\n

\"editor_env\"

\n

# Tab 等设置

\n
    \n
  1. Insert spaces for tabs tab 转空格
  2. \n
  3. Show line numbers 显示行号
  4. \n
\n

\"editor_tab\"

\n

# 模版

\n

这个功能可以快速插入自己定义好的模版代码,比如 if __name__ == '__main__': 等等,比如我这里配置的创建文件的模版

\n

\"editor_tpl\"

\n

# 常用快捷键

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
格式化对齐Ctrl + Shift + F
自动导包Ctrl + Shift + O
快捷提示Alt + /
\n", + "tags": [ + "编辑器" + ] + }, + { + "id": "https://pyqt5.com/bindsignals.html", + "url": "https://pyqt5.com/bindsignals.html", + "title": "三种方式绑定信号槽", + "date_published": "2019-05-04T08:07:06.000Z", + "content_html": "

网上关于 PyQt5 的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在 PyQt5 中无法使用,这里归纳总结下已有的几种绑定信号槽的方式,
\n这几种方式各有各的优点和缺点。

\n\n

# 方式一

\n

这个方式是最开始接触设计师的时候知道的,主要是通过控件的 objectNameQtCore.QMetaObject.connectSlotsByName(Form) 提供的连接函数来自动完成注册,
\n比如带有按钮的界面 ui 文件转成 py 文件后会发现如下代码:

\n
self.pushButton = QtWidgets.QPushButton(Form)\nself.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28))\nself.pushButton.setObjectName(\"pushButton\")\n\n# 通过这里自动完成连接信号槽\nQtCore.QMetaObject.connectSlotsByName(Form)
\n

此时只需要继承该 UI 文件类然后增加如下方法:

\n
\n@pyqtSlot()\ndef on_pushButton_clicked(self):\n    print('button clicked')
\n

这里解释一下, @pyqtSlot() 装饰器把函数 on_pushButton_clicked 包装为一个槽函数,
\n而 QtCore.QMetaObject.connectSlotsByName(Form) 这句代码的意思就是自动去寻找满足的槽函数

\n

注意:这里有个规范(on_xxxx_clicked),这里必须要满足 on_控件的objectName_控件的信号 这样下划线连接起来的函数名才能被识别,
\n比如按钮的点击: on_pushButton_clicked 、勾选框的选中: on_checkbox_toggled(self, checked)

\n

# 方式二

\n

这种方式则直接通过代码里调用控件的信号的 connect 方法来进行绑定,比如:

\n
# 按钮点击函数\ndef doClicked(self):\n    print(self.sender(), 'clicked')\n\n# 绑定点击信号\nself.pushButton.clicked.connect(self.doClicked)
\n

注意: connect 的是函数名字self.sender() 这句代码是获取信号发送者(比如这里就是得到这个按钮对象),
\n用处在于有时候要循环创建一堆按钮

\n

# 方式三

\n

通过参数这种方式其实比较特殊,在 PyQt 中大部分存在,但是在 PySide 中则很少,原因是两者的封装方式不同。

\n

同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如:

\n
\n# 按钮点击函数\ndef doClicked(self):\n    print(self.sender(), 'clicked')\n\npushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40)
\n

这里可以通过参数(信号名字) = 函数来绑定信号

\n

同时也可以设置其它参数,比如
\n button.setMinimumHeight(40) 也可以像参数里那样设置 minimumHeight=40

\n", + "tags": [ + "PyQt", + "信号" + ] + }, + { + "id": "https://pyqt5.com/virtualenvpy_625781186.html", + "url": "https://pyqt5.com/virtualenvpy_625781186.html", + "title": "python 拷贝虚拟环境(一)  ", + "date_published": "2019-05-02T07:21:01.000Z", + "content_html": "

通常来说,刚开始使用 python 的时候都是把包装到全局路径,随着各个项目安装的包越来越多,之后每开始一个项目,pycharm 创建索引的时间都越来越漫长,所以不可避免得开始使用虚拟环境。
\n经过一番了解 ,虚拟环境的优点有这些:

\n\n\n

python 虚拟环境库除了自带的 venv , 还有三方库 virtualenv , 此外 在 virtualenv 基础上又开发了 virtualenvwrapper(virtualenvwrapper_win) 来管理

\n

本文基于 virtualenvwrapper 创建的虚拟环境来讲解.

\n
以下是收集的一些virtualenvwrapper配置教程:\n# linux平台\nhttps://www.cnblogs.com/netfoxman/p/5994697.html\n# window平台\nhttps://blog.csdn.net/shaququ/article/details/54292043   \nhttps://blog.csdn.net/iaau0908/article/details/54021518\n
\n

虚拟环境创建多了我们就会发现,
\n 有时候使用相同版本的环境,一些常用的库是需要重新安装的,
\n 那么能不能创建一个基础环境,默认拥有这些库,然后在这个基础环境上继续安装三方库呢?

\n

本文经过试验发现是可行的:

\n
    \n
  1. \n

    创建基础虚拟环境 mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径] . 例如 mkvirtualenv py34 -p c:\\Python34\\python.exe

    \n
  2. \n
  3. \n

    切换到虚拟环境 workon py34 , 然后安装一下三方库,然后复制 py34 这个文件夹备份一下;

    \n
  4. \n
  5. \n

    接着复制这个 py34 文件夹,把复制后的文件夹改名为我们需要需要的文件夹例如 new34

    \n
  6. \n
  7. \n

    进入 new34文件夹 ,用任意编辑器全路径搜索 py34 (替换虚拟环境的路径)

    \n
  8. \n
  9. \n

    删除 new34/Scripts 下的 pip.exe, pip3.exe, pip3.x.exe, easy_install.exe (因为安装路径硬编码到这里面了,改不了,需要重新安装)

    \n
  10. \n
  11. \n

    https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二,源码安装 setuptools 后再用 easy_install pip 安装 pip 后,完成;
    \n 如果有问题,就继续按照方式一的源码安装 pip;

    \n
  12. \n
  13. \n

    new34 环境下 用 pip show 三方库 来看一些库的位置,确保正确.

    \n
  14. \n
\n", + "tags": [ + "Python", + "virtualenvwrapper", + "virtualenv" + ] + }, + { + "id": "https://pyqt5.com/runnablesignal_625781186.html", + "url": "https://pyqt5.com/runnablesignal_625781186.html", + "title": "QRunnable线程池发信号", + "date_published": "2019-04-30T07:58:09.000Z", + "content_html": "

因为只有继承 QObject 的类才能有信号和自定义信号,而 QRunnable 并不是继承自 QObject ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。

\n\n

pools 是 QThreadPool 实例

\n

# 看图说话

\n
    \n
  1. \"runnablesignal1\"
  2. \n
  3. 定义一个全局信号类
    \n\"runnablesignal2\"
  4. \n
  5. 在 QRunnable 中发送
    \n\"runnablesignal3\"
  6. \n
\n", + "tags": [ + "PyQt", + "信号", + "线程" + ] + }, + { + "id": "https://pyqt5.com/viewpyindesigner_625781186.html", + "url": "https://pyqt5.com/viewpyindesigner_625781186.html", + "title": "如何和设计师中查看ui转换的py代码", + "date_published": "2019-04-30T05:11:09.000Z", + "content_html": "

通过 设计师  查看 ui 转换的 py 代码

\n

当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。

\n

后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。

\n\n

再来后发现了官方文档,查一查函数就行了 .

\n

但是有些 api 文档找起来麻烦,用设计师点几下就行了,然后把转换出来的代码拷贝一下就完事了.

\n

可是需要单独把 ui 转为 py 文件,之后再删除这个文件也是很烦的一件事 .

\n

好,话不多说,接下来手把手教你如何快速在 ui 中查看 py 代码 .

\n

官方也考虑过这种情况,所以 设计师中 是有这个功能的,但是 qt 的是没问题的,pyqt 的毕竟是绑定过来的,所以正常来说 你点击之后会弹出一个找不到应用程序的提示 .

\n

看到这个东西是不是很眼熟,我们用的命令 pyuic5 和这个东西应该是一样的 .

\n

\"viewpyindesigner1\"

\n

所以接下来,我们找找电脑上有没有这个东西

\n

\"viewpyindesigner2\"

\n

果然在 pyqt5-toos 文件夹下有这个东西,

\n

我们根据第一张图的提示,把这个东西拷贝到相应的目录 (如果没有那个 bin 文件夹,手动创建),

\n

\"viewpyindesigner3\"

\n

好了,大功告成!

\n", + "tags": [ + "PyQt", + "Designer", + "设计师" + ] + }, + { + "id": "https://pyqt5.com/showframe.html", + "url": "https://pyqt5.com/showframe.html", + "title": "PyQt5调整窗口显示边框", + "date_published": "2019-04-26T14:19:26.000Z", + "content_html": "

windows 某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 windows 在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -> 调整 Windows 的外观和性能 -> 去掉勾选 拖动时显示窗口内容。

\n\n

由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。

\n
    \n
  1. 一种是自己绘制一个边框效果,放开鼠标时才操作真正的窗口。
  2. \n
  3. 二是替换窗口的处理过程函数 wndproc 处理 WM_NCLBUTTONDOWN 消息事件。
  4. \n
\n

今天讲第二种方法:

\n
    \n
  1. 需要了解 SystemParametersInfo  API 函数
  2. \n
  3. SPI_GETDRAGFULLWINDOWS :确定是否允许拖拉到最大窗口
  4. \n
  5. SPI_SETDRAGFULLWINDOWS :设置是否允许拖至最大窗口
  6. \n
\n

效果就是这样的:

\n

\"ShowFrameWhenDrag\"

\n

正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。

\n

# 代码

\n

https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月23日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: ShowFrameWhenDrag\n@description: 调整窗口显示边框\n\"\"\"\nfrom ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\\\n    c_ulonglong, WINFUNCTYPE, c_uint\n\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\nif sizeof(c_long) == sizeof(c_void_p):\n    WPARAM = c_ulong\n    LPARAM = c_long\nelif sizeof(c_longlong) == sizeof(c_void_p):\n    WPARAM = c_ulonglong\n    LPARAM = c_longlong\n\nWM_NCLBUTTONDOWN = 0x00a1\nGWL_WNDPROC = -4\nSPI_GETDRAGFULLWINDOWS = 38\nSPI_SETDRAGFULLWINDOWS = 37\nWNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM)\n\ntry:\n    CallWindowProc = windll.user32.CallWindowProcW\n    SetWindowLong = windll.user32.SetWindowLongW\n    SystemParametersInfo = windll.user32.SystemParametersInfoW\nexcept:\n    CallWindowProc = windll.user32.CallWindowProcA\n    SetWindowLong = windll.user32.SetWindowLongA\n    SystemParametersInfo = windll.user32.SystemParametersInfoA\n\n\ndef GetDragFullwindows():\n    rv = c_int()\n    SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0)\n    return rv.value\n\n\ndef SetDragFullwindows(value):\n    SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0)\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        layout.addWidget(QLabel('拖动或者调整窗口试试看'))\n\n        # 重点替换窗口处理过程\n        self._newwndproc = WNDPROC(self._wndproc)\n        self._oldwndproc = SetWindowLong(\n            int(self.winId()), GWL_WNDPROC, self._newwndproc)\n\n    def _wndproc(self, hwnd, msg, wparam, lparam):\n        if msg == WM_NCLBUTTONDOWN:\n            # 获取系统本身是否已经开启\n            isDragFullWindow = GetDragFullwindows()\n            if isDragFullWindow != 0:\n                # 开启虚线框\n                SetDragFullwindows(0)\n                # 系统本身处理\n                ret = CallWindowProc(\n                    self._oldwndproc, hwnd, msg, wparam, lparam)\n                # 关闭虚线框\n                SetDragFullwindows(1)\n                return ret\n        return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

# 片尾

\n

替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。

\n", + "tags": [ + "PyQt", + "边框" + ] + }, + { + "id": "https://pyqt5.com/issignalconnected.html", + "url": "https://pyqt5.com/issignalconnected.html", + "title": "PyQt5判断信号是否连接", + "date_published": "2019-04-26T14:06:26.000Z", + "content_html": "

PyQt 中某些情况下需要取消原来的信号连接,此时需要使用 disconnect 方法,但是在逻辑不严谨的情况下可能会导致多次调用 disconnect 方法而导致报错,当然可以通过 try except 来包裹代码。这里通过  isSignalConnected  来判断信号是否连接。

\n\n

在 QOjbect 文档中这样写到:

\n
static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged);\nif (isSignalConnected(valueChangedSignal)) {\n    QByteArray data;\n    data = get_the_value();       // expensive operation\n    emit valueChanged(data);\n}
\n

通过直接传入信号就行了,但是这在 PyQt 中不可行。需要这么做

\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年2月24日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: IsSignalConnected\n@description: 判断信号是否连接\n\"\"\"\n\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.button1 = QPushButton('已连接', self, clicked=self.doTest)\n        self.button2 = QPushButton('未连接', self)\n        self.retView = QTextBrowser(self)\n        layout.addWidget(self.button1)\n        layout.addWidget(self.button2)\n        layout.addWidget(self.retView)\n\n    def doTest(self):\n        self.retView.append(\"\"\"\n        # button1 clicked 是否连接: %s\n        # button2 clicked 是否连接: %s\n        \"\"\" % (\n            self.isSignalConnected(self.button1, 'clicked()'),\n            self.isSignalConnected(self.button2, 'clicked()')\n        ))\n\n    def isSignalConnected(self, obj, name):\n        \"\"\"判断信号是否连接\n        :param obj:        对象\n        :param name:       信号名,如 clicked()\n        \"\"\"\n        index = obj.metaObject().indexOfMethod(name)\n        if index > -1:\n            method = obj.metaObject().method(index)\n            if method:\n                return obj.isSignalConnected(method)\n        return False\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
\n

# 效果图

\n

\"IsSignalConnected\"

\n", + "tags": [ + "PyQt", + "信号" + ] + } + ] +} \ No newline at end of file diff --git a/ffmpeghls.html b/ffmpeghls.html new file mode 100644 index 00000000..7f29a3a7 --- /dev/null +++ b/ffmpeghls.html @@ -0,0 +1,339 @@ +FFmpeg合成加密HLS记录 | PyQt + + + + + + + + + + + + + + + +

FFmpeg合成加密HLS记录

记录在某个需求中要求截图并合成加密视频文件,这里采用 FFmpeg 的管道流来实现生成 HLS 加密文件。

+ +
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2019年3月4日
+@author: Irony
+@site: https://pyqt5.com https://github.com/892768447
+@email: 892768447@qq.com
+@file: 
+@description: 
+"""
+
+from pathlib import Path
+from subprocess import Popen, PIPE
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = 'Copyright (c) 2019 Irony'
+__Version__ = 1.0
+
+
+# p = Popen([r'D:\soft\ffmpeg\bin\ffmpeg.exe', '-y',
+#            '-threads', '2',
+#            '-f', 'image2pipe',
+#            '-vcodec', 'mjpeg', '-r', '24', '-i', '-',
+#            '-vcodec', 'h264', '-r', '24',
+#            #            '-encryption_scheme', 'cenc-aes-ctr',
+#            #            '-encryption_key', '617D8A125A284DF48E3C6B1866348A3F',
+#            #            '-encryption_kid', 'B326F895B6A24CC5A4DC70995728059C',
+#            r'F:\Workspace\Test\videos\video.mp4'], stdin=PIPE)
+
+p = Popen([r'D:\soft\ffmpeg\bin\ffmpeg.exe',
+           '-re',   # 按照实际帧率读取输入文件
+           '-y',        # 覆盖已存在文件
+           '-threads', '2',  # 线程数量
+           '-f', 'image2pipe',  # PIPE图片流
+           '-vcodec', 'mjpeg',  # 图片编码
+           '-r', '24',  # 帧率
+           '-i', '-',  # 指定输入流为PIPE
+           '-vcodec', 'h264',  # 输出编码
+           '-r', '24',  # 帧率
+           '-map', '0',
+#            '-crf','20',     # 降低质量
+           '-b', '720k',        # 码率
+           '-f', 'hls',
+           '-codec:v', 'libx264',
+           '-vbsf', 'h264_mp4toannexb',
+           # 指定加密密匙文件
+           '-hls_key_info_file', r'F:\Workspace\Test\videokey.info',
+           '-hls_time', '20',
+           '-hls_list_size', '0',
+           '-hls_wrap', '0',
+#            '-hls_flags', 'single_file',  # 生成单个文件(有bug)
+           r'F:\Workspace\Test\videos\playlist.m3u8'], stdin=PIPE)
+print(p)
+
+t = 1 / 24
+for i, path in enumerate(Path('frames').rglob('*.jpg')):
+    #     print(i, path)
+    p.stdin.write(open(str(path), 'rb').read())
+
+p.stdin.close()
+p.wait()
+print('ok')
文章作者: Irony
文章链接: https://pyqt5.com/ffmpeghls.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git "a/source/files/\346\227\240\350\276\271\346\241\206\345\234\206\350\247\222\351\230\264\345\275\261.zip" "b/files/\346\227\240\350\276\271\346\241\206\345\234\206\350\247\222\351\230\264\345\275\261.zip" similarity index 100% rename from "source/files/\346\227\240\350\276\271\346\241\206\345\234\206\350\247\222\351\230\264\345\275\261.zip" rename to "files/\346\227\240\350\276\271\346\241\206\345\234\206\350\247\222\351\230\264\345\275\261.zip" diff --git "a/source/files/\350\260\203\347\224\250java\347\224\237\346\210\220\346\212\245\350\241\250.7z" "b/files/\350\260\203\347\224\250java\347\224\237\346\210\220\346\212\245\350\241\250.7z" similarity index 100% rename from "source/files/\350\260\203\347\224\250java\347\224\237\346\210\220\346\212\245\350\241\250.7z" rename to "files/\350\260\203\347\224\250java\347\224\237\346\210\220\346\212\245\350\241\250.7z" diff --git a/flipwidgetanimation.html b/flipwidgetanimation.html new file mode 100644 index 00000000..15785687 --- /dev/null +++ b/flipwidgetanimation.html @@ -0,0 +1,341 @@ +PyQt5窗口翻转动画 | PyQt + + + + + + + + + + + + + + + +

PyQt5窗口翻转动画

QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 QStackedWidget 包含两个控件做切换,同时单独使用一个窗口做动画绘制。

+ +

# 原理说明

+
    +
  1. 用了两个 QLabel 来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
  2. +
  3. 用了 QStackedWidget 来存放上面的两个界面 QLabel
  4. +
  5. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口
  6. +
  7. 通过 setWindowOpacity 控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
  8. +
  9. 动画窗口 FlipWidget.py 主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画
  10. +
+

# 核心实现

+
    +
  1. 主要是在 paintEvent 方法中使用 QTransformQPainter 进行圆心变换以及 rotate 设置翻转角度
  2. +
  3. 同时根据翻转的角度范围对图片进行切换和缩放
  4. +
+
def paintEvent(self, event):
+    super(FlipWidget, self).paintEvent(event)
+
+    if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible():
+
+        painter = QPainter(self)
+        painter.setRenderHint(QPainter.Antialiasing, True)
+        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
+
+        # 变换
+        transform = QTransform()
+        # 把圆心设置为矩形中心
+        transform.translate(self.width() / 2, self.height() / 2)
+
+        if self._angle >= -90 and self._angle <= 90:
+            # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程
+            painter.save()
+            # 设置翻转角度
+            transform.rotate(self._angle, Qt.YAxis)
+            painter.setTransform(transform)
+            # 缩放图片高度
+            width = self.image1.width() / 2
+            height = int(self.image1.height() *
+                         (1 - abs(self._angle / self.Scale) / 100))
+            image = self.image1.scaled(
+                self.image1.width(), height,
+                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
+            painter.drawPixmap(
+                QPointF(-width, -height / 2), image)
+            painter.restore()
+        else:
+            # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程
+            painter.save()
+            if self._angle > 0:
+                angle = 180 + self._angle
+            else:
+                angle = self._angle - 180
+            # 设置翻转角度, 注意这里角度有差异
+            transform.rotate(angle, Qt.YAxis)
+            painter.setTransform(transform)
+            # 缩放图片高度
+            width = self.image2.width() / 2
+            height = int(self.image2.height() *
+                         (1 - ((360 - abs(angle)) / self.Scale / 100)))
+            image = self.image2.scaled(
+                self.image2.width(), height,
+                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
+            painter.drawPixmap(
+                QPointF(-width, -height / 2), image)
+            painter.restore()
+

# 代码

+

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py

+

# 效果图

+

FlipWidgetAnimation

+
文章作者: Irony
文章链接: https://pyqt5.com/flipwidgetanimation.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/followwindow.html b/followwindow.html new file mode 100644 index 00000000..784ca3f6 --- /dev/null +++ b/followwindow.html @@ -0,0 +1,350 @@ +PyQt5窗口跟随其它窗口 | PyQt + + + + + + + + + + + + + + +

PyQt5窗口跟随其它窗口

要实现 PyQt 窗口跟随其它外部的窗口,能想到两点办法,一个是 hook 系统事件得到目标窗口的位置和大小以及是否关闭等,二是通过循环检测窗口的位置来实现。

+ +

# 基于 Windows 定时检测目标窗口

+
    +
  1. 利用 win32gui 模块获取目标窗口的句柄
  2. +
  3. 通过句柄获取目标窗口的大小位置,并设置自己的位置
  4. +
  5. 主要是检测时间,在 10 毫秒以下很流畅
  6. +
  7. 窗口关闭是根据目标句柄无效来判断
  8. +
+

https://github.com/PyQt5/PyQt/blob/master/Demo/FollowWindow.py

+

# 代码

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2018年10月22日
+@author: Irony
+@site: https://github.com/892768447
+@email: 892768447@qq.com
+@file: FollowWindow
+@description: 
+"""
+import os
+
+from PyQt5.QtCore import QTimer
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
+import win32gui
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = "Copyright (c) 2018 Irony"
+__Version__ = "Version 1.0"
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QVBoxLayout(self)
+        layout.addWidget(QPushButton('test', self))
+        self.tmpHwnd = None
+        # 启动定时器检测记事本的位置大小和是否关闭
+        self.checkTimer = QTimer(self, timeout=self.checkWindow)
+        self.checkTimer.start(10)  # 10毫秒比较流畅
+
+    def checkWindow(self):
+        # 查找
+        hwnd = win32gui.FindWindow('Notepad', None)
+        if self.tmpHwnd and not hwnd:
+            # 表示记事本关闭了
+            self.checkTimer.stop()
+            self.close()  # 关闭自己
+            return
+        if not hwnd:
+            return
+        self.tmpHwnd = hwnd
+        # 获取位置
+        rect = win32gui.GetWindowRect(hwnd)
+        print(rect)
+        self.move(rect[2], rect[1])
+
+
+if __name__ == '__main__':
+    import sys
+    from PyQt5.QtWidgets import QApplication
+    # 先检测是否已有记事本打开
+    hwnd = win32gui.FindWindow('Notepad', None)
+    print('hwnd', hwnd)
+    if not hwnd:
+        # 启动记事本
+        os.startfile('notepad')
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
+

# 效果图

+

FollowWindow

+
文章作者: Irony
文章链接: https://pyqt5.com/followwindow.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/guestbook/index.html b/guestbook/index.html new file mode 100644 index 00000000..354582f3 --- /dev/null +++ b/guestbook/index.html @@ -0,0 +1,276 @@ +留言板 | PyQt + + + + + + + + + + + +
\ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 3e6a1ab5..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,123 +0,0 @@ -var gulp = require('gulp'); -var minifycss = require('gulp-minify-css'); -var uglify = require('gulp-uglify'); -var htmlmin = require('gulp-htmlmin'); -var htmlclean = require('gulp-htmlclean'); -var minifyInline = require('gulp-minify-inline'); -var inline = require('gulp-inline'); -var inlineimage = require('gulp-inline-image'); -var rename = require('gulp-rename'); -var jshint=require('gulp-jshint'); -var concat = require('gulp-concat'); - -// 获取 gulp-imagemin 模块 -var imagemin = require('gulp-imagemin') - -//语法检查 -gulp.task('jshint', function () { - return gulp.src('js/*.js') - .pipe(jshint()) - .pipe(jshint.reporter('default')); -}); - -// public 目录 css -gulp.task('minify-css', function () { - return gulp.src('./public/css/*.css') - .pipe(inlineimage()) - .pipe(minifycss()) - .pipe(gulp.dest('./public/css')); -}); -gulp.task('plugins-css', function () { - return gulp.src('./public/plugins/**/*.css') - .pipe(inlineimage()) - .pipe(minifycss()) - .pipe(gulp.dest('./public/plugins')); -}); -gulp.task('special-css', function () { - return gulp.src('./public/plugins/css/special.css') - .pipe(inlineimage()) - .pipe(minifycss()) - .pipe(gulp.dest('./public/plugins/css/')); -}); -gulp.task('search-css', function () { - return gulp.src('./public/search/**/*.css') - .pipe(inlineimage()) - .pipe(minifycss()) - .pipe(gulp.dest('./public/search')); -}); -gulp.task('mylove-css', function () { - return gulp.src('./public/mylove/css/*.css') - .pipe(inlineimage()) - .pipe(minifycss()) - .pipe(gulp.dest('./public/mylove/css')); -}); - -// 压缩 public 目录内 html -gulp.task('minify-html', function () { - var opts = { - removeComments: true,//清除 HTML 注释 - minifyJS: true,////压缩页面 JS - minifyCSS: true,//压缩页面 CSS - minifyURLs: true, - collapseWhitespace: true,//压缩 HTML - collapseBooleanAttributes: true,//省略布尔属性的值 ==> - removeEmptyAttributes: true,//删除所有空格作属性值 ==> - removeScriptTypeAttributes: true,//删除 +
PyQtClient例子客户端
PyQt学习心得
pytest-qt 测试模态窗体.
如何在Mac M1上快速安装PyQt5
python 判断屏幕等宽字符串的长度  
修改pyuic代替pyside2-uic.
python 状态机模块  
在pyqt中使用python全局钩子模块
像读文章一样读源码
python 获取子进程print信息  
\ No newline at end of file diff --git a/issignalconnected.html b/issignalconnected.html new file mode 100644 index 00000000..51cdda03 --- /dev/null +++ b/issignalconnected.html @@ -0,0 +1,346 @@ +PyQt5判断信号是否连接 | PyQt + + + + + + + + + + + + + + +

PyQt5判断信号是否连接

PyQt 中某些情况下需要取消原来的信号连接,此时需要使用 disconnect 方法,但是在逻辑不严谨的情况下可能会导致多次调用 disconnect 方法而导致报错,当然可以通过 try except 来包裹代码。这里通过  isSignalConnected  来判断信号是否连接。

+ +

在 QOjbect 文档中这样写到:

+
static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged);
+if (isSignalConnected(valueChangedSignal)) {
+    QByteArray data;
+    data = get_the_value();       // expensive operation
+    emit valueChanged(data);
+}
+

通过直接传入信号就行了,但是这在 PyQt 中不可行。需要这么做

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2019年2月24日
+@author: Irony
+@site: https://pyqt5.com https://github.com/892768447
+@email: 892768447@qq.com
+@file: IsSignalConnected
+@description: 判断信号是否连接
+"""
+
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = 'Copyright (c) 2019 Irony'
+__Version__ = 1.0
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QVBoxLayout(self)
+        self.button1 = QPushButton('已连接', self, clicked=self.doTest)
+        self.button2 = QPushButton('未连接', self)
+        self.retView = QTextBrowser(self)
+        layout.addWidget(self.button1)
+        layout.addWidget(self.button2)
+        layout.addWidget(self.retView)
+
+    def doTest(self):
+        self.retView.append("""
+        # button1 clicked 是否连接: %s
+        # button2 clicked 是否连接: %s
+        """ % (
+            self.isSignalConnected(self.button1, 'clicked()'),
+            self.isSignalConnected(self.button2, 'clicked()')
+        ))
+
+    def isSignalConnected(self, obj, name):
+        """判断信号是否连接
+        :param obj:        对象
+        :param name:       信号名,如 clicked()
+        """
+        index = obj.metaObject().indexOfMethod(name)
+        if index > -1:
+            method = obj.metaObject().method(index)
+            if method:
+                return obj.isSignalConnected(method)
+        return False
+
+
+if __name__ == '__main__':
+    import sys
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
+

# 效果图

+

IsSignalConnected

+
文章作者: Irony
文章链接: https://pyqt5.com/issignalconnected.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/issues/index.html b/issues/index.html new file mode 100644 index 00000000..d642c25d --- /dev/null +++ b/issues/index.html @@ -0,0 +1,367 @@ +PyQt5讨论 | PyQt + + + + + + + + + + + + + +
+ + + + + +
\ No newline at end of file diff --git a/themes/butterfly/source/js/main.js b/js/main.js similarity index 100% rename from themes/butterfly/source/js/main.js rename to js/main.js diff --git a/themes/butterfly/source/js/search/algolia.js b/js/search/algolia.js similarity index 100% rename from themes/butterfly/source/js/search/algolia.js rename to js/search/algolia.js diff --git a/themes/butterfly/source/js/search/local-search.js b/js/search/local-search.js similarity index 100% rename from themes/butterfly/source/js/search/local-search.js rename to js/search/local-search.js diff --git a/themes/butterfly/source/js/tw_cn.js b/js/tw_cn.js similarity index 100% rename from themes/butterfly/source/js/tw_cn.js rename to js/tw_cn.js diff --git a/themes/butterfly/source/js/utils.js b/js/utils.js similarity index 100% rename from themes/butterfly/source/js/utils.js rename to js/utils.js diff --git a/jumpslider.html b/jumpslider.html new file mode 100644 index 00000000..54e87e81 --- /dev/null +++ b/jumpslider.html @@ -0,0 +1,371 @@ +PyQt5之QSlider滑动条点击定位 | PyQt + + + + + + + + + + + + + + +

PyQt5之QSlider滑动条点击定位

QSlider 在通常情况下支持鼠标点击可以任意拖动,或者鼠标点击则往鼠标点击的方向移动一小格,这种移动一小格通常情况下用起来很不方便,比如我要做一个播放器的播放进度条,肯定是点击某个位置就直接跳到该位置,为此需要对 QSlider 的鼠标事件 mousePressEvent 进行重写。

+ +

# 实现方法

+

一般的想法就是重写 mousePressEvent 后,得到鼠标点击的 x 和 y 点然后进行比例换算,再通过 setValue 来设置值,其实 QSliderstyle 里面是有一个 sliderValueFromPosition 方法来计算值的。直接调用这个方法即可。

+
    +
  1. 首先通过 QSlider.style().subControlRect 方法计算得到滑块的区域,当鼠标点击区域在此次时则交给系统自己处理(比如按住不放拖动)
  2. +
  3. 通过 orientation 判断滑动条的方向(横竖)
  4. +
  5. 通过 invertedAppearance 判断滑动条是否反向(左右、上下)
  6. +
  7. 通过 QSlider.style().sliderValueFromPosition(最小值, 最大值, x或者y坐标, 宽度或者高度) 来计算得到值
  8. +
  9. 最后通过 setValue 来设置值
  10. +
+

# 代码

+

https://github.com/PyQt5/PyQt/blob/master/QSlider/ClickJumpSlider.py

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2018年11月5日
+@author: Irony
+@site: https://pyqt5.com https://github.com/892768447
+@email: 892768447@qq.com
+@file: JumpSlider
+@description: 
+"""
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle, QWidget,\
+    QFormLayout, QLabel
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = "Copyright (c) 2018 Irony"
+__Version__ = "Version 1.0"
+
+
+class JumpSlider(QSlider):
+
+    def mousePressEvent(self, event):
+        # 获取上面的拉动块位置
+        option = QStyleOptionSlider()
+        self.initStyleOption(option)
+        rect = self.style().subControlRect(
+            QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)
+        if rect.contains(event.pos()):
+            # 如果鼠标点击的位置在滑块上则交给Qt自行处理
+            super(JumpSlider, self).mousePressEvent(event)
+            return
+        if self.orientation() == Qt.Horizontal:
+            # 横向,要考虑invertedAppearance是否反向显示的问题
+            self.setValue(self.style().sliderValueFromPosition(
+                self.minimum(), self.maximum(),
+                event.x() if not self.invertedAppearance() else (self.width(
+                ) - event.x()), self.width()))
+        else:
+            # 纵向
+            self.setValue(self.style().sliderValueFromPosition(
+                self.minimum(), self.maximum(),
+                (self.height() - event.y()) if not self.invertedAppearance(
+                ) else event.y(), self.height()))
+
+
+class TestWindow(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(TestWindow, self).__init__(*args, **kwargs)
+        layout = QFormLayout(self)
+
+        self.label1 = QLabel('0', self)
+        layout.addRow(self.label1, JumpSlider(
+            Qt.Horizontal, valueChanged=lambda v: self.label1.setText(str(v))))
+
+        # 横向-反向显示
+        self.label2 = QLabel('0', self)
+        layout.addRow(self.label2, JumpSlider(
+            Qt.Horizontal, invertedAppearance=True,
+            valueChanged=lambda v: self.label2.setText(str(v))))
+
+        self.label3 = QLabel('0', self)
+        layout.addRow(self.label3, JumpSlider(
+            Qt.Vertical, minimumHeight=200, valueChanged=lambda v: self.label3.setText(str(v))))
+
+        # 纵向反向显示
+        self.label4 = QLabel('0', self)
+        layout.addRow(self.label4, JumpSlider(
+            Qt.Vertical, invertedAppearance=True,
+            minimumHeight=200, valueChanged=lambda v: self.label4.setText(str(v))))
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+    sys.excepthook = cgitb.enable(1, None, 5, '')
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = TestWindow()
+    w.show()
+    sys.exit(app.exec_())
+

# 效果图

+

ClickJumpSlider

+
文章作者: Irony
文章链接: https://pyqt5.com/jumpslider.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/likehtmleffect.html b/likehtmleffect.html new file mode 100644 index 00000000..ceb10d84 --- /dev/null +++ b/likehtmleffect.html @@ -0,0 +1,363 @@ +PyQt5仿网页图片鼠标移动特效 | PyQt + + + + + + + + + + + + + + +

PyQt5仿网页图片鼠标移动特效

em,就是类似于那种游戏官网首页的图片,鼠标放上去后来回移动,图片的前景和背景错位移动。

+ +

# 原理分析

+
    +
  1. 2 张一样大小的透明图片,1 张作为背景,一张作为前景(比如说人物)。
  2. +
  3. 当鼠标往左移动时,前景人物跟着往左移动,背景往右移动
  4. +
  5. 计算好偏移量(见代码中)
  6. +
+

https://github.com/PyQt5/PyQt/blob/master/QLabel/ImageSlipped.py

+

# 关键代码

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2018年10月18日
+@author: Irony
+@site: https://pyqt5.com https://github.com/892768447
+@email: 892768447@qq.com
+@file: ImageSlipped
+@description: 
+"""
+from PyQt5.QtGui import QPixmap, QPainter
+from PyQt5.QtWidgets import QWidget
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = "Copyright (c) 2018 Irony"
+__Version__ = "Version 1.0"
+
+
+class SlippedImgWidget(QWidget):
+
+    def __init__(self, bg, fg, *args, **kwargs):
+        super(SlippedImgWidget, self).__init__(*args, **kwargs)
+        # 开启鼠标跟踪
+        self.setMouseTracking(True)
+        # 背景
+        self.bgPixmap = QPixmap(bg)
+        # 前景
+        self.pePixmap = QPixmap(fg)
+        # 最小尺寸(背景右边和下方隐藏10个像素)
+        size = self.bgPixmap.size()
+        self.setMinimumSize(size.width() - 10, size.height() - 10)
+        self.setMaximumSize(size.width() - 10, size.height() - 10)
+        # 分成10份用于鼠标移动判断
+        self.stepX = size.width() / 10
+        self.stepY = size.height() / 10
+        # 偏移量
+        self._offsets = [-4, -4, -4, -4]  # 背景(-4,-4),前景(-4,-4)
+
+    def mouseMoveEvent(self, event):
+        super(SlippedImgWidget, self).mouseMoveEvent(event)
+        pos = event.pos()
+
+        # 偏移量
+        offsetX = 5 - int(pos.x() / self.stepX)
+        offsetY = 5 - int(pos.y() / self.stepY)
+        self._offsets[0] = offsetX
+        self._offsets[1] = offsetY
+        self._offsets[2] = offsetX
+        self._offsets[3] = offsetY
+        # 刷新
+        self.update()
+
+    def paintEvent(self, event):
+        super(SlippedImgWidget, self).paintEvent(event)
+        # 绘制图形
+        painter = QPainter(self)
+        painter.setRenderHint(QPainter.Antialiasing)
+        # 左上角偏移5个像素画背景图片
+        painter.drawPixmap(
+            -5 + self._offsets[0],
+            -5 + self._offsets[1], self.bgPixmap)
+        # 右下角偏移5个像素画前景图片
+        painter.drawPixmap(
+            self.width() - self.pePixmap.width() + 5 - self._offsets[2],
+            self.height() - self.pePixmap.height() + 5 - self._offsets[3],
+            self.pePixmap
+        )
+
+
+if __name__ == '__main__':
+    import sys
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = SlippedImgWidget('images/bg.png', 'images/fg.png')
+    w.show()
+    sys.exit(app.exec_())
+

# 效果图

+

ImageSlipped

+
文章作者: Irony
文章链接: https://pyqt5.com/likehtmleffect.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/link/index.html b/link/index.html new file mode 100644 index 00000000..47af3084 --- /dev/null +++ b/link/index.html @@ -0,0 +1,306 @@ +友情链接 | PyQt + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/macm1pyqt.html b/macm1pyqt.html new file mode 100644 index 00000000..41e134e8 --- /dev/null +++ b/macm1pyqt.html @@ -0,0 +1,291 @@ +如何在Mac M1上快速安装PyQt5 | PyQt + + + + + + + + + + + + + + + +

如何在Mac M1上快速安装PyQt5

由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。
+M1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。

+ +

1. 安装 Python
+python-3.9.13-macosx10.9.pkg

+

2. 勾选自定义同时只勾选安装 pip

+

step1.png

+

step1.png

+

3. 设置 pip 源

+
/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi
+/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna
+

4. 安装 PyQt5

+
/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5
+

5. 测试

+
/Library/Frameworks/Python.framework/Versions/3.9/bin/python3
+

step3.png

+

📢📢📢

+

也可以直接安装 Miniconda

+

然后:conda install -c conda-forge pyqt

+
文章作者: Irony
文章链接: https://pyqt5.com/macm1pyqt.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/mselectmenu.html b/mselectmenu.html new file mode 100644 index 00000000..96f9166e --- /dev/null +++ b/mselectmenu.html @@ -0,0 +1,372 @@ +PyQt5菜单之多选功能 | PyQt + + + + + + + + + + + + + + +

PyQt5菜单之多选功能

有时候会遇到这种需求:在界面某个位置弹出一个菜单,其中里面的菜单项可以多选(类似配置选项),此时用 QMenu 会遇到点击一个菜单项就会自动关闭,当然可以通过其他方式实现该功能,不过这里就采用 QMenu 通过特殊的方式来实现该需求。

+ +

# 需求

+

要实现的效果:

+
    +
  1. 菜单 1
  2. +
  3. 菜单 2
  4. +
  5. 菜单 3
  6. +
  7. 菜单 4
  8. +
+

点击菜单 1、2、3 可以多选不关闭菜单

+

点击菜单 4 可以勾选,并且关闭菜单

+

# 原理

+
    +
  1. 设置菜单项可勾选:通过 QAction.setCheckable(True) 方法实现
  2. +
  3. 设置菜单不可关闭:通过覆盖 QMenu 的鼠标释放 mouseReleaseEvent 方法(可直接替换或者通过 installEventFilter 安装事件过滤器实现)
  4. +
  5. 在菜单的鼠标释放事件中,当点击菜单项后是通过点击点坐标来查找是否有 QAction ,然后触发对应的 QAction
  6. +
  7. 故在没有 QAction 的地方则直接交还给 QMenu 自行处理逻辑,在有 QAction 的地方可以根据自己的需求进行处理(如上所提)
  8. +
+

# 代码

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2018年10月24日
+@author: Irony
+@site: https://github.com/892768447
+@email: 892768447@qq.com
+@file: 菜单多选不关闭
+@description: 
+"""
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QMenu,\
+    QAction
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = "Copyright (c) 2018 Irony"
+__Version__ = "Version 1.0"
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QVBoxLayout(self)
+        self.labelInfo = QLabel(self)
+        self.button = QPushButton('带按钮的菜单', self)
+        layout.addWidget(self.labelInfo)
+        layout.addWidget(self.button)
+
+        # 添加菜单
+        self._initMenu()
+
+    def _initMenu(self):
+        # 创建菜单
+        self._menu = QMenu(self.button)
+        # 替换menu的鼠标释放事件达到选择性不关闭菜单
+        self._menu.mouseReleaseEvent = self._menu_mouseReleaseEvent
+        self._menu.addAction('菜单1', self._checkAction)
+        self._menu.addAction('菜单2', self._checkAction)
+        self._menu.addAction(
+            QAction('菜单3', self._menu, triggered=self._checkAction))
+        action = QAction('菜单4', self._menu, triggered=self._checkAction)
+        # 添加自定义的属性,判断该属性可以关闭菜单
+        action.setProperty('canHide', True)
+        self._menu.addAction(action)
+        for action in self._menu.actions():
+            # 循环设置可勾选
+            action.setCheckable(True)
+        self.button.setMenu(self._menu)
+
+    def _menu_mouseReleaseEvent(self, event):
+        action = self._menu.actionAt(event.pos())
+        if not action:
+            # 没有找到action就交给QMenu自己处理
+            return QMenu.mouseReleaseEvent(self._menu, event)
+        if action.property('canHide'):  # 如果有该属性则给菜单自己处理
+            return QMenu.mouseReleaseEvent(self._menu, event)
+        # 找到了QAction则只触发Action
+        action.activate(action.Trigger)
+
+    def _checkAction(self):
+        # 三个action都响应该函数
+        self.labelInfo.setText('\n'.join(['{}\t选中:{}'.format(
+            action.text(), action.isChecked()) for action in self._menu.actions()]))
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+    sys.excepthook = cgitb.enable(1, None, 5, 'text')
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = Window()
+    w.resize(400, 400)
+    w.show()
+    sys.exit(app.exec_())
+

# 效果图

+

MultiSelect

+
文章作者: Irony
文章链接: https://pyqt5.com/mselectmenu.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index ab0b944b..00000000 --- a/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "pyqt5-blog", - "version": "0.0.1", - "private": true, - "hexo": { - "version": "6.3.0" - }, - "scripts": { - "build": "hexo generate", - "clean": "hexo clean", - "deploy": "hexo deploy", - "server": "hexo server" - }, - "dependencies": { - "hexo": "^6.3.0", - "hexo-algoliasearch": "^0.3.2", - "hexo-autoprefixer": "^0.0.1", - "hexo-bilibili-bangumi": "^1.8.9", - "hexo-butterfly-envelope": "^1.0.15", - "hexo-cli": "^4.3.1", - "hexo-deployer-git": "^4.0.0", - "hexo-feed": "^1.1.1", - "hexo-generator-archive": "^2.0.0", - "hexo-generator-baidu-sitemap": "^0.1.9", - "hexo-generator-category": "^2.0.0", - "hexo-generator-feed": "^3.0.0", - "hexo-generator-index": "^3.0.0", - "hexo-generator-search": "^2.4.3", - "hexo-generator-seo-friendly-sitemap": "^0.2.1", - "hexo-generator-sitemap": "^3.0.1", - "hexo-generator-tag": "^2.0.0", - "hexo-lazyload-image": "^1.0.13", - "hexo-renderer-ejs": "^2.0.0", - "hexo-renderer-marked": "^6.1.1", - "hexo-renderer-multi-markdown-it": "^0.1.5", - "hexo-renderer-pug": "^3.0.0", - "hexo-renderer-stylus": "^3.0.0", - "hexo-search-data-plugin": "^1.1.1", - "hexo-server": "^3.0.0", - "hexo-symbols-count-time": "^0.6.2", - "hexo-tag-aplayer": "^3.0.4", - "hexo-tag-bilibili": "^0.3.1", - "hexo-wordcount": "^6.0.1", - "markdown-it-emoji": "^2.0.2", - "markdown-it-furigana": "^1.0.1", - "markdown-it-multimd-table": "^4.2.3", - "markdown-it-spoiler": "^1.1.1", - "markdown-it-toc-and-anchor": "^4.2.0" - } -} diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 00000000..9e4eed9c --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,466 @@ +PyQt - 个人学习经验分享 + + + + + + + + + + + +
QtWebkit和QWebEngineView与Javascript交互
PyQt5窗口翻转动画
PyQt属性动画(QPropertyAnimation)
如何查阅Qt文档
推荐编辑器LiClipse
三种方式绑定信号槽
python 拷贝虚拟环境(一)  
QRunnable线程池发信号
如何和设计师中查看ui转换的py代码
PyQt5调整窗口显示边框
\ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 00000000..48b982c4 --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,471 @@ +PyQt - 个人学习经验分享 + + + + + + + + + + + +
PyQt5判断信号是否连接
PyQt5无边框圆角阴影
解决GitHub下载速度缓慢的问题
在Mac上以正确的姿势使用PyQtClient看Demo
Python调用Java对Excel截图
FFmpeg合成加密HLS记录
PyQt5编译QWebView与QWebEngineView共存
PyQt5之图片轮播
python 在类里使用进程池
PyQt5之QSlider滑动条点击定位
\ No newline at end of file diff --git a/page/4/index.html b/page/4/index.html new file mode 100644 index 00000000..48678dfe --- /dev/null +++ b/page/4/index.html @@ -0,0 +1,514 @@ +PyQt - 个人学习经验分享 + + + + + + + + + + + +
PyQt5仿网页鼠标移动点阵特效
QDataWidgetMapper 数据库绑定 QLineEdit控件
PyQt5显示.9格式的PNG图片
PyQt5菜单之多选功能
多线程之守护线程和阻塞线程
PyQt5结合Asyncio异步
PyQt5仿网页图片鼠标移动特效
PyQt5窗口跟随其它窗口
PyQt5动画边框阴影
PyQt5圆形图片
\ No newline at end of file diff --git a/page/5/index.html b/page/5/index.html new file mode 100644 index 00000000..812031df --- /dev/null +++ b/page/5/index.html @@ -0,0 +1,331 @@ +PyQt - 个人学习经验分享 + + + + + + + + + + + +
异常捕获之cgitb模块
\ No newline at end of file diff --git a/pageswitching.html b/pageswitching.html new file mode 100644 index 00000000..1d6b6eb7 --- /dev/null +++ b/pageswitching.html @@ -0,0 +1,315 @@ +PyQt5之图片轮播 | PyQt + + + + + + + + + + + + + + + +

PyQt5之图片轮播

之前看到了 QStackedWidget 做切换动画,让界面不那么生硬,于是参考了 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt 做了一个 QStackedWidget 的切换动画,然后利用 QStackedWidget 结合多个 QLabel 显示图片来做一个轮播效果。

+

其实在写之前也在网上找了很多例子,参看过后发现大多例子都是利用到了 paintEvent 去绘制,这样其实还是比较麻烦,个人觉得更好的方式是使用 QPropertyAnimation 属性动画修改控件中 QLabel 图片控件的 pos 位置属性就可以达到移动效果了。

+ +
    +
  1. 比较核心的算法就是要计算当前页面和下一个页面的位置偏移量,比如:
  2. +
+
# 计算偏移量
+offsetX = self.frameRect().width()
+offsetY = self.frameRect().height()
+w_next.setGeometry(0, 0, offsetX, offsetY)
+
+if direction == self.BOTTOM2TOP:
+    offsetX = 0
+    offsetY = -offsetY
+elif direction == self.TOP2BOTTOM:
+    offsetX = 0
+elif direction == self.RIGHT2LEFT:
+    offsetX = -offsetX
+    offsetY = 0
+elif direction == self.LEFT2RIGHT:
+    offsetY = 0
+
+# 重新定位显示区域外部/旁边的下一个窗口小部件
+pnext = w_next.pos()
+pnow = w_now.pos()
+self._pnow = pnow
+
+# 移动到指定位置并显示
+w_next.move(pnext.x() - offsetX, pnext.y() - offsetY)
+w_next.show()
+w_next.raise_()
+
    +
  1. +

    其次是对这两个页面增加关联 pos 属性的 QPropertyAnimation 动画,然后加入到并行动画组 QParallelAnimationGroup 中再启动即可。

    +
  2. +
  3. +

    QStackedWidgetsetCurrentIndexsetCurrentWidget 这两个函数进行了覆盖重写达到及时手动调用这两个函数也会产生动画效果的目的。

    +
  4. +
+

# 代码

+

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/PageSwitching.py

+

# 效果图

+

PageSwitching

+
文章作者: Irony
文章链接: https://pyqt5.com/pageswitching.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/source/plugins/css/history.css b/plugins/css/history.css similarity index 100% rename from source/plugins/css/history.css rename to plugins/css/history.css diff --git a/source/plugins/css/netease.css b/plugins/css/netease.css similarity index 100% rename from source/plugins/css/netease.css rename to plugins/css/netease.css diff --git a/source/plugins/css/special.css b/plugins/css/special.css similarity index 100% rename from source/plugins/css/special.css rename to plugins/css/special.css diff --git a/source/plugins/font/style3.css b/plugins/font/style3.css similarity index 100% rename from source/plugins/font/style3.css rename to plugins/font/style3.css diff --git a/source/plugins/heart/images/web_heart_animation.png b/plugins/heart/images/web_heart_animation.png similarity index 100% rename from source/plugins/heart/images/web_heart_animation.png rename to plugins/heart/images/web_heart_animation.png diff --git a/source/plugins/heart/style.css b/plugins/heart/style.css similarity index 100% rename from source/plugins/heart/style.css rename to plugins/heart/style.css diff --git a/source/plugins/hint/hint.min.css b/plugins/hint/hint.min.css similarity index 100% rename from source/plugins/hint/hint.min.css rename to plugins/hint/hint.min.css diff --git a/source/plugins/images/bottom.png b/plugins/images/bottom.png similarity index 100% rename from source/plugins/images/bottom.png rename to plugins/images/bottom.png diff --git a/source/plugins/images/clock.png b/plugins/images/clock.png similarity index 100% rename from source/plugins/images/clock.png rename to plugins/images/clock.png diff --git a/source/plugins/images/date.png b/plugins/images/date.png similarity index 100% rename from source/plugins/images/date.png rename to plugins/images/date.png diff --git a/source/plugins/images/footprint.png b/plugins/images/footprint.png similarity index 100% rename from source/plugins/images/footprint.png rename to plugins/images/footprint.png diff --git a/source/plugins/images/icons.png b/plugins/images/icons.png similarity index 100% rename from source/plugins/images/icons.png rename to plugins/images/icons.png diff --git a/source/plugins/images/icons2.png b/plugins/images/icons2.png similarity index 100% rename from source/plugins/images/icons2.png rename to plugins/images/icons2.png diff --git a/source/plugins/images/icons3.png b/plugins/images/icons3.png similarity index 100% rename from source/plugins/images/icons3.png rename to plugins/images/icons3.png diff --git a/source/plugins/images/point.png b/plugins/images/point.png similarity index 100% rename from source/plugins/images/point.png rename to plugins/images/point.png diff --git a/source/plugins/images/top.png b/plugins/images/top.png similarity index 100% rename from source/plugins/images/top.png rename to plugins/images/top.png diff --git a/source/plugins/images/vertical.png b/plugins/images/vertical.png similarity index 100% rename from source/plugins/images/vertical.png rename to plugins/images/vertical.png diff --git a/source/plugins/js/easying.js b/plugins/js/easying.js similarity index 100% rename from source/plugins/js/easying.js rename to plugins/js/easying.js diff --git a/source/plugins/js/gotop.js b/plugins/js/gotop.js similarity index 100% rename from source/plugins/js/gotop.js rename to plugins/js/gotop.js diff --git a/source/plugins/js/history.js b/plugins/js/history.js similarity index 100% rename from source/plugins/js/history.js rename to plugins/js/history.js diff --git a/source/plugins/js/jquery.bumpytext.packed.js b/plugins/js/jquery.bumpytext.packed.js similarity index 100% rename from source/plugins/js/jquery.bumpytext.packed.js rename to plugins/js/jquery.bumpytext.packed.js diff --git a/source/plugins/js/jquery.easing.js b/plugins/js/jquery.easing.js similarity index 100% rename from source/plugins/js/jquery.easing.js rename to plugins/js/jquery.easing.js diff --git a/source/plugins/js/jquery.js b/plugins/js/jquery.js similarity index 100% rename from source/plugins/js/jquery.js rename to plugins/js/jquery.js diff --git a/source/plugins/js/jquery.lazyload.min.js b/plugins/js/jquery.lazyload.min.js similarity index 100% rename from source/plugins/js/jquery.lazyload.min.js rename to plugins/js/jquery.lazyload.min.js diff --git a/source/plugins/js/jquery.mousewheel.js b/plugins/js/jquery.mousewheel.js similarity index 100% rename from source/plugins/js/jquery.mousewheel.js rename to plugins/js/jquery.mousewheel.js diff --git a/source/plugins/js/scrollHighlight.js b/plugins/js/scrollHighlight.js similarity index 100% rename from source/plugins/js/scrollHighlight.js rename to plugins/js/scrollHighlight.js diff --git a/source/plugins/prettify/lang-apollo.js b/plugins/prettify/lang-apollo.js similarity index 100% rename from source/plugins/prettify/lang-apollo.js rename to plugins/prettify/lang-apollo.js diff --git a/source/plugins/prettify/lang-basic.js b/plugins/prettify/lang-basic.js similarity index 100% rename from source/plugins/prettify/lang-basic.js rename to plugins/prettify/lang-basic.js diff --git a/source/plugins/prettify/lang-clj.js b/plugins/prettify/lang-clj.js similarity index 100% rename from source/plugins/prettify/lang-clj.js rename to plugins/prettify/lang-clj.js diff --git a/source/plugins/prettify/lang-css.js b/plugins/prettify/lang-css.js similarity index 100% rename from source/plugins/prettify/lang-css.js rename to plugins/prettify/lang-css.js diff --git a/source/plugins/prettify/lang-dart.js b/plugins/prettify/lang-dart.js similarity index 100% rename from source/plugins/prettify/lang-dart.js rename to plugins/prettify/lang-dart.js diff --git a/source/plugins/prettify/lang-erlang.js b/plugins/prettify/lang-erlang.js similarity index 100% rename from source/plugins/prettify/lang-erlang.js rename to plugins/prettify/lang-erlang.js diff --git a/source/plugins/prettify/lang-go.js b/plugins/prettify/lang-go.js similarity index 100% rename from source/plugins/prettify/lang-go.js rename to plugins/prettify/lang-go.js diff --git a/source/plugins/prettify/lang-hs.js b/plugins/prettify/lang-hs.js similarity index 100% rename from source/plugins/prettify/lang-hs.js rename to plugins/prettify/lang-hs.js diff --git a/source/plugins/prettify/lang-lasso.js b/plugins/prettify/lang-lasso.js similarity index 100% rename from source/plugins/prettify/lang-lasso.js rename to plugins/prettify/lang-lasso.js diff --git a/source/plugins/prettify/lang-lisp.js b/plugins/prettify/lang-lisp.js similarity index 100% rename from source/plugins/prettify/lang-lisp.js rename to plugins/prettify/lang-lisp.js diff --git a/source/plugins/prettify/lang-llvm.js b/plugins/prettify/lang-llvm.js similarity index 100% rename from source/plugins/prettify/lang-llvm.js rename to plugins/prettify/lang-llvm.js diff --git a/source/plugins/prettify/lang-logtalk.js b/plugins/prettify/lang-logtalk.js similarity index 100% rename from source/plugins/prettify/lang-logtalk.js rename to plugins/prettify/lang-logtalk.js diff --git a/source/plugins/prettify/lang-lua.js b/plugins/prettify/lang-lua.js similarity index 100% rename from source/plugins/prettify/lang-lua.js rename to plugins/prettify/lang-lua.js diff --git a/source/plugins/prettify/lang-matlab.js b/plugins/prettify/lang-matlab.js similarity index 100% rename from source/plugins/prettify/lang-matlab.js rename to plugins/prettify/lang-matlab.js diff --git a/source/plugins/prettify/lang-ml.js b/plugins/prettify/lang-ml.js similarity index 100% rename from source/plugins/prettify/lang-ml.js rename to plugins/prettify/lang-ml.js diff --git a/source/plugins/prettify/lang-mumps.js b/plugins/prettify/lang-mumps.js similarity index 100% rename from source/plugins/prettify/lang-mumps.js rename to plugins/prettify/lang-mumps.js diff --git a/source/plugins/prettify/lang-n.js b/plugins/prettify/lang-n.js similarity index 100% rename from source/plugins/prettify/lang-n.js rename to plugins/prettify/lang-n.js diff --git a/source/plugins/prettify/lang-pascal.js b/plugins/prettify/lang-pascal.js similarity index 100% rename from source/plugins/prettify/lang-pascal.js rename to plugins/prettify/lang-pascal.js diff --git a/source/plugins/prettify/lang-proto.js b/plugins/prettify/lang-proto.js similarity index 100% rename from source/plugins/prettify/lang-proto.js rename to plugins/prettify/lang-proto.js diff --git a/source/plugins/prettify/lang-r.js b/plugins/prettify/lang-r.js similarity index 100% rename from source/plugins/prettify/lang-r.js rename to plugins/prettify/lang-r.js diff --git a/source/plugins/prettify/lang-rd.js b/plugins/prettify/lang-rd.js similarity index 100% rename from source/plugins/prettify/lang-rd.js rename to plugins/prettify/lang-rd.js diff --git a/source/plugins/prettify/lang-rust.js b/plugins/prettify/lang-rust.js similarity index 100% rename from source/plugins/prettify/lang-rust.js rename to plugins/prettify/lang-rust.js diff --git a/source/plugins/prettify/lang-scala.js b/plugins/prettify/lang-scala.js similarity index 100% rename from source/plugins/prettify/lang-scala.js rename to plugins/prettify/lang-scala.js diff --git a/source/plugins/prettify/lang-sql.js b/plugins/prettify/lang-sql.js similarity index 100% rename from source/plugins/prettify/lang-sql.js rename to plugins/prettify/lang-sql.js diff --git a/source/plugins/prettify/lang-swift.js b/plugins/prettify/lang-swift.js similarity index 100% rename from source/plugins/prettify/lang-swift.js rename to plugins/prettify/lang-swift.js diff --git a/source/plugins/prettify/lang-tcl.js b/plugins/prettify/lang-tcl.js similarity index 100% rename from source/plugins/prettify/lang-tcl.js rename to plugins/prettify/lang-tcl.js diff --git a/source/plugins/prettify/lang-tex.js b/plugins/prettify/lang-tex.js similarity index 100% rename from source/plugins/prettify/lang-tex.js rename to plugins/prettify/lang-tex.js diff --git a/source/plugins/prettify/lang-vb.js b/plugins/prettify/lang-vb.js similarity index 100% rename from source/plugins/prettify/lang-vb.js rename to plugins/prettify/lang-vb.js diff --git a/source/plugins/prettify/lang-vhdl.js b/plugins/prettify/lang-vhdl.js similarity index 100% rename from source/plugins/prettify/lang-vhdl.js rename to plugins/prettify/lang-vhdl.js diff --git a/source/plugins/prettify/lang-wiki.js b/plugins/prettify/lang-wiki.js similarity index 100% rename from source/plugins/prettify/lang-wiki.js rename to plugins/prettify/lang-wiki.js diff --git a/source/plugins/prettify/lang-xq.js b/plugins/prettify/lang-xq.js similarity index 100% rename from source/plugins/prettify/lang-xq.js rename to plugins/prettify/lang-xq.js diff --git a/source/plugins/prettify/lang-yaml.js b/plugins/prettify/lang-yaml.js similarity index 100% rename from source/plugins/prettify/lang-yaml.js rename to plugins/prettify/lang-yaml.js diff --git a/source/plugins/prettify/prettify-default.css b/plugins/prettify/prettify-default.css similarity index 100% rename from source/plugins/prettify/prettify-default.css rename to plugins/prettify/prettify-default.css diff --git a/source/plugins/prettify/prettify.js b/plugins/prettify/prettify.js similarity index 100% rename from source/plugins/prettify/prettify.js rename to plugins/prettify/prettify.js diff --git a/source/plugins/prettify/prettify.min.js b/plugins/prettify/prettify.min.js similarity index 100% rename from source/plugins/prettify/prettify.min.js rename to plugins/prettify/prettify.min.js diff --git a/source/plugins/prettify/run_prettify.js b/plugins/prettify/run_prettify.js similarity index 100% rename from source/plugins/prettify/run_prettify.js rename to plugins/prettify/run_prettify.js diff --git a/source/plugins/prettify/themes/atelier-dune-dark.css b/plugins/prettify/themes/atelier-dune-dark.css similarity index 100% rename from source/plugins/prettify/themes/atelier-dune-dark.css rename to plugins/prettify/themes/atelier-dune-dark.css diff --git a/source/plugins/prettify/themes/atelier-forest-dark.css b/plugins/prettify/themes/atelier-forest-dark.css similarity index 100% rename from source/plugins/prettify/themes/atelier-forest-dark.css rename to plugins/prettify/themes/atelier-forest-dark.css diff --git a/source/plugins/prettify/themes/atelier-savanna-dark.css b/plugins/prettify/themes/atelier-savanna-dark.css similarity index 100% rename from source/plugins/prettify/themes/atelier-savanna-dark.css rename to plugins/prettify/themes/atelier-savanna-dark.css diff --git a/source/plugins/prettify/themes/tomorrow-light.css b/plugins/prettify/themes/tomorrow-light.css similarity index 100% rename from source/plugins/prettify/themes/tomorrow-light.css rename to plugins/prettify/themes/tomorrow-light.css diff --git a/source/plugins/prettify/themes/tomorrow-night-eighties.css b/plugins/prettify/themes/tomorrow-night-eighties.css similarity index 100% rename from source/plugins/prettify/themes/tomorrow-night-eighties.css rename to plugins/prettify/themes/tomorrow-night-eighties.css diff --git a/processinclass_625781186.html b/processinclass_625781186.html new file mode 100644 index 00000000..34450ab4 --- /dev/null +++ b/processinclass_625781186.html @@ -0,0 +1,291 @@ +python 在类里使用进程池 | PyQt + + + + + + + + + + + + + +

python 在类里使用进程池

    +
  1. 首先, 进程池的作用就是减少进程的创建和释放 开销的, 所以在类中作为局部变量是不合适的;
  2. +
  3. 其次, 进程池必须在 if __name__ == "__main__" 里 ,否则会报 frozen_ 什么什么的错误;(这一点可能解释有误);
  4. +
+ +
    +
  1. 然后, 线程池的 apply_async 中如果传入 self.xxx 方法,会报 multiprocessing.Pool pickling error 什么的错误, 具体解释见 https://blog.csdn.net/dutsoft/article/details/70336462, 里面有解决方法,但是我没有成功(最开始测试没有现在理解的透彻, 不过应该是可以的); 由于第 1 点 不合理, 所以有什么办法在类 函数中获取 进程池对象 po 的地址:
  2. +
+

processinclass1

+

我的解决思路和方法是:

+
    +
  1. 通过 globals () 取得全局变量 , 测试证明 :不同文件的 globals() 是不同的: 如 Tab2.py globals() main_extra_func_file.py 中的 globals() 是不同的 , 所以 这样在 Tab2.py 中取不到 po 对象;
  2. +
  3. 通过 __main__.po 来获取 (为什么会想到这个呢, 因为有时候导包 import .xxx 和 import xxx 会报 __main__ 没有什么属性什么的):
  4. +
+
def getPoolObject():
+# po 的名字在main函数中定义
+# __main__ 模块在sys.modules 的键是"__mp_main__"
+    return sys.modules["__mp_main__"].po
+

ps : (图没截好 , rglob_worker 是外部函数 , 非类内函数 ,po = getPoolBojcet () 这一行是类内函数 ,红色箭头 2. 在的那条白色分割线 是 2 个函数。)

+

processinclass2

+

len(po._cache) == 1 : po._cache 是当前有任务的进程数, ==1 表示所有任务结束;利用回调 , 可以更轻松地进行进程通信。

+
文章作者: Irony
文章链接: https://pyqt5.com/processinclass_625781186.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/pyqt5_hook_key_625781186.html b/pyqt5_hook_key_625781186.html new file mode 100644 index 00000000..b10d239e --- /dev/null +++ b/pyqt5_hook_key_625781186.html @@ -0,0 +1,312 @@ +在pyqt中使用python全局钩子模块 | PyQt + + + + + + + + + + + + + + +

在pyqt中使用python全局钩子模块

在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像 QQ 一样可以全局热键截图。这里介绍几个方法实现在 PyQt 中使用 Python 全局钩子模块实现全局热键功能。

+ +
    +
  1. pyHook3
  2. +
+

安装命令 : pip install pyhook3

+

https://blog.csdn.net/q871063970/article/details/86648386

+

似乎将 pyhook 支持 py3 版本的了?没有太多研究.

+

缺点:只支持 win 平台.

+

2. keyboard & mouse

+

安装命令: pip install keyboard mouse

+

+from PyQt5 import  QtGui, QtWidgets, QtCore
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+import keyboard
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QVBoxLayout(self)
+        self.testBtn = QPushButton(self)
+        layout.addWidget(self.testBtn)
+
+        keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey'))
+        keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc"))
+
+    def abc(self,a,b,c):
+        print(a,b,c)
+        
+if __name__ == '__main__':
+    import sys
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
+

更详细例子 : pyqt 中使用 keyboard 全局热键

+

优点:跨平台;

+

缺点:模块名字取得太差,不容易被发现.

+
文章作者: Irony
文章链接: https://pyqt5.com/pyqt5_hook_key_625781186.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/pyqt5asyncio.html b/pyqt5asyncio.html new file mode 100644 index 00000000..aa17c9f9 --- /dev/null +++ b/pyqt5asyncio.html @@ -0,0 +1,484 @@ +PyQt5结合Asyncio异步 | PyQt + + + + + + + + + + + + + + + +

PyQt5结合Asyncio异步

今天尝试了下 quamash 框架,该框架是一个 PyQt 的异步事件循环封装库,使用 Python3+ 的 asyncio 这个异步库。在看了该项目的内容后发现只有一个简单的进度条例子,故尝试用其来下载网络图片并显示。

+ +

# 安装依赖

+
    +
  1. pip install quamash
  2. +
  3. pip install aiohttp
  4. +
  5. Python3.5+ 和 PyQt5
  6. +
+

这里使用 aiohttp 是因为它基于 asyncio 封装的网络操作库,常见的 getpost 等方法,不过它只支持 Python3.5 及以上的版本,主要是它使用了 async def 这样的语法。

+

# 说明

+
    +
  1. 在创建 QApplication 后随即设置替换事件循环 loop
  2. +
+
app = QApplication(sys.argv)
+loop = QEventLoop(app)
+asyncio.set_event_loop(loop)
+w = Window()
+
    +
  1. 通过 asyncio.ensure_future(func(), loop=loop) 来执行某个异步函数
  2. +
+

# 流程

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
WindowinitSession(初始化 session)
下载按钮doDownload(执行_doDownload 方法)
session.get(下载 json 数据进行解析)
添加到界面_doDownloadImage(对单张图片进行下载)
+

# 源码

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2018年10月24日
+@author: Irony
+@site: https://github.com/892768447
+@email: 892768447@qq.com
+@file: AsyncioUiClient
+@description: 
+"""
+import asyncio
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QMovie
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton,\
+    QApplication, QListWidget, QListWidgetItem, QLabel, QMessageBox
+import aiohttp
+from quamash import QEventLoop
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = "Copyright (c) 2018 Irony"
+__Version__ = "Version 1.0"
+
+Url = 'https://www.doutula.com/api/search?keyword=%E6%9C%80%E6%96%B0%E8%A1%A8%E6%83%85&mime=0&page={}'
+Headers = {
+    ':authority': 'www.doutula.com',
+    ':method': 'GET',
+    ':scheme': 'https',
+    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
+    'accept-language': 'zh-CN,zh;q=0.9',
+    'cache-control': 'max-age=0',
+    'dnt': '1',
+    'upgrade-insecure-requests': '1',
+    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6756.400 QQBrowser/10.2.2498.400'
+}
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        layout = QVBoxLayout(self)
+        self.listWidget = QListWidget(self)
+        self.listWidget.setSpacing(2)  # item直接的间隔
+        # 隐藏横向滚动条
+        self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        # 让list 从左到右排列
+        self.listWidget.setFlow(self.listWidget.LeftToRight)
+        # 自动换行
+        self.listWidget.setWrapping(True)
+        self.listWidget.setResizeMode(self.listWidget.Adjust)
+
+        self.buttonMsg = QPushButton('弹出提示框', self, clicked=self.showMessage)
+        self.buttonDown = QPushButton('下载图片', self, clicked=self.doDownload)
+        layout.addWidget(self.listWidget)
+        layout.addWidget(self.buttonMsg)
+        layout.addWidget(self.buttonDown)
+        self.currentPage = 0
+        self.initSession()  # 其实没必要,session主要用在需要登录的网站。缓存cookie用
+
+    def initSession(self):
+        async def _initSession():
+            # 初始化session
+            self.session = aiohttp.ClientSession(loop=loop)
+            print(self.session)
+        asyncio.ensure_future(_initSession(), loop=loop)
+
+    async def _doDownloadImage(self, url):
+        # 下载图片并添加到界面
+        async with self.session.get(url) as resp:
+            data = await resp.read()
+            if not data:
+                print('下载失败: ', url)
+                return
+            path = os.path.join('tmp', os.path.basename(url))
+            with open(path, 'wb') as fp:
+                fp.write(data)
+            item = QListWidgetItem(url, self.listWidget)
+            image = QPixmap(path)
+            item.setSizeHint(image.size())
+            label = QLabel(self.listWidget)
+            label.setPixmap(image)
+            if path.endswith('.gif'):  # 可能是动态图
+                label.setMovie(QMovie(path))
+            self.listWidget.setItemWidget(item, label)
+            self.listWidget.scrollToBottom()
+
+    async def _doDownload(self):
+        # 下载工作
+        if self.currentPage == -1:
+            QMessageBox.information(self, '提示', '已经没有更多了')
+            return
+        self.currentPage += 1
+        url = Url.format(self.currentPage)
+        print('get url: ', url)
+        async with self.session.get(url, headers=Headers) as resp:
+            data = await resp.json()
+            if not data:
+                return
+            data = data.get('data', None)
+            if not data:
+                self.currentPage = -1
+                print('已经是最后一页了')
+                return
+            # 解析json并生成item添加到界面中
+            for entity in data.get('list', []):
+                url = entity.get('image_url', None)
+                if not url:
+                    continue
+                await self._doDownloadImage(url)  # 下载图片
+
+    def doDownload(self):
+        # 响应按钮点击调用
+        asyncio.ensure_future(self._doDownload(), loop=loop)
+
+    def showMessage(self):
+        # 显示对话框
+        app.aboutQt()
+
+    def closeEvent(self, event):
+        if not self.session.closed:
+            asyncio.ensure_future(self.session.close(), loop=loop)
+        super(Window, self).closeEvent(event)
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+    import os
+    os.makedirs('tmp', exist_ok=True)
+    sys.excepthook = cgitb.enable(1, None, 5, 'text')
+    app = QApplication(sys.argv)
+    loop = QEventLoop(app)
+    asyncio.set_event_loop(loop)
+    w = Window()
+    w.show()
+    with loop:
+        loop.run_forever()
+

# 效果图

+

pyqt5asyncio

+
文章作者: Irony
文章链接: https://pyqt5.com/pyqt5asyncio.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/pyqt_get_subprocess_pipeline_625781186.html b/pyqt_get_subprocess_pipeline_625781186.html new file mode 100644 index 00000000..1171ce38 --- /dev/null +++ b/pyqt_get_subprocess_pipeline_625781186.html @@ -0,0 +1,314 @@ +python 获取子进程print信息   | PyQt + + + + + + + + + + + + + + + +

python 获取子进程print信息  

在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。

+ +

问题所在:

+

image.png

+

如果模块都由自己开发, 正常操作

+

image.png

+

但是因为不能改,所以只能拦截:
+代码:

+
pythonPath = self.pythonPath_cb.currentText()
+
+if suffix == "py":
+    # 首次
+    self.pyCommand = [pythonPath, path]
+    self.modifiedReloadPython(path)
+def modifiedReloadPython(self, path_):
+    os.chdir(os.path.dirname(path_))
+    # 子进程调用
+    self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    # self.stdoutWorker.p = self.p
+    self.stdoutWorker = Worker(self.p)
+    self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append("PYDEBUG:\n" + x))
+    self.stdoutWorker.start()
+class Worker(QThread):
+    stdout_signal = pyqtSignal(str)
+
+    def __init__(self, p, parent=None):
+        super().__init__(parent)
+        self.p = p
+
+    def run(self):
+        while True:
+            QApplication.processEvents()
+            if self.p is not None:
+                line = self.p.stdout.readline()
+                # line = line.strip()
+                if line != b'':
+                    try:
+                        info = line.decode()
+                        self.stdout_signal.emit(info)
+                    except:
+                        self.stdout_signal.emit(repr(line))
+    
+
文章作者: Irony
文章链接: https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/pyqtclient.html b/pyqtclient.html new file mode 100644 index 00000000..0e398c2c --- /dev/null +++ b/pyqtclient.html @@ -0,0 +1,282 @@ +PyQtClient例子客户端 | PyQt + + + + + + + + + + + + + +

PyQtClient例子客户端

1.gif 对本博客所写的项目 PyQt 例子进行一个客户端的编写,客户端主要实现一些动画效果,更换皮肤,运行例子等功能。2.gif

+ +

# 项目地址

+
+

# Windows 客户端下载

+
    +
  1. 包含部分例子
  2. +
  3. 不包含例子
  4. +
  5. 百度网盘 提取码: nadv
  6. +
+

# 效果图

+

PyQtClient

+
文章作者: Irony
文章链接: https://pyqt5.com/pyqtclient.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/pyqtclientmac.html b/pyqtclientmac.html new file mode 100644 index 00000000..20d71709 --- /dev/null +++ b/pyqtclientmac.html @@ -0,0 +1,316 @@ +在Mac上以正确的姿势使用PyQtClient看Demo | PyQt + + + + + + + + + + + + + +

在Mac上以正确的姿势使用PyQtClient看Demo

由于 PyQtClient 只提供了 Windows 的版本,这里记录下编译后在 Mac 上运行。

+ +

# 下载项目

+

安装 git 略。没有的东西可以都先去试试 brew install xxx。没安装 homebrew 的建议使用搜索引擎

+

git clone https://github.com/PyQt5/PyQtClient.git

+

# 配置环境

+
    +
  1. 打开 IDE 配置 python 环境,使用 anaconda 比较方便
  2. +
  3. 推荐用 pycharm,我是习惯了用 idea。anaconda 安装可以去官网下载。
  4. +
  5. 环境原因,选择新建一个 python 3.6 p.s. 我取的环境名字就是 3.6 所以后面的 3.6 其实是这个原因
  6. +
+

# conda 源

+

最好是加环境变量,不加也可以,就是以后用到的都需要指定路径,不太常用,我就没加

+
~/anaconda3/bin/conda config --add channels conda-forge
+~/anaconda3/bin/conda config --add channels defaults
+~/anaconda3/bin/conda config --add channels r
+~/anaconda3/bin/conda config --add channels bioconda
+~/anaconda3/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ 
+

# pip 源

+
mkdir ~/.pip && vim ~/.pip/pip.conf
+
[global]
+index-url = http://mirrors.aliyun.com/pypi/simple/
+[install]
+trusted-host = mirrors.aliyun.com
+

# 安装编译依赖

+
~/.conda/envs/3.6/bin/pip install -r PyQtClient/requirements.txt
+

运行提示没有 webkit,开始手动编译

+
    +
  1. wget http://download.qt.io/archive/qt/5.9/5.9.0/qt-opensource-mac-x64-5.9.0.dmg
  2. +
  3. wget https://github.com/annulen/webkit/releases/download/qtwebkit-5.212.0-alpha2/qtwebkit-5.212.0_alpha2-qt59-darwin-x64.tar.xz
  4. +
  5. wget https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.10.1/PyQt5_gpl-5.10.1.zip
  6. +
  7. wget https://www.riverbankcomputing.com/static/Downloads/sip/4.19.8/sip-4.19.8.tar.gz
  8. +
  9. 编译 sip: ~/.conda/envs/3.6/bin/python configure.py --platform macx-g++ && make && sudo make install
  10. +
  11. 编译 Webkit.so 没有 qmake 和 sip 的环境变量, 所以后面都是手动指定的
  12. +
+
~/.conda/envs/3.6/bin/python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc --qmake=~/Qt5.9.0/5.9/clang_64/bin/qmake --sip=~/.conda/3.6/bin/sip && make && sudo make install
+

# 插曲

+
    +
  1. libcurl 版本要求 10.0,而我的是 9.0,原因是前面我自己摸索,乱装依赖,所以遇到了
  2. +
+
~/anaconda3/bin/conda install -n 3.6 -c conda-forge libcurl
+
    +
  1. 结果这个 libcurl 10.0.0 是装上了,可是 pygit2 版本不对了,conda 给升级了,PyQtClient 里 requirements.txt 要求这个包的版本(pygit2==0.27.2)几乎决定了其他的环境版本。后来还是老实的用 conda 去装了。这个连 python 版本什么的都会跟着变的。最后降级的结果是 python 3.6.7
  2. +
+
~/anaconda3/bin/conda install -n 3.6 -c conda-forge libgit2==0.27.2
+

至此总算是启动正常了。

+
文章作者: Irony
文章链接: https://pyqt5.com/pyqtclientmac.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/pytest_qt_modal_625781186.html b/pytest_qt_modal_625781186.html new file mode 100644 index 00000000..46ef6fa4 --- /dev/null +++ b/pytest_qt_modal_625781186.html @@ -0,0 +1,303 @@ +pytest-qt 测试模态窗体. | PyQt + + + + + + + + + + + + + +

pytest-qt 测试模态窗体.

步骤分别是 :

+
    +
  1. +

    点击 开始扫描 弹出 选择路径窗口;

    +
  2. +
  3. +

    勾选路基;

    +
  4. +
+

3. 点击确定;

+ +

大概想测一下这个界面 :

+

image.png

+

步骤分别是 :

+
    +
  1. +

    点击 开始扫描 弹出 选择路径窗口;

    +
  2. +
  3. +

    勾选路基;

    +
  4. +
+

3. 点击确定;

+

需要测试的函数 :

+

image.png

+

测试函数 :

+

image.png

+

可以发现断言失败 .

+

image.png

+

官方文档:测试模态窗体.

+

https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html

+

用的是官方的 monkeypatch 方式 .

+

大致意思就是替换 FileSelectPathDialog 类的 exec 函数.

+
文章作者: Irony
文章链接: https://pyqt5.com/pytest_qt_modal_625781186.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/python_statemachine_625781186.html b/python_statemachine_625781186.html new file mode 100644 index 00000000..1785cc94 --- /dev/null +++ b/python_statemachine_625781186.html @@ -0,0 +1,289 @@ +python 状态机模块   | PyQt + + + + + + + + + + + + + + +

python 状态机模块  

用状态来取代 if…else 判断。

+ +

GUI 涉及到挺多的状态改变,以前一直用 if…else 来判断,最近读了设计模式,发现有个状态模式,随后发现了状态机这个东西 .

+

python 的状态机模块挺多的,不过好像很多都不更新了.
+ 推荐 2 个状态机模块,但是也没有太深入的使用经验,就跑跑例子,以后有更详细的 pyqt 例子再补上 .

+

1: pip install python-statemachine

+

官方例子 : https://github.com/fgmacedo/python-statemachine

+

2. pip install state_machine

+

官方例子 : https://github.com/jtushman/state_machine

+

1 的 最近一次更新在 6 个月以前,使用 类继承mixin 方式,不过有些地方不如 2 个人性化;

+

2 的设计更人性化一些,包括状态改变 beforeafter , 不过由于是装饰器实现的动态增加属性,有些地方编辑器智能提示可能就靠不上了.

+

两者实现实现方式不一样,有兴趣可以读读源码 .

+
    +
  1. qt 内置状态机框架
  2. +
+

https://blog.csdn.net/amnes1a/article/details/62418196

+

https://blog.csdn.net/dongfenghuojian/article/details/78187131

+

http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程)

+
文章作者: Irony
文章链接: https://pyqt5.com/python_statemachine_625781186.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/qtninepatch.html b/qtninepatch.html new file mode 100644 index 00000000..57a84688 --- /dev/null +++ b/qtninepatch.html @@ -0,0 +1,310 @@ +PyQt5显示.9格式的PNG图片 | PyQt + + + + + + + + + + + + + + + + +

PyQt5显示.9格式的PNG图片

做过安卓开发的和使用过 QQ 的都知道 .9.png 这种图片格式,效果就如 QQ 的聊天气泡一样可以拉伸,这种格式的图片允许开发人员定义可扩展区域,当需要延伸图片以填充比图片本身更大区域时,可扩展区的内容被延展;允许开发人员定义内容显示区,用于显示文字或其他内容。目前在 Github 上有两个 C++ 版本的,在这里我把它们都用 Python 实现了一遍。另外一个我也为 PyQt 提供了编译好的 pyd 文件。

+ +

# C++ 版本

+

在 Github 开源库中搜索到两个 C++ 版本的

+
    +
  1. 一个是 NinePatchQt
  2. +
  3. 一个是 QtNinePatch
  4. +
+

# PyQt5 版本

+

这里也分为两个版本,都是基于上面的 C++ 源码翻译改写过来的,具体的例子见项目里面的测试代码吧。

+
    +
  1. QtNinePatch 是参考第一个源码编写,用法是在 paintEvent 中调用
  2. +
  3. QtNinePatch2 是参考第二个源码编写,用法是 pixmap = QtNinePatch.createPixmapFromNinePatchImage(self.image, self.width(), self.height()) 直接得到一个处理好的 QPixmap 对象来使用
  4. +
+

# 说明

+
    +
  1. 建议优先使用 pyd 版本的(后续提供 Python3.4 3.5 3.6 3.7 编译好的 32 为库文件),也可以自行编译,编译步骤见下文。
  2. +
  3. 其次可以使用纯 python 版本 2 的(个人觉得方便调用)
  4. +
  5. 最后再考虑纯 python 版本 1 的吧
  6. +
  7. 以上为个人意见,两个 C++ 版本的写法不一样,但是核心算法应该是类似的。
  8. +
+

# 自行编译

+
    +
  1. 首先要安装好 Qt、PyQt5、编译安装对应的 sip、对应的 VC++ 编译工具
  2. +
  3. 用 Qt Creator 打开 pro 文件进行编译
  4. +
  5. 进入源码中的 sip 文件夹修改 configure.py 文件
  6. +
+
# 这里是你的VC版本和对应的Qt目录中的文件夹
+config.platform = "win32-msvc2010"
+qt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010'
+
    +
  1. 最后执行 python configure.py 来编译
  2. +
+

# 下载

+

https://github.com/PyQt5/PyQt/tree/master/QLabel

+

# 效果图

+

NinePatchImage

+
文章作者: Irony
文章链接: https://pyqt5.com/qtninepatch.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/qtwebjs.html b/qtwebjs.html new file mode 100644 index 00000000..da85c456 --- /dev/null +++ b/qtwebjs.html @@ -0,0 +1,368 @@ +QtWebkit和QWebEngineView与Javascript交互 | PyQt + + + + + + + + + + + + + + + + +

QtWebkit和QWebEngineView与Javascript交互

以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

+ +

# 说明

+
    +
  1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
  2. +
  3. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
  4. +
  5. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
  6. +
+
@pyqtSlot(str)
+def callFromJs(self, text):
+    QMessageBox.information(self, "提示", "来自js调用:{}".format(text))
+
    +
  1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
  2. +
+
// 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)
+Bridge.windowTitleChanged.connect({fun: function(title) {
+    showLog("标题被修改为:" + title);
+}}, "fun");
+
+// 绑定自定义的信号customSignal
+Bridge.customSignal.connect({fun: function(text) {
+    showLog("收到自定义信号内容:" + text);
+}}, "fun");
+
    +
  1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
  2. +
+
new QWebChannel(qt.webChannelTransport,
+    function(channel) {
+        window.Bridge = channel.objects.Bridge;
+        
+        // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)
+        Bridge.windowTitleChanged.connect(function(title) {
+            showLog("标题被修改为:" + title);
+        });
+        
+        // 绑定自定义的信号customSignal
+        Bridge.customSignal.connect(function(text) {
+           showLog("收到自定义信号内容:" + text);
+        });
+    }
+);
+

# 代码

+

QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

+

QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

+
    +
  1. 针对 QWebView 的核心实现
  2. +
+
class WebView(QWebView):
+
+    customSignal = pyqtSignal(str)
+
+    def __init__(self, *args, **kwargs):
+        super(WebView, self).__init__(*args, **kwargs)
+        self.initSettings()
+        # 暴露接口对象
+        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)
+
+    def _exposeInterface(self):
+        """向Js暴露调用本地方法接口
+        """
+        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)
+
+    # 注意pyqtSlot用于把该函数暴露给js可以调用
+    @pyqtSlot(str)
+    def callFromJs(self, text):
+        QMessageBox.information(self, "提示", "来自js调用:{}".format(text))
+
+    def sendCustomSignal(self):
+        # 发送自定义信号
+        self.customSignal.emit('当前时间: ' + str(time()))
+
    +
  1. 针对 QWebEngineView 的核心实现
  2. +
+
class WebEngineView(QWebEngineView):
+
+    customSignal = pyqtSignal(str)
+
+    def __init__(self, *args, **kwargs):
+        super(WebEngineView, self).__init__(*args, **kwargs)
+        self.channel = QWebChannel(self)
+        # 把自身对象传递进去
+        self.channel.registerObject('Bridge', self)
+        # 设置交互接口
+        self.page().setWebChannel(self.channel)
+
+    # 注意pyqtSlot用于把该函数暴露给js可以调用
+    @pyqtSlot(str)
+    def callFromJs(self, text):
+        QMessageBox.information(self, "提示", "来自js调用:{}".format(text))
+
+    def sendCustomSignal(self):
+        # 发送自定义信号
+        self.customSignal.emit('当前时间: ' + str(time()))
+

# 效果图

+

JsSignals

+
文章作者: Irony
文章链接: https://pyqt5.com/qtwebjs.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/read_open_source.html b/read_open_source.html new file mode 100644 index 00000000..d2411bb4 --- /dev/null +++ b/read_open_source.html @@ -0,0 +1,312 @@ +像读文章一样读源码 | PyQt + + + + + + + + + + + + + + + +

像读文章一样读源码

使用 snoop, 像读文章一样读源码。

+ +

不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
+image.png

+

没有目的地概览 , 不知不觉就追究到细节里面去了。

+

image.png

+

所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。

+

开始 :

+
    +
  1. python DEBUG 模块介绍 :
    + 前段时间看过挺多文章提到 pysoonper 这个调试模块,有兴趣的可以百度一下.
    + 个人尝试了一下,篇幅过大的 DEBUG 不适合用 pysoonper , 因为没有缩进!
    + 这几天偶然遇到一个二次封装的模块 snoop, 完美地解决了这个问题.
  2. +
  3. 操作步骤 :
  4. +
+
    +
  • 1 . 在 eric6.pymain() 函数上加 snoop 装饰器;
    +image.png
  • +
  • 2 . 用 vscode 打开 eric6start_.log 文件 (8 层深度 log 文件 34W 行,pycharm 对大文件支持很差);
    +log文件
  • +
+

发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。

+
    +
  • 3 . vscode 配置 log 文件关键字高亮;
    +安装高亮插件
    +image.png
    + 配置高亮关键字
    +image.png
  • +
+

callreturn 给加进去.

+
    +
  • 4 . 增加阶段关键字;
  • +
+

eric6启动阶段

+

image.png

+

#000 是为了方便搜索 。
+需要自己手动折叠 。
+可以发现 每个 splash.showMessage() 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。

+
+

# ps: vscode 阅读 log 文件还是有一些不方便的地方,除了在 2. 中提到的,还有包括关闭文件再打开,折叠状态不会保留,有其他更好的方式 请留言告诉我,谢谢.

+
文章作者: Irony
文章链接: https://pyqt5.com/read_open_source.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/rlatticeeffect.html b/rlatticeeffect.html new file mode 100644 index 00000000..4d724544 --- /dev/null +++ b/rlatticeeffect.html @@ -0,0 +1,521 @@ +PyQt5仿网页鼠标移动点阵特效 | PyQt + + + + + + + + + + + + + + + +

PyQt5仿网页鼠标移动点阵特效

Orz,前段时间有个 zz 需求,就是要做一个类似网页上很多个多点连线、鼠标移动跟随的那种炫酷特效,然后花了点时间在网上找了 js 做的,刚开始打算是嵌入 QWebView 来显示网页,后来研究了下 js 的算法代码,遂改用 QWidgetpaintEvent 直接绘制。

+ +

# 大概思路

+
    +
  1. 先根据窗口大小随机创建一些点
  2. +
  3. 遍历这些点并找到与之相关联的点
  4. +
  5. 在动画过程中绘制圆点和画两点之间的连线
  6. +
  7. 属性动画 QPropertyAnimation 改变颜色的透明度
  8. +
+

# 题外

+
    +
  1. 这里没有仔细去研究 js 里的算法优化,在浏览器里嗖嗖的就生成了,在 py 里好慢…
  2. +
  3. 尽量在 py 里优化了循环操作,也简单的做了个 cython 加速也才提高了 1s ? 1 倍?..
  4. +
  5. 不要只是为了好看用这玩意儿,和网页的效果一样,占 CPU !!! 没有任何意义
  6. +
  7. 如果有更好的优化算法请告知,3Q
  8. +
  9. pyd 是 python3.4 生成的,删掉 pyd 也能运行
  10. +
+

# 代码

+

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/RlatticeEffect.py

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Created on 2018年11月22日
+@author: Irony
+@site: https://pyqt5.com, https://github.com/892768447
+@email: 892768447@qq.com
+@file: 
+@description: 
+"""
+from random import random
+from time import time
+
+from PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\
+    Qt, QRectF, pyqtSignal
+from PyQt5.QtGui import QColor, QPainterPath, QPainter
+from PyQt5.QtWidgets import QWidget
+
+
+__Author__ = """By: Irony
+QQ: 892768447
+Email: 892768447@qq.com"""
+__Copyright__ = 'Copyright (c) 2018 Irony'
+__Version__ = 1.0
+
+
+try:
+    import pointtool  # @UnusedImport @UnresolvedImport
+    getDistance = pointtool.getDistance
+    findClose = pointtool.findClose
+except:
+    import math
+
+    def getDistance(p1, p2):
+        return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2)
+
+    def findClose(points):
+        plen = len(points)
+        for i in range(plen):
+            closest = [None, None, None, None, None]
+            p1 = points[i]
+            for j in range(plen):
+                p2 = points[j]
+                dte1 = getDistance(p1, p2)
+                if p1 != p2:
+                    placed = False
+                    for k in range(5):
+                        if not placed:
+                            if not closest[k]:
+                                closest[k] = p2
+                                placed = True
+                    for k in range(5):
+                        if not placed:
+                            if dte1 < getDistance(p1, closest[k]):
+                                closest[k] = p2
+                                placed = True
+            p1.closest = closest
+
+
+class Target:
+
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
+
+
+class Point(QObject):
+
+    valueChanged = pyqtSignal()
+
+    def __init__(self, x, ox, y, oy, *args, **kwargs):
+        super(Point, self).__init__(*args, **kwargs)
+        self.__x = x
+        self._x = x
+        self.originX = ox
+        self._y = y
+        self.__y = y
+        self.originY = oy
+        # 5个闭合点
+        self.closest = [0, 0, 0, 0, 0]
+        # 圆半径
+        self.radius = 2 + random() * 2
+        # 连线颜色
+        self.lineColor = QColor(156, 217, 249)
+        # 圆颜色
+        self.circleColor = QColor(156, 217, 249)
+
+    def initAnimation(self):
+        # 属性动画
+        if not hasattr(self, 'xanimation'):
+            self.xanimation = QPropertyAnimation(
+                self, b'x', self, valueChanged=self.valueChanged.emit,
+                easingCurve=QEasingCurve.InOutSine)
+            self.yanimation = QPropertyAnimation(
+                self, b'y', self, valueChanged=self.valueChanged.emit,
+                easingCurve=QEasingCurve.InOutSine,
+                finished=self.updateAnimation)
+            self.updateAnimation()
+
+    def updateAnimation(self):
+        self.xanimation.stop()
+        self.yanimation.stop()
+        duration = (1 + random()) * 1000
+        self.xanimation.setDuration(duration)
+        self.yanimation.setDuration(duration)
+        self.xanimation.setStartValue(self.__x)
+        self.xanimation.setEndValue(self.originX - 50 + random() * 100)
+        self.yanimation.setStartValue(self.__y)
+        self.yanimation.setEndValue(self.originY - 50 + random() * 100)
+        self.xanimation.start()
+        self.yanimation.start()
+
+    @pyqtProperty(float)
+    def x(self):
+        return self._x
+
+    @x.setter
+    def x(self, x):
+        self._x = x
+
+    @pyqtProperty(float)
+    def y(self):
+        return self._y
+
+    @y.setter
+    def y(self, y):
+        self._y = y
+
+
+class Window(QWidget):
+
+    def __init__(self, *args, **kwargs):
+        super(Window, self).__init__(*args, **kwargs)
+        self.setMouseTracking(True)
+        self.resize(800, 600)
+        self.points = []
+        self.target = Target(self.width() / 2, self.height() / 2)
+        self.initPoints()
+
+    def paintEvent(self, event):
+        super(Window, self).paintEvent(event)
+        painter = QPainter()
+        painter.begin(self)
+        painter.setRenderHint(QPainter.Antialiasing)
+        painter.fillRect(self.rect(), Qt.black)
+        self.animate(painter)
+        painter.end()
+
+    def mouseMoveEvent(self, event):
+        super(Window, self).mouseMoveEvent(event)
+        # 鼠标移动时更新xy坐标
+        self.target.x = event.x()
+        self.target.y = event.y()
+        self.update()
+
+    def initPoints(self):
+        t = time()
+        self.points.clear()
+        # 创建点
+        stepX = self.width() / 20
+        stepY = self.height() / 20
+        for x in range(0, self.width(), int(stepX)):
+            for y in range(0, self.height(), int(stepY)):
+                ox = x + random() * stepX
+                oy = y + random() * stepY
+                point = Point(ox, ox, oy, oy)
+                point.valueChanged.connect(self.update)
+                self.points.append(point)
+        print(time() - t)
+
+        t = time()
+        # 每个点寻找5个闭合点
+        findClose(self.points)
+        print(time() - t)
+
+    def animate(self, painter):
+        for p in self.points:
+            # 检测点的范围
+            value = abs(getDistance(self.target, p))
+            if value < 4000:
+                # 其实就是修改颜色透明度
+                p.lineColor.setAlphaF(0.3)
+                p.circleColor.setAlphaF(0.6)
+            elif value < 20000:
+                p.lineColor.setAlphaF(0.1)
+                p.circleColor.setAlphaF(0.3)
+            elif value < 40000:
+                p.lineColor.setAlphaF(0.02)
+                p.circleColor.setAlphaF(0.1)
+            else:
+                p.lineColor.setAlphaF(0)
+                p.circleColor.setAlphaF(0)
+
+            # 画线条
+            if p.lineColor.alpha():
+                for pc in p.closest:
+                    if not pc:
+                        continue
+                    path = QPainterPath()
+                    path.moveTo(p.x, p.y)
+                    path.lineTo(pc.x, pc.y)
+                    painter.save()
+                    painter.setPen(p.lineColor)
+                    painter.drawPath(path)
+                    painter.restore()
+
+            # 画圆
+            painter.save()
+            painter.setPen(Qt.NoPen)
+            painter.setBrush(p.circleColor)
+            painter.drawRoundedRect(QRectF(
+                p.x - p.radius, p.y - p.radius, 2 * p.radius, 2 * p.radius), p.radius, p.radius)
+            painter.restore()
+
+            # 开启动画
+            p.initAnimation()
+
+
+if __name__ == '__main__':
+    import sys
+    import cgitb
+    sys.excepthook = cgitb.enable(1, None, 5, '')
+    from PyQt5.QtWidgets import QApplication
+    app = QApplication(sys.argv)
+    w = Window()
+    w.show()
+    sys.exit(app.exec_())
+

# 效果图

+

RlatticeEffect

+
文章作者: Irony
文章链接: https://pyqt5.com/rlatticeeffect.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/rss.xml b/rss.xml new file mode 100644 index 00000000..66b7a58f --- /dev/null +++ b/rss.xml @@ -0,0 +1,1303 @@ + + + + PyQt + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 30 Apr 2024 01:46:52 +0000 + Tue, 30 Apr 2024 01:46:52 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pytest_qt_modal_625781186.html + pytest-qt 测试模态窗体. + https://pyqt5.com/pytest_qt_modal_625781186.html + pytest-qt + Tue, 30 Apr 2024 01:46:52 +0000 + + + + https://pyqt5.com/macm1pyqt.html + 如何在Mac M1上快速安装PyQt5 + https://pyqt5.com/macm1pyqt.html + PyQt + Mac + M1 + Sat, 07 Oct 2023 06:08:06 +0000 + + + + https://pyqt5.com/equal_str_width_625781186.html + python 判断屏幕等宽字符串的长度   + https://pyqt5.com/equal_str_width_625781186.html + Python + Thu, 26 Dec 2019 11:49:41 +0000 + + + + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + 修改pyuic代替pyside2-uic. + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + PyQt5 PySide2 + Thu, 26 Dec 2019 11:49:41 +0000 + + + + https://pyqt5.com/studynotes.html + PyQt学习心得 + https://pyqt5.com/studynotes.html + PyQt + Mon, 26 Aug 2019 01:00:00 +0000 + + + + https://pyqt5.com/python_statemachine_625781186.html + python 状态机模块   + https://pyqt5.com/python_statemachine_625781186.html + Python + python 状态机 + Wed, 17 Jul 2019 09:03:33 +0000 + + + + https://pyqt5.com/pyqt5_hook_key_625781186.html + 在pyqt中使用python全局钩子模块 + https://pyqt5.com/pyqt5_hook_key_625781186.html + Python + pyqt hook key + Sat, 06 Jul 2019 17:37:22 +0000 + + + + https://pyqt5.com/read_open_source.html + 像读文章一样读源码 + https://pyqt5.com/read_open_source.html + Python + debug + snoop + Sat, 06 Jul 2019 17:37:22 +0000 + + + + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + python 获取子进程print信息   + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + Python + subprocess.Popen + 拦截print + Fri, 24 May 2019 06:39:44 +0000 + + + + https://pyqt5.com/qtwebjs.html + QtWebkit和QWebEngineView与Javascript交互 + https://pyqt5.com/qtwebjs.html + PyQt + QWebView + QWebEngineView + 浏览器 + Wed, 22 May 2019 03:30:36 +0000 + + + + https://pyqt5.com/flipwidgetanimation.html + PyQt5窗口翻转动画 + https://pyqt5.com/flipwidgetanimation.html + PyQt + 动画 + 翻转 + Wed, 15 May 2019 14:48:00 +0000 + + + + https://pyqt5.com/QPropertyAnimation.html + PyQt属性动画(QPropertyAnimation) + https://pyqt5.com/QPropertyAnimation.html + PyQt + 动画 + Wed, 08 May 2019 07:43:06 +0000 + + + + https://pyqt5.com/viewapi.html + 如何查阅Qt文档 + https://pyqt5.com/viewapi.html + PyQt + Sat, 04 May 2019 12:50:20 +0000 + + + + https://pyqt5.com/suggesteditor.html + 推荐编辑器LiClipse + https://pyqt5.com/suggesteditor.html + 编辑器 + Sat, 04 May 2019 10:04:08 +0000 + + + + https://pyqt5.com/bindsignals.html + 三种方式绑定信号槽 + https://pyqt5.com/bindsignals.html + PyQt + 信号 + Sat, 04 May 2019 08:07:06 +0000 + + + + https://pyqt5.com/virtualenvpy_625781186.html + python 拷贝虚拟环境(一)   + https://pyqt5.com/virtualenvpy_625781186.html + Python + virtualenvwrapper + virtualenv + Thu, 02 May 2019 07:21:01 +0000 + + + + https://pyqt5.com/runnablesignal_625781186.html + QRunnable线程池发信号 + https://pyqt5.com/runnablesignal_625781186.html + PyQt + 信号 + 线程 + Tue, 30 Apr 2019 07:58:09 +0000 + + + + https://pyqt5.com/viewpyindesigner_625781186.html + 如何和设计师中查看ui转换的py代码 + https://pyqt5.com/viewpyindesigner_625781186.html + PyQt + Designer + 设计师 + Tue, 30 Apr 2019 05:11:09 +0000 + + + + https://pyqt5.com/showframe.html + PyQt5调整窗口显示边框 + https://pyqt5.com/showframe.html + PyQt + 边框 + Fri, 26 Apr 2019 14:19:26 +0000 + + + + https://pyqt5.com/issignalconnected.html + PyQt5判断信号是否连接 + https://pyqt5.com/issignalconnected.html + PyQt + 信号 + Fri, 26 Apr 2019 14:06:26 +0000 + + + + diff --git a/runnablesignal_625781186.html b/runnablesignal_625781186.html new file mode 100644 index 00000000..4945fe44 --- /dev/null +++ b/runnablesignal_625781186.html @@ -0,0 +1,283 @@ +QRunnable线程池发信号 | PyQt + + + + + + + + + + + + + + + +

QRunnable线程池发信号

因为只有继承 QObject 的类才能有信号和自定义信号,而 QRunnable 并不是继承自 QObject ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。

+ +

pools 是 QThreadPool 实例

+

# 看图说话

+
    +
  1. runnablesignal1
  2. +
  3. 定义一个全局信号类
    +runnablesignal2
  4. +
  5. 在 QRunnable 中发送
    +runnablesignal3
  6. +
+
文章作者: Irony
文章链接: https://pyqt5.com/runnablesignal_625781186.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
赞助
  • 微信付
    微信付
  • 支付宝
    支付宝
\ No newline at end of file diff --git a/scaffolds/draft.md b/scaffolds/draft.md deleted file mode 100644 index 498e95ba..00000000 --- a/scaffolds/draft.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: {{ title }} -tags: ---- diff --git a/scaffolds/page.md b/scaffolds/page.md deleted file mode 100644 index b74fbd89..00000000 --- a/scaffolds/page.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: {{ title }} -date: {{ date }} -comments: false ---- \ No newline at end of file diff --git a/scaffolds/post.md b/scaffolds/post.md deleted file mode 100644 index 021fca74..00000000 --- a/scaffolds/post.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -author: -title: {{ title }} -date: {{ date }} -top: 1 -tags: -categories: ---- diff --git a/source/search.html b/search.html similarity index 100% rename from source/search.html rename to search.html diff --git a/search.xml b/search.xml new file mode 100644 index 00000000..26bd9355 --- /dev/null +++ b/search.xml @@ -0,0 +1,1134 @@ + + + + + + + pytest-qt 测试模态窗体. + + /pytest_qt_modal_625781186.html + + 步骤分别是 :

  1. 点击 开始扫描 弹出 选择路径窗口;

  2. 勾选路基;

3. 点击确定;

大概想测一下这个界面 :

image.png

步骤分别是 :

  1. 点击 开始扫描 弹出 选择路径窗口;

  2. 勾选路基;

3. 点击确定;

需要测试的函数 :

image.png

测试函数 :

image.png

可以发现断言失败 .

image.png

官方文档:测试模态窗体.

https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html

用的是官方的 monkeypatch 方式 .

大致意思就是替换 FileSelectPathDialog 类的 exec 函数.

]]>
+ + + + + 教程 + + + + + + + pytest-qt + + + +
+ + + + + 如何在Mac M1上快速安装PyQt5 + + /macm1pyqt.html + + 由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。
M1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。

1. 安装 Python
python-3.9.13-macosx10.9.pkg

2. 勾选自定义同时只勾选安装 pip

step1.png

step1.png

3. 设置 pip 源

/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna

4. 安装 PyQt5

/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5

5. 测试

/Library/Frameworks/Python.framework/Versions/3.9/bin/python3

step3.png

📢📢📢

也可以直接安装 Miniconda

然后:conda install -c conda-forge pyqt

]]>
+ + + + + 笔记 + + + + + + + PyQt + + Mac + + M1 + + + +
+ + + + + python 判断屏幕等宽字符串的长度   + + /equal_str_width_625781186.html + + 判断屏幕等宽字符串的长度?

判断屏幕等宽字符串的长度?

image.png

【新手】重庆 - 搬砖 - NoWait 22:41:50 @北京 - BUG 开发 - 黑择明 求指点
【专家】北京 - BUG 开发 - 黑择明 22:43:04 fontMetrics
【专家】 https://pyqt.site (892768447) 22:43:54 QFontMetrics
【专家】 https://pyqt.site (892768447) 22:44:09 通过 QLabel.font ().fontMetrics () 得到

【新手】重庆 - 搬砖 - NoWait 22:52:00
https://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size
image.png

【新手】重庆 - 搬砖 - NoWait 22:53:15 感觉和 fontMetrics 应该是差不多的

image.png


【专家】北京 - BUG 开发 - 黑择明 (996742224) 11:29:04
fm = QFontMetrics(QFont())
fm.width(“qweqwe”)

]]>
+ + + + + 随笔 + + + + + + + Python + + + +
+ + + + + 修改pyuic代替pyside2-uic. + + /use_pyuic_insteadof_pyside2uic.html + + 修改 pyuic 代替 pyside2-uic

修改 pyuic 代替 pyside2-uic.

最近看到挺多人用 pyside2 的 uic 编译 ui 文件有问题 .
写个解决办法.

首先,
pip install qtpy ,
这个是兼容 pyqt5 和 pyside2 的,无缝转换 .

然后,
修改 pyqt5 的 uic ,

image.png

最后用 pyuic5 , 生成 Ui_XXX.py 文件 .

]]>
+ + + + + 随笔 + + + + + + + PyQt5 PySide2 + + + +
+ + + + + PyQt学习心得 + + /studynotes.html + + 在学习 PyQt 的过程中由于资料的缺乏或者没有中文导致大多数人感叹资料太少,学习困难,又或者急于求进,赶鸭子上架的情况,此时有系统的学习方法很重要。每个人都需要有自己的学习方法,别人的学习方法并不一定适合自己但可以采纳一些。笔者在这里列举了一些当初自己自学的一些心得和方法,希望帮助大家建立一套自己的学习 PyQt 的方法,提高自身的学习能力。

# Python 基础

在学习和使用 PyQt 之前需要熟练使用 Python,经过对 QQ 群里经常提问的问题的分析,发现大部分人对 Python 中的基础知识掌握不牢固导致很多基础问题,如果要想更好的使用 Python 以及它的扩展必需要进行系统的学习。这里列举一下常用的知识点。

  1. 类         参考资料
  2. 类的继承
  3. 类的多继承
  4. 类方法重写     参考资料
  5. 类中的 super 函数  参考资料
  6. 函数调用 / 参数类型
  7. 对象调用 (参考第 1 点)

必须熟练掌握上面的知识点后入门 PyQt 才比较容易,如果初学者对上面的知识点还不是很了解,本文不适合继续往下阅读。

# 设计师

Qt 设计师除了方便快速设计一些简单的界面外,其实笔者觉得更大的作用在于帮助用户熟悉各类控件、属性、信号等

  1. 这里建议初学者不要急于求成,打开设计师新建一个 Widget 的窗口,比如

desiger_create

  1. 然后把左侧的所有控件挨个拖动到中间的窗口中,比如这里拖动一个 Push Button 按钮

desiger_drag

  1. 在设计师右下角的属性编辑器中列举了该控件的所有父类,意味着可以调用和重写父类的所有方法,建议初学者把这个属性编辑器的所有属性挨个调整看看效果,部分控件可能需要 Ctrl+R 预览界面才能看到,同时像 QListWidget,QTreeWidget,QTableWidget 等某些控件需要在控件上右键增加数据才可以

desiger_property
desiger_property2

  1. 两个控件之间简单的信号槽关联可以通过设计师快速的设置

desiger_signal
desiger_signal2

  1. 提高进阶的方法,当你需要手动写代码实现界面的时候,不妨把 UI 文件转出 PY 文件,看看是如何构造的(这里涉及到布局等知识见后文)

# 布局

Qt 界面提供了方便的 4 种基本布局,QVboxLayout,QHboxLayout,QFormLayout,QGridLayout,初学者需要数量掌握这 4 种布局外加 2 种拉伸器(占位挤压)

首先需要知道 Qt 界面的中控件的层级顺序以及 parent,parent 的作用既作为子控件的父元素也可以自动管理 Qt 的对象(具体可以搜索下关于 Qt parent 的资料)

  1. 在没有布局的情况下,在设计师中拖动摆放的控件是一层一层的叠加覆盖,此时每个添加的子控件的 parent 都是最外层的控件

desiger_stack

  1. 如果需要界面中的控件自动适应高度宽度,此时则需要使用 4 种布局来包裹里面的子控件,注意的是:布局不是控件不能设置高度宽度和样式等,是一个抽象的东西,就好比是一根橡皮筋包裹几个矩形的物品;布局也可以设置一些属性(在设计师属性编辑器中),比如设置两者直接的间距,设置距离上下左右的间距,设置比例等

desiger_layout

  1. 在没有布局或者有布局的时候。可以添加容器控件(QWidget,QFrame,QGroupBox,QScrollArea,QToolBox,QTabWidget,QStackedWidget,QMidArea,QDockWidget)这些容器可以放置子控件,从而循环嵌套。

# 例子

在 PyQt5.5 的时候自带了一个例子文件夹(后面的版本没有的话可以下载 PyQt5 源码,里面有个 examples 文件夹),想要熟练的掌握 PyQt 还需要从自带的例子中学习,必须要每个例子都运行一次然后看看这个例子实现了什么,这样才能记忆深刻。
同时很多开发者在 https://github.com/PyQt5/PyQt 分享了各类进阶例子,同时也欢迎大家共同完善该项目,提供更多更好的例子。另外也可以下载该项目的客户端 PyQtClient 软件,支持运行其中的例子

建议在更深入的学习 PyQt 之前多看看一些例子。

# 文档

接下来要说的就是 Qt 的 api 文档,官网文档,这里其实不要害怕是英文就不想看,觉得看不懂了,其实官网的文档还是比较简洁的,而且函数名也比较直观就能知道意思。也可以用谷歌浏览器打开右键翻译,基本上都能看懂。笔者前期写过一篇如何查阅 Qt 文档的文档可以阅读学习一番。

这里就拿 QWebEngineView 举一个例子,首先初学者在使用这个浏览器控件时候,会有诸多的问题比如:Cookie,拦截器等就不知道如何去调用函数来设置

  1. 首先打开官网文档 https://doc.qt.io/qt-5/qwebengineview.html,可以看到只有少量的函数可以调用,寻找一番并没有发现和 Cookie 相关的东西,这个时候就需要把重点放在有特俗返回值的函数上,比如:
QWebEngineHistory *          history() constQWebEnginePage *          page() constQWebEngineSettings *      settings() const

这三个函数返回了一个类实例,就意味着可以调用其中的方法。

  1. 点击 page () 打开 https://doc.qt.io/qt-5/qwebenginepage.html,发现没有 cookie 相关的东西,只有 QWebEngineProfile *profile () const 这个函数比较可疑。

  2. 点击 **profile ()** 打开 https://doc.qt.io/qt-5/qwebengineprofile.html,在浏览器中搜索 cookie 发现这个类中包含大量和 cookie 相关的东西,比如:**QWebEngineCookieStore *cookieStore ()`** 从名字上可以猜测大概意思为 cookie 储存

  3. 点击 **cookieStore ()** 打开 https://doc.qt.io/qt-5/qwebenginecookiestore.html,此时就会发现这个类里面包含了删除和设置 cookie 的方法。

  4. 但是找到了这些方法后,面对初学者又一个问题来了,该如何去用?根据上面 4 点整理一下,把他们当做简单的 Python 对象,方法和操作方法和 class 一样的。

self.webview = QWebEngineView()# 得到pagepage = self.webview.page()# 得到profileprofile = page.profile()# 得到cookieStorecookieStore = profile.cookieStore()# 清空cookiecookieStore.deleteAllCookies()# 用简短代码来表达就是cookieStore = self.webview.page().profile().cookieStore()cookieStore.deleteAllCookies()

# 异常调试

可能有时候由于粗心,或者调用了一些非法函数,参数错误等会导致程序出现一些异常,首先第一步复制最后一行的错误去百度或者谷歌搜索,大多时候能找到问题所在。其次如果搜索不到或者自己的异常可能是由于某个变量的值不对引起的,就需要在编辑器中打断点使用 DEBUG 模式调试变量值(如果不会可以采用麻烦一点的办法:用 print 打印出变量值)

遇到问题后首先需要自己多调试排查问题,不要一遇到问题就去问,自己多尝试一个一个排查直到找到问题所在并解决,这也是一种提高自身能力的地方。

# 检索资料

作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。信息检索是每个程序猿必备的能力之一,其好处在于可以更快更准确的在茫茫网络海洋中找到自己所需要的东西,这个过程需要长期不断积累和练习。

  1. 中文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt 拖拽
  2. 英文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt Drag Drop

# 片尾

好了,笔者基本上的学习过程就整理如上,这并不是说每个人都适合这样的方法,但至少笔者是这样一步一步走过来的。当你养成了一个学习、发现和解决问题的好习惯时就会慢慢得心应手。

]]>
+ + + + + 笔记 + + + + + + + PyQt + + + +
+ + + + + python 状态机模块   + + /python_statemachine_625781186.html + + 用状态来取代 if…else 判断。

GUI 涉及到挺多的状态改变,以前一直用 if…else 来判断,最近读了设计模式,发现有个状态模式,随后发现了状态机这个东西 .

python 的状态机模块挺多的,不过好像很多都不更新了.
推荐 2 个状态机模块,但是也没有太深入的使用经验,就跑跑例子,以后有更详细的 pyqt 例子再补上 .

1: pip install python-statemachine

官方例子 : https://github.com/fgmacedo/python-statemachine

2. pip install state_machine

官方例子 : https://github.com/jtushman/state_machine

1 的 最近一次更新在 6 个月以前,使用 类继承mixin 方式,不过有些地方不如 2 个人性化;

2 的设计更人性化一些,包括状态改变 beforeafter , 不过由于是装饰器实现的动态增加属性,有些地方编辑器智能提示可能就靠不上了.

两者实现实现方式不一样,有兴趣可以读读源码 .

  1. qt 内置状态机框架

https://blog.csdn.net/amnes1a/article/details/62418196

https://blog.csdn.net/dongfenghuojian/article/details/78187131

http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程)

]]>
+ + + + + 随笔 + + + + + + + Python + + python 状态机 + + + +
+ + + + + 在pyqt中使用python全局钩子模块 + + /pyqt5_hook_key_625781186.html + + 在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像 QQ 一样可以全局热键截图。这里介绍几个方法实现在 PyQt 中使用 Python 全局钩子模块实现全局热键功能。

  1. pyHook3

安装命令 : pip install pyhook3

https://blog.csdn.net/q871063970/article/details/86648386

似乎将 pyhook 支持 py3 版本的了?没有太多研究.

缺点:只支持 win 平台.

2. keyboard & mouse

安装命令: pip install keyboard mouse

from PyQt5 import  QtGui, QtWidgets, QtCorefrom PyQt5.QtCore import *from PyQt5.QtGui import *from PyQt5.QtWidgets import *import keyboardclass Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        self.testBtn = QPushButton(self)        layout.addWidget(self.testBtn)        keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey'))        keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc"))    def abc(self,a,b,c):        print(a,b,c)        if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

更详细例子 : pyqt 中使用 keyboard 全局热键

优点:跨平台;

缺点:模块名字取得太差,不容易被发现.

]]>
+ + + + + 随笔 + + + + + + + Python + + pyqt hook key + + + +
+ + + + + 像读文章一样读源码 + + /read_open_source.html + + 使用 snoop, 像读文章一样读源码。

不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
image.png

没有目的地概览 , 不知不觉就追究到细节里面去了。

image.png

所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。

开始 :

  1. python DEBUG 模块介绍 :
     前段时间看过挺多文章提到 pysoonper 这个调试模块,有兴趣的可以百度一下.
    个人尝试了一下,篇幅过大的 DEBUG 不适合用 pysoonper , 因为没有缩进!
     这几天偶然遇到一个二次封装的模块 snoop, 完美地解决了这个问题.
  2. 操作步骤 :
  • 1 . 在 eric6.pymain() 函数上加 snoop 装饰器;
    image.png
  • 2 . 用 vscode 打开 eric6start_.log 文件 (8 层深度 log 文件 34W 行,pycharm 对大文件支持很差);
    log文件

发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。

  • 3 . vscode 配置 log 文件关键字高亮;
    安装高亮插件
    image.png
    配置高亮关键字
    image.png

callreturn 给加进去.

  • 4 . 增加阶段关键字;

eric6启动阶段

image.png

#000 是为了方便搜索 。
需要自己手动折叠 。
可以发现 每个 splash.showMessage() 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。


# ps: vscode 阅读 log 文件还是有一些不方便的地方,除了在 2. 中提到的,还有包括关闭文件再打开,折叠状态不会保留,有其他更好的方式 请留言告诉我,谢谢.

]]>
+ + + + + 随笔 + + + + + + + Python + + debug + + snoop + + + +
+ + + + + python 获取子进程print信息   + + /pyqt_get_subprocess_pipeline_625781186.html + + 在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。

问题所在:

image.png

如果模块都由自己开发, 正常操作

image.png

但是因为不能改,所以只能拦截:
代码:

pythonPath = self.pythonPath_cb.currentText()if suffix == "py":    # 首次    self.pyCommand = [pythonPath, path]    self.modifiedReloadPython(path)def modifiedReloadPython(self, path_):    os.chdir(os.path.dirname(path_))    # 子进程调用    self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)    # self.stdoutWorker.p = self.p    self.stdoutWorker = Worker(self.p)    self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append("PYDEBUG:\n" + x))    self.stdoutWorker.start()class Worker(QThread):    stdout_signal = pyqtSignal(str)    def __init__(self, p, parent=None):        super().__init__(parent)        self.p = p    def run(self):        while True:            QApplication.processEvents()            if self.p is not None:                line = self.p.stdout.readline()                # line = line.strip()                if line != b'':                    try:                        info = line.decode()                        self.stdout_signal.emit(info)                    except:                        self.stdout_signal.emit(repr(line))    
]]>
+ + + + + 随笔 + + + + + + + Python + + subprocess.Popen + + 拦截print + + + +
+ + + + + QtWebkit和QWebEngineView与Javascript交互 + + /qtwebjs.html + + 以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

# 说明

  1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
  2. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
  3. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
@pyqtSlot(str)def callFromJs(self, text):    QMessageBox.information(self, "提示", "来自js调用:{}".format(text))
  1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
// 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)Bridge.windowTitleChanged.connect({fun: function(title) {    showLog("标题被修改为:" + title);}}, "fun");// 绑定自定义的信号customSignalBridge.customSignal.connect({fun: function(text) {    showLog("收到自定义信号内容:" + text);}}, "fun");
  1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
new QWebChannel(qt.webChannelTransport,    function(channel) {        window.Bridge = channel.objects.Bridge;                // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)        Bridge.windowTitleChanged.connect(function(title) {            showLog("标题被修改为:" + title);        });                // 绑定自定义的信号customSignal        Bridge.customSignal.connect(function(text) {           showLog("收到自定义信号内容:" + text);        });    });

# 代码

QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

  1. 针对 QWebView 的核心实现
class WebView(QWebView):    customSignal = pyqtSignal(str)    def __init__(self, *args, **kwargs):        super(WebView, self).__init__(*args, **kwargs)        self.initSettings()        # 暴露接口对象        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)    def _exposeInterface(self):        """向Js暴露调用本地方法接口        """        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)    # 注意pyqtSlot用于把该函数暴露给js可以调用    @pyqtSlot(str)    def callFromJs(self, text):        QMessageBox.information(self, "提示", "来自js调用:{}".format(text))    def sendCustomSignal(self):        # 发送自定义信号        self.customSignal.emit('当前时间: ' + str(time()))
  1. 针对 QWebEngineView 的核心实现
class WebEngineView(QWebEngineView):    customSignal = pyqtSignal(str)    def __init__(self, *args, **kwargs):        super(WebEngineView, self).__init__(*args, **kwargs)        self.channel = QWebChannel(self)        # 把自身对象传递进去        self.channel.registerObject('Bridge', self)        # 设置交互接口        self.page().setWebChannel(self.channel)    # 注意pyqtSlot用于把该函数暴露给js可以调用    @pyqtSlot(str)    def callFromJs(self, text):        QMessageBox.information(self, "提示", "来自js调用:{}".format(text))    def sendCustomSignal(self):        # 发送自定义信号        self.customSignal.emit('当前时间: ' + str(time()))

# 效果图

JsSignals

]]>
+ + + + + 例子 + + + + + + + PyQt + + QWebView + + QWebEngineView + + 浏览器 + + + +
+ + + + + PyQt5窗口翻转动画 + + /flipwidgetanimation.html + + QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 QStackedWidget 包含两个控件做切换,同时单独使用一个窗口做动画绘制。

# 原理说明

  1. 用了两个 QLabel 来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
  2. 用了 QStackedWidget 来存放上面的两个界面 QLabel
  3. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口
  4. 通过 setWindowOpacity 控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
  5. 动画窗口 FlipWidget.py 主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画

# 核心实现

  1. 主要是在 paintEvent 方法中使用 QTransformQPainter 进行圆心变换以及 rotate 设置翻转角度
  2. 同时根据翻转的角度范围对图片进行切换和缩放
def paintEvent(self, event):    super(FlipWidget, self).paintEvent(event)    if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible():        painter = QPainter(self)        painter.setRenderHint(QPainter.Antialiasing, True)        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)        # 变换        transform = QTransform()        # 把圆心设置为矩形中心        transform.translate(self.width() / 2, self.height() / 2)        if self._angle >= -90 and self._angle <= 90:            # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程            painter.save()            # 设置翻转角度            transform.rotate(self._angle, Qt.YAxis)            painter.setTransform(transform)            # 缩放图片高度            width = self.image1.width() / 2            height = int(self.image1.height() *                         (1 - abs(self._angle / self.Scale) / 100))            image = self.image1.scaled(                self.image1.width(), height,                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)            painter.drawPixmap(                QPointF(-width, -height / 2), image)            painter.restore()        else:            # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程            painter.save()            if self._angle > 0:                angle = 180 + self._angle            else:                angle = self._angle - 180            # 设置翻转角度, 注意这里角度有差异            transform.rotate(angle, Qt.YAxis)            painter.setTransform(transform)            # 缩放图片高度            width = self.image2.width() / 2            height = int(self.image2.height() *                         (1 - ((360 - abs(angle)) / self.Scale / 100)))            image = self.image2.scaled(                self.image2.width(), height,                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)            painter.drawPixmap(                QPointF(-width, -height / 2), image)            painter.restore()

# 代码

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py

# 效果图

FlipWidgetAnimation

]]>
+ + + + + 例子 + + + + + + + PyQt + + 动画 + + 翻转 + + + +
+ + + + + PyQt属性动画(QPropertyAnimation) + + /QPropertyAnimation.html + + QPropertyAnimation 继承自 QVariantAnimation ,其作为 Qt 的属性动画用于针对控件的属性或者继承自 QObject 的对象中定义的属性做修改,
简单来说就是基类是 QObject 且定义了属性变量,就可以用 QPropertyAnimation 来做属性动画。同时也可以通过 pyqtProperty 来增加自定义属性。

首先,通过构造函数 QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None) 创建一个对象,其中

  1. 第一个参数是动画作用的对象,也可以通过 setTargetObject 设置
  2. 第二个参数是属性名,在 py3 中类型是 bytes,也可以通过 setPropertyName 设置

# 函数

一些常见的设置函数

setPropertyName设置属性名
setTargetObject设置动画作用对象
setDuration设置动画持续时间(毫秒)
setStartValue设置开始值
setEndValue设置结束值
setEasingCurve设置动画曲线
setKeyValueAt插入线性值
setLoopCount设置循环次数(-1 为永久)

# 示例

比如这个例子:

  1. 修改控件的 geometry 大小
  2. 修改自定义属性
  3. 修改进度条的 value 值

QPropertyAnimation

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年5月8日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: @description: """from PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurvefrom PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\    QLabel, QProgressBar, QSpacerItem, QSizePolicy__Author__ = 'Irony'__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        self.resize(400, 400)        self._value = 0        self.button = QPushButton('属性动画测试', self)        self.button.clicked.connect(self.doStart)        self.button.setGeometry(0, 0, 80, 40)        self.buttonc = QPushButton('自定义属性 测试', self)        self.buttonc.clicked.connect(self.doStartCustom)        self.label = QLabel('', self)        self.progressbar = QProgressBar(self)        self.progressbar.setRange(0, 99)        layout = QVBoxLayout(self)        layout.addItem(QSpacerItem(            20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed))        layout.addWidget(self.buttonc)        layout.addWidget(self.label)        layout.addWidget(self.progressbar)        # 进度条动画        self.progressStart()    # 此处是自定义属性,并通过动画修改后,设置QLabel的值    @pyqtProperty(int)    def value(self):        return self._value    @value.setter    def value(self, v):        self._value = v        self.label.setText('当前值:{}'.format(v))    def doStart(self):        # 第一个参数是要执行的对象        animation = QPropertyAnimation(self.button, b'geometry', self)        animation.setDuration(2000)  # 持续时间        # 缓和曲线风格,加了曲线动画会很大程度影响        animation.setEasingCurve(QEasingCurve.OutBounce)        animation.setStartValue(QRect(0, 0, 40, 40))        animation.setEndValue(QRect(250, 250, 80, 80))        animation.start(animation.DeleteWhenStopped)    def doStartCustom(self):        # 自定义属性动画        # 由于定义的属性是在继承的QWidget, 所以第一个参数是self        # 第二个参数就是 value        animation = QPropertyAnimation(self, b'value', self)        animation.setDuration(2000)  # 持续时间        animation.setStartValue(0)        animation.setEndValue(100)        animation.start(animation.DeleteWhenStopped)    def progressStart(self):        # 进度条动画        # 这里 value是QProgressBar自带的属性,具体可以看文档        # https://doc.qt.io/qt-5/qprogressbar.html#properties        animation = QPropertyAnimation(self.progressbar, b'value', self)        animation.setDuration(2000)  # 持续时间        animation.setLoopCount(-1)        # 这里采用插入线性值,第一个参数的范围是(0-1)        # 第二个参数的范围是进度(最小值-最大值)        animation.setKeyValueAt(0, self.progressbar.minimum())        animation.setKeyValueAt(0.1, 10)        animation.setKeyValueAt(0.2, 30)        animation.setKeyValueAt(0.5, 60)        animation.setKeyValueAt(0.7, 80)        animation.setKeyValueAt(1, self.progressbar.maximum())        animation.start(animation.DeleteWhenStopped)if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())
]]>
+ + + + + 笔记 + + + + + + + PyQt + + 动画 + + + +
+ + + + + 如何查阅Qt文档 + + /viewapi.html + + 很多网友在问有没有 PyQt5 的文档之类的问题,在 PyQt4 的时候 PyQt 官网有了英文版的文档,随后有网友翻译成了中文。不过现在 PyQt5 官方的文档都指向了 C 的 Qt 文档,其实 C 的 Qt API 文档结构很清晰,翻阅很容易的,需要注意几点。

作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。

拿 Qt C++ 文档来说,官网地址是:https://doc.qt.io/qt-5/qtwidgets-module.html 这里面记录了所有控件的详细函数文档。

比如拿 输入框 QLineEdit 来说,怎么去查询它的用法和信号槽等资料?

https://doc.qt.io/qt-5/qlineedit.html

# 左侧目录

在文档左侧目录中有如下几个:

Properties - 控件里的属性(比如宽高等,通常需要当作函数调用)

Public Slots - 这个是控件自己的槽函数(当作普通函数就行)

Signals - 这个是输入框的包含的信号

Public Functions、Reimplemented Public Functions、Static Public Members、Protected Functions、Reimplemented Protected Functions - 这几个都是函数列表

howtoviewapi1

# 类说明

howtoviewapi2

这里有两个注意点

  1. 红色方框内的表示该控件(输入框)继承于 QWidget ,所以该控件(输入框)拥有父类的所有方法和信号,当当前文档找不到相关资料和函数时,可以去父类找找看。
  2. 紫色方框内表示列举所有的方法(包括父类)

# 函数列表

howtoviewapi3

这里列举的就是该控件(输入框)的函数,同理点击上面的紫色方框是查看所有方法,一般这里主要用来查询你需要的功能函数,Qt 的函数名比较容易理解,比如:只读 ReadOnly,选择文字:setSelection。

所以再查下这部分资料的时候建议在浏览器中 Ctrl + F 打开浏览器的搜索框,并输入英文关键词来检索你所需要的函数在哪里。

howtoviewapi8

# 槽函数

howtoviewapi4

这部分列举的是槽函数,其实在 PyQt 中槽函数可以当作普通的函数。普通的函数也可以作为槽函数,直接通过信号连接即可,注意方框所示,还有很多函数是在父类里面。

# 信号

howtoviewapi5

这部分列举了该控件(输入框)所定义的信号,主要还是看名字,大多都能知道是做什么的,比如:

  1. editingFinished - 编辑完成信号
  2. returnPressed - 回车键信号
  3. textChanged (const QString &text) - 内容改变信号

这里还有个问题就是参数问题,一般 & 后面的 text 作为参数传递到槽函数中

# 函数详细说明

当不明确这个函数是做什么的,可以点击该函数跳转到下面的说明,比如回车键信号 returnPressed

howtoviewapi6

如图上所示,用翻译插件翻译,大部分就明白了,如下:

howtoviewapi7

# 关于如何搜索资料

比如当你要搜索输入框内容改变事件,一般建议两种搜索,且搜索的时候用空格把关键词分开搜索,而且直接用控件名

  1. 中文搜索引擎:QLineEdit 内容 改变
  2. 英文搜索引擎:QLineEdit text change
]]>
+ + + + + 笔记 + + + + + + + PyQt + + + +
+ + + + + 推荐编辑器LiClipse + + /suggesteditor.html + + 关于 Python 的开发编辑器有很多,每个人有每个人的喜好,经常看到很多在问什么编辑器好用,有人推荐 Sublime,有人推荐 Pycharm 等等,这里就不去比较其它编辑器的优缺点了,只谈谈关于 LiClipse 这个编辑器在初级使用阶段的智能提示功能等。开箱即用,支持多种语言,RST,Markdown 和 HTML 编辑器的 HTML 预览。

其实 LiClipse 这个编辑器就是以前的 PyDev 插件的独立版本,基于 Eclipse 编辑器开发,去掉了 Java 的相关开发功能,关于软件的详细说明可以去官网查看: http://www.liclipse.com/

编辑器只需要少量的配置,打开即可使用,快速自动 import,也可以根据需要安装自己所需的插件,比如 json、svn、主题插件等。个人推荐:适合刚入门的新手使用

由于新版的 PyQt 和 PyDev 去掉了详细的函数提示,所以 PyQt 的智能提示只有函数和返回值,并没有英文注释,但是以前的比如 PyQt4 的智能提示应该是有详细的英文注释提示。

# 界面预览

  1. 主界面
    editor1
  2. 鼠标悬停提示
    editor2
  3. 输入提示
    editor3
  4. Git 面板
    editor4
  5. 全局搜索(Ctrl + H)
    editor5
    editor6

# 自动导包

其实这个功能我是非常喜欢的,通过按下快捷键即可自动寻找包名导入,快捷键 Ctrl + Shift + O

editor_import

也可以在标红的代码上按下 Ctrl + F1 进行导入

editor_import2

# 配置

打开编辑器后首先要配置【Window -> Preferences】的就是 Python 的环境变量,可以同时添加多个 Python 版本

editor_env

# Tab 等设置

  1. Insert spaces for tabs tab 转空格
  2. Show line numbers 显示行号

editor_tab

# 模版

这个功能可以快速插入自己定义好的模版代码,比如 if __name__ == '__main__': 等等,比如我这里配置的创建文件的模版

editor_tpl

# 常用快捷键

格式化对齐Ctrl + Shift + F
自动导包Ctrl + Shift + O
快捷提示Alt + /
]]>
+ + + + + 随笔 + + + + + + + 编辑器 + + + +
+ + + + + 三种方式绑定信号槽 + + /bindsignals.html + + 网上关于 PyQt5 的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在 PyQt5 中无法使用,这里归纳总结下已有的几种绑定信号槽的方式,
这几种方式各有各的优点和缺点。

# 方式一

这个方式是最开始接触设计师的时候知道的,主要是通过控件的 objectNameQtCore.QMetaObject.connectSlotsByName(Form) 提供的连接函数来自动完成注册,
比如带有按钮的界面 ui 文件转成 py 文件后会发现如下代码:

self.pushButton = QtWidgets.QPushButton(Form)self.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28))self.pushButton.setObjectName("pushButton")# 通过这里自动完成连接信号槽QtCore.QMetaObject.connectSlotsByName(Form)

此时只需要继承该 UI 文件类然后增加如下方法:

@pyqtSlot()def on_pushButton_clicked(self):    print('button clicked')

这里解释一下, @pyqtSlot() 装饰器把函数 on_pushButton_clicked 包装为一个槽函数,
QtCore.QMetaObject.connectSlotsByName(Form) 这句代码的意思就是自动去寻找满足的槽函数

注意:这里有个规范(on_xxxx_clicked),这里必须要满足 on_控件的objectName_控件的信号 这样下划线连接起来的函数名才能被识别,
比如按钮的点击: on_pushButton_clicked 、勾选框的选中: on_checkbox_toggled(self, checked)

# 方式二

这种方式则直接通过代码里调用控件的信号的 connect 方法来进行绑定,比如:

# 按钮点击函数def doClicked(self):    print(self.sender(), 'clicked')# 绑定点击信号self.pushButton.clicked.connect(self.doClicked)

注意: connect 的是函数名字self.sender() 这句代码是获取信号发送者(比如这里就是得到这个按钮对象),
用处在于有时候要循环创建一堆按钮

# 方式三

通过参数这种方式其实比较特殊,在 PyQt 中大部分存在,但是在 PySide 中则很少,原因是两者的封装方式不同。

同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如:

# 按钮点击函数def doClicked(self):    print(self.sender(), 'clicked')pushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40)

这里可以通过参数(信号名字) = 函数来绑定信号

同时也可以设置其它参数,比如
button.setMinimumHeight(40) 也可以像参数里那样设置 minimumHeight=40

]]>
+ + + + + 教程 + + + + + + + PyQt + + 信号 + + + +
+ + + + + python 拷贝虚拟环境(一)   + + /virtualenvpy_625781186.html + + 通常来说,刚开始使用 python 的时候都是把包装到全局路径,随着各个项目安装的包越来越多,之后每开始一个项目,pycharm 创建索引的时间都越来越漫长,所以不可避免得开始使用虚拟环境。
经过一番了解 ,虚拟环境的优点有这些:

  • 改善 pycharm 索引时间;
  • 各个项目的库不会冲突;
  • 理论上虚拟环境可以给同版本的操作系统使用 (未试验过);
  • pip freeze > requestment.txt 导出的依赖清晰;
  • 各个版本的 python 共存;

python 虚拟环境库除了自带的 venv , 还有三方库 virtualenv , 此外 在 virtualenv 基础上又开发了 virtualenvwrapper(virtualenvwrapper_win) 来管理

本文基于 virtualenvwrapper 创建的虚拟环境来讲解.

以下是收集的一些virtualenvwrapper配置教程:# linux平台https://www.cnblogs.com/netfoxman/p/5994697.html# window平台https://blog.csdn.net/shaququ/article/details/54292043   https://blog.csdn.net/iaau0908/article/details/54021518

虚拟环境创建多了我们就会发现,
有时候使用相同版本的环境,一些常用的库是需要重新安装的,
那么能不能创建一个基础环境,默认拥有这些库,然后在这个基础环境上继续安装三方库呢?

本文经过试验发现是可行的:

  1. 创建基础虚拟环境 mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径] . 例如 mkvirtualenv py34 -p c:\Python34\python.exe

  2. 切换到虚拟环境 workon py34 , 然后安装一下三方库,然后复制 py34 这个文件夹备份一下;

  3. 接着复制这个 py34 文件夹,把复制后的文件夹改名为我们需要需要的文件夹例如 new34

  4. 进入 new34文件夹 ,用任意编辑器全路径搜索 py34 (替换虚拟环境的路径)

  5. 删除 new34/Scripts 下的 pip.exe, pip3.exe, pip3.x.exe, easy_install.exe (因为安装路径硬编码到这里面了,改不了,需要重新安装)

  6. https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二,源码安装 setuptools 后再用 easy_install pip 安装 pip 后,完成;
    如果有问题,就继续按照方式一的源码安装 pip;

  7. new34 环境下 用 pip show 三方库 来看一些库的位置,确保正确.

]]>
+ + + + + 随笔 + + + + + + + Python + + virtualenvwrapper + + virtualenv + + + +
+ + + + + QRunnable线程池发信号 + + /runnablesignal_625781186.html + + 因为只有继承 QObject 的类才能有信号和自定义信号,而 QRunnable 并不是继承自 QObject ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。

pools 是 QThreadPool 实例

# 看图说话

  1. runnablesignal1
  2. 定义一个全局信号类
    runnablesignal2
  3. 在 QRunnable 中发送
    runnablesignal3
]]>
+ + + + + 教程 + + + + + + + PyQt + + 信号 + + 线程 + + + +
+ + + + + 如何和设计师中查看ui转换的py代码 + + /viewpyindesigner_625781186.html + + 通过 设计师  查看 ui 转换的 py 代码

当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。

后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。

再来后发现了官方文档,查一查函数就行了 .

但是有些 api 文档找起来麻烦,用设计师点几下就行了,然后把转换出来的代码拷贝一下就完事了.

可是需要单独把 ui 转为 py 文件,之后再删除这个文件也是很烦的一件事 .

好,话不多说,接下来手把手教你如何快速在 ui 中查看 py 代码 .

官方也考虑过这种情况,所以 设计师中 是有这个功能的,但是 qt 的是没问题的,pyqt 的毕竟是绑定过来的,所以正常来说 你点击之后会弹出一个找不到应用程序的提示 .

看到这个东西是不是很眼熟,我们用的命令 pyuic5 和这个东西应该是一样的 .

viewpyindesigner1

所以接下来,我们找找电脑上有没有这个东西

viewpyindesigner2

果然在 pyqt5-toos 文件夹下有这个东西,

我们根据第一张图的提示,把这个东西拷贝到相应的目录 (如果没有那个 bin 文件夹,手动创建),

viewpyindesigner3

好了,大功告成!

]]>
+ + + + + 教程 + + + + + + + PyQt + + Designer + + 设计师 + + + +
+ + + + + PyQt5调整窗口显示边框 + + /showframe.html + + windows 某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 windows 在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -> 调整 Windows 的外观和性能 -> 去掉勾选 拖动时显示窗口内容。

由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。

  1. 一种是自己绘制一个边框效果,放开鼠标时才操作真正的窗口。
  2. 二是替换窗口的处理过程函数 wndproc 处理 WM_NCLBUTTONDOWN 消息事件。

今天讲第二种方法:

  1. 需要了解 SystemParametersInfo  API 函数
  2. SPI_GETDRAGFULLWINDOWS :确定是否允许拖拉到最大窗口
  3. SPI_SETDRAGFULLWINDOWS :设置是否允许拖至最大窗口

效果就是这样的:

ShowFrameWhenDrag

正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。

# 代码

https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年4月23日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: ShowFrameWhenDrag@description: 调整窗口显示边框"""from ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\    c_ulonglong, WINFUNCTYPE, c_uintfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel__Author__ = 'Irony'__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0if sizeof(c_long) == sizeof(c_void_p):    WPARAM = c_ulong    LPARAM = c_longelif sizeof(c_longlong) == sizeof(c_void_p):    WPARAM = c_ulonglong    LPARAM = c_longlongWM_NCLBUTTONDOWN = 0x00a1GWL_WNDPROC = -4SPI_GETDRAGFULLWINDOWS = 38SPI_SETDRAGFULLWINDOWS = 37WNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM)try:    CallWindowProc = windll.user32.CallWindowProcW    SetWindowLong = windll.user32.SetWindowLongW    SystemParametersInfo = windll.user32.SystemParametersInfoWexcept:    CallWindowProc = windll.user32.CallWindowProcA    SetWindowLong = windll.user32.SetWindowLongA    SystemParametersInfo = windll.user32.SystemParametersInfoAdef GetDragFullwindows():    rv = c_int()    SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0)    return rv.valuedef SetDragFullwindows(value):    SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0)class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        layout.addWidget(QLabel('拖动或者调整窗口试试看'))        # 重点替换窗口处理过程        self._newwndproc = WNDPROC(self._wndproc)        self._oldwndproc = SetWindowLong(            int(self.winId()), GWL_WNDPROC, self._newwndproc)    def _wndproc(self, hwnd, msg, wparam, lparam):        if msg == WM_NCLBUTTONDOWN:            # 获取系统本身是否已经开启            isDragFullWindow = GetDragFullwindows()            if isDragFullWindow != 0:                # 开启虚线框                SetDragFullwindows(0)                # 系统本身处理                ret = CallWindowProc(                    self._oldwndproc, hwnd, msg, wparam, lparam)                # 关闭虚线框                SetDragFullwindows(1)                return ret        return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

# 片尾

替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。

]]>
+ + + + + 教程 + + + + + + + PyQt + + 边框 + + + +
+ + + + + PyQt5判断信号是否连接 + + /issignalconnected.html + + PyQt 中某些情况下需要取消原来的信号连接,此时需要使用 disconnect 方法,但是在逻辑不严谨的情况下可能会导致多次调用 disconnect 方法而导致报错,当然可以通过 try except 来包裹代码。这里通过  isSignalConnected  来判断信号是否连接。

在 QOjbect 文档中这样写到:

static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged);if (isSignalConnected(valueChangedSignal)) {    QByteArray data;    data = get_the_value();       // expensive operation    emit valueChanged(data);}

通过直接传入信号就行了,但是这在 PyQt 中不可行。需要这么做

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年2月24日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: IsSignalConnected@description: 判断信号是否连接"""from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        self.button1 = QPushButton('已连接', self, clicked=self.doTest)        self.button2 = QPushButton('未连接', self)        self.retView = QTextBrowser(self)        layout.addWidget(self.button1)        layout.addWidget(self.button2)        layout.addWidget(self.retView)    def doTest(self):        self.retView.append("""        # button1 clicked 是否连接: %s        # button2 clicked 是否连接: %s        """ % (            self.isSignalConnected(self.button1, 'clicked()'),            self.isSignalConnected(self.button2, 'clicked()')        ))    def isSignalConnected(self, obj, name):        """判断信号是否连接        :param obj:        对象        :param name:       信号名,如 clicked()        """        index = obj.metaObject().indexOfMethod(name)        if index > -1:            method = obj.metaObject().method(index)            if method:                return obj.isSignalConnected(method)        return Falseif __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

# 效果图

IsSignalConnected

]]>
+ + + + + 教程 + + + + + + + PyQt + + 信号 + + + +
+ + + + + PyQt5无边框圆角阴影 + + /shadowradius.html + + 在做 PyQt 窗口开发中经常会遇到要做一些无边框不规则的窗口,可能还会带有阴影效果,这里演示做一个简单的无边框圆角的窗口,原理就在于背景窗口的透明和一层有色背景控件的叠加。

# 原理说明

  1. 黑色(方便说明)的 QDialog 或者 QWidget 作为全透明无边框窗口。
  2. 其中白色的 QWidget 才是主要显示圆角和阴影的窗口,用于承载其它控件的显示。
  3. 注意红色和紫色的方框内的层次。
  4. 另:如果要熟悉纯代码编写请看 FramelessDialog.py

如图:

FramelessDialog1

# 代码

https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年4月25日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: FramelessWidget@description: 无边框圆角带阴影窗口 """from PyQt5.QtCore import Qtfrom PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffectfrom frameless import Ui_Dialog__Author__ = 'Irony'__Copyright__ = 'Copyright (c) 2019'class Window(QDialog, Ui_Dialog):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        self.mPos = None        self.setupUi(self)        self.closeButton.clicked.connect(self.close)        # 重点        # 无边框        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)        # 背景透明(就是ui中黑色背景的那个控件)        self.setAttribute(Qt.WA_TranslucentBackground, True)        # 添加阴影        effect = QGraphicsDropShadowEffect(self)        effect.setBlurRadius(12)        effect.setOffset(0, 0)        effect.setColor(Qt.gray)        self.setGraphicsEffect(effect)    # 加上简单的移动功能    def mousePressEvent(self, event):        """鼠标点击事件"""        if event.button() == Qt.LeftButton:            self.mPos = event.pos()        event.accept()    def mouseReleaseEvent(self, event):        '''鼠标弹起事件'''        self.mPos = None        event.accept()    def mouseMoveEvent(self, event):        if event.buttons() == Qt.LeftButton and self.mPos:            self.move(self.mapToGlobal(event.pos() - self.mPos))        event.accept()if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

# 效果图

FramelessDialog

# 下载

无边框圆角阴影.zip

]]>
+ + + + + 例子 + + + + + + + PyQt + + 阴影 + + 无边框 + + 圆角 + + + +
+ + + + + 解决GitHub下载速度缓慢的问题 + + /speedgithub.html + + 由于 Github 的下载走的是 AWS - 亚马逊的路线,,so slow,跟乌龟一样慢。。照着一些方法改了 hosts 文件,偶尔能提提速度。

# Windows

Hosts 文件的路径是:

C:\Windows\System32\drivers\etc

# Mac

终端内输入:

sudo vim /etc/hosts

# 追加域名的 IP 地址

利用 https://www.ipaddress.com/ 来获得以下两个 GitHub 域名的 IP 地址:

(1) github.com

(2) github.global.ssl.fastly.net

打开网页后,利用输入框内分别查询两个域名

将以上两段 IP 写入 Hosts 文件中:

192.30.253.112               github.com151.101.185.194              github.global.ssl.fastly.net

保存。

刷新 DNS 缓存

在终端或 CMD 中,执行以下命令:

ipconfig /flushdns

]]>
+ + + + + 笔记 + + + + + + + Github + + + +
+ + + + + 在Mac上以正确的姿势使用PyQtClient看Demo + + /pyqtclientmac.html + + 由于 PyQtClient 只提供了 Windows 的版本,这里记录下编译后在 Mac 上运行。

# 下载项目

安装 git 略。没有的东西可以都先去试试 brew install xxx。没安装 homebrew 的建议使用搜索引擎

git clone https://github.com/PyQt5/PyQtClient.git

# 配置环境

  1. 打开 IDE 配置 python 环境,使用 anaconda 比较方便
  2. 推荐用 pycharm,我是习惯了用 idea。anaconda 安装可以去官网下载。
  3. 环境原因,选择新建一个 python 3.6 p.s. 我取的环境名字就是 3.6 所以后面的 3.6 其实是这个原因

# conda 源

最好是加环境变量,不加也可以,就是以后用到的都需要指定路径,不太常用,我就没加

~/anaconda3/bin/conda config --add channels conda-forge~/anaconda3/bin/conda config --add channels defaults~/anaconda3/bin/conda config --add channels r~/anaconda3/bin/conda config --add channels bioconda~/anaconda3/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ 

# pip 源

mkdir ~/.pip && vim ~/.pip/pip.conf
[global]index-url = http://mirrors.aliyun.com/pypi/simple/[install]trusted-host = mirrors.aliyun.com

# 安装编译依赖

~/.conda/envs/3.6/bin/pip install -r PyQtClient/requirements.txt

运行提示没有 webkit,开始手动编译

  1. wget http://download.qt.io/archive/qt/5.9/5.9.0/qt-opensource-mac-x64-5.9.0.dmg
  2. wget https://github.com/annulen/webkit/releases/download/qtwebkit-5.212.0-alpha2/qtwebkit-5.212.0_alpha2-qt59-darwin-x64.tar.xz
  3. wget https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.10.1/PyQt5_gpl-5.10.1.zip
  4. wget https://www.riverbankcomputing.com/static/Downloads/sip/4.19.8/sip-4.19.8.tar.gz
  5. 编译 sip: ~/.conda/envs/3.6/bin/python configure.py --platform macx-g++ && make && sudo make install
  6. 编译 Webkit.so 没有 qmake 和 sip 的环境变量, 所以后面都是手动指定的
~/.conda/envs/3.6/bin/python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc --qmake=~/Qt5.9.0/5.9/clang_64/bin/qmake --sip=~/.conda/3.6/bin/sip && make && sudo make install

# 插曲

  1. libcurl 版本要求 10.0,而我的是 9.0,原因是前面我自己摸索,乱装依赖,所以遇到了
~/anaconda3/bin/conda install -n 3.6 -c conda-forge libcurl
  1. 结果这个 libcurl 10.0.0 是装上了,可是 pygit2 版本不对了,conda 给升级了,PyQtClient 里 requirements.txt 要求这个包的版本(pygit2==0.27.2)几乎决定了其他的环境版本。后来还是老实的用 conda 去装了。这个连 python 版本什么的都会跟着变的。最后降级的结果是 python 3.6.7
~/anaconda3/bin/conda install -n 3.6 -c conda-forge libgit2==0.27.2

至此总算是启动正常了。

]]>
+ + + + + 教程 + + + + + + + PyQt + + + +
+ + + + + Python调用Java对Excel截图 + + /calljava.html + + 有的时候会遇到一些奇葩的需求,就是用 Excel 做报表,但是需要对里面的数据进行填充并生成报表图片,发送出去。这里记录用 python 调用 jar 包对 excel 文件进行公式计算和截图,数据填充可以用 xlrd 或者 openpyxl

利用 jpype 模块初始化 java 虚拟机加载 jar 包然后执行其中的功能。

# 代码

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年3月12日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: CallJava@description: """import osimport jpype__Author__ = 'Irony'__Copyright__ = 'Copyright (c) 2019'def convertToImage():    Workbook = jpype.JClass('com.aspose.cells.Workbook')    ImageFormat = jpype.JClass('com.aspose.cells.ImageFormat')    ImageOrPrintOptions = jpype.JClass(        'com.aspose.cells.ImageOrPrintOptions')    SheetRender = jpype.JClass('com.aspose.cells.SheetRender')    book = Workbook(os.path.abspath('data/test.xlsx').replace('\\', '/'))    # 保存为html    book.save('data/index.html', 12)    # 保存为pdf    book.save('data/test.pdf')    # 截图    imgOptions = ImageOrPrintOptions()    # imgOptions.setQuality(100)    imgOptions.setOnePagePerSheet(True)    # 输出图片格式#     imgOptions.setImageFormat(ImageFormat.getJpeg())    imgOptions.setImageFormat(ImageFormat.getPng())    # 计算    CalculationOptions = jpype.JClass(        'com.aspose.cells.CalculationOptions')    opt = CalculationOptions()    # 对Sheet1中的公式进行计算    sheet = book.getWorksheets().get('Sheet1')    sheet.calculateFormula(opt, True)    # 设置区域    pageSetup = sheet.getPageSetup()    # 去掉边距    pageSetup.setBottomMargin(0.)    pageSetup.setLeftMargin(0.)    pageSetup.setRightMargin(0.)    pageSetup.setTopMargin(0.)    # 设置要截图的区域(对角线)    pageSetup.setPrintArea('A0:C2')    # Create a SheetRender object for the target sheet    sr = SheetRender(sheet, imgOptions)    for page in range(sr.getPageCount()):        # Generate an image for the worksheet        sr.toImage(            page, os.path.join('data', '%d.png' % (page + 1)))def test():    # emm这里不知道什么用绝对路径就报错    libs = '{};{}'.format(        'libs/bcprov-jdk16-146.jar',        'libs/aspose-cells-19.2.jar'    )    command = (jpype.getDefaultJVMPath(),                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',                   '-Djava.class.path={0}'.format(libs))    print(command)    jpype.startJVM(jpype.getDefaultJVMPath(),                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',                   '-Djava.class.path={0}'.format(libs)                   )    # 解决多线程问题    jpype.attachThreadToJVM()    # 对excel截图    convertToImage()    # 关闭虚拟机    jpype.shutdownJVM()    print('截图完成')if __name__ == '__main__':    test()

# 附件

调用 java 生成报表.7z

解压后进入 whls 文件夹安装对应版本的 jpype 包

# 效果图

calljava

]]>
+ + + + + 教程 + + + + + + + Python + + 截图 + + + +
+ + + + + PyQtClient例子客户端 + + /pyqtclient.html + + 1.gif 对本博客所写的项目 PyQt 例子进行一个客户端的编写,客户端主要实现一些动画效果,更换皮肤,运行例子等功能。2.gif

# 项目地址

# Windows 客户端下载

  1. 包含部分例子
  2. 不包含例子
  3. 百度网盘 提取码: nadv

# 效果图

PyQtClient

]]>
+ + + + + 随笔 + + + + + + + PyQt + + + +
+ + + + + FFmpeg合成加密HLS记录 + + /ffmpeghls.html + + 记录在某个需求中要求截图并合成加密视频文件,这里采用 FFmpeg 的管道流来实现生成 HLS 加密文件。

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2019年3月4日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: @description: """from pathlib import Pathfrom subprocess import Popen, PIPE__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = 'Copyright (c) 2019 Irony'__Version__ = 1.0# p = Popen([r'D:\soft\ffmpeg\bin\ffmpeg.exe', '-y',#            '-threads', '2',#            '-f', 'image2pipe',#            '-vcodec', 'mjpeg', '-r', '24', '-i', '-',#            '-vcodec', 'h264', '-r', '24',#            #            '-encryption_scheme', 'cenc-aes-ctr',#            #            '-encryption_key', '617D8A125A284DF48E3C6B1866348A3F',#            #            '-encryption_kid', 'B326F895B6A24CC5A4DC70995728059C',#            r'F:\Workspace\Test\videos\video.mp4'], stdin=PIPE)p = Popen([r'D:\soft\ffmpeg\bin\ffmpeg.exe',           '-re',   # 按照实际帧率读取输入文件           '-y',        # 覆盖已存在文件           '-threads', '2',  # 线程数量           '-f', 'image2pipe',  # PIPE图片流           '-vcodec', 'mjpeg',  # 图片编码           '-r', '24',  # 帧率           '-i', '-',  # 指定输入流为PIPE           '-vcodec', 'h264',  # 输出编码           '-r', '24',  # 帧率           '-map', '0',#            '-crf','20',     # 降低质量           '-b', '720k',        # 码率           '-f', 'hls',           '-codec:v', 'libx264',           '-vbsf', 'h264_mp4toannexb',           # 指定加密密匙文件           '-hls_key_info_file', r'F:\Workspace\Test\videokey.info',           '-hls_time', '20',           '-hls_list_size', '0',           '-hls_wrap', '0',#            '-hls_flags', 'single_file',  # 生成单个文件(有bug)           r'F:\Workspace\Test\videos\playlist.m3u8'], stdin=PIPE)print(p)t = 1 / 24for i, path in enumerate(Path('frames').rglob('*.jpg')):    #     print(i, path)    p.stdin.write(open(str(path), 'rb').read())p.stdin.close()p.wait()print('ok')
]]>
+ + + + + 笔记 + + + + + + + Python + + FFmpeg + + HLS + + + +
+ + + + + PyQt5编译QWebView与QWebEngineView共存 + + /webviewnew.html + + 在 PyQt5.5 过后移除了 QWebView 控件,改用 QWebEngineView ,但是这个刚开始用起来不是很方便,最近在整理一些例子的时候需要同时使用 QWebViewQWebEngineView ,故希望把 QWebView 重新加入到后面的 PyQt5 版本中,查看 PyQt5.10.1 的源码发现里面其实是有 QWebView 的,只是因为 Qt5.10.1 中没有编译好的 dll 等导致无法编译。

# 准备工作

  1. 安装 VS2015
  2. 安装 Qt5.10.1
  3. 前往 https://github.com/annulen/webkit/releases 下载对应的文件,比如:qtwebkit-5.212.0_alpha2-qt59-msvc2015-x86.zip
  4. 下载 PyQt5.10.1 源码
  5. 下载对应版本的 sip 源码

# 编译

  1. 设置环境变量 set PATH=D:\soft\Qt\Qt5.10.1\5.10.1\msvc2015\bin;%PATH%
  2. 首先进入 vs2015 命令行编译 sip 并安装, python configure.py && nmake && nmake install
  3. 进入 PyQt5.10.1 源码编译安装即可
  4. 如果要减少 PyQt5.10.1 的编译可以试试以下代码
D:\soft\Python35\python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc
]]>
+ + + + + 教程 + + + + + + + PyQt + + QWebView + + 浏览器 + + + +
+ + + + + PyQt5之图片轮播 + + /pageswitching.html + + 之前看到了 QStackedWidget 做切换动画,让界面不那么生硬,于是参考了 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt 做了一个 QStackedWidget 的切换动画,然后利用 QStackedWidget 结合多个 QLabel 显示图片来做一个轮播效果。

其实在写之前也在网上找了很多例子,参看过后发现大多例子都是利用到了 paintEvent 去绘制,这样其实还是比较麻烦,个人觉得更好的方式是使用 QPropertyAnimation 属性动画修改控件中 QLabel 图片控件的 pos 位置属性就可以达到移动效果了。

  1. 比较核心的算法就是要计算当前页面和下一个页面的位置偏移量,比如:
# 计算偏移量offsetX = self.frameRect().width()offsetY = self.frameRect().height()w_next.setGeometry(0, 0, offsetX, offsetY)if direction == self.BOTTOM2TOP:    offsetX = 0    offsetY = -offsetYelif direction == self.TOP2BOTTOM:    offsetX = 0elif direction == self.RIGHT2LEFT:    offsetX = -offsetX    offsetY = 0elif direction == self.LEFT2RIGHT:    offsetY = 0# 重新定位显示区域外部/旁边的下一个窗口小部件pnext = w_next.pos()pnow = w_now.pos()self._pnow = pnow# 移动到指定位置并显示w_next.move(pnext.x() - offsetX, pnext.y() - offsetY)w_next.show()w_next.raise_()
  1. 其次是对这两个页面增加关联 pos 属性的 QPropertyAnimation 动画,然后加入到并行动画组 QParallelAnimationGroup 中再启动即可。

  2. QStackedWidgetsetCurrentIndexsetCurrentWidget 这两个函数进行了覆盖重写达到及时手动调用这两个函数也会产生动画效果的目的。

# 代码

https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/PageSwitching.py

# 效果图

PageSwitching

]]>
+ + + + + 例子 + + + + + + + PyQt + + 动画 + + 轮播 + + + +
+ + + + + python 在类里使用进程池 + + /processinclass_625781186.html + +
  • 首先, 进程池的作用就是减少进程的创建和释放 开销的, 所以在类中作为局部变量是不合适的;
  • 其次, 进程池必须在 if __name__ == "__main__" 里 ,否则会报 frozen_ 什么什么的错误;(这一点可能解释有误);
    1. 然后, 线程池的 apply_async 中如果传入 self.xxx 方法,会报 multiprocessing.Pool pickling error 什么的错误, 具体解释见 https://blog.csdn.net/dutsoft/article/details/70336462, 里面有解决方法,但是我没有成功(最开始测试没有现在理解的透彻, 不过应该是可以的); 由于第 1 点 不合理, 所以有什么办法在类 函数中获取 进程池对象 po 的地址:

    processinclass1

    我的解决思路和方法是:

    1. 通过 globals () 取得全局变量 , 测试证明 :不同文件的 globals() 是不同的: 如 Tab2.py globals() main_extra_func_file.py 中的 globals() 是不同的 , 所以 这样在 Tab2.py 中取不到 po 对象;
    2. 通过 __main__.po 来获取 (为什么会想到这个呢, 因为有时候导包 import .xxx 和 import xxx 会报 __main__ 没有什么属性什么的):
    def getPoolObject():# po 的名字在main函数中定义# __main__ 模块在sys.modules 的键是"__mp_main__"    return sys.modules["__mp_main__"].po

    ps : (图没截好 , rglob_worker 是外部函数 , 非类内函数 ,po = getPoolBojcet () 这一行是类内函数 ,红色箭头 2. 在的那条白色分割线 是 2 个函数。)

    processinclass2

    len(po._cache) == 1 : po._cache 是当前有任务的进程数, ==1 表示所有任务结束;利用回调 , 可以更轻松地进行进程通信。

    ]]>
    + + + + + 笔记 + + + + + + + 进程 + + + +
    + + + + + PyQt5之QSlider滑动条点击定位 + + /jumpslider.html + + QSlider 在通常情况下支持鼠标点击可以任意拖动,或者鼠标点击则往鼠标点击的方向移动一小格,这种移动一小格通常情况下用起来很不方便,比如我要做一个播放器的播放进度条,肯定是点击某个位置就直接跳到该位置,为此需要对 QSlider 的鼠标事件 mousePressEvent 进行重写。

    # 实现方法

    一般的想法就是重写 mousePressEvent 后,得到鼠标点击的 x 和 y 点然后进行比例换算,再通过 setValue 来设置值,其实 QSliderstyle 里面是有一个 sliderValueFromPosition 方法来计算值的。直接调用这个方法即可。

    1. 首先通过 QSlider.style().subControlRect 方法计算得到滑块的区域,当鼠标点击区域在此次时则交给系统自己处理(比如按住不放拖动)
    2. 通过 orientation 判断滑动条的方向(横竖)
    3. 通过 invertedAppearance 判断滑动条是否反向(左右、上下)
    4. 通过 QSlider.style().sliderValueFromPosition(最小值, 最大值, x或者y坐标, 宽度或者高度) 来计算得到值
    5. 最后通过 setValue 来设置值

    # 代码

    https://github.com/PyQt5/PyQt/blob/master/QSlider/ClickJumpSlider.py

    #!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2018年11月5日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: JumpSlider@description: """from PyQt5.QtCore import Qtfrom PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle, QWidget,\    QFormLayout, QLabel__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = "Copyright (c) 2018 Irony"__Version__ = "Version 1.0"class JumpSlider(QSlider):    def mousePressEvent(self, event):        # 获取上面的拉动块位置        option = QStyleOptionSlider()        self.initStyleOption(option)        rect = self.style().subControlRect(            QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)        if rect.contains(event.pos()):            # 如果鼠标点击的位置在滑块上则交给Qt自行处理            super(JumpSlider, self).mousePressEvent(event)            return        if self.orientation() == Qt.Horizontal:            # 横向,要考虑invertedAppearance是否反向显示的问题            self.setValue(self.style().sliderValueFromPosition(                self.minimum(), self.maximum(),                event.x() if not self.invertedAppearance() else (self.width(                ) - event.x()), self.width()))        else:            # 纵向            self.setValue(self.style().sliderValueFromPosition(                self.minimum(), self.maximum(),                (self.height() - event.y()) if not self.invertedAppearance(                ) else event.y(), self.height()))class TestWindow(QWidget):    def __init__(self, *args, **kwargs):        super(TestWindow, self).__init__(*args, **kwargs)        layout = QFormLayout(self)        self.label1 = QLabel('0', self)        layout.addRow(self.label1, JumpSlider(            Qt.Horizontal, valueChanged=lambda v: self.label1.setText(str(v))))        # 横向-反向显示        self.label2 = QLabel('0', self)        layout.addRow(self.label2, JumpSlider(            Qt.Horizontal, invertedAppearance=True,            valueChanged=lambda v: self.label2.setText(str(v))))        self.label3 = QLabel('0', self)        layout.addRow(self.label3, JumpSlider(            Qt.Vertical, minimumHeight=200, valueChanged=lambda v: self.label3.setText(str(v))))        # 纵向反向显示        self.label4 = QLabel('0', self)        layout.addRow(self.label4, JumpSlider(            Qt.Vertical, invertedAppearance=True,            minimumHeight=200, valueChanged=lambda v: self.label4.setText(str(v))))if __name__ == '__main__':    import sys    import cgitb    sys.excepthook = cgitb.enable(1, None, 5, '')    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = TestWindow()    w.show()    sys.exit(app.exec_())

    # 效果图

    ClickJumpSlider

    ]]>
    + + + + + 例子 + + + + + + + PyQt + + 滑动条 + + + +
    + + + + + PyQt5仿网页鼠标移动点阵特效 + + /rlatticeeffect.html + + Orz,前段时间有个 zz 需求,就是要做一个类似网页上很多个多点连线、鼠标移动跟随的那种炫酷特效,然后花了点时间在网上找了 js 做的,刚开始打算是嵌入 QWebView 来显示网页,后来研究了下 js 的算法代码,遂改用 QWidgetpaintEvent 直接绘制。

    # 大概思路

    1. 先根据窗口大小随机创建一些点
    2. 遍历这些点并找到与之相关联的点
    3. 在动画过程中绘制圆点和画两点之间的连线
    4. 属性动画 QPropertyAnimation 改变颜色的透明度

    # 题外

    1. 这里没有仔细去研究 js 里的算法优化,在浏览器里嗖嗖的就生成了,在 py 里好慢…
    2. 尽量在 py 里优化了循环操作,也简单的做了个 cython 加速也才提高了 1s ? 1 倍?..
    3. 不要只是为了好看用这玩意儿,和网页的效果一样,占 CPU !!! 没有任何意义
    4. 如果有更好的优化算法请告知,3Q
    5. pyd 是 python3.4 生成的,删掉 pyd 也能运行

    # 代码

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/RlatticeEffect.py

    #!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2018年11月22日@author: Irony@site: https://pyqt5.com, https://github.com/892768447@email: 892768447@qq.com@file: @description: """from random import randomfrom time import timefrom PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\    Qt, QRectF, pyqtSignalfrom PyQt5.QtGui import QColor, QPainterPath, QPainterfrom PyQt5.QtWidgets import QWidget__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = 'Copyright (c) 2018 Irony'__Version__ = 1.0try:    import pointtool  # @UnusedImport @UnresolvedImport    getDistance = pointtool.getDistance    findClose = pointtool.findCloseexcept:    import math    def getDistance(p1, p2):        return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2)    def findClose(points):        plen = len(points)        for i in range(plen):            closest = [None, None, None, None, None]            p1 = points[i]            for j in range(plen):                p2 = points[j]                dte1 = getDistance(p1, p2)                if p1 != p2:                    placed = False                    for k in range(5):                        if not placed:                            if not closest[k]:                                closest[k] = p2                                placed = True                    for k in range(5):                        if not placed:                            if dte1 < getDistance(p1, closest[k]):                                closest[k] = p2                                placed = True            p1.closest = closestclass Target:    def __init__(self, x, y):        self.x = x        self.y = yclass Point(QObject):    valueChanged = pyqtSignal()    def __init__(self, x, ox, y, oy, *args, **kwargs):        super(Point, self).__init__(*args, **kwargs)        self.__x = x        self._x = x        self.originX = ox        self._y = y        self.__y = y        self.originY = oy        # 5个闭合点        self.closest = [0, 0, 0, 0, 0]        # 圆半径        self.radius = 2 + random() * 2        # 连线颜色        self.lineColor = QColor(156, 217, 249)        # 圆颜色        self.circleColor = QColor(156, 217, 249)    def initAnimation(self):        # 属性动画        if not hasattr(self, 'xanimation'):            self.xanimation = QPropertyAnimation(                self, b'x', self, valueChanged=self.valueChanged.emit,                easingCurve=QEasingCurve.InOutSine)            self.yanimation = QPropertyAnimation(                self, b'y', self, valueChanged=self.valueChanged.emit,                easingCurve=QEasingCurve.InOutSine,                finished=self.updateAnimation)            self.updateAnimation()    def updateAnimation(self):        self.xanimation.stop()        self.yanimation.stop()        duration = (1 + random()) * 1000        self.xanimation.setDuration(duration)        self.yanimation.setDuration(duration)        self.xanimation.setStartValue(self.__x)        self.xanimation.setEndValue(self.originX - 50 + random() * 100)        self.yanimation.setStartValue(self.__y)        self.yanimation.setEndValue(self.originY - 50 + random() * 100)        self.xanimation.start()        self.yanimation.start()    @pyqtProperty(float)    def x(self):        return self._x    @x.setter    def x(self, x):        self._x = x    @pyqtProperty(float)    def y(self):        return self._y    @y.setter    def y(self, y):        self._y = yclass Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        self.setMouseTracking(True)        self.resize(800, 600)        self.points = []        self.target = Target(self.width() / 2, self.height() / 2)        self.initPoints()    def paintEvent(self, event):        super(Window, self).paintEvent(event)        painter = QPainter()        painter.begin(self)        painter.setRenderHint(QPainter.Antialiasing)        painter.fillRect(self.rect(), Qt.black)        self.animate(painter)        painter.end()    def mouseMoveEvent(self, event):        super(Window, self).mouseMoveEvent(event)        # 鼠标移动时更新xy坐标        self.target.x = event.x()        self.target.y = event.y()        self.update()    def initPoints(self):        t = time()        self.points.clear()        # 创建点        stepX = self.width() / 20        stepY = self.height() / 20        for x in range(0, self.width(), int(stepX)):            for y in range(0, self.height(), int(stepY)):                ox = x + random() * stepX                oy = y + random() * stepY                point = Point(ox, ox, oy, oy)                point.valueChanged.connect(self.update)                self.points.append(point)        print(time() - t)        t = time()        # 每个点寻找5个闭合点        findClose(self.points)        print(time() - t)    def animate(self, painter):        for p in self.points:            # 检测点的范围            value = abs(getDistance(self.target, p))            if value < 4000:                # 其实就是修改颜色透明度                p.lineColor.setAlphaF(0.3)                p.circleColor.setAlphaF(0.6)            elif value < 20000:                p.lineColor.setAlphaF(0.1)                p.circleColor.setAlphaF(0.3)            elif value < 40000:                p.lineColor.setAlphaF(0.02)                p.circleColor.setAlphaF(0.1)            else:                p.lineColor.setAlphaF(0)                p.circleColor.setAlphaF(0)            # 画线条            if p.lineColor.alpha():                for pc in p.closest:                    if not pc:                        continue                    path = QPainterPath()                    path.moveTo(p.x, p.y)                    path.lineTo(pc.x, pc.y)                    painter.save()                    painter.setPen(p.lineColor)                    painter.drawPath(path)                    painter.restore()            # 画圆            painter.save()            painter.setPen(Qt.NoPen)            painter.setBrush(p.circleColor)            painter.drawRoundedRect(QRectF(                p.x - p.radius, p.y - p.radius, 2 * p.radius, 2 * p.radius), p.radius, p.radius)            painter.restore()            # 开启动画            p.initAnimation()if __name__ == '__main__':    import sys    import cgitb    sys.excepthook = cgitb.enable(1, None, 5, '')    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

    # 效果图

    RlatticeEffect

    ]]>
    + + + + + 例子 + + + + + + + PyQt + + 动画 + + 特效 + + + +
    + + + + + QDataWidgetMapper 数据库绑定 QLineEdit控件 + + /datawidgetmapper_625781186.html + + qt 为操作数据库提供了一个 model+view 的模式,这样简单的出入库逻辑就不需要自己编写。

    QDataWidgetMapper 可以 将数据库的数据 映射到其他控件 。

    注意:表格里的数据修改 还没有提交到数据库,需要点击提交按钮才生效。

    https://github.com/PyQt5/PyQt/tree/master/Test/partner_625781186/16_sqlModel/01_mapper

    # 代码

    #-*- coding: utf-8 -*-from PyQt5 import  QtWidgets, QtGui, QtCorefrom PyQt5.QtCore import *from PyQt5.QtGui import *from PyQt5.QtWidgets import *from PyQt5.QtSql import *import syssys.path.append('./ui')from Ui_MainWindow import Ui_MainWindowclass MainWindow(QMainWindow, Ui_MainWindow):    def __init__(self, parent=None, *args):        super(MainWindow, self).__init__(parent,  *args)        self.setupUi(self)        self.resize(800,600)                #===============================   db   ======================================#        # self.db = QSqlDatabase.addDatabase('QMYSQL')        # self.db.setHostName("127.0.0.1")  # set address        # self.db.setUserName("root")  # set user name        # self.db.setPassword('123456')  # set user pwd           # self.db.setDatabaseName("database")                self.db = QSqlDatabase.addDatabase('QSQLITE')        self.db.setDatabaseName('./db/database.db')        #================================= codemodel =====================================#        # 实例化model        self.codeModel = QSqlRelationalTableModel()        # model设置表        self.initializeModel(self.codeModel, 'Mongo')        # 设置编辑策略        # self.codeModel.setEditStrategy(QSqlTableModel.OnFieldChange)        # !!! 这里要注意 , 只能用这个策略 , 才可以实现自动提交        self.codeModel.setEditStrategy(QSqlTableModel.OnManualSubmit)        self.codeView = self.createView("code_View", self.codeModel)        self.verticalLayout.addWidget(self.codeView)          #================================ initData ==================================#        # 数据映射        self.mapper = QDataWidgetMapper()        # 提交策略        self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)        # 映射的模型源        self.mapper.setModel(self.codeModel)        self.mapper.addMapping(self.l1,0)        self.mapper.addMapping(self.l2,1)        self.mapper.addMapping(self.l3,2)        self.mapper.addMapping(self.l4,3)        self.mapper.addMapping(self.l5,4)        self.mapper.toFirst()                #================================ pushButton ==================================#        self.sub_btn.clicked.connect(self.mapper.submit)        self.sub_btn.clicked.connect(self.codeModel.submitAll)        self.pre_btn.clicked.connect(self.mapper.toPrevious)        self.next_btn.clicked.connect(self.mapper.toNext)            def initializeModel(self, model, tablename):        '''重关联。'''        model.setTable(tablename)#        model.setEditStrategy(QSqlTableModel.OnRowChange)        model.select()            def createView(self, title, model):        '''创建TableView视图'''        view =  QTableView()        view.setModel(model)        view.setWindowTitle(title)        #列宽设置        view.horizontalHeader().setSectionResizeMode(3)        #行高设置        view.verticalHeader().setSectionResizeMode(1)        #充满列宽        view.horizontalHeader().setStretchLastSection(True) #        view.verticalHeader().setVisible(False)#隐藏行标题        #标题左对齐        view.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)        #标题左对齐        view.verticalHeader().setDefaultAlignment(Qt.AlignLeft)                return view      if __name__ == "__main__":    import sys        app = QApplication(sys.argv)    app.setStyle(QStyleFactory.create("Fusion"))    ui = MainWindow()    ui.show()    sys.exit(app.exec_())

    # 效果图

    datawidgetmapper

    ]]>
    + + + + + 例子 + + + + + + + PyQt + + Model + + + +
    + + + + + PyQt5显示.9格式的PNG图片 + + /qtninepatch.html + + 做过安卓开发的和使用过 QQ 的都知道 .9.png 这种图片格式,效果就如 QQ 的聊天气泡一样可以拉伸,这种格式的图片允许开发人员定义可扩展区域,当需要延伸图片以填充比图片本身更大区域时,可扩展区的内容被延展;允许开发人员定义内容显示区,用于显示文字或其他内容。目前在 Github 上有两个 C++ 版本的,在这里我把它们都用 Python 实现了一遍。另外一个我也为 PyQt 提供了编译好的 pyd 文件。

    # C++ 版本

    在 Github 开源库中搜索到两个 C++ 版本的

    1. 一个是 NinePatchQt
    2. 一个是 QtNinePatch

    # PyQt5 版本

    这里也分为两个版本,都是基于上面的 C++ 源码翻译改写过来的,具体的例子见项目里面的测试代码吧。

    1. QtNinePatch 是参考第一个源码编写,用法是在 paintEvent 中调用
    2. QtNinePatch2 是参考第二个源码编写,用法是 pixmap = QtNinePatch.createPixmapFromNinePatchImage(self.image, self.width(), self.height()) 直接得到一个处理好的 QPixmap 对象来使用

    # 说明

    1. 建议优先使用 pyd 版本的(后续提供 Python3.4 3.5 3.6 3.7 编译好的 32 为库文件),也可以自行编译,编译步骤见下文。
    2. 其次可以使用纯 python 版本 2 的(个人觉得方便调用)
    3. 最后再考虑纯 python 版本 1 的吧
    4. 以上为个人意见,两个 C++ 版本的写法不一样,但是核心算法应该是类似的。

    # 自行编译

    1. 首先要安装好 Qt、PyQt5、编译安装对应的 sip、对应的 VC++ 编译工具
    2. 用 Qt Creator 打开 pro 文件进行编译
    3. 进入源码中的 sip 文件夹修改 configure.py 文件
    # 这里是你的VC版本和对应的Qt目录中的文件夹config.platform = "win32-msvc2010"qt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010'
    1. 最后执行 python configure.py 来编译

    # 下载

    https://github.com/PyQt5/PyQt/tree/master/QLabel

    # 效果图

    NinePatchImage

    ]]>
    + + + + + 例子 + + + + + + + PyQt + + 图片 + + 气泡 + + .9png + + + +
    + + + + + PyQt5菜单之多选功能 + + /mselectmenu.html + + 有时候会遇到这种需求:在界面某个位置弹出一个菜单,其中里面的菜单项可以多选(类似配置选项),此时用 QMenu 会遇到点击一个菜单项就会自动关闭,当然可以通过其他方式实现该功能,不过这里就采用 QMenu 通过特殊的方式来实现该需求。

    # 需求

    要实现的效果:

    1. 菜单 1
    2. 菜单 2
    3. 菜单 3
    4. 菜单 4

    点击菜单 1、2、3 可以多选不关闭菜单

    点击菜单 4 可以勾选,并且关闭菜单

    # 原理

    1. 设置菜单项可勾选:通过 QAction.setCheckable(True) 方法实现
    2. 设置菜单不可关闭:通过覆盖 QMenu 的鼠标释放 mouseReleaseEvent 方法(可直接替换或者通过 installEventFilter 安装事件过滤器实现)
    3. 在菜单的鼠标释放事件中,当点击菜单项后是通过点击点坐标来查找是否有 QAction ,然后触发对应的 QAction
    4. 故在没有 QAction 的地方则直接交还给 QMenu 自行处理逻辑,在有 QAction 的地方可以根据自己的需求进行处理(如上所提)

    # 代码

    #!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2018年10月24日@author: Irony@site: https://github.com/892768447@email: 892768447@qq.com@file: 菜单多选不关闭@description: """from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QMenu,\    QAction__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = "Copyright (c) 2018 Irony"__Version__ = "Version 1.0"class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        self.labelInfo = QLabel(self)        self.button = QPushButton('带按钮的菜单', self)        layout.addWidget(self.labelInfo)        layout.addWidget(self.button)        # 添加菜单        self._initMenu()    def _initMenu(self):        # 创建菜单        self._menu = QMenu(self.button)        # 替换menu的鼠标释放事件达到选择性不关闭菜单        self._menu.mouseReleaseEvent = self._menu_mouseReleaseEvent        self._menu.addAction('菜单1', self._checkAction)        self._menu.addAction('菜单2', self._checkAction)        self._menu.addAction(            QAction('菜单3', self._menu, triggered=self._checkAction))        action = QAction('菜单4', self._menu, triggered=self._checkAction)        # 添加自定义的属性,判断该属性可以关闭菜单        action.setProperty('canHide', True)        self._menu.addAction(action)        for action in self._menu.actions():            # 循环设置可勾选            action.setCheckable(True)        self.button.setMenu(self._menu)    def _menu_mouseReleaseEvent(self, event):        action = self._menu.actionAt(event.pos())        if not action:            # 没有找到action就交给QMenu自己处理            return QMenu.mouseReleaseEvent(self._menu, event)        if action.property('canHide'):  # 如果有该属性则给菜单自己处理            return QMenu.mouseReleaseEvent(self._menu, event)        # 找到了QAction则只触发Action        action.activate(action.Trigger)    def _checkAction(self):        # 三个action都响应该函数        self.labelInfo.setText('\n'.join(['{}\t选中:{}'.format(            action.text(), action.isChecked()) for action in self._menu.actions()]))if __name__ == '__main__':    import sys    import cgitb    sys.excepthook = cgitb.enable(1, None, 5, 'text')    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.resize(400, 400)    w.show()    sys.exit(app.exec_())

    # 效果图

    MultiSelect

    ]]>
    + + + + + 例子 + + + + + + + PyQt + + 菜单 + + + +
    + + + + + 多线程之守护线程和阻塞线程 + + /daemonthread.html + + 如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的 daemon 属性。

    即在线程开始(thread.start ())之前,调用 setDeamon()函数,设定线程的 daemon 标志。

    (thread.setDaemon (True))就表示这个线程 “不重要”。

    如果你想等待子线程完成再退出,那就什么都不用做,或者显示地调用 thread.setDaemon (False),设置 daemon 的值为 false。新的子线程会继承父线程的 daemon 标志。

    整个 Python 会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

    setDaemon () 函数要放在 start 之前设置才行。

    import threadingimport timedef func():    print("子线程开启:", time.localtime())    time.sleep(2)    print("子线程结束:", time.localtime())print("主线程开启:", time.localtime())t = threading.Thread(target=func, args=())# t.setDaemon(True)t.start()print("主线程关闭:", time.localtime())

    在 Python 的多线程编程中,在实例代码中经常有 thread1.join () 这样的代码。那么今天咱们用实际代码来解释一下 join 函数的作用。

    join 的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的 join 函数。

    先看看这个:

    1. 阻塞主进程,专注于执行多线程中的程序。
    2. 多线程多 join 的情况下,依次执行各线程的 join 方法,前头一个结束了才能执行后面一个。
    3. 无参数,则等待到该线程结束,才开始执行下一个线程的 join。
    4. 参数 timeout 为线程的阻塞时间,如 timeout=2 就是罩着这个线程 2s 以后,就不管他了,继续执行下面的代码。
    5. 下面的例子是一次阻塞子线程,每个子线程都会等上个子线程 join 结束才会执行,如果注释掉 t.join 则会同时执行 5 个子线程,多线程在做网络访问的时候可以减少等待时间,那么在一个工作流程中可以将访问网络接口的情况做成多线程。
    import threading, timedef func():    print("hello world!")    time.sleep(1)print("hello main start")for i in range(5):    t = threading.Thread(target=func, args=())    print(t.getName())    t.start()    t.join()
    ]]>
    + + + + + 笔记 + + + + + + + Python + + 线程 + + + +
    + + + + + PyQt5结合Asyncio异步 + + /pyqt5asyncio.html + + 今天尝试了下 quamash 框架,该框架是一个 PyQt 的异步事件循环封装库,使用 Python3+ 的 asyncio 这个异步库。在看了该项目的内容后发现只有一个简单的进度条例子,故尝试用其来下载网络图片并显示。

    # 安装依赖

    1. pip install quamash
    2. pip install aiohttp
    3. Python3.5+ 和 PyQt5

    这里使用 aiohttp 是因为它基于 asyncio 封装的网络操作库,常见的 getpost 等方法,不过它只支持 Python3.5 及以上的版本,主要是它使用了 async def 这样的语法。

    # 说明

    1. 在创建 QApplication 后随即设置替换事件循环 loop
    app = QApplication(sys.argv)loop = QEventLoop(app)asyncio.set_event_loop(loop)w = Window()
    1. 通过 asyncio.ensure_future(func(), loop=loop) 来执行某个异步函数

    # 流程

    WindowinitSession(初始化 session)
    下载按钮doDownload(执行_doDownload 方法)
    session.get(下载 json 数据进行解析)
    添加到界面_doDownloadImage(对单张图片进行下载)

    # 源码

    #!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2018年10月24日@author: Irony@site: https://github.com/892768447@email: 892768447@qq.com@file: AsyncioUiClient@description: """import asynciofrom PyQt5.QtCore import Qtfrom PyQt5.QtGui import QPixmap, QMoviefrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton,\    QApplication, QListWidget, QListWidgetItem, QLabel, QMessageBoximport aiohttpfrom quamash import QEventLoop__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = "Copyright (c) 2018 Irony"__Version__ = "Version 1.0"Url = 'https://www.doutula.com/api/search?keyword=%E6%9C%80%E6%96%B0%E8%A1%A8%E6%83%85&mime=0&page={}'Headers = {    ':authority': 'www.doutula.com',    ':method': 'GET',    ':scheme': 'https',    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',    'accept-language': 'zh-CN,zh;q=0.9',    'cache-control': 'max-age=0',    'dnt': '1',    'upgrade-insecure-requests': '1',    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6756.400 QQBrowser/10.2.2498.400'}class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        self.listWidget = QListWidget(self)        self.listWidget.setSpacing(2)  # item直接的间隔        # 隐藏横向滚动条        self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)        # 让list 从左到右排列        self.listWidget.setFlow(self.listWidget.LeftToRight)        # 自动换行        self.listWidget.setWrapping(True)        self.listWidget.setResizeMode(self.listWidget.Adjust)        self.buttonMsg = QPushButton('弹出提示框', self, clicked=self.showMessage)        self.buttonDown = QPushButton('下载图片', self, clicked=self.doDownload)        layout.addWidget(self.listWidget)        layout.addWidget(self.buttonMsg)        layout.addWidget(self.buttonDown)        self.currentPage = 0        self.initSession()  # 其实没必要,session主要用在需要登录的网站。缓存cookie用    def initSession(self):        async def _initSession():            # 初始化session            self.session = aiohttp.ClientSession(loop=loop)            print(self.session)        asyncio.ensure_future(_initSession(), loop=loop)    async def _doDownloadImage(self, url):        # 下载图片并添加到界面        async with self.session.get(url) as resp:            data = await resp.read()            if not data:                print('下载失败: ', url)                return            path = os.path.join('tmp', os.path.basename(url))            with open(path, 'wb') as fp:                fp.write(data)            item = QListWidgetItem(url, self.listWidget)            image = QPixmap(path)            item.setSizeHint(image.size())            label = QLabel(self.listWidget)            label.setPixmap(image)            if path.endswith('.gif'):  # 可能是动态图                label.setMovie(QMovie(path))            self.listWidget.setItemWidget(item, label)            self.listWidget.scrollToBottom()    async def _doDownload(self):        # 下载工作        if self.currentPage == -1:            QMessageBox.information(self, '提示', '已经没有更多了')            return        self.currentPage += 1        url = Url.format(self.currentPage)        print('get url: ', url)        async with self.session.get(url, headers=Headers) as resp:            data = await resp.json()            if not data:                return            data = data.get('data', None)            if not data:                self.currentPage = -1                print('已经是最后一页了')                return            # 解析json并生成item添加到界面中            for entity in data.get('list', []):                url = entity.get('image_url', None)                if not url:                    continue                await self._doDownloadImage(url)  # 下载图片    def doDownload(self):        # 响应按钮点击调用        asyncio.ensure_future(self._doDownload(), loop=loop)    def showMessage(self):        # 显示对话框        app.aboutQt()    def closeEvent(self, event):        if not self.session.closed:            asyncio.ensure_future(self.session.close(), loop=loop)        super(Window, self).closeEvent(event)if __name__ == '__main__':    import sys    import cgitb    import os    os.makedirs('tmp', exist_ok=True)    sys.excepthook = cgitb.enable(1, None, 5, 'text')    app = QApplication(sys.argv)    loop = QEventLoop(app)    asyncio.set_event_loop(loop)    w = Window()    w.show()    with loop:        loop.run_forever()

    # 效果图

    pyqt5asyncio

    ]]>
    + + + + + 例子 + + + + + + + PyQt + + Asyncio + + 异步 + + + +
    + + + + + PyQt5仿网页图片鼠标移动特效 + + /likehtmleffect.html + + em,就是类似于那种游戏官网首页的图片,鼠标放上去后来回移动,图片的前景和背景错位移动。

    # 原理分析

    1. 2 张一样大小的透明图片,1 张作为背景,一张作为前景(比如说人物)。
    2. 当鼠标往左移动时,前景人物跟着往左移动,背景往右移动
    3. 计算好偏移量(见代码中)

    https://github.com/PyQt5/PyQt/blob/master/QLabel/ImageSlipped.py

    # 关键代码

    #!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2018年10月18日@author: Irony@site: https://pyqt5.com https://github.com/892768447@email: 892768447@qq.com@file: ImageSlipped@description: """from PyQt5.QtGui import QPixmap, QPainterfrom PyQt5.QtWidgets import QWidget__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = "Copyright (c) 2018 Irony"__Version__ = "Version 1.0"class SlippedImgWidget(QWidget):    def __init__(self, bg, fg, *args, **kwargs):        super(SlippedImgWidget, self).__init__(*args, **kwargs)        # 开启鼠标跟踪        self.setMouseTracking(True)        # 背景        self.bgPixmap = QPixmap(bg)        # 前景        self.pePixmap = QPixmap(fg)        # 最小尺寸(背景右边和下方隐藏10个像素)        size = self.bgPixmap.size()        self.setMinimumSize(size.width() - 10, size.height() - 10)        self.setMaximumSize(size.width() - 10, size.height() - 10)        # 分成10份用于鼠标移动判断        self.stepX = size.width() / 10        self.stepY = size.height() / 10        # 偏移量        self._offsets = [-4, -4, -4, -4]  # 背景(-4,-4),前景(-4,-4)    def mouseMoveEvent(self, event):        super(SlippedImgWidget, self).mouseMoveEvent(event)        pos = event.pos()        # 偏移量        offsetX = 5 - int(pos.x() / self.stepX)        offsetY = 5 - int(pos.y() / self.stepY)        self._offsets[0] = offsetX        self._offsets[1] = offsetY        self._offsets[2] = offsetX        self._offsets[3] = offsetY        # 刷新        self.update()    def paintEvent(self, event):        super(SlippedImgWidget, self).paintEvent(event)        # 绘制图形        painter = QPainter(self)        painter.setRenderHint(QPainter.Antialiasing)        # 左上角偏移5个像素画背景图片        painter.drawPixmap(            -5 + self._offsets[0],            -5 + self._offsets[1], self.bgPixmap)        # 右下角偏移5个像素画前景图片        painter.drawPixmap(            self.width() - self.pePixmap.width() + 5 - self._offsets[2],            self.height() - self.pePixmap.height() + 5 - self._offsets[3],            self.pePixmap        )if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = SlippedImgWidget('images/bg.png', 'images/fg.png')    w.show()    sys.exit(app.exec_())

    # 效果图

    ImageSlipped

    ]]>
    + + + + + 例子 + + + + + + + PyQt + + 特效 + + + +
    + + + + + PyQt5窗口跟随其它窗口 + + /followwindow.html + + 要实现 PyQt 窗口跟随其它外部的窗口,能想到两点办法,一个是 hook 系统事件得到目标窗口的位置和大小以及是否关闭等,二是通过循环检测窗口的位置来实现。

    # 基于 Windows 定时检测目标窗口

    1. 利用 win32gui 模块获取目标窗口的句柄
    2. 通过句柄获取目标窗口的大小位置,并设置自己的位置
    3. 主要是检测时间,在 10 毫秒以下很流畅
    4. 窗口关闭是根据目标句柄无效来判断

    https://github.com/PyQt5/PyQt/blob/master/Demo/FollowWindow.py

    # 代码

    #!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2018年10月22日@author: Irony@site: https://github.com/892768447@email: 892768447@qq.com@file: FollowWindow@description: """import osfrom PyQt5.QtCore import QTimerfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButtonimport win32gui__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = "Copyright (c) 2018 Irony"__Version__ = "Version 1.0"class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QVBoxLayout(self)        layout.addWidget(QPushButton('test', self))        self.tmpHwnd = None        # 启动定时器检测记事本的位置大小和是否关闭        self.checkTimer = QTimer(self, timeout=self.checkWindow)        self.checkTimer.start(10)  # 10毫秒比较流畅    def checkWindow(self):        # 查找        hwnd = win32gui.FindWindow('Notepad', None)        if self.tmpHwnd and not hwnd:            # 表示记事本关闭了            self.checkTimer.stop()            self.close()  # 关闭自己            return        if not hwnd:            return        self.tmpHwnd = hwnd        # 获取位置        rect = win32gui.GetWindowRect(hwnd)        print(rect)        self.move(rect[2], rect[1])if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    # 先检测是否已有记事本打开    hwnd = win32gui.FindWindow('Notepad', None)    print('hwnd', hwnd)    if not hwnd:        # 启动记事本        os.startfile('notepad')    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

    # 效果图

    FollowWindow

    ]]>
    + + + + + 例子 + + + + + + + PyQt + + 窗口 + + + +
    + + + + + PyQt5动画边框阴影 + + /animateshadow.html + + 为子控件增加动画阴影效果,结合 QGraphicsDropShadowEffectQPropertyAnimation 动态改变阴影半径达到效果,在旧版本的 Qt 中 QGraphicsDropShadowEffect 可能会有点问题(父控件会影响子控件)

    # 原理

    原理是利用 QGraphicsDropShadowEffect 添加边框阴影,然后使用动画不停改变阴影的模糊半径来达到效果,如图:

    ShadowEffect

    # 简单说明

    1. 继承 QGraphicsDropShadowEffect 增加动态属性 radius
    2. 通过 setGraphicsEffect 方法设置控件的边框阴影
    3. 通过 QPropertyAnimation 属性动画不断改变 radius 的值并调用 setBlurRadius 更新半径值

    https://github.com/PyQt5/PyQt/blob/master/QGraphicsDropShadowEffect/ShadowEffect.py

    # 自定义类

    #!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2018年9月25日@author: Irony@site: https://pyqt5.com, https://github.com/892768447@email: 892768447@qq.com@file: AnimationShadowEffect@description: 边框动画阴影动画"""from PyQt5.QtCore import QPropertyAnimation, pyqtPropertyfrom PyQt5.QtWidgets import QGraphicsDropShadowEffect__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = 'Copyright (c) 2018 Irony'__Version__ = 1.0class AnimationShadowEffect(QGraphicsDropShadowEffect):    def __init__(self, color, *args, **kwargs):        super(AnimationShadowEffect, self).__init__(*args, **kwargs)        self.setColor(color)        self.setOffset(0, 0)        self.setBlurRadius(0)        self._radius = 0        self.animation = QPropertyAnimation(self)        self.animation.setTargetObject(self)        self.animation.setDuration(2000)  # 一次循环时间        self.animation.setLoopCount(-1)  # 永久循环        self.animation.setPropertyName(b'radius')        # 插入线行值        self.animation.setKeyValueAt(0, 1)        self.animation.setKeyValueAt(0.5, 30)        self.animation.setKeyValueAt(1, 1)    def start(self):        self.animation.start()    @pyqtProperty(int)    def radius(self):        return self._radius    @radius.setter    def radius(self, r):        self._radius = r        self.setBlurRadius(r)

    # 测试代码

    #!/usr/bin/env python# -*- coding: utf-8 -*-"""Created on 2018年9月25日@author: Irony@site: https://pyqt5.com, https://github.com/892768447@email: 892768447@qq.com@file: Test@description: """from PyQt5.QtCore import Qtfrom PyQt5.QtGui import QPixmapfrom PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEditfrom AnimationShadowEffect import AnimationShadowEffect  # @UnresolvedImport__Author__ = """By: IronyQQ: 892768447Email: 892768447@qq.com"""__Copyright__ = 'Copyright (c) 2018 Irony'__Version__ = 1.0class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QHBoxLayout(self)        # 绿色边框        labelGreen = QLabel(self, pixmap=QPixmap('1.jpg').scaled(100, 100))        layout.addWidget(labelGreen)        aniGreen = AnimationShadowEffect(Qt.darkGreen, labelGreen)        labelGreen.setGraphicsEffect(aniGreen)        aniGreen.start()        # 红色边框,圆形图片        labelRed = QLabel(self)        labelRed.setMinimumSize(100, 100)        labelRed.setMaximumSize(100, 100)        labelRed.setStyleSheet('border-image: url(1.jpg);border-radius: 50px;')        layout.addWidget(labelRed)        aniRed = AnimationShadowEffect(Qt.red, labelGreen)        labelRed.setGraphicsEffect(aniRed)        aniRed.start()        # 蓝色边框按钮        button = QPushButton('按钮', self)        aniButton = AnimationShadowEffect(Qt.blue, button)        layout.addWidget(button)        button.setGraphicsEffect(aniButton)        aniButton.start()        # 青色边框输入框        lineedit = QLineEdit(self)        aniEdit = AnimationShadowEffect(Qt.cyan, lineedit)        layout.addWidget(lineedit)        lineedit.setGraphicsEffect(aniEdit)        aniEdit.start()if __name__ == '__main__':    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())
    ]]>
    + + + + + 例子 + + + + + + + PyQt + + 动画 + + 阴影 + + + +
    + + + + + PyQt5圆形图片 + + /circleimage.html + + 实现圆形图片的方法有很多,比如用遮罩(mask), 裁切等等。这里比较几种实现方式,选出个人认为最优的方案。

    https://github.com/PyQt5/PyQt/blob/master/QLabel/CircleImage.py

    # 采用 mask 方式

    具体参考 【Qt】QLabel 实现的圆形图像 - 米罗西 - 博客园

    # 画圆形遮盖(适合纯色背景)

    原理是在原图片上画一个 4 角有颜色,中间圆形镂空的图片。

    circleimage1

    #!/usr/bin/env python# -*- coding: utf-8 -*-'''Created on 2017年8月25日@author: Irony."[讽刺]@site: https://pyqt5.com, https://github.com/892768447@email: 892768447@qq.com@description: '''from PyQt5.QtCore import Qtfrom PyQt5.QtGui import QPixmap, QPainter, QPainterPathfrom PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"__Version__ = "Version 1.0"class Label(QLabel):    def __init__(self, *args, antialiasing=True, **kwargs):        super(Label, self).__init__(*args, **kwargs)        self.Antialiasing = antialiasing        self.setMaximumSize(200, 200)        self.setMinimumSize(200, 200)        self.radius = 100        #加载图片并缩放        self.image = QPixmap("head.jpg").scaled(            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)        painter = QPainter(self.image)        if self.Antialiasing:            painter.setRenderHint(QPainter.Antialiasing, True)            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)        path = QPainterPath()        path.addRoundedRect(            0, 0, self.width(), self.height(), self.radius, self.radius)        path.addRect(0,0,self.width(),self.height())        painter.setPen(Qt.NoPen)        painter.setBrush(Qt.green)        painter.drawPath(path)        self.setPixmap(self.image)class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QHBoxLayout(self)        layout.addWidget(Label(self))        layout.addWidget(Label(self, antialiasing=False))        self.setStyleSheet("background: black;")if __name__ == "__main__":    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())

    # 使用 QPainter 的 setCompositionMode

    具体参考 Qt 圆形头像制作工具 抗锯齿 可缩放编辑

    //result_avatar_size 是我们最后生成的图片的长宽,可以是QSize(200, 200)的正圆destination_image = QImage(result_avatar_size, QImage::Format_ARGB32_Premultiplied);//在黑色的正方形中间画一个透明的圆,作为头像遮罩QPainter painter(&destination_image);painter.setRenderHint(QPainter::Antialiasing);//全涂黑painter.fillRect(destination_image.rect(), QBrush(Qt::black, Qt::SolidPattern));painter.setCompositionMode(QPainter::CompositionMode_SourceOut);painter.setPen(Qt::NoPen);painter.setBrush(QBrush(Qt::transparent, Qt::SolidPattern));//画透明区域painter.drawEllipse(destination_image.rect());

    # 使用 QPainter 的切割方法(推荐)

    利用 QPainter.setClipPath 方法切割一个圆形的 QPainterPath

    circleimage2

    #!/usr/bin/env python# -*- coding: utf-8 -*-'''Created on 2017年8月25日@author: Irony."[讽刺]@site: https://pyqt5.com, https://github.com/892768447@email: 892768447@qq.com@file: @description: '''from PyQt5.QtCore import Qtfrom PyQt5.QtGui import QPixmap, QPainter, QPainterPath, QPenfrom PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"__Version__ = "Version 1.0"class Label(QLabel):    def __init__(self, *args, antialiasing=True, **kwargs):        super(Label, self).__init__(*args, **kwargs)        self.Antialiasing = antialiasing        self.setMaximumSize(200, 200)        self.setMinimumSize(200, 200)        self.radius = 100        #####################核心实现#########################        self.target = QPixmap(self.size())  # 大小和控件一样        self.target.fill(Qt.transparent)  # 填充背景为透明        p = QPixmap("head.jpg").scaled(  # 加载图片并缩放和控件一样大            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)        painter = QPainter(self.target)        if self.Antialiasing:            # 抗锯齿            painter.setRenderHint(QPainter.Antialiasing, True)            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)#         painter.setPen(# 测试黑色圆圈#             QPen(Qt.black, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))        path = QPainterPath()        path.addRoundedRect(            0, 0, self.width(), self.height(), self.radius, self.radius)        #**** 切割为圆形 ****#        painter.setClipPath(path)#         painter.drawPath(path)  # 测试黑色圆圈        painter.drawPixmap(0, 0, p)        self.setPixmap(self.target)        #####################核心实现#########################class Window(QWidget):    def __init__(self, *args, **kwargs):        super(Window, self).__init__(*args, **kwargs)        layout = QHBoxLayout(self)        layout.addWidget(Label(self))        layout.addWidget(Label(self, antialiasing=False))        self.setStyleSheet("background: black;")if __name__ == "__main__":    import sys    from PyQt5.QtWidgets import QApplication    app = QApplication(sys.argv)    w = Window()    w.show()    sys.exit(app.exec_())
    ]]>
    + + + + + 例子 + + + + + + + PyQt + + 圆形 + + 图片 + + + +
    + + + + + 异常捕获之cgitb模块 + + /cgitb.html + + cgitb 模块为 Python 脚本提供了一个特殊的异常管理器。名字有点误导人,它最初设计是为了以 HTML 格式展示 cgi 脚本的大量异常信息。后来,他扩展为也可以展示纯文本信息。该模块激活后,如果发生了未捕获的异常,将会展示格式化的输出报告。该报告包括源代码每一层的回溯,以及当前执行程序的参数和局部变量。以及,你可以选择将这些信息存到一个文件里,而不是发送到浏览器。

    # 用途

    当编辑器中无法显示错误信息时,尤其是 PyQt ,可以尝试在 cmd 中运行代码,或者使用此模块来得到错误信息。

    # 介绍

    # cgitb.enable

    cgitb.enable(display=1, logdir=None, context=5, format="html")

    参数说明

    1. display 1,发送至浏览器;0, 不发送
    2. logdir 如果有的话,写到该目录下
    3. context 显示错误代码周围的代码行数
    4. format 是否显示为 HTML,除了’html’之外的所有值,都会显示为纯文本

    # cgitb.handle

    cgitb.handle(info=None)

    参数说明

    1. 如果你想用 cgitb 处理异常,你可以调用这个函数。
    2. info 应当是含有异常类型、异常值和 traceback 对象的三元组
    3. 如同 sys.exc_info () 返回的那样。如果不提供 info,则从 sys.exc_info 中获取。

    # 如何使用

    以下代码放在最开始执行

    import cgitbimport syssys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
    ]]>
    + + + + + 笔记 + + + + + + + Python + + 异常 + + + +
    + + + + +
    diff --git a/search/cb-search.json b/search/cb-search.json new file mode 100644 index 00000000..dec9e993 --- /dev/null +++ b/search/cb-search.json @@ -0,0 +1 @@ +{"code" : 0 ,"data" : [{ "title": "PyQt属性动画(QPropertyAnimation) - PyQt,动画","url":"https://pyqt5.com/QPropertyAnimation.html"},{ "title": "PyQt5动画边框阴影 - PyQt,动画,阴影","url":"https://pyqt5.com/animateshadow.html"},{ "title": "三种方式绑定信号槽 - PyQt,信号","url":"https://pyqt5.com/bindsignals.html"},{ "title": "Python调用Java对Excel截图 - Python,截图","url":"https://pyqt5.com/calljava.html"},{ "title": "异常捕获之cgitb模块 - Python,异常","url":"https://pyqt5.com/cgitb.html"},{ "title": "PyQt5圆形图片 - PyQt,圆形,图片","url":"https://pyqt5.com/circleimage.html"},{ "title": "多线程之守护线程和阻塞线程 - Python,线程","url":"https://pyqt5.com/daemonthread.html"},{ "title": "QDataWidgetMapper 数据库绑定 QLineEdit控件 - PyQt,Model","url":"https://pyqt5.com/datawidgetmapper_625781186.html"},{ "title": "python 判断屏幕等宽字符串的长度   - Python","url":"https://pyqt5.com/equal_str_width_625781186.html"},{ "title": "FFmpeg合成加密HLS记录 - Python,FFmpeg,HLS","url":"https://pyqt5.com/ffmpeghls.html"},{ "title": "PyQt5窗口翻转动画 - PyQt,动画,翻转","url":"https://pyqt5.com/flipwidgetanimation.html"},{ "title": "PyQt5窗口跟随其它窗口 - PyQt,窗口","url":"https://pyqt5.com/followwindow.html"},{ "title": "PyQt5判断信号是否连接 - PyQt,信号","url":"https://pyqt5.com/issignalconnected.html"},{ "title": "PyQt5之QSlider滑动条点击定位 - PyQt,滑动条","url":"https://pyqt5.com/jumpslider.html"},{ "title": "PyQt5仿网页图片鼠标移动特效 - PyQt,特效","url":"https://pyqt5.com/likehtmleffect.html"},{ "title": "如何在Mac M1上快速安装PyQt5 - PyQt,Mac,M1","url":"https://pyqt5.com/macm1pyqt.html"},{ "title": "PyQt5菜单之多选功能 - PyQt,菜单","url":"https://pyqt5.com/mselectmenu.html"},{ "title": "PyQt5之图片轮播 - PyQt,动画,轮播","url":"https://pyqt5.com/pageswitching.html"},{ "title": "python 在类里使用进程池 - 进程","url":"https://pyqt5.com/processinclass_625781186.html"},{ "title": "在pyqt中使用python全局钩子模块 - Python,pyqt hook key","url":"https://pyqt5.com/pyqt5_hook_key_625781186.html"},{ "title": "PyQt5结合Asyncio异步 - PyQt,Asyncio,异步","url":"https://pyqt5.com/pyqt5asyncio.html"},{ "title": "python 获取子进程print信息   - Python,subprocess.Popen,拦截print","url":"https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html"},{ "title": "PyQtClient例子客户端 - PyQt","url":"https://pyqt5.com/pyqtclient.html"},{ "title": "在Mac上以正确的姿势使用PyQtClient看Demo - PyQt","url":"https://pyqt5.com/pyqtclientmac.html"},{ "title": "pytest-qt 测试模态窗体. - pytest-qt","url":"https://pyqt5.com/pytest_qt_modal_625781186.html"},{ "title": "python 状态机模块   - Python,python 状态机","url":"https://pyqt5.com/python_statemachine_625781186.html"},{ "title": "PyQt5显示.9格式的PNG图片 - PyQt,图片,气泡,.9png","url":"https://pyqt5.com/qtninepatch.html"},{ "title": "QtWebkit和QWebEngineView与Javascript交互 - PyQt,QWebView,QWebEngineView,浏览器","url":"https://pyqt5.com/qtwebjs.html"},{ "title": "像读文章一样读源码 - Python,debug,snoop","url":"https://pyqt5.com/read_open_source.html"},{ "title": "PyQt5仿网页鼠标移动点阵特效 - PyQt,动画,特效","url":"https://pyqt5.com/rlatticeeffect.html"},{ "title": "QRunnable线程池发信号 - PyQt,信号,线程","url":"https://pyqt5.com/runnablesignal_625781186.html"},{ "title": "PyQt5无边框圆角阴影 - PyQt,阴影,无边框,圆角","url":"https://pyqt5.com/shadowradius.html"},{ "title": "PyQt5调整窗口显示边框 - PyQt,边框","url":"https://pyqt5.com/showframe.html"},{ "title": "解决GitHub下载速度缓慢的问题 - Github","url":"https://pyqt5.com/speedgithub.html"},{ "title": "PyQt学习心得 - PyQt","url":"https://pyqt5.com/studynotes.html"},{ "title": "推荐编辑器LiClipse - 编辑器","url":"https://pyqt5.com/suggesteditor.html"},{ "title": "修改pyuic代替pyside2-uic. - PyQt5 PySide2","url":"https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html"},{ "title": "如何查阅Qt文档 - PyQt","url":"https://pyqt5.com/viewapi.html"},{ "title": "如何和设计师中查看ui转换的py代码 - PyQt,Designer,设计师","url":"https://pyqt5.com/viewpyindesigner_625781186.html"},{ "title": "PyQt5编译QWebView与QWebEngineView共存 - PyQt,QWebView,浏览器","url":"https://pyqt5.com/webviewnew.html"},{ "title": "python 拷贝虚拟环境(一)   - Python,virtualenvwrapper,virtualenv","url":"https://pyqt5.com/virtualenvpy_625781186.html"}]} \ No newline at end of file diff --git a/source/self/btf.css b/self/btf.css similarity index 100% rename from source/self/btf.css rename to self/btf.css diff --git a/source/self/btf.js b/self/btf.js similarity index 100% rename from source/self/btf.js rename to self/btf.js diff --git a/shadowradius.html b/shadowradius.html new file mode 100644 index 00000000..7f4ffedc --- /dev/null +++ b/shadowradius.html @@ -0,0 +1,356 @@ +PyQt5无边框圆角阴影 | PyQt + + + + + + + + + + + + + + + + +

    PyQt5无边框圆角阴影

    在做 PyQt 窗口开发中经常会遇到要做一些无边框不规则的窗口,可能还会带有阴影效果,这里演示做一个简单的无边框圆角的窗口,原理就在于背景窗口的透明和一层有色背景控件的叠加。

    + +

    # 原理说明

    +
      +
    1. 黑色(方便说明)的 QDialog 或者 QWidget 作为全透明无边框窗口。
    2. +
    3. 其中白色的 QWidget 才是主要显示圆角和阴影的窗口,用于承载其它控件的显示。
    4. +
    5. 注意红色和紫色的方框内的层次。
    6. +
    7. 另:如果要熟悉纯代码编写请看 FramelessDialog.py
    8. +
    +

    如图:

    +

    FramelessDialog1

    +

    # 代码

    +

    https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py

    +
    #!/usr/bin/env python
    +# -*- coding: utf-8 -*-
    +
    +"""
    +Created on 2019年4月25日
    +@author: Irony
    +@site: https://pyqt5.com https://github.com/892768447
    +@email: 892768447@qq.com
    +@file: FramelessWidget
    +@description: 无边框圆角带阴影窗口 
    +"""
    +from PyQt5.QtCore import Qt
    +from PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect
    +from frameless import Ui_Dialog
    +
    +
    +__Author__ = 'Irony'
    +__Copyright__ = 'Copyright (c) 2019'
    +
    +
    +class Window(QDialog, Ui_Dialog):
    +
    +    def __init__(self, *args, **kwargs):
    +        super(Window, self).__init__(*args, **kwargs)
    +        self.mPos = None
    +        self.setupUi(self)
    +        self.closeButton.clicked.connect(self.close)
    +        # 重点
    +        # 无边框
    +        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
    +        # 背景透明(就是ui中黑色背景的那个控件)
    +        self.setAttribute(Qt.WA_TranslucentBackground, True)
    +
    +        # 添加阴影
    +        effect = QGraphicsDropShadowEffect(self)
    +        effect.setBlurRadius(12)
    +        effect.setOffset(0, 0)
    +        effect.setColor(Qt.gray)
    +        self.setGraphicsEffect(effect)
    +
    +    # 加上简单的移动功能
    +
    +    def mousePressEvent(self, event):
    +        """鼠标点击事件"""
    +        if event.button() == Qt.LeftButton:
    +            self.mPos = event.pos()
    +        event.accept()
    +
    +    def mouseReleaseEvent(self, event):
    +        '''鼠标弹起事件'''
    +        self.mPos = None
    +        event.accept()
    +
    +    def mouseMoveEvent(self, event):
    +        if event.buttons() == Qt.LeftButton and self.mPos:
    +            self.move(self.mapToGlobal(event.pos() - self.mPos))
    +        event.accept()
    +
    +
    +if __name__ == '__main__':
    +    import sys
    +    from PyQt5.QtWidgets import QApplication
    +    app = QApplication(sys.argv)
    +    w = Window()
    +    w.show()
    +    sys.exit(app.exec_())
    +

    # 效果图

    +

    FramelessDialog

    +

    # 下载

    +

    无边框圆角阴影.zip

    +
    文章作者: Irony
    文章链接: https://pyqt5.com/shadowradius.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
    赞助
    • 微信付
      微信付
    • 支付宝
      支付宝
    \ No newline at end of file diff --git a/showframe.html b/showframe.html new file mode 100644 index 00000000..ea12d65a --- /dev/null +++ b/showframe.html @@ -0,0 +1,380 @@ +PyQt5调整窗口显示边框 | PyQt + + + + + + + + + + + + + + +

    PyQt5调整窗口显示边框

    windows 某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 windows 在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -> 调整 Windows 的外观和性能 -> 去掉勾选 拖动时显示窗口内容。

    + +

    由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。

    +
      +
    1. 一种是自己绘制一个边框效果,放开鼠标时才操作真正的窗口。
    2. +
    3. 二是替换窗口的处理过程函数 wndproc 处理 WM_NCLBUTTONDOWN 消息事件。
    4. +
    +

    今天讲第二种方法:

    +
      +
    1. 需要了解 SystemParametersInfo  API 函数
    2. +
    3. SPI_GETDRAGFULLWINDOWS :确定是否允许拖拉到最大窗口
    4. +
    5. SPI_SETDRAGFULLWINDOWS :设置是否允许拖至最大窗口
    6. +
    +

    效果就是这样的:

    +

    ShowFrameWhenDrag

    +

    正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。

    +

    # 代码

    +

    https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py

    +
    #!/usr/bin/env python
    +# -*- coding: utf-8 -*-
    +
    +"""
    +Created on 2019年4月23日
    +@author: Irony
    +@site: https://pyqt5.com https://github.com/892768447
    +@email: 892768447@qq.com
    +@file: ShowFrameWhenDrag
    +@description: 调整窗口显示边框
    +"""
    +from ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\
    +    c_ulonglong, WINFUNCTYPE, c_uint
    +
    +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
    +
    +
    +__Author__ = 'Irony'
    +__Copyright__ = 'Copyright (c) 2019 Irony'
    +__Version__ = 1.0
    +
    +if sizeof(c_long) == sizeof(c_void_p):
    +    WPARAM = c_ulong
    +    LPARAM = c_long
    +elif sizeof(c_longlong) == sizeof(c_void_p):
    +    WPARAM = c_ulonglong
    +    LPARAM = c_longlong
    +
    +WM_NCLBUTTONDOWN = 0x00a1
    +GWL_WNDPROC = -4
    +SPI_GETDRAGFULLWINDOWS = 38
    +SPI_SETDRAGFULLWINDOWS = 37
    +WNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM)
    +
    +try:
    +    CallWindowProc = windll.user32.CallWindowProcW
    +    SetWindowLong = windll.user32.SetWindowLongW
    +    SystemParametersInfo = windll.user32.SystemParametersInfoW
    +except:
    +    CallWindowProc = windll.user32.CallWindowProcA
    +    SetWindowLong = windll.user32.SetWindowLongA
    +    SystemParametersInfo = windll.user32.SystemParametersInfoA
    +
    +
    +def GetDragFullwindows():
    +    rv = c_int()
    +    SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0)
    +    return rv.value
    +
    +
    +def SetDragFullwindows(value):
    +    SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0)
    +
    +
    +class Window(QWidget):
    +
    +    def __init__(self, *args, **kwargs):
    +        super(Window, self).__init__(*args, **kwargs)
    +        layout = QVBoxLayout(self)
    +        layout.addWidget(QLabel('拖动或者调整窗口试试看'))
    +
    +        # 重点替换窗口处理过程
    +        self._newwndproc = WNDPROC(self._wndproc)
    +        self._oldwndproc = SetWindowLong(
    +            int(self.winId()), GWL_WNDPROC, self._newwndproc)
    +
    +    def _wndproc(self, hwnd, msg, wparam, lparam):
    +        if msg == WM_NCLBUTTONDOWN:
    +            # 获取系统本身是否已经开启
    +            isDragFullWindow = GetDragFullwindows()
    +            if isDragFullWindow != 0:
    +                # 开启虚线框
    +                SetDragFullwindows(0)
    +                # 系统本身处理
    +                ret = CallWindowProc(
    +                    self._oldwndproc, hwnd, msg, wparam, lparam)
    +                # 关闭虚线框
    +                SetDragFullwindows(1)
    +                return ret
    +        return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)
    +
    +
    +if __name__ == '__main__':
    +    import sys
    +    from PyQt5.QtWidgets import QApplication
    +    app = QApplication(sys.argv)
    +    w = Window()
    +    w.show()
    +    sys.exit(app.exec_())
    +

    # 片尾

    +

    替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。

    +
    文章作者: Irony
    文章链接: https://pyqt5.com/showframe.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
    赞助
    • 微信付
      微信付
    • 支付宝
      支付宝
    \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..a52ae3b4 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,806 @@ + + + + + https://pyqt5.com/tags/index.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/issues/index.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/link/index.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/guestbook/index.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/suggesteditor.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/viewapi.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/viewpyindesigner_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/webviewnew.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/virtualenvpy_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/about/index.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/categories/index.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/faq/index.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/QPropertyAnimation.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/animateshadow.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/bindsignals.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/calljava.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/cgitb.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/circleimage.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/daemonthread.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/datawidgetmapper_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/equal_str_width_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/ffmpeghls.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/flipwidgetanimation.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/followwindow.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/issignalconnected.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/jumpslider.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/likehtmleffect.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/macm1pyqt.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/mselectmenu.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/pageswitching.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/processinclass_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/pyqt5_hook_key_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/pyqt5asyncio.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/pyqtclient.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/pyqtclientmac.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/pytest_qt_modal_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/python_statemachine_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/qtninepatch.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/qtwebjs.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/read_open_source.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/rlatticeeffect.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/runnablesignal_625781186.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/shadowradius.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/showframe.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/speedgithub.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/studynotes.html + + 2024-04-30 + + monthly + 0.6 + + + + https://pyqt5.com/404.html + + 2024-04-30 + + monthly + 0.6 + + + + + https://pyqt5.com/ + 2024-04-30 + daily + 1.0 + + + + + https://pyqt5.com/tags/PyQt/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E5%8A%A8%E7%94%BB/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E9%98%B4%E5%BD%B1/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E4%BF%A1%E5%8F%B7/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/Python/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E6%88%AA%E5%9B%BE/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E5%BC%82%E5%B8%B8/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E5%9C%86%E5%BD%A2/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E5%9B%BE%E7%89%87/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E7%BA%BF%E7%A8%8B/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/Model/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/FFmpeg/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/HLS/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E7%BF%BB%E8%BD%AC/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E7%AA%97%E5%8F%A3/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E6%BB%91%E5%8A%A8%E6%9D%A1/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E7%89%B9%E6%95%88/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/Mac/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/M1/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E8%8F%9C%E5%8D%95/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E8%BD%AE%E6%92%AD/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E8%BF%9B%E7%A8%8B/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/pyqt-hook-key/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/Asyncio/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E5%BC%82%E6%AD%A5/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/subprocess-Popen/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E6%8B%A6%E6%88%AAprint/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/pytest-qt/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/python-%E7%8A%B6%E6%80%81%E6%9C%BA/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E6%B0%94%E6%B3%A1/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/9png/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/QWebView/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/QWebEngineView/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/debug/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/snoop/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E6%97%A0%E8%BE%B9%E6%A1%86/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E5%9C%86%E8%A7%92/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E8%BE%B9%E6%A1%86/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/Github/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E7%BC%96%E8%BE%91%E5%99%A8/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/PyQt5-PySide2/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/Designer/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/%E8%AE%BE%E8%AE%A1%E5%B8%88/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/virtualenvwrapper/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/tags/virtualenv/ + 2024-04-30 + weekly + 0.2 + + + + + + https://pyqt5.com/categories/%E7%AC%94%E8%AE%B0/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/categories/%E4%BE%8B%E5%AD%90/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/categories/%E6%95%99%E7%A8%8B/ + 2024-04-30 + weekly + 0.2 + + + + https://pyqt5.com/categories/%E9%9A%8F%E7%AC%94/ + 2024-04-30 + weekly + 0.2 + + + diff --git a/source/404/index.md b/source/404/index.md deleted file mode 100644 index b744d290..00000000 --- a/source/404/index.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: -share: false -toc: false -comments: false -permalink: /404 -type: "404" -date: 2016-08-14 18:12:17 ---- - - - -
    - - - -
    - -

    回主页 · 所有文章 · 留言板

    - -

    您还可以在左边工具搜索框中对本站进行检索,以获取相关信息。

    - - \ No newline at end of file diff --git a/source/_data/link.yml b/source/_data/link.yml deleted file mode 100644 index 483010ee..00000000 --- a/source/_data/link.yml +++ /dev/null @@ -1,15 +0,0 @@ -- class_name: 网站 - class_desc: 一些常用的网站 - link_list: - - name: Qt - link: https://doc.qt.io/qt-5/qwidget.html - avatar: https://d33sqmjvzgs8hq.cloudfront.net/wp-content/themes/oneqt/assets/images/favicon-32x32.png - descr: Qt官方文档 - - name: Python - link: https://www.python.org/ - avatar: https://www.python.org/static/favicon.ico - descr: 强有力的编程语言 - - name: GitHub - link: https://github.com/ - avatar: https://github.githubassets.com/favicons/favicon.png - descr: 全球最大的交友网站 diff --git a/source/_posts/QPropertyAnimation.md b/source/_posts/QPropertyAnimation.md deleted file mode 100644 index 36f57a76..00000000 --- a/source/_posts/QPropertyAnimation.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -author: Irony -title: PyQt属性动画(QPropertyAnimation) -date: 2019-05-08 15:43:06 -tags: - - PyQt - - 动画 -categories: 笔记 ---- - -`QPropertyAnimation`继承自`QVariantAnimation`,其作为Qt的属性动画用于针对控件的属性或者继承自`QObject`的对象中定义的属性做修改, -简单来说就是基类是`QObject`且定义了属性变量,就可以用`QPropertyAnimation`来做属性动画。同时也可以通过`pyqtProperty`来增加自定义属性。 - - -首先,通过构造函数`QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None)`创建一个对象,其中 - -1. 第一个参数是动画作用的对象,也可以通过`setTargetObject`设置 -2. 第二个参数是属性名,在py3中类型是bytes,也可以通过`setPropertyName`设置 - -## 函数 - -一些常见的设置函数 - -| | | -| :-------------- | :---------- | -| setPropertyName | 设置属性名 | -| setTargetObject | 设置动画作用对象 | -| setDuration | 设置动画持续时间(毫秒) | -| setStartValue | 设置开始值 | -| setEndValue | 设置结束值 | -| setEasingCurve | 设置动画曲线 | -| setKeyValueAt | 插入线性值 | -| setLoopCount | 设置循环次数(-1为永久) | - -## 示例 - -比如这个例子: - -1. 修改控件的`geometry`大小 -2. 修改自定义属性 -3. 修改进度条的value值 - -![QPropertyAnimation](/images/QPropertyAnimation.gif) - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2019年5月8日 -@author: Irony -@site: https://pyqt5.com https://github.com/892768447 -@email: 892768447@qq.com -@file: -@description: -""" -from PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurve -from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\ - QLabel, QProgressBar, QSpacerItem, QSizePolicy - - -__Author__ = 'Irony' -__Copyright__ = 'Copyright (c) 2019 Irony' -__Version__ = 1.0 - - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - self.resize(400, 400) - self._value = 0 - self.button = QPushButton('属性动画测试', self) - self.button.clicked.connect(self.doStart) - self.button.setGeometry(0, 0, 80, 40) - - self.buttonc = QPushButton('自定义属性 测试', self) - self.buttonc.clicked.connect(self.doStartCustom) - - self.label = QLabel('', self) - - self.progressbar = QProgressBar(self) - self.progressbar.setRange(0, 99) - - layout = QVBoxLayout(self) - layout.addItem(QSpacerItem( - 20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed)) - layout.addWidget(self.buttonc) - layout.addWidget(self.label) - layout.addWidget(self.progressbar) - - # 进度条动画 - self.progressStart() - - # 此处是自定义属性,并通过动画修改后,设置QLabel的值 - @pyqtProperty(int) - def value(self): - return self._value - - @value.setter - def value(self, v): - self._value = v - self.label.setText('当前值:{}'.format(v)) - - def doStart(self): - # 第一个参数是要执行的对象 - animation = QPropertyAnimation(self.button, b'geometry', self) - animation.setDuration(2000) # 持续时间 - # 缓和曲线风格,加了曲线动画会很大程度影响 - animation.setEasingCurve(QEasingCurve.OutBounce) - animation.setStartValue(QRect(0, 0, 40, 40)) - animation.setEndValue(QRect(250, 250, 80, 80)) - animation.start(animation.DeleteWhenStopped) - - def doStartCustom(self): - # 自定义属性动画 - # 由于定义的属性是在继承的QWidget, 所以第一个参数是self - # 第二个参数就是 value - animation = QPropertyAnimation(self, b'value', self) - animation.setDuration(2000) # 持续时间 - animation.setStartValue(0) - animation.setEndValue(100) - animation.start(animation.DeleteWhenStopped) - - def progressStart(self): - # 进度条动画 - # 这里 value是QProgressBar自带的属性,具体可以看文档 - # https://doc.qt.io/qt-5/qprogressbar.html#properties - animation = QPropertyAnimation(self.progressbar, b'value', self) - animation.setDuration(2000) # 持续时间 - animation.setLoopCount(-1) - # 这里采用插入线性值,第一个参数的范围是(0-1) - # 第二个参数的范围是进度(最小值-最大值) - animation.setKeyValueAt(0, self.progressbar.minimum()) - animation.setKeyValueAt(0.1, 10) - animation.setKeyValueAt(0.2, 30) - animation.setKeyValueAt(0.5, 60) - animation.setKeyValueAt(0.7, 80) - animation.setKeyValueAt(1, self.progressbar.maximum()) - animation.start(animation.DeleteWhenStopped) - - -if __name__ == '__main__': - import sys - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) - -``` \ No newline at end of file diff --git a/source/_posts/animateshadow.md b/source/_posts/animateshadow.md deleted file mode 100644 index 78fb8b78..00000000 --- a/source/_posts/animateshadow.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -author: Irony -title: PyQt5动画边框阴影 -date: 2018-09-25 23:38:12 -tags: - - PyQt - - 动画 - - 阴影 -categories: 例子 ---- - -为子控件增加动画阴影效果,结合`QGraphicsDropShadowEffect`和`QPropertyAnimation`动态改变阴影半径达到效果,在旧版本的Qt中`QGraphicsDropShadowEffect`可能会有点问题(父控件会影响子控件) - - -## 原理 - -原理是利用QGraphicsDropShadowEffect添加边框阴影,然后使用动画不停改变阴影的模糊半径来达到效果,如图: - -![ShadowEffect](/PyQt/QGraphicsDropShadowEffect/ScreenShot/ShadowEffect.gif) - -## 简单说明 - -1. 继承`QGraphicsDropShadowEffect`增加动态属性`radius` -2. 通过`setGraphicsEffect`方法设置控件的边框阴影 -3. 通过`QPropertyAnimation`属性动画不断改变`radius`的值并调用`setBlurRadius`更新半径值 - -https://github.com/PyQt5/PyQt/blob/master/QGraphicsDropShadowEffect/ShadowEffect.py - -## 自定义类 - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2018年9月25日 -@author: Irony -@site: https://pyqt5.com, https://github.com/892768447 -@email: 892768447@qq.com -@file: AnimationShadowEffect -@description: 边框动画阴影动画 -""" -from PyQt5.QtCore import QPropertyAnimation, pyqtProperty -from PyQt5.QtWidgets import QGraphicsDropShadowEffect - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - - -class AnimationShadowEffect(QGraphicsDropShadowEffect): - - def __init__(self, color, *args, **kwargs): - super(AnimationShadowEffect, self).__init__(*args, **kwargs) - self.setColor(color) - self.setOffset(0, 0) - self.setBlurRadius(0) - self._radius = 0 - self.animation = QPropertyAnimation(self) - self.animation.setTargetObject(self) - self.animation.setDuration(2000) # 一次循环时间 - self.animation.setLoopCount(-1) # 永久循环 - self.animation.setPropertyName(b'radius') - # 插入线行值 - self.animation.setKeyValueAt(0, 1) - self.animation.setKeyValueAt(0.5, 30) - self.animation.setKeyValueAt(1, 1) - - def start(self): - self.animation.start() - - @pyqtProperty(int) - def radius(self): - return self._radius - - @radius.setter - def radius(self, r): - self._radius = r - self.setBlurRadius(r) -``` - -## 测试代码 - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2018年9月25日 -@author: Irony -@site: https://pyqt5.com, https://github.com/892768447 -@email: 892768447@qq.com -@file: Test -@description: -""" -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit - -from AnimationShadowEffect import AnimationShadowEffect # @UnresolvedImport - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - layout = QHBoxLayout(self) - - # 绿色边框 - labelGreen = QLabel(self, pixmap=QPixmap('1.jpg').scaled(100, 100)) - layout.addWidget(labelGreen) - aniGreen = AnimationShadowEffect(Qt.darkGreen, labelGreen) - labelGreen.setGraphicsEffect(aniGreen) - aniGreen.start() - - # 红色边框,圆形图片 - labelRed = QLabel(self) - labelRed.setMinimumSize(100, 100) - labelRed.setMaximumSize(100, 100) - labelRed.setStyleSheet('border-image: url(1.jpg);border-radius: 50px;') - layout.addWidget(labelRed) - aniRed = AnimationShadowEffect(Qt.red, labelGreen) - labelRed.setGraphicsEffect(aniRed) - aniRed.start() - - # 蓝色边框按钮 - button = QPushButton('按钮', self) - aniButton = AnimationShadowEffect(Qt.blue, button) - layout.addWidget(button) - button.setGraphicsEffect(aniButton) - aniButton.start() - - # 青色边框输入框 - lineedit = QLineEdit(self) - aniEdit = AnimationShadowEffect(Qt.cyan, lineedit) - layout.addWidget(lineedit) - lineedit.setGraphicsEffect(aniEdit) - aniEdit.start() - - -if __name__ == '__main__': - import sys - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) -``` \ No newline at end of file diff --git a/source/_posts/bindsignals.md b/source/_posts/bindsignals.md deleted file mode 100644 index 2a68c1e6..00000000 --- a/source/_posts/bindsignals.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -author: Irony -title: 三种方式绑定信号槽 -date: 2019-05-04 16:07:06 -tags: - - PyQt - - 信号 -categories: 教程 ---- -网上关于PyQt5的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在PyQt5中无法使用,这里归纳总结下已有的几种绑定信号槽的方式, -这几种方式各有各的优点和缺点。 - - -## 方式一 - -这个方式是最开始接触设计师的时候知道的,主要是通过控件的`objectName`和`QtCore.QMetaObject.connectSlotsByName(Form)`提供的连接函数来自动完成注册, -比如带有按钮的界面ui文件转成py文件后会发现如下代码: -```python -self.pushButton = QtWidgets.QPushButton(Form) -self.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28)) -self.pushButton.setObjectName("pushButton") - -# 通过这里自动完成连接信号槽 -QtCore.QMetaObject.connectSlotsByName(Form) -``` - -此时只需要继承该UI文件类然后增加如下方法: -```python - -@pyqtSlot() -def on_pushButton_clicked(self): - print('button clicked') -``` - -这里解释一下,`@pyqtSlot()`装饰器把函数`on_pushButton_clicked`包装为一个槽函数, -而`QtCore.QMetaObject.connectSlotsByName(Form)`这句代码的意思就是自动去寻找满足的槽函数 - -注意:这里有个规范(on_xxxx_clicked),这里必须要满足`on_控件的objectName_控件的信号`这样下划线连接起来的函数名才能被识别, -比如按钮的点击:`on_pushButton_clicked`、勾选框的选中:`on_checkbox_toggled(self, checked)` - -## 方式二 - -这种方式则直接通过代码里调用控件的信号的`connect`方法来进行绑定,比如: -```python -# 按钮点击函数 -def doClicked(self): - print(self.sender(), 'clicked') - -# 绑定点击信号 -self.pushButton.clicked.connect(self.doClicked) -``` - -注意:`connect`的是函数名字,`self.sender()`这句代码是获取信号发送者(比如这里就是得到这个按钮对象), -用处在于有时候要循环创建一堆按钮 - -## 方式三 - -通过参数这种方式其实比较特殊,在PyQt中大部分存在,但是在PySide中则很少,原因是两者的封装方式不同。 - -同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如: -```python - -# 按钮点击函数 -def doClicked(self): - print(self.sender(), 'clicked') - -pushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40) -``` - -这里可以通过参数(信号名字) = 函数来绑定信号 - -同时也可以设置其它参数,比如 -`button.setMinimumHeight(40)`也可以像参数里那样设置`minimumHeight=40` diff --git a/source/_posts/calljava.md b/source/_posts/calljava.md deleted file mode 100644 index 5c9b3981..00000000 --- a/source/_posts/calljava.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -author: Irony -title: Python调用Java对Excel截图 -date: 2019-03-12 21:15:06 -tags: - - Python - - 截图 -categories: 教程 ---- - -有的时候会遇到一些奇葩的需求,就是用Excel做报表,但是需要对里面的数据进行填充并生成报表图片,发送出去。这里记录用python调用jar包对excel文件进行公式计算和截图,数据填充可以用xlrd或者openpyxl - - -利用`jpype`模块初始化java虚拟机加载jar包然后执行其中的功能。 - -## 代码 - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2019年3月12日 -@author: Irony -@site: https://pyqt5.com https://github.com/892768447 -@email: 892768447@qq.com -@file: CallJava -@description: -""" -import os - -import jpype - - -__Author__ = 'Irony' -__Copyright__ = 'Copyright (c) 2019' - - -def convertToImage(): - Workbook = jpype.JClass('com.aspose.cells.Workbook') - ImageFormat = jpype.JClass('com.aspose.cells.ImageFormat') - ImageOrPrintOptions = jpype.JClass( - 'com.aspose.cells.ImageOrPrintOptions') - SheetRender = jpype.JClass('com.aspose.cells.SheetRender') - - book = Workbook(os.path.abspath('data/test.xlsx').replace('\\', '/')) - # 保存为html - book.save('data/index.html', 12) - # 保存为pdf - book.save('data/test.pdf') - - # 截图 - imgOptions = ImageOrPrintOptions() - # imgOptions.setQuality(100) - imgOptions.setOnePagePerSheet(True) - - # 输出图片格式 -# imgOptions.setImageFormat(ImageFormat.getJpeg()) - imgOptions.setImageFormat(ImageFormat.getPng()) - - # 计算 - CalculationOptions = jpype.JClass( - 'com.aspose.cells.CalculationOptions') - opt = CalculationOptions() - # 对Sheet1中的公式进行计算 - sheet = book.getWorksheets().get('Sheet1') - sheet.calculateFormula(opt, True) - - # 设置区域 - pageSetup = sheet.getPageSetup() - # 去掉边距 - pageSetup.setBottomMargin(0.) - pageSetup.setLeftMargin(0.) - pageSetup.setRightMargin(0.) - pageSetup.setTopMargin(0.) - # 设置要截图的区域(对角线) - pageSetup.setPrintArea('A0:C2') - # Create a SheetRender object for the target sheet - sr = SheetRender(sheet, imgOptions) - for page in range(sr.getPageCount()): - # Generate an image for the worksheet - sr.toImage( - page, os.path.join('data', '%d.png' % (page + 1))) - - -def test(): - # emm这里不知道什么用绝对路径就报错 - libs = '{};{}'.format( - 'libs/bcprov-jdk16-146.jar', - 'libs/aspose-cells-19.2.jar' - ) - command = (jpype.getDefaultJVMPath(), - '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M', - '-Djava.class.path={0}'.format(libs)) - print(command) - jpype.startJVM(jpype.getDefaultJVMPath(), - '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M', - '-Djava.class.path={0}'.format(libs) - ) - # 解决多线程问题 - jpype.attachThreadToJVM() - # 对excel截图 - convertToImage() - # 关闭虚拟机 - jpype.shutdownJVM() - print('截图完成') - - -if __name__ == '__main__': - test() -``` - -## 附件 - -[调用java生成报表.7z](/files/调用java生成报表.7z) - -解压后进入whls文件夹安装对应版本的jpype包 - -## 效果图 - -![calljava](/images/calljava.png) \ No newline at end of file diff --git a/source/_posts/cgitb.md b/source/_posts/cgitb.md deleted file mode 100644 index 21da9df4..00000000 --- a/source/_posts/cgitb.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -author: Irony -title: 异常捕获之cgitb模块 -date: 2018-09-17 23:17:06 -tags: - - Python - - 异常 -categories: 笔记 ---- - -`cgitb`模块为`Python`脚本提供了一个特殊的异常管理器。名字有点误导人,它最初设计是为了以HTML格式展示cgi脚本的大量异常信息。后来,他扩展为也可以展示纯文本信息。该模块激活后,如果发生了未捕获的异常,将会展示格式化的输出报告。该报告包括源代码每一层的回溯,以及当前执行程序的参数和局部变量。以及,你可以选择将这些信息存到一个文件里,而不是发送到浏览器。 - - -## 用途 - -当编辑器中无法显示错误信息时,尤其是`PyQt`,可以尝试在cmd中运行代码,或者使用此模块来得到错误信息。 - -## 介绍 - -### cgitb.enable - -```python -cgitb.enable(display=1, logdir=None, context=5, format="html") -``` - -参数说明 - -1. display 1,发送至浏览器;0, 不发送 -2. logdir 如果有的话,写到该目录下 -3. context 显示错误代码周围的代码行数 -4. format 是否显示为HTML,除了'html'之外的所有值,都会显示为纯文本 - -### cgitb.handle - -```python -cgitb.handle(info=None) -``` - -参数说明 - -1. 如果你想用cgitb处理异常,你可以调用这个函数。 -2. info 应当是含有异常类型、异常值和traceback对象的三元组 -3. 如同sys.exc_info()返回的那样。如果不提供info,则从sys.exc_info中获取。 - -## 如何使用 - -以下代码放在最开始执行 - -```python -import cgitb -import sys -sys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text') -``` diff --git a/source/_posts/circleimage.md b/source/_posts/circleimage.md deleted file mode 100644 index 31e436e5..00000000 --- a/source/_posts/circleimage.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -author: Irony -title: PyQt5圆形图片 -date: 2018-09-25 22:13:26 -tags: - - PyQt - - 圆形 - - 图片 -categories: 例子 ---- - -实现圆形图片的方法有很多,比如用遮罩(mask),裁切等等。这里比较几种实现方式,选出个人认为最优的方案。 - - -https://github.com/PyQt5/PyQt/blob/master/QLabel/CircleImage.py - -## 采用mask方式 - -具体参考 [【Qt】QLabel实现的圆形图像 - 米罗西 - 博客园](https://www.cnblogs.com/zhehan54/p/9515124.html) - -## 画圆形遮盖(适合纯色背景) - -原理是在原图片上画一个4角有颜色,中间圆形镂空的图片。 - -![circleimage1](/images/circleimage1.png) - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -''' -Created on 2017年8月25日 -@author: Irony."[讽刺] -@site: https://pyqt5.com, https://github.com/892768447 -@email: 892768447@qq.com -@description: -''' -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap, QPainter, QPainterPath -from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - - -class Label(QLabel): - - def __init__(self, *args, antialiasing=True, **kwargs): - super(Label, self).__init__(*args, **kwargs) - self.Antialiasing = antialiasing - self.setMaximumSize(200, 200) - self.setMinimumSize(200, 200) - self.radius = 100 - - #加载图片并缩放 - self.image = QPixmap("head.jpg").scaled( - 200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation) - - painter = QPainter(self.image) - if self.Antialiasing: - painter.setRenderHint(QPainter.Antialiasing, True) - painter.setRenderHint(QPainter.HighQualityAntialiasing, True) - painter.setRenderHint(QPainter.SmoothPixmapTransform, True) - - path = QPainterPath() - path.addRoundedRect( - 0, 0, self.width(), self.height(), self.radius, self.radius) - path.addRect(0,0,self.width(),self.height()) - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.green) - painter.drawPath(path) - self.setPixmap(self.image) - - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - layout = QHBoxLayout(self) - layout.addWidget(Label(self)) - layout.addWidget(Label(self, antialiasing=False)) - self.setStyleSheet("background: black;") - -if __name__ == "__main__": - import sys - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) -``` - -## 使用QPainter的setCompositionMode - -具体参考 [Qt 圆形头像制作工具 抗锯齿 可缩放编辑](https://qtdream.com/topic/911/qt-%E5%9C%86%E5%BD%A2%E5%A4%B4%E5%83%8F%E5%88%B6%E4%BD%9C%E5%B7%A5%E5%85%B7-%E6%8A%97%E9%94%AF%E9%BD%BF-%E5%8F%AF%E7%BC%A9%E6%94%BE%E7%BC%96%E8%BE%91) - -```c++ -//result_avatar_size 是我们最后生成的图片的长宽,可以是QSize(200, 200)的正圆 -destination_image = QImage(result_avatar_size, QImage::Format_ARGB32_Premultiplied); -//在黑色的正方形中间画一个透明的圆,作为头像遮罩 -QPainter painter(&destination_image); -painter.setRenderHint(QPainter::Antialiasing); -//全涂黑 -painter.fillRect(destination_image.rect(), QBrush(Qt::black, Qt::SolidPattern)); -painter.setCompositionMode(QPainter::CompositionMode_SourceOut); -painter.setPen(Qt::NoPen); -painter.setBrush(QBrush(Qt::transparent, Qt::SolidPattern)); -//画透明区域 -painter.drawEllipse(destination_image.rect()); -``` - -## 使用QPainter的切割方法(推荐) - -利用`QPainter.setClipPath`方法切割一个圆形的`QPainterPath` - -![circleimage2](/images/circleimage2.png) - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -''' -Created on 2017年8月25日 -@author: Irony."[讽刺] -@site: https://pyqt5.com, https://github.com/892768447 -@email: 892768447@qq.com -@file: -@description: -''' -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap, QPainter, QPainterPath, QPen -from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout - - -__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" -__Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" -__Version__ = "Version 1.0" - - -class Label(QLabel): - - def __init__(self, *args, antialiasing=True, **kwargs): - super(Label, self).__init__(*args, **kwargs) - self.Antialiasing = antialiasing - self.setMaximumSize(200, 200) - self.setMinimumSize(200, 200) - self.radius = 100 - - #####################核心实现######################### - self.target = QPixmap(self.size()) # 大小和控件一样 - self.target.fill(Qt.transparent) # 填充背景为透明 - - p = QPixmap("head.jpg").scaled( # 加载图片并缩放和控件一样大 - 200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation) - - painter = QPainter(self.target) - if self.Antialiasing: - # 抗锯齿 - painter.setRenderHint(QPainter.Antialiasing, True) - painter.setRenderHint(QPainter.HighQualityAntialiasing, True) - painter.setRenderHint(QPainter.SmoothPixmapTransform, True) - -# painter.setPen(# 测试黑色圆圈 -# QPen(Qt.black, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) - path = QPainterPath() - path.addRoundedRect( - 0, 0, self.width(), self.height(), self.radius, self.radius) - #**** 切割为圆形 ****# - painter.setClipPath(path) -# painter.drawPath(path) # 测试黑色圆圈 - - painter.drawPixmap(0, 0, p) - self.setPixmap(self.target) - #####################核心实现######################### - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - layout = QHBoxLayout(self) - layout.addWidget(Label(self)) - layout.addWidget(Label(self, antialiasing=False)) - self.setStyleSheet("background: black;") - -if __name__ == "__main__": - import sys - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) -``` \ No newline at end of file diff --git a/source/_posts/daemonthread.md b/source/_posts/daemonthread.md deleted file mode 100644 index e5d689d7..00000000 --- a/source/_posts/daemonthread.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -author: 丑读书穷加班 -title: 多线程之守护线程和阻塞线程 -date: 2018-10-24 15:51:15 -tags: - - Python - - 线程 -categories: 笔记 ---- - -如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的daemon属性。 - - -即在线程开始(thread.start())之前,调用setDeamon()函数,设定线程的daemon标志。 - -(thread.setDaemon(True))就表示这个线程“不重要”。 - -如果你想等待子线程完成再退出,那就什么都不用做,或者显示地调用thread.setDaemon(False),设置daemon的值为false。新的子线程会继承父线程的daemon标志。 - -整个Python会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。 - -setDaemon()函数要放在start之前设置才行。 - -```python -import threading -import time - -def func(): - print("子线程开启:", time.localtime()) - time.sleep(2) - print("子线程结束:", time.localtime()) - - -print("主线程开启:", time.localtime()) -t = threading.Thread(target=func, args=()) -# t.setDaemon(True) -t.start() -print("主线程关闭:", time.localtime()) -``` - -在 Python 的多线程编程中,在实例代码中经常有 thread1.join()这样的代码。那么今天咱们用实际代码来解释一下 join 函数的作用。 - -join的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的join函数。 - -先看看这个: - -1. 阻塞主进程,专注于执行多线程中的程序。 -2. 多线程多join的情况下,依次执行各线程的join方法,前头一个结束了才能执行后面一个。 -3. 无参数,则等待到该线程结束,才开始执行下一个线程的join。 -4. 参数timeout为线程的阻塞时间,如 timeout=2 就是罩着这个线程2s 以后,就不管他了,继续执行下面的代码。 -5. 下面的例子是一次阻塞子线程,每个子线程都会等上个子线程join结束才会执行,如果注释掉t.join则会同时执行5个子线程,多线程在做网络访问的时候可以减少等待时间,那么在一个工作流程中可以将访问网络接口的情况做成多线程。 - -```python -import threading, time - -def func(): - print("hello world!") - time.sleep(1) - -print("hello main start") -for i in range(5): - t = threading.Thread(target=func, args=()) - print(t.getName()) - t.start() - t.join() -``` \ No newline at end of file diff --git a/source/_posts/datawidgetmapper_625781186.md b/source/_posts/datawidgetmapper_625781186.md deleted file mode 100644 index 1d976502..00000000 --- a/source/_posts/datawidgetmapper_625781186.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -author: 不许人间见白头 -title: QDataWidgetMapper 数据库绑定 QLineEdit控件 -date: 2018-10-29 16:17:59 -tags: - - PyQt - - Model -categories: 例子 ---- - -qt为操作数据库提供了一个model+view的模式 , 这样简单的出入库逻辑就不需要自己编写。 - -`QDataWidgetMapper` 可以 将数据库的数据 映射到其他控件 。 - -注意: 表格里的数据修改 还没有提交到数据库 , 需要点击提交按钮才生效。 - - -https://github.com/PyQt5/PyQt/tree/master/Test/partner_625781186/16_sqlModel/01_mapper - -## 代码 - -```python -#-*- coding: utf-8 -*- - -from PyQt5 import QtWidgets, QtGui, QtCore -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtSql import * - -import sys - -sys.path.append('./ui') -from Ui_MainWindow import Ui_MainWindow - - -class MainWindow(QMainWindow, Ui_MainWindow): - def __init__(self, parent=None, *args): - - super(MainWindow, self).__init__(parent, *args) - self.setupUi(self) - self.resize(800,600) - - #=============================== db ======================================# - # self.db = QSqlDatabase.addDatabase('QMYSQL') - # self.db.setHostName("127.0.0.1") # set address - # self.db.setUserName("root") # set user name - # self.db.setPassword('123456') # set user pwd - # self.db.setDatabaseName("database") - - self.db = QSqlDatabase.addDatabase('QSQLITE') - self.db.setDatabaseName('./db/database.db') - - #================================= codemodel =====================================# - # 实例化model - self.codeModel = QSqlRelationalTableModel() - # model设置表 - self.initializeModel(self.codeModel, 'Mongo') - # 设置编辑策略 - # self.codeModel.setEditStrategy(QSqlTableModel.OnFieldChange) - # !!! 这里要注意 , 只能用这个策略 , 才可以实现自动提交 - self.codeModel.setEditStrategy(QSqlTableModel.OnManualSubmit) - - self.codeView = self.createView("code_View", self.codeModel) - self.verticalLayout.addWidget(self.codeView) - - #================================ initData ==================================# - # 数据映射 - self.mapper = QDataWidgetMapper() - # 提交策略 - self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit) - # 映射的模型源 - self.mapper.setModel(self.codeModel) - self.mapper.addMapping(self.l1,0) - self.mapper.addMapping(self.l2,1) - self.mapper.addMapping(self.l3,2) - self.mapper.addMapping(self.l4,3) - self.mapper.addMapping(self.l5,4) - - self.mapper.toFirst() - - #================================ pushButton ==================================# - self.sub_btn.clicked.connect(self.mapper.submit) - self.sub_btn.clicked.connect(self.codeModel.submitAll) - self.pre_btn.clicked.connect(self.mapper.toPrevious) - self.next_btn.clicked.connect(self.mapper.toNext) - - def initializeModel(self, model, tablename): - '''重关联。''' - model.setTable(tablename) -# model.setEditStrategy(QSqlTableModel.OnRowChange) - model.select() - - def createView(self, title, model): - '''创建TableView视图''' - view = QTableView() - view.setModel(model) - view.setWindowTitle(title) - #列宽设置 - view.horizontalHeader().setSectionResizeMode(3) - #行高设置 - view.verticalHeader().setSectionResizeMode(1) - #充满列宽 - view.horizontalHeader().setStretchLastSection(True) -# view.verticalHeader().setVisible(False)#隐藏行标题 - #标题左对齐 - view.horizontalHeader().setDefaultAlignment(Qt.AlignLeft) - #标题左对齐 - view.verticalHeader().setDefaultAlignment(Qt.AlignLeft) - - return view - - -if __name__ == "__main__": - import sys - - app = QApplication(sys.argv) - app.setStyle(QStyleFactory.create("Fusion")) - ui = MainWindow() - ui.show() - sys.exit(app.exec_()) -``` - -## 效果图 - -![datawidgetmapper](/images/datawidgetmapper.gif) - diff --git a/source/_posts/equal_str_width_625781186.md b/source/_posts/equal_str_width_625781186.md deleted file mode 100644 index e8893fbe..00000000 --- a/source/_posts/equal_str_width_625781186.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -author: 人间白头  -title: python 判断屏幕等宽字符串的长度   -date: 2019-12-26 19:49:41 -tags: - - Python - -categories: 随笔 ---- - -判断屏幕等宽字符串的长度 ? - - -判断屏幕等宽字符串的长度 ? - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-58b19652011e153a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -【新手】重庆-搬砖-NoWait 22:41:50 @北京-BUG开发-黑择明 求指点 -【专家】北京-BUG开发-黑择明 22:43:04 fontMetrics -【专家】 [https://pyqt.site](https://pyqt.site) (892768447) 22:43:54 QFontMetrics -【专家】 [https://pyqt.site](https://pyqt.site) (892768447) 22:44:09 通过QLabel.font().fontMetrics()得到 - -【新手】重庆-搬砖-NoWait 22:52:00 -[https://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size](https://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size) -![image.png](https://upload-images.jianshu.io/upload_images/10769157-9dc2f83609106252.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -【新手】重庆-搬砖-NoWait 22:53:15 感觉和fontMetrics应该是差不多的 - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-411570eeadf51793.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - ---- - -【专家】北京-BUG开发-黑择明 (996742224) 11:29:04 -fm = QFontMetrics(QFont()) -fm.width("qweqwe") diff --git a/source/_posts/ffmpeghls.md b/source/_posts/ffmpeghls.md deleted file mode 100644 index bd9737d0..00000000 --- a/source/_posts/ffmpeghls.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -author: Irony -title: FFmpeg合成加密HLS记录 -date: 2019-01-12 19:28:06 -tags: - - Python - - FFmpeg - - HLS -categories: 笔记 ---- - -记录在某个需求中要求截图并合成加密视频文件,这里采用FFmpeg的管道流来实现生成HLS加密文件。 - - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2019年3月4日 -@author: Irony -@site: https://pyqt5.com https://github.com/892768447 -@email: 892768447@qq.com -@file: -@description: -""" - -from pathlib import Path -from subprocess import Popen, PIPE - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2019 Irony' -__Version__ = 1.0 - - -# p = Popen([r'D:\soft\ffmpeg\bin\ffmpeg.exe', '-y', -# '-threads', '2', -# '-f', 'image2pipe', -# '-vcodec', 'mjpeg', '-r', '24', '-i', '-', -# '-vcodec', 'h264', '-r', '24', -# # '-encryption_scheme', 'cenc-aes-ctr', -# # '-encryption_key', '617D8A125A284DF48E3C6B1866348A3F', -# # '-encryption_kid', 'B326F895B6A24CC5A4DC70995728059C', -# r'F:\Workspace\Test\videos\video.mp4'], stdin=PIPE) - -p = Popen([r'D:\soft\ffmpeg\bin\ffmpeg.exe', - '-re', # 按照实际帧率读取输入文件 - '-y', # 覆盖已存在文件 - '-threads', '2', # 线程数量 - '-f', 'image2pipe', # PIPE图片流 - '-vcodec', 'mjpeg', # 图片编码 - '-r', '24', # 帧率 - '-i', '-', # 指定输入流为PIPE - '-vcodec', 'h264', # 输出编码 - '-r', '24', # 帧率 - '-map', '0', -# '-crf','20', # 降低质量 - '-b', '720k', # 码率 - '-f', 'hls', - '-codec:v', 'libx264', - '-vbsf', 'h264_mp4toannexb', - # 指定加密密匙文件 - '-hls_key_info_file', r'F:\Workspace\Test\videokey.info', - '-hls_time', '20', - '-hls_list_size', '0', - '-hls_wrap', '0', -# '-hls_flags', 'single_file', # 生成单个文件(有bug) - r'F:\Workspace\Test\videos\playlist.m3u8'], stdin=PIPE) -print(p) - -t = 1 / 24 -for i, path in enumerate(Path('frames').rglob('*.jpg')): - # print(i, path) - p.stdin.write(open(str(path), 'rb').read()) - -p.stdin.close() -p.wait() -print('ok') -``` \ No newline at end of file diff --git a/source/_posts/flipwidgetanimation.md b/source/_posts/flipwidgetanimation.md deleted file mode 100644 index 5825bcaf..00000000 --- a/source/_posts/flipwidgetanimation.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -author: Irony -title: PyQt5窗口翻转动画 -date: 2019-05-15 22:48:00 -tags: - - PyQt - - 动画 - - 翻转 -categories: 例子 ---- - -QQ的界面一直是用来模仿练习做界面的好东西,这里就有一个类似QQ登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了`QStackedWidget`包含两个控件做切换,同时单独使用一个窗口做动画绘制。 - - -## 原理说明 - -1. 用了两个`QLabel`来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击 -2. 用了`QStackedWidget`来存放上面的两个界面`QLabel` -3. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口 -4. 通过`setWindowOpacity`控制主窗口的显示隐藏(保留任务栏),当然也可以用`hide` -5. 动画窗口`FlipWidget.py`主要实现两张图片的翻转显示,考虑到0-90和90-180之前的情况,以及图片的缩放动画 - -## 核心实现 - -1. 主要是在`paintEvent`方法中使用`QTransform`对`QPainter`进行圆心变换以及`rotate`设置翻转角度 -2. 同时根据翻转的角度范围对图片进行切换和缩放 - -```python -def paintEvent(self, event): - super(FlipWidget, self).paintEvent(event) - - if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible(): - - painter = QPainter(self) - painter.setRenderHint(QPainter.Antialiasing, True) - painter.setRenderHint(QPainter.SmoothPixmapTransform, True) - - # 变换 - transform = QTransform() - # 把圆心设置为矩形中心 - transform.translate(self.width() / 2, self.height() / 2) - - if self._angle >= -90 and self._angle <= 90: - # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程 - painter.save() - # 设置翻转角度 - transform.rotate(self._angle, Qt.YAxis) - painter.setTransform(transform) - # 缩放图片高度 - width = self.image1.width() / 2 - height = int(self.image1.height() * - (1 - abs(self._angle / self.Scale) / 100)) - image = self.image1.scaled( - self.image1.width(), height, - Qt.IgnoreAspectRatio, Qt.SmoothTransformation) - painter.drawPixmap( - QPointF(-width, -height / 2), image) - painter.restore() - else: - # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程 - painter.save() - if self._angle > 0: - angle = 180 + self._angle - else: - angle = self._angle - 180 - # 设置翻转角度, 注意这里角度有差异 - transform.rotate(angle, Qt.YAxis) - painter.setTransform(transform) - # 缩放图片高度 - width = self.image2.width() / 2 - height = int(self.image2.height() * - (1 - ((360 - abs(angle)) / self.Scale / 100))) - image = self.image2.scaled( - self.image2.width(), height, - Qt.IgnoreAspectRatio, Qt.SmoothTransformation) - painter.drawPixmap( - QPointF(-width, -height / 2), image) - painter.restore() -``` - -## 代码 - -https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py - - -## 效果图 - -![FlipWidgetAnimation](/PyQt/QPropertyAnimation/ScreenShot/FlipWidgetAnimation.gif) \ No newline at end of file diff --git a/source/_posts/followwindow.md b/source/_posts/followwindow.md deleted file mode 100644 index fb5dea98..00000000 --- a/source/_posts/followwindow.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -author: Irony -title: PyQt5窗口跟随其它窗口 -date: 2018-10-23 15:08:56 -tags: - - PyQt - - 窗口 -categories: 例子 ---- - -要实现`PyQt`窗口跟随其它外部的窗口,能想到两点办法,一个是hook系统事件得到目标窗口的位置和大小以及是否关闭等,二是通过循环检测窗口的位置来实现。 - - -## 基于Windows定时检测目标窗口 - -1. 利用`win32gui`模块获取目标窗口的句柄 -2. 通过句柄获取目标窗口的大小位置,并设置自己的位置 -3. 主要是检测时间,在10毫秒以下很流畅 -4. 窗口关闭是根据目标句柄无效来判断 - -https://github.com/PyQt5/PyQt/blob/master/Demo/FollowWindow.py - -## 代码 - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2018年10月22日 -@author: Irony -@site: https://github.com/892768447 -@email: 892768447@qq.com -@file: FollowWindow -@description: -""" -import os - -from PyQt5.QtCore import QTimer -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton -import win32gui - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - layout = QVBoxLayout(self) - layout.addWidget(QPushButton('test', self)) - self.tmpHwnd = None - # 启动定时器检测记事本的位置大小和是否关闭 - self.checkTimer = QTimer(self, timeout=self.checkWindow) - self.checkTimer.start(10) # 10毫秒比较流畅 - - def checkWindow(self): - # 查找 - hwnd = win32gui.FindWindow('Notepad', None) - if self.tmpHwnd and not hwnd: - # 表示记事本关闭了 - self.checkTimer.stop() - self.close() # 关闭自己 - return - if not hwnd: - return - self.tmpHwnd = hwnd - # 获取位置 - rect = win32gui.GetWindowRect(hwnd) - print(rect) - self.move(rect[2], rect[1]) - - -if __name__ == '__main__': - import sys - from PyQt5.QtWidgets import QApplication - # 先检测是否已有记事本打开 - hwnd = win32gui.FindWindow('Notepad', None) - print('hwnd', hwnd) - if not hwnd: - # 启动记事本 - os.startfile('notepad') - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) -``` - -## 效果图 - -![FollowWindow](/PyQt/Demo/ScreenShot/FollowWindow.gif) \ No newline at end of file diff --git a/source/_posts/issignalconnected.md b/source/_posts/issignalconnected.md deleted file mode 100644 index c99fd484..00000000 --- a/source/_posts/issignalconnected.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -author: Irony -title: PyQt5判断信号是否连接 -date: 2019-04-26 22:06:26 -tags: - - PyQt - - 信号 -categories: 教程 ---- - -在`PyQt`中某些情况下需要取消原来的信号连接,此时需要使用`disconnect`方法,但是在逻辑不严谨的情况下可能会导致多次调用`disconnect`方法而导致报错,当然可以通过try except来包裹代码。这里通过 `isSignalConnected` 来判断信号是否连接。 - - -在QOjbect文档中这样写到: - -```c++ -static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged); -if (isSignalConnected(valueChangedSignal)) { - QByteArray data; - data = get_the_value(); // expensive operation - emit valueChanged(data); -} -``` - -通过直接传入信号就行了,但是这在PyQt中不可行。需要这么做 - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2019年2月24日 -@author: Irony -@site: https://pyqt5.com https://github.com/892768447 -@email: 892768447@qq.com -@file: IsSignalConnected -@description: 判断信号是否连接 -""" - -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2019 Irony' -__Version__ = 1.0 - - -class Window(QWidget): - -    def __init__(self, *args, **kwargs): -        super(Window, self).__init__(*args, **kwargs) -        layout = QVBoxLayout(self) -        self.button1 = QPushButton('已连接', self, clicked=self.doTest) -        self.button2 = QPushButton('未连接', self) -        self.retView = QTextBrowser(self) -        layout.addWidget(self.button1) -        layout.addWidget(self.button2) -        layout.addWidget(self.retView) - -    def doTest(self): -        self.retView.append(""" -        # button1 clicked 是否连接: %s -        # button2 clicked 是否连接: %s -        """ % ( -            self.isSignalConnected(self.button1, 'clicked()'), -            self.isSignalConnected(self.button2, 'clicked()') -        )) - -    def isSignalConnected(self, obj, name): -        """判断信号是否连接 -        :param obj:        对象 -        :param name:       信号名,如 clicked() -        """ -        index = obj.metaObject().indexOfMethod(name) -        if index > -1: -            method = obj.metaObject().method(index) -            if method: -                return obj.isSignalConnected(method) -        return False - - -if __name__ == '__main__': -    import sys -    from PyQt5.QtWidgets import QApplication -    app = QApplication(sys.argv) -    w = Window() -    w.show() -    sys.exit(app.exec_()) -``` - -## 效果图 - -![IsSignalConnected](/PyQt/Demo/ScreenShot/IsSignalConnected.png) \ No newline at end of file diff --git a/source/_posts/jumpslider.md b/source/_posts/jumpslider.md deleted file mode 100644 index ff9b10ca..00000000 --- a/source/_posts/jumpslider.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -author: Irony -title: PyQt5之QSlider滑动条点击定位 -date: 2018-11-05 23:12:26 -tags: - - PyQt - - 滑动条 -categories: 例子 ---- - -`QSlider` 在通常情况下支持鼠标点击可以任意拖动,或者鼠标点击则往鼠标点击的方向移动一小格,这种移动一小格通常情况下用起来很不方便,比如我要做一个播放器的播放进度条,肯定是点击某个位置就直接跳到该位置,为此需要对 `QSlider` 的鼠标事件`mousePressEvent`进行重写。 - - -## 实现方法 - -一般的想法就是重写`mousePressEvent`后,得到鼠标点击的x和y点然后进行比例换算,再通过`setValue`来设置值,其实`QSlider`的`style`里面是有一个`sliderValueFromPosition`方法来计算值的。直接调用这个方法即可。 - -1. 首先通过`QSlider.style().subControlRect`方法计算得到滑块的区域,当鼠标点击区域在此次时则交给系统自己处理(比如按住不放拖动) -2. 通过`orientation`判断滑动条的方向(横竖) -3. 通过`invertedAppearance`判断滑动条是否反向(左右、上下) -4. 通过`QSlider.style().sliderValueFromPosition(最小值, 最大值, x或者y坐标, 宽度或者高度)`来计算得到值 -5. 最后通过`setValue`来设置值 - -## 代码 - -https://github.com/PyQt5/PyQt/blob/master/QSlider/ClickJumpSlider.py - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2018年11月5日 -@author: Irony -@site: https://pyqt5.com https://github.com/892768447 -@email: 892768447@qq.com -@file: JumpSlider -@description: -""" -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle, QWidget,\ - QFormLayout, QLabel - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - - -class JumpSlider(QSlider): - - def mousePressEvent(self, event): - # 获取上面的拉动块位置 - option = QStyleOptionSlider() - self.initStyleOption(option) - rect = self.style().subControlRect( - QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self) - if rect.contains(event.pos()): - # 如果鼠标点击的位置在滑块上则交给Qt自行处理 - super(JumpSlider, self).mousePressEvent(event) - return - if self.orientation() == Qt.Horizontal: - # 横向,要考虑invertedAppearance是否反向显示的问题 - self.setValue(self.style().sliderValueFromPosition( - self.minimum(), self.maximum(), - event.x() if not self.invertedAppearance() else (self.width( - ) - event.x()), self.width())) - else: - # 纵向 - self.setValue(self.style().sliderValueFromPosition( - self.minimum(), self.maximum(), - (self.height() - event.y()) if not self.invertedAppearance( - ) else event.y(), self.height())) - - -class TestWindow(QWidget): - - def __init__(self, *args, **kwargs): - super(TestWindow, self).__init__(*args, **kwargs) - layout = QFormLayout(self) - - self.label1 = QLabel('0', self) - layout.addRow(self.label1, JumpSlider( - Qt.Horizontal, valueChanged=lambda v: self.label1.setText(str(v)))) - - # 横向-反向显示 - self.label2 = QLabel('0', self) - layout.addRow(self.label2, JumpSlider( - Qt.Horizontal, invertedAppearance=True, - valueChanged=lambda v: self.label2.setText(str(v)))) - - self.label3 = QLabel('0', self) - layout.addRow(self.label3, JumpSlider( - Qt.Vertical, minimumHeight=200, valueChanged=lambda v: self.label3.setText(str(v)))) - - # 纵向反向显示 - self.label4 = QLabel('0', self) - layout.addRow(self.label4, JumpSlider( - Qt.Vertical, invertedAppearance=True, - minimumHeight=200, valueChanged=lambda v: self.label4.setText(str(v)))) - - -if __name__ == '__main__': - import sys - import cgitb - sys.excepthook = cgitb.enable(1, None, 5, '') - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = TestWindow() - w.show() - sys.exit(app.exec_()) -``` - -## 效果图 - -![ClickJumpSlider](/PyQt/QSlider/ScreenShot/ClickJumpSlider.gif) - diff --git a/source/_posts/likehtmleffect.md b/source/_posts/likehtmleffect.md deleted file mode 100644 index 02800ced..00000000 --- a/source/_posts/likehtmleffect.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -author: Irony -title: PyQt5仿网页图片鼠标移动特效 -date: 2018-10-23 17:57:03 -tags: - - PyQt - - 特效 -categories: 例子 ---- - -em,就是类似于那种游戏官网首页的图片,鼠标放上去后来回移动,图片的前景和背景错位移动。 - - -## 原理分析 - -1. 2张一样大小的透明图片,1张作为背景,一张作为前景(比如说人物)。 -2. 当鼠标往左移动时,前景人物跟着往左移动,背景往右移动 -3. 计算好偏移量(见代码中) - -https://github.com/PyQt5/PyQt/blob/master/QLabel/ImageSlipped.py - -## 关键代码 - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2018年10月18日 -@author: Irony -@site: https://pyqt5.com https://github.com/892768447 -@email: 892768447@qq.com -@file: ImageSlipped -@description: -""" -from PyQt5.QtGui import QPixmap, QPainter -from PyQt5.QtWidgets import QWidget - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - - -class SlippedImgWidget(QWidget): - - def __init__(self, bg, fg, *args, **kwargs): - super(SlippedImgWidget, self).__init__(*args, **kwargs) - # 开启鼠标跟踪 - self.setMouseTracking(True) - # 背景 - self.bgPixmap = QPixmap(bg) - # 前景 - self.pePixmap = QPixmap(fg) - # 最小尺寸(背景右边和下方隐藏10个像素) - size = self.bgPixmap.size() - self.setMinimumSize(size.width() - 10, size.height() - 10) - self.setMaximumSize(size.width() - 10, size.height() - 10) - # 分成10份用于鼠标移动判断 - self.stepX = size.width() / 10 - self.stepY = size.height() / 10 - # 偏移量 - self._offsets = [-4, -4, -4, -4] # 背景(-4,-4),前景(-4,-4) - - def mouseMoveEvent(self, event): - super(SlippedImgWidget, self).mouseMoveEvent(event) - pos = event.pos() - - # 偏移量 - offsetX = 5 - int(pos.x() / self.stepX) - offsetY = 5 - int(pos.y() / self.stepY) - self._offsets[0] = offsetX - self._offsets[1] = offsetY - self._offsets[2] = offsetX - self._offsets[3] = offsetY - # 刷新 - self.update() - - def paintEvent(self, event): - super(SlippedImgWidget, self).paintEvent(event) - # 绘制图形 - painter = QPainter(self) - painter.setRenderHint(QPainter.Antialiasing) - # 左上角偏移5个像素画背景图片 - painter.drawPixmap( - -5 + self._offsets[0], - -5 + self._offsets[1], self.bgPixmap) - # 右下角偏移5个像素画前景图片 - painter.drawPixmap( - self.width() - self.pePixmap.width() + 5 - self._offsets[2], - self.height() - self.pePixmap.height() + 5 - self._offsets[3], - self.pePixmap - ) - - -if __name__ == '__main__': - import sys - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = SlippedImgWidget('images/bg.png', 'images/fg.png') - w.show() - sys.exit(app.exec_()) -``` - -## 效果图 - -![ImageSlipped](/PyQt/QLabel/ScreenShot/ImageSlipped.gif) \ No newline at end of file diff --git a/source/_posts/macm1pyqt.md b/source/_posts/macm1pyqt.md deleted file mode 100644 index f8e6a13e..00000000 --- a/source/_posts/macm1pyqt.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -author: Irony -title: 如何在Mac M1上快速安装PyQt5 -date: 2023-10-07 14:08:06 -tags: - - PyQt - - Mac - - M1 -categories: 笔记 ---- - -由于官方并没有在M1上编译PyQt导致安装存在一些问题。 -M1上的Python不能直接使用x64的 PyQt5。但是M1上可以运行x64的Python。所以通过安装x64的Python然后再安装PyQt5即可。 - - -**1. 安装Python** -[python-3.9.13-macosx10.9.pkg](https://www.python.org/ftp/python/3.9.13/python-3.9.13-macosx10.9.pkg) - -**2. 勾选自定义同时只勾选安装pip** - -![step1.png](/images/macpyqtstep1.png) - -![step1.png](/images/macpyqtstep2.png) - -**3. 设置pip源** -```shell -/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi -/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna -``` - -**4. 安装PyQt5** -```shell -/Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5 -``` - -**5. 测试** -```shell -/Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -``` - -![step3.png](/images/macpyqtstep3.png) - - - -📢📢📢 - -也可以直接安装 [Miniconda](https://docs.anaconda.com/free/miniconda/miniconda-install/) - -然后:conda install -c conda-forge pyqt diff --git a/source/_posts/mselectmenu.md b/source/_posts/mselectmenu.md deleted file mode 100644 index c4a0036d..00000000 --- a/source/_posts/mselectmenu.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -author: Irony -title: PyQt5菜单之多选功能 -date: 2018-10-25 09:53:34 -tags: - - PyQt - - 菜单 -categories: 例子 ---- - -有时候会遇到这种需求:在界面某个位置弹出一个菜单,其中里面的菜单项可以多选(类似配置选项),此时用`QMenu`会遇到点击一个菜单项就会自动关闭,当然可以通过其他方式实现该功能,不过这里就采用`QMenu`通过特殊的方式来实现该需求。 - - -## 需求 - -要实现的效果: - -1. 菜单1 -2. 菜单2 -3. 菜单3 -4. 菜单4 - -点击菜单1、2、3可以多选不关闭菜单 - -点击菜单4可以勾选,并且关闭菜单 - -## 原理 - -1. 设置菜单项可勾选:通过`QAction.setCheckable(True)`方法实现 -2. 设置菜单不可关闭:通过覆盖QMenu的鼠标释放`mouseReleaseEvent`方法(可直接替换或者通过`installEventFilter`安装事件过滤器实现) -3. 在菜单的鼠标释放事件中,当点击菜单项后是通过点击点坐标来查找是否有`QAction`,然后触发对应的`QAction`。 -4. 故在没有`QAction`的地方则直接交还给`QMenu`自行处理逻辑,在有`QAction`的地方可以根据自己的需求进行处理(如上所提) - -## 代码 - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2018年10月24日 -@author: Irony -@site: https://github.com/892768447 -@email: 892768447@qq.com -@file: 菜单多选不关闭 -@description: -""" -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QMenu,\ - QAction - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - layout = QVBoxLayout(self) - self.labelInfo = QLabel(self) - self.button = QPushButton('带按钮的菜单', self) - layout.addWidget(self.labelInfo) - layout.addWidget(self.button) - - # 添加菜单 - self._initMenu() - - def _initMenu(self): - # 创建菜单 - self._menu = QMenu(self.button) - # 替换menu的鼠标释放事件达到选择性不关闭菜单 - self._menu.mouseReleaseEvent = self._menu_mouseReleaseEvent - self._menu.addAction('菜单1', self._checkAction) - self._menu.addAction('菜单2', self._checkAction) - self._menu.addAction( - QAction('菜单3', self._menu, triggered=self._checkAction)) - action = QAction('菜单4', self._menu, triggered=self._checkAction) - # 添加自定义的属性,判断该属性可以关闭菜单 - action.setProperty('canHide', True) - self._menu.addAction(action) - for action in self._menu.actions(): - # 循环设置可勾选 - action.setCheckable(True) - self.button.setMenu(self._menu) - - def _menu_mouseReleaseEvent(self, event): - action = self._menu.actionAt(event.pos()) - if not action: - # 没有找到action就交给QMenu自己处理 - return QMenu.mouseReleaseEvent(self._menu, event) - if action.property('canHide'): # 如果有该属性则给菜单自己处理 - return QMenu.mouseReleaseEvent(self._menu, event) - # 找到了QAction则只触发Action - action.activate(action.Trigger) - - def _checkAction(self): - # 三个action都响应该函数 - self.labelInfo.setText('\n'.join(['{}\t选中:{}'.format( - action.text(), action.isChecked()) for action in self._menu.actions()])) - - -if __name__ == '__main__': - import sys - import cgitb - sys.excepthook = cgitb.enable(1, None, 5, 'text') - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.resize(400, 400) - w.show() - sys.exit(app.exec_()) -``` - -## 效果图 - -![MultiSelect](/PyQt/QMenu/ScreenShot/MultiSelect.gif) \ No newline at end of file diff --git a/source/_posts/pageswitching.md b/source/_posts/pageswitching.md deleted file mode 100644 index 99fa4136..00000000 --- a/source/_posts/pageswitching.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -author: Irony -title: PyQt5之图片轮播 -date: 2018-11-24 21:45:06 -tags: - - PyQt - - 轮播 - - 动画 -categories: 例子 ---- - -之前看到了`QStackedWidget`做切换动画,让界面不那么生硬,于是参考了 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt 做了一个`QStackedWidget`的切换动画,然后利用`QStackedWidget`结合多个`QLabel`显示图片来做一个轮播效果。 - -其实在写之前也在网上找了很多例子,参看过后发现大多例子都是利用到了`paintEvent`去绘制,这样其实还是比较麻烦,个人觉得更好的方式是使用`QPropertyAnimation`属性动画修改控件中`QLabel`图片控件的pos位置属性就可以达到移动效果了。 - - -1. 比较核心的算法就是要计算当前页面和下一个页面的位置偏移量,比如: - -```python -# 计算偏移量 -offsetX = self.frameRect().width() -offsetY = self.frameRect().height() -w_next.setGeometry(0, 0, offsetX, offsetY) - -if direction == self.BOTTOM2TOP: - offsetX = 0 - offsetY = -offsetY -elif direction == self.TOP2BOTTOM: - offsetX = 0 -elif direction == self.RIGHT2LEFT: - offsetX = -offsetX - offsetY = 0 -elif direction == self.LEFT2RIGHT: - offsetY = 0 - -# 重新定位显示区域外部/旁边的下一个窗口小部件 -pnext = w_next.pos() -pnow = w_now.pos() -self._pnow = pnow - -# 移动到指定位置并显示 -w_next.move(pnext.x() - offsetX, pnext.y() - offsetY) -w_next.show() -w_next.raise_() -``` - -2. 其次是对这两个页面增加关联`pos`属性的`QPropertyAnimation`动画,然后加入到并行动画组`QParallelAnimationGroup`中再启动即可。 - -3. 对`QStackedWidget`的`setCurrentIndex`和`setCurrentWidget`这两个函数进行了覆盖重写达到及时手动调用这两个函数也会产生动画效果的目的。 - -## 代码 - -https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/PageSwitching.py - -## 效果图 - -![PageSwitching](/PyQt/QPropertyAnimation/ScreenShot/PageSwitching.gif) \ No newline at end of file diff --git a/source/_posts/processinclass_625781186.md b/source/_posts/processinclass_625781186.md deleted file mode 100644 index 51ea2dbd..00000000 --- a/source/_posts/processinclass_625781186.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -author: 不许人间见白头 -title: python 在类里使用进程池 -date: 2018-11-16 21:37:31 -tags: - - 进程 -categories: 笔记 ---- - -1. 首先, 进程池的作用就是减少进程的创建和释放 开销的, 所以在类中作为局部变量是不合适的; -2. 其次, 进程池必须在`if __name__ == "__main__" `里 ,否则会报 frozen_ 什么什么的错误;(这一点可能解释有误); - - -3. 然后, 线程池的`apply_async`中如果传入`self.xxx`方法,会报`multiprocessing.Pool pickling error`什么的错误, 具体解释见https://blog.csdn.net/dutsoft/article/details/70336462, 里面有解决方法,但是我没有成功(最开始测试没有现在理解的透彻, 不过应该是可以的); 由于第1点 不合理, 所以有什么办法在类 函数中获取 进程池对象po的地址: - -![processinclass1](/images/processinclass1.png) - -我的解决思路和方法是: -1. 通过globals() 取得全局变量 , 测试证明 :不同文件的`globals()`是不同的: 如`Tab2.py `的 `globals()` 和` main_extra_func_file.py`中的 `globals() `是不同的 , 所以 这样在`Tab2.py`中取不到po对象; -2. 通过`__main__.po` 来获取 (为什么会想到这个呢, 因为有时候导包 import .xxx 和import xxx 会报 `__main__` 没有什么属性什么的): - -```python -def getPoolObject(): -# po 的名字在main函数中定义 -# __main__ 模块在sys.modules 的键是"__mp_main__" - return sys.modules["__mp_main__"].po -``` - -ps : (图没截好 , `rglob_worker` 是外部函数 , 非类内函数 ,po = getPoolBojcet() 这一行是类内函数 ,红色箭头 2. 在的那条白色分割线 是2个函数。 ) - -![processinclass2](/images/processinclass2.png) - -`len(po._cache) == 1` : po._cache 是当前有任务的进程数, ==1表示所有任务结束; 利用回调 , 可以更轻松地进行进程通信。 diff --git a/source/_posts/pyqt5_hook_key_625781186.md b/source/_posts/pyqt5_hook_key_625781186.md deleted file mode 100644 index 48747315..00000000 --- a/source/_posts/pyqt5_hook_key_625781186.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -author: 人间白头  -title: 在pyqt中使用python全局钩子模块 -date: 2019-07-07 01:37:22 -tags: - - Python - - pyqt hook key -categories: 随笔 ---- - -在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像QQ一样可以全局热键截图。这里介绍几个方法实现在PyQt中使用Python全局钩子模块实现全局热键功能。 - - - - -1. `pyHook3` - -安装命令 : `pip install pyhook3` - -[https://blog.csdn.net/q871063970/article/details/86648386](https://blog.csdn.net/q871063970/article/details/86648386) - -似乎将pyhook支持py3版本的了? 没有太多研究. - -缺点: 只支持win平台. - -2.`keyboard` & `mouse` - -安装命令: `pip install keyboard mouse` -``` - -from PyQt5 import QtGui, QtWidgets, QtCore -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -import keyboard -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - layout = QVBoxLayout(self) - self.testBtn = QPushButton(self) - layout.addWidget(self.testBtn) - - keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey')) - keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc")) - - def abc(self,a,b,c): - print(a,b,c) - -if __name__ == '__main__': - import sys - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) -``` - -更详细例子 : [pyqt中使用keyboard全局热键](https://github.com/PyQt5/PyQt/blob/63c6376358acb1863313fb5593097e6e0210cad6/Test/%E5%85%A8%E5%B1%80%E7%83%AD%E9%94%AE/HotKey.py) - -优点: 跨平台 ; - -缺点: 模块名字取得太差, 不容易被发现. diff --git a/source/_posts/pyqt5asyncio.md b/source/_posts/pyqt5asyncio.md deleted file mode 100644 index fab3b80b..00000000 --- a/source/_posts/pyqt5asyncio.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -author: Irony -title: PyQt5结合Asyncio异步 -date: 2018-10-24 14:32:26 -tags: - - PyQt - - Asyncio - - 异步 -categories: 例子 ---- - -今天尝试了下[quamash](https://github.com/harvimt/quamash)框架,该框架是一个`PyQt`的异步事件循环封装库,使用Python3+ 的`asyncio`这个异步库。在看了该项目的内容后发现只有一个简单的进度条例子,故尝试用其来下载网络图片并显示。 - - -## 安装依赖 - -1. pip install quamash -2. pip install aiohttp -3. Python3.5+ 和 PyQt5 - -这里使用`aiohttp`是因为它基于`asyncio`封装的网络操作库,常见的`get`、`post`等方法,不过它只支持Python3.5及以上的版本,主要是它使用了async def 这样的语法。 - -## 说明 - -1. 在创建`QApplication`后随即设置替换事件循环`loop` -```python -app = QApplication(sys.argv) -loop = QEventLoop(app) -asyncio.set_event_loop(loop) -w = Window() -``` -2. 通过`asyncio.ensure_future(func(), loop=loop)`来执行某个异步函数 - -## 流程 - -| | | | -|:----------|:--:|----------------------------:| -| Window | → | initSession(初始化session) | -| ↓ | | | -| 下载按钮 | → | doDownload(执行_doDownload方法) | -| ↓ | | | -| session.get(下载json数据进行解析) | | | -| ↓ | | | -| 添加到界面 | ← | _doDownloadImage(对单张图片进行下载) | - -## 源码 - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2018年10月24日 -@author: Irony -@site: https://github.com/892768447 -@email: 892768447@qq.com -@file: AsyncioUiClient -@description: -""" -import asyncio - -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap, QMovie -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton,\ - QApplication, QListWidget, QListWidgetItem, QLabel, QMessageBox -import aiohttp -from quamash import QEventLoop - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = "Copyright (c) 2018 Irony" -__Version__ = "Version 1.0" - -Url = 'https://www.doutula.com/api/search?keyword=%E6%9C%80%E6%96%B0%E8%A1%A8%E6%83%85&mime=0&page={}' -Headers = { - ':authority': 'www.doutula.com', - ':method': 'GET', - ':scheme': 'https', - 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', - 'accept-language': 'zh-CN,zh;q=0.9', - 'cache-control': 'max-age=0', - 'dnt': '1', - 'upgrade-insecure-requests': '1', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6756.400 QQBrowser/10.2.2498.400' -} - - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - layout = QVBoxLayout(self) - self.listWidget = QListWidget(self) - self.listWidget.setSpacing(2) # item直接的间隔 - # 隐藏横向滚动条 - self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - # 让list 从左到右排列 - self.listWidget.setFlow(self.listWidget.LeftToRight) - # 自动换行 - self.listWidget.setWrapping(True) - self.listWidget.setResizeMode(self.listWidget.Adjust) - - self.buttonMsg = QPushButton('弹出提示框', self, clicked=self.showMessage) - self.buttonDown = QPushButton('下载图片', self, clicked=self.doDownload) - layout.addWidget(self.listWidget) - layout.addWidget(self.buttonMsg) - layout.addWidget(self.buttonDown) - self.currentPage = 0 - self.initSession() # 其实没必要,session主要用在需要登录的网站。缓存cookie用 - - def initSession(self): - async def _initSession(): - # 初始化session - self.session = aiohttp.ClientSession(loop=loop) - print(self.session) - asyncio.ensure_future(_initSession(), loop=loop) - - async def _doDownloadImage(self, url): - # 下载图片并添加到界面 - async with self.session.get(url) as resp: - data = await resp.read() - if not data: - print('下载失败: ', url) - return - path = os.path.join('tmp', os.path.basename(url)) - with open(path, 'wb') as fp: - fp.write(data) - item = QListWidgetItem(url, self.listWidget) - image = QPixmap(path) - item.setSizeHint(image.size()) - label = QLabel(self.listWidget) - label.setPixmap(image) - if path.endswith('.gif'): # 可能是动态图 - label.setMovie(QMovie(path)) - self.listWidget.setItemWidget(item, label) - self.listWidget.scrollToBottom() - - async def _doDownload(self): - # 下载工作 - if self.currentPage == -1: - QMessageBox.information(self, '提示', '已经没有更多了') - return - self.currentPage += 1 - url = Url.format(self.currentPage) - print('get url: ', url) - async with self.session.get(url, headers=Headers) as resp: - data = await resp.json() - if not data: - return - data = data.get('data', None) - if not data: - self.currentPage = -1 - print('已经是最后一页了') - return - # 解析json并生成item添加到界面中 - for entity in data.get('list', []): - url = entity.get('image_url', None) - if not url: - continue - await self._doDownloadImage(url) # 下载图片 - - def doDownload(self): - # 响应按钮点击调用 - asyncio.ensure_future(self._doDownload(), loop=loop) - - def showMessage(self): - # 显示对话框 - app.aboutQt() - - def closeEvent(self, event): - if not self.session.closed: - asyncio.ensure_future(self.session.close(), loop=loop) - super(Window, self).closeEvent(event) - - -if __name__ == '__main__': - import sys - import cgitb - import os - os.makedirs('tmp', exist_ok=True) - sys.excepthook = cgitb.enable(1, None, 5, 'text') - app = QApplication(sys.argv) - loop = QEventLoop(app) - asyncio.set_event_loop(loop) - w = Window() - w.show() - with loop: - loop.run_forever() -``` - -## 效果图 - -![pyqt5asyncio](/images/pyqt5asyncio.gif) \ No newline at end of file diff --git a/source/_posts/pyqt_get_subprocess_pipeline_625781186.md b/source/_posts/pyqt_get_subprocess_pipeline_625781186.md deleted file mode 100644 index 1c10fc05..00000000 --- a/source/_posts/pyqt_get_subprocess_pipeline_625781186.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -author: 人间白头  -title: python 获取子进程print信息   -date: 2019-05-24 14:39:44 -tags: - - Python - - subprocess.Popen - - 拦截print -categories: 随笔 ---- - -在PyQt中使用子线程读取子进程Python脚本的print输出流内容。 - - - -问题所在: - -![image.png](https://i.loli.net/2019/05/24/5ce793171984f27031.png) - -如果模块都由自己开发, 正常操作 - -![image.png](https://i.loli.net/2019/05/24/5ce7933994a0090037.png) - -但是因为不能改, 所以只能拦截: -代码: -```python -pythonPath = self.pythonPath_cb.currentText() - -if suffix == "py": - # 首次 - self.pyCommand = [pythonPath, path] - self.modifiedReloadPython(path) -def modifiedReloadPython(self, path_): - os.chdir(os.path.dirname(path_)) - # 子进程调用 - self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - # self.stdoutWorker.p = self.p - self.stdoutWorker = Worker(self.p) - self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append("PYDEBUG:\n" + x)) - self.stdoutWorker.start() -class Worker(QThread): - stdout_signal = pyqtSignal(str) - - def __init__(self, p, parent=None): - super().__init__(parent) - self.p = p - - def run(self): - while True: - QApplication.processEvents() - if self.p is not None: - line = self.p.stdout.readline() - # line = line.strip() - if line != b'': - try: - info = line.decode() - self.stdout_signal.emit(info) - except: - self.stdout_signal.emit(repr(line)) - -``` diff --git a/source/_posts/pyqtclient.md b/source/_posts/pyqtclient.md deleted file mode 100644 index 00a056f2..00000000 --- a/source/_posts/pyqtclient.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -author: Irony -title: PyQtClient例子客户端 -date: 2019-02-02 15:15:06 -sticky: 999 -tags: - - PyQt -categories: 随笔 ---- - -![1.gif](/images/1.gif)对本博客所写的项目PyQt例子进行一个客户端的编写,客户端主要实现一些动画效果,更换皮肤,运行例子等功能。![2.gif](/images/2.gif) - - -## 项目地址 - -
    - -## Windows客户端下载 - -1. [包含部分例子](https://github.com/PyQt5/PyQtClient/releases/download/1.0.1/PyQtClient-x86-win32-exe.7z) -2. [不包含例子](https://github.com/PyQt5/PyQtClient/releases/download/1.0.1/PyQtClient-x86-win32-exe.7z) -3. [百度网盘](https://pan.baidu.com/s/14j9tMqGlAy_8y3067xh-vw) 提取码: nadv - -## 效果图 - -![PyQtClient](https://github.com/PyQt5/PyQtClient/raw/master/ScreenShot/PyQtClient.gif) - diff --git a/source/_posts/pyqtclientmac.md b/source/_posts/pyqtclientmac.md deleted file mode 100644 index 9a69b69a..00000000 --- a/source/_posts/pyqtclientmac.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -author: myphper -title: 在Mac上以正确的姿势使用PyQtClient看Demo -date: 2019-04-02 17:18:43 -tags: - - PyQt -categories: 教程 ---- - -由于PyQtClient只提供了Windows的版本,这里记录下编译后在Mac上运行。 - - -## 下载项目 - -安装git略。没有的东西可以都先去试试brew install xxx。没安装homebrew的建议使用搜索引擎 - -```git clone https://github.com/PyQt5/PyQtClient.git``` - -## 配置环境 - -1. 打开IDE配置python环境,使用anaconda比较方便 -2. 推荐用pycharm,我是习惯了用idea。anaconda安装可以去官网下载。 -3. 环境原因,选择新建一个 python 3.6 p.s. 我取的环境名字就是 3.6 所以后面的3.6其实是这个原因 - -## conda源 - -最好是加环境变量,不加也可以,就是以后用到的都需要指定路径,不太常用,我就没加 - -``` -~/anaconda3/bin/conda config --add channels conda-forge -~/anaconda3/bin/conda config --add channels defaults -~/anaconda3/bin/conda config --add channels r -~/anaconda3/bin/conda config --add channels bioconda -~/anaconda3/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ -``` - -## pip源 - -``` -mkdir ~/.pip && vim ~/.pip/pip.conf -``` - -``` -[global] -index-url = http://mirrors.aliyun.com/pypi/simple/ -[install] -trusted-host = mirrors.aliyun.com -``` - -## 安装编译依赖 - -``` -~/.conda/envs/3.6/bin/pip install -r PyQtClient/requirements.txt -``` - -运行提示没有webkit,开始手动编译 - -1. `wget http://download.qt.io/archive/qt/5.9/5.9.0/qt-opensource-mac-x64-5.9.0.dmg` -2. `wget https://github.com/annulen/webkit/releases/download/qtwebkit-5.212.0-alpha2/qtwebkit-5.212.0_alpha2-qt59-darwin-x64.tar.xz` -3. `wget https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.10.1/PyQt5_gpl-5.10.1.zip` -4. `wget https://www.riverbankcomputing.com/static/Downloads/sip/4.19.8/sip-4.19.8.tar.gz` -5. 编译sip:`~/.conda/envs/3.6/bin/python configure.py --platform macx-g++ && make && sudo make install` -6. 编译Webkit.so 没有qmake 和 sip的环境变量, 所以后面都是手动指定的 -``` -~/.conda/envs/3.6/bin/python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc --qmake=~/Qt5.9.0/5.9/clang_64/bin/qmake --sip=~/.conda/3.6/bin/sip && make && sudo make install -``` - -## 插曲 - -1. libcurl版本要求10.0,而我的是9.0,原因是前面我自己摸索,乱装依赖,所以遇到了 -``` -~/anaconda3/bin/conda install -n 3.6 -c conda-forge libcurl -``` -2. 结果这个libcurl 10.0.0 是装上了,可是pygit2版本不对了,conda给升级了,PyQtClient里requirements.txt要求这个包的版本(pygit2==0.27.2)几乎决定了其他的环境版本。后来还是老实的用conda去装了。这个连python版本什么的都会跟着变的。最后降级的结果是python 3.6.7 -``` -~/anaconda3/bin/conda install -n 3.6 -c conda-forge libgit2==0.27.2 -``` - -至此总算是启动正常了。 \ No newline at end of file diff --git a/source/_posts/pytest_qt_modal_625781186.md b/source/_posts/pytest_qt_modal_625781186.md deleted file mode 100644 index 6bb21a0d..00000000 --- a/source/_posts/pytest_qt_modal_625781186.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -author: 人间白头 -title: pytest-qt 测试模态窗体. -date: 2020年4月27日22:19:27 -tags: - - pytest-qt -categories: 教程 ---- - -步骤分别是 : - -1. 点击 开始扫描 弹出 选择路径窗口 ; - -2. 勾选路基 ; - -3.点击确定 ; - - -大概想测一下这个界面 : - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-c1ed0bd52808c5b8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -步骤分别是 : - -1. 点击 开始扫描 弹出 选择路径窗口 ; - -2. 勾选路基 ; - -3.点击确定 ; - -需要测试的函数 : - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-b5265859d74b269d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -测试函数 : - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-58f4310a54a815ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -可以发现断言失败 . - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-939c73b5be19c21e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -官方文档 : 测试模态窗体. - -[https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html](https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html) - -用的是官方的 `monkeypatch` 方式 . - -大致意思就是替换 `FileSelectPathDialog` 类的exec函数. diff --git a/source/_posts/python_statemachine_625781186.md b/source/_posts/python_statemachine_625781186.md deleted file mode 100644 index 42d4f0a4..00000000 --- a/source/_posts/python_statemachine_625781186.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -author: 人间白头  -title: python 状态机模块   -date: 2019-7-17 17:03:33 -tags: - - Python - - python 状态机 - -categories: 随笔 ---- - -用状态来取代if...else判断。 - - - -GUI涉及到挺多的状态改变 , 以前一直用 if...else 来判断 , 最近读了设计模式 ,发现有个状态模式 , 随后发现了状态机这个东西 . - -python的状态机模块挺多的 , 不过好像很多都不更新了. -推荐2个状态机模块 , 但是也没有太深入的使用经验 , 就跑跑例子 , 以后有更详细的pyqt例子再补上 . - -1: `pip install python-statemachine` - -官方例子 : [https://github.com/fgmacedo/python-statemachine](https://github.com/fgmacedo/python-statemachine) - -2.`pip install state_machine` - -官方例子 : [https://github.com/jtushman/state_machine](https://github.com/jtushman/state_machine) - -1的 最近一次更新在6个月以前 , 使用`类继承`和`mixin`方式 , 不过有些地方不如2个人性化; - -2的设计更人性化一些 , 包括状态改变`before`和 `after` , 不过由于是装饰器实现的动态增加属性 , 有些地方编辑器智能提示可能就靠不上了. - -两者实现实现方式不一样 , 有兴趣可以读读源码 . - -3. qt内置状态机框架 - -https://blog.csdn.net/amnes1a/article/details/62418196 - -https://blog.csdn.net/dongfenghuojian/article/details/78187131 - -http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程) diff --git a/source/_posts/qtninepatch.md b/source/_posts/qtninepatch.md deleted file mode 100644 index 61fa68c6..00000000 --- a/source/_posts/qtninepatch.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -author: Irony -title: PyQt5显示.9格式的PNG图片 -date: 2018-10-26 10:00:08 -tags: - - PyQt - - 图片 - - 气泡 - - .9png -categories: 例子 ---- - -做过安卓开发的和使用过QQ的都知道`.9.png`这种图片格式,效果就如QQ的聊天气泡一样可以拉伸,这种格式的图片允许开发人员定义可扩展区域,当需要延伸图片以填充比图片本身更大区域时,可扩展区的内容被延展;允许开发人员定义内容显示区,用于显示文字或其他内容。目前在`Github`上有两个C++版本的,在这里我把它们都用Python实现了一遍。另外一个我也为`PyQt`提供了编译好的pyd文件。 - - -## C++版本 - -在Github开源库中搜索到两个C++版本的 - -1. 一个是 [NinePatchQt](https://github.com/Roninsc2/NinePatchQt) -2. 一个是 [QtNinePatch](https://github.com/soramimi/QtNinePatch) - -## PyQt5版本 - -这里也分为两个版本,都是基于上面的C++源码翻译改写过来的,具体的例子见项目里面的测试代码吧。 - -1. [QtNinePatch](https://github.com/PyQt5/PyQt/blob/master/QLabel/QtNinePatch.py)是参考第一个源码编写,用法是在`paintEvent`中调用 -2. [QtNinePatch2](https://github.com/PyQt5/PyQt/blob/master/QLabel/QtNinePatch2.py)是参考第二个源码编写,用法是`pixmap = QtNinePatch.createPixmapFromNinePatchImage(self.image, self.width(), self.height())`直接得到一个处理好的`QPixmap`对象来使用 - -## 说明 - -1. 建议优先使用pyd版本的(后续提供Python3.4 3.5 3.6 3.7 编译好的32为库文件),也可以自行编译,编译步骤见下文。 -2. 其次可以使用纯python版本2的(个人觉得方便调用) -3. 最后再考虑纯python版本1的吧 -4. 以上为个人意见,两个C++版本的写法不一样,但是核心算法应该是类似的。 - -## 自行编译 - -1. 首先要安装好Qt、PyQt5、编译安装对应的sip、对应的VC++编译工具 -2. 用Qt Creator 打开pro文件进行编译 -3. 进入源码中的sip文件夹修改configure.py文件 -``` -# 这里是你的VC版本和对应的Qt目录中的文件夹 -config.platform = "win32-msvc2010" -qt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010' -``` -4. 最后执行python configure.py来编译 - -## 下载 - -https://github.com/PyQt5/PyQt/tree/master/QLabel - -## 效果图 - -![NinePatchImage](/PyQt/QLabel/ScreenShot/NinePatchImage.gif) - diff --git a/source/_posts/qtwebjs.md b/source/_posts/qtwebjs.md deleted file mode 100644 index 511332ac..00000000 --- a/source/_posts/qtwebjs.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -author: Irony -title: QtWebkit和QWebEngineView与Javascript交互 -date: 2019-05-22 11:30:36 -tags: - - PyQt - - QWebView - - QWebEngineView - - 浏览器 -categories: 例子 ---- - -以前还是`QWebView`的时候和`Javascript`交互起来很方便,但是到了Qt5.6以后改用了`QWebEngineView`,并通过其提供的`qwebchannel.js`来进行交互。可能是由于刚出来的原因,这玩意儿有个bug就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对`QWebView`和`QWebEngineView`与Js交互都做了一个示例。 - - -## 说明 - -1. 针对`QWebView`通过`QWebFrame`的`addToJavaScriptWindowObject`把对象传递到`Javascript`中 -2. 针对`QWebEngineView`通过`QWebChannel.registerObject('Bridge', QObject)`把对象传递到`Javascript`中 -3. 可以通过`@pyqtSlot`装饰器来申明该方法可以暴露给`Javascript`调用 - -```python -@pyqtSlot(str) -def callFromJs(self, text): - QMessageBox.information(self, "提示", "来自js调用:{}".format(text)) -``` - -4. 针对`QWebView`在`Javascript`中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用 - -```javascript -// 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的) -Bridge.windowTitleChanged.connect({fun: function(title) { - showLog("标题被修改为:" + title); -}}, "fun"); - -// 绑定自定义的信号customSignal -Bridge.customSignal.connect({fun: function(text) { - showLog("收到自定义信号内容:" + text); -}}, "fun"); -``` - -5. 针对`QWebEngineView`在`Javascript`中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用 - -```javascript -new QWebChannel(qt.webChannelTransport, - function(channel) { - window.Bridge = channel.objects.Bridge; - - // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的) - Bridge.windowTitleChanged.connect(function(title) { - showLog("标题被修改为:" + title); - }); - - // 绑定自定义的信号customSignal - Bridge.customSignal.connect(function(text) { - showLog("收到自定义信号内容:" + text); - }); - } -); -``` - -## 代码 - -`QWebView`: https://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py - -`QWebEngineView`: https://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py - -1. 针对`QWebView`的核心实现 - -```python -class WebView(QWebView): - - customSignal = pyqtSignal(str) - - def __init__(self, *args, **kwargs): - super(WebView, self).__init__(*args, **kwargs) - self.initSettings() - # 暴露接口对象 - self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface) - - def _exposeInterface(self): - """向Js暴露调用本地方法接口 - """ - self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self) - - # 注意pyqtSlot用于把该函数暴露给js可以调用 - @pyqtSlot(str) - def callFromJs(self, text): - QMessageBox.information(self, "提示", "来自js调用:{}".format(text)) - - def sendCustomSignal(self): - # 发送自定义信号 - self.customSignal.emit('当前时间: ' + str(time())) -``` - -2. 针对`QWebEngineView`的核心实现 - -```python -class WebEngineView(QWebEngineView): - - customSignal = pyqtSignal(str) - - def __init__(self, *args, **kwargs): - super(WebEngineView, self).__init__(*args, **kwargs) - self.channel = QWebChannel(self) - # 把自身对象传递进去 - self.channel.registerObject('Bridge', self) - # 设置交互接口 - self.page().setWebChannel(self.channel) - - # 注意pyqtSlot用于把该函数暴露给js可以调用 - @pyqtSlot(str) - def callFromJs(self, text): - QMessageBox.information(self, "提示", "来自js调用:{}".format(text)) - - def sendCustomSignal(self): - # 发送自定义信号 - self.customSignal.emit('当前时间: ' + str(time())) -``` - - -## 效果图 - -![JsSignals](/PyQt/QWebEngineView/ScreenShot/JsSignals.gif) \ No newline at end of file diff --git a/source/_posts/read_open_source.md b/source/_posts/read_open_source.md deleted file mode 100644 index f419b9ec..00000000 --- a/source/_posts/read_open_source.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -author: 人间白头  -title: 像读文章一样读源码 -date: 2019-07-07 01:37:22 -tags: - - Python - - debug - - snoop -categories: 随笔 ---- - -使用snoop, 像读文章一样读源码。 - - - -不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。 -![image.png](https://upload-images.jianshu.io/upload_images/10769157-b274b7acaecf49bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -没有目的地概览 , 不知不觉就追究到细节里面去了。 - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-1304cc87fcd42cae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。 - -开始 : -1. python DEBUG 模块介绍 : - 前段时间看过挺多文章提到pysoonper这个调试模块 , 有兴趣的可以百度一下. -个人尝试了一下 , 篇幅过大的DEBUG不适合用 pysoonper , 因为没有缩进 ! - 这几天偶然遇到一个二次封装的模块[snoop]([https://github.com/alexmojaki/snoop](https://github.com/alexmojaki/snoop) -), 完美地解决了这个问题. -2. 操作步骤 : - -- 1 . 在`eric6.py`的`main()`函数上加snoop装饰器; -![image.png](https://upload-images.jianshu.io/upload_images/10769157-74129f6a6c303b25.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -- 2 . 用vscode 打开 `eric6start_.log` 文件 (8层深度log文件34W行, pycharm对大文件支持很差); -![log文件](https://upload-images.jianshu.io/upload_images/10769157-ae946c117a082c24.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -发现可以折叠 , 但是最大可折叠等级只到5级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log格式选得不太好, 不知道是否有更好的后缀格式。 -- 3 . vscode配置log文件关键字高亮; -安装高亮插件 -![image.png](https://upload-images.jianshu.io/upload_images/10769157-8f6fee2356d7071d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -配置高亮关键字 -![image.png](https://upload-images.jianshu.io/upload_images/10769157-a135fd015409b3da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -将`call` 和`return` 给加进去. - -- 4 .增加阶段关键字; - -![eric6启动阶段](https://upload-images.jianshu.io/upload_images/10769157-c39d01a02149e808.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-aef5704c36824dcc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -加`#000` 是为了方便搜索 。 -需要自己手动折叠 。 -可以发现 每个`splash.showMessage()` 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。 - ---- - -### ps: vscode 阅读log文件还是有一些不方便的地方 , 除了在2.中提到的, 还有包括关闭文件再打开, 折叠状态不会保留 , 有其他更好的方式 请留言告诉我 , 谢谢. diff --git a/source/_posts/rlatticeeffect.md b/source/_posts/rlatticeeffect.md deleted file mode 100644 index 88deac19..00000000 --- a/source/_posts/rlatticeeffect.md +++ /dev/null @@ -1,268 +0,0 @@ ---- -author: Irony -title: PyQt5仿网页鼠标移动点阵特效 -date: 2018-10-29 16:49:10 -tags: - - PyQt - - 动画 - - 特效 -categories: 例子 ---- - -Orz,前段时间有个zz需求,就是要做一个类似网页上很多个多点连线、鼠标移动跟随的那种炫酷特效,然后花了点时间在网上找了js做的,刚开始打算是嵌入`QWebView`来显示网页,后来研究了下js的算法代码,遂改用`QWidget`的`paintEvent`直接绘制。 - - -## 大概思路 - -1. 先根据窗口大小随机创建一些点 -2. 遍历这些点并找到与之相关联的点 -3. 在动画过程中绘制圆点和画两点之间的连线 -4. 属性动画`QPropertyAnimation`改变颜色的透明度 - -## 题外 - -1. 这里没有仔细去研究js里的算法优化,在浏览器里嗖嗖的就生成了,在py里好慢.... -2. 尽量在py里优化了循环操作,也简单的做了个cython加速也才提高了1s ? 1倍?... -3. 不要只是为了好看用这玩意儿,和网页的效果一样,占CPU !!!!!!没有任何意义 -4. 如果有更好的优化算法请告知, 3Q -5. pyd是python3.4生成的,删掉pyd也能运行 - -## 代码 - -https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/RlatticeEffect.py - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2018年11月22日 -@author: Irony -@site: https://pyqt5.com, https://github.com/892768447 -@email: 892768447@qq.com -@file: -@description: -""" -from random import random -from time import time - -from PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\ - Qt, QRectF, pyqtSignal -from PyQt5.QtGui import QColor, QPainterPath, QPainter -from PyQt5.QtWidgets import QWidget - - -__Author__ = """By: Irony -QQ: 892768447 -Email: 892768447@qq.com""" -__Copyright__ = 'Copyright (c) 2018 Irony' -__Version__ = 1.0 - - -try: - import pointtool # @UnusedImport @UnresolvedImport - getDistance = pointtool.getDistance - findClose = pointtool.findClose -except: - import math - - def getDistance(p1, p2): - return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2) - - def findClose(points): - plen = len(points) - for i in range(plen): - closest = [None, None, None, None, None] - p1 = points[i] - for j in range(plen): - p2 = points[j] - dte1 = getDistance(p1, p2) - if p1 != p2: - placed = False - for k in range(5): - if not placed: - if not closest[k]: - closest[k] = p2 - placed = True - for k in range(5): - if not placed: - if dte1 < getDistance(p1, closest[k]): - closest[k] = p2 - placed = True - p1.closest = closest - - -class Target: - - def __init__(self, x, y): - self.x = x - self.y = y - - -class Point(QObject): - - valueChanged = pyqtSignal() - - def __init__(self, x, ox, y, oy, *args, **kwargs): - super(Point, self).__init__(*args, **kwargs) - self.__x = x - self._x = x - self.originX = ox - self._y = y - self.__y = y - self.originY = oy - # 5个闭合点 - self.closest = [0, 0, 0, 0, 0] - # 圆半径 - self.radius = 2 + random() * 2 - # 连线颜色 - self.lineColor = QColor(156, 217, 249) - # 圆颜色 - self.circleColor = QColor(156, 217, 249) - - def initAnimation(self): - # 属性动画 - if not hasattr(self, 'xanimation'): - self.xanimation = QPropertyAnimation( - self, b'x', self, valueChanged=self.valueChanged.emit, - easingCurve=QEasingCurve.InOutSine) - self.yanimation = QPropertyAnimation( - self, b'y', self, valueChanged=self.valueChanged.emit, - easingCurve=QEasingCurve.InOutSine, - finished=self.updateAnimation) - self.updateAnimation() - - def updateAnimation(self): - self.xanimation.stop() - self.yanimation.stop() - duration = (1 + random()) * 1000 - self.xanimation.setDuration(duration) - self.yanimation.setDuration(duration) - self.xanimation.setStartValue(self.__x) - self.xanimation.setEndValue(self.originX - 50 + random() * 100) - self.yanimation.setStartValue(self.__y) - self.yanimation.setEndValue(self.originY - 50 + random() * 100) - self.xanimation.start() - self.yanimation.start() - - @pyqtProperty(float) - def x(self): - return self._x - - @x.setter - def x(self, x): - self._x = x - - @pyqtProperty(float) - def y(self): - return self._y - - @y.setter - def y(self, y): - self._y = y - - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - self.setMouseTracking(True) - self.resize(800, 600) - self.points = [] - self.target = Target(self.width() / 2, self.height() / 2) - self.initPoints() - - def paintEvent(self, event): - super(Window, self).paintEvent(event) - painter = QPainter() - painter.begin(self) - painter.setRenderHint(QPainter.Antialiasing) - painter.fillRect(self.rect(), Qt.black) - self.animate(painter) - painter.end() - - def mouseMoveEvent(self, event): - super(Window, self).mouseMoveEvent(event) - # 鼠标移动时更新xy坐标 - self.target.x = event.x() - self.target.y = event.y() - self.update() - - def initPoints(self): - t = time() - self.points.clear() - # 创建点 - stepX = self.width() / 20 - stepY = self.height() / 20 - for x in range(0, self.width(), int(stepX)): - for y in range(0, self.height(), int(stepY)): - ox = x + random() * stepX - oy = y + random() * stepY - point = Point(ox, ox, oy, oy) - point.valueChanged.connect(self.update) - self.points.append(point) - print(time() - t) - - t = time() - # 每个点寻找5个闭合点 - findClose(self.points) - print(time() - t) - - def animate(self, painter): - for p in self.points: - # 检测点的范围 - value = abs(getDistance(self.target, p)) - if value < 4000: - # 其实就是修改颜色透明度 - p.lineColor.setAlphaF(0.3) - p.circleColor.setAlphaF(0.6) - elif value < 20000: - p.lineColor.setAlphaF(0.1) - p.circleColor.setAlphaF(0.3) - elif value < 40000: - p.lineColor.setAlphaF(0.02) - p.circleColor.setAlphaF(0.1) - else: - p.lineColor.setAlphaF(0) - p.circleColor.setAlphaF(0) - - # 画线条 - if p.lineColor.alpha(): - for pc in p.closest: - if not pc: - continue - path = QPainterPath() - path.moveTo(p.x, p.y) - path.lineTo(pc.x, pc.y) - painter.save() - painter.setPen(p.lineColor) - painter.drawPath(path) - painter.restore() - - # 画圆 - painter.save() - painter.setPen(Qt.NoPen) - painter.setBrush(p.circleColor) - painter.drawRoundedRect(QRectF( - p.x - p.radius, p.y - p.radius, 2 * p.radius, 2 * p.radius), p.radius, p.radius) - painter.restore() - - # 开启动画 - p.initAnimation() - - -if __name__ == '__main__': - import sys - import cgitb - sys.excepthook = cgitb.enable(1, None, 5, '') - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) -``` - -## 效果图 - -![RlatticeEffect](/PyQt/QPropertyAnimation/ScreenShot/RlatticeEffect.gif) - diff --git a/source/_posts/runnablesignal_625781186.md b/source/_posts/runnablesignal_625781186.md deleted file mode 100644 index 5f27c9cc..00000000 --- a/source/_posts/runnablesignal_625781186.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -author: 不许人间见白头 -title: QRunnable线程池发信号 -date: 2019-04-30 15:58:09 -tags: - - PyQt - - 信号 - - 线程 -categories: 教程 ---- - -因为只有继承`QObject`的类才能有信号和自定义信号,而`QRunnable`并不是继承自`QObject`,也不能用多继承的方式,这里考虑定义个全局的QObject变量用来存放一些定义好的可复用的信号。 - - -pools 是 `QThreadPool` 实例 - -## 看图说话 - -1. ![runnablesignal1](/images/runnablesignal1.png) -2. 定义一个全局信号类 -![runnablesignal2](/images/runnablesignal2.png) -3. 在QRunnable 中发送 -![runnablesignal3](/images/runnablesignal3.png) diff --git a/source/_posts/shadowradius.md b/source/_posts/shadowradius.md deleted file mode 100644 index 7e5da355..00000000 --- a/source/_posts/shadowradius.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -author: Irony -title: PyQt5无边框圆角阴影 -date: 2019-04-26 00:06:26 -tags: - - PyQt - - 无边框 - - 阴影 - - 圆角 -categories: 例子 ---- - -在做PyQt窗口开发中经常会遇到要做一些无边框不规则的窗口,可能还会带有阴影效果,这里演示做一个简单的无边框圆角的窗口,原理就在于背景窗口的透明和一层有色背景控件的叠加。 - - -## 原理说明 - -1. 黑色(方便说明)的`QDialog`或者`QWidget`作为全透明无边框窗口。 -2. 其中白色的`QWidget`才是主要显示圆角和阴影的窗口,用于承载其它控件的显示。 -3. 注意红色和紫色的方框内的层次。 -4. 另:如果要熟悉纯代码编写请看 [FramelessDialog.py](https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py) - -如图: - -![FramelessDialog1](/PyQt/Demo/ScreenShot/FramelessDialog1.png) - -## 代码 - -https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2019年4月25日 -@author: Irony -@site: https://pyqt5.com https://github.com/892768447 -@email: 892768447@qq.com -@file: FramelessWidget -@description: 无边框圆角带阴影窗口 -""" -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect -from frameless import Ui_Dialog - - -__Author__ = 'Irony' -__Copyright__ = 'Copyright (c) 2019' - - -class Window(QDialog, Ui_Dialog): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - self.mPos = None - self.setupUi(self) - self.closeButton.clicked.connect(self.close) - # 重点 - # 无边框 - self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) - # 背景透明(就是ui中黑色背景的那个控件) - self.setAttribute(Qt.WA_TranslucentBackground, True) - - # 添加阴影 - effect = QGraphicsDropShadowEffect(self) - effect.setBlurRadius(12) - effect.setOffset(0, 0) - effect.setColor(Qt.gray) - self.setGraphicsEffect(effect) - - # 加上简单的移动功能 - - def mousePressEvent(self, event): - """鼠标点击事件""" - if event.button() == Qt.LeftButton: - self.mPos = event.pos() - event.accept() - - def mouseReleaseEvent(self, event): - '''鼠标弹起事件''' - self.mPos = None - event.accept() - - def mouseMoveEvent(self, event): - if event.buttons() == Qt.LeftButton and self.mPos: - self.move(self.mapToGlobal(event.pos() - self.mPos)) - event.accept() - - -if __name__ == '__main__': - import sys - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) -``` - - -## 效果图 - -![FramelessDialog](/PyQt/Demo/ScreenShot/FramelessDialog.png) - -## 下载 - -[无边框圆角阴影.zip](/files/无边框圆角阴影.zip) \ No newline at end of file diff --git a/source/_posts/showframe.md b/source/_posts/showframe.md deleted file mode 100644 index 8da70064..00000000 --- a/source/_posts/showframe.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -author: Irony -title: PyQt5调整窗口显示边框 -date: 2019-04-26 22:19:26 -tags: - - PyQt - - 边框 -categories: 教程 ---- - -在`windows`某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前`windows`在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中->调整`Windows`的外观和性能->去掉勾选 拖动时显示窗口内容。 - - -由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。 - -1. 一种是自己绘制一个边框效果,放开鼠标时才操作真正的窗口。 -2. 二是替换窗口的处理过程函数`wndproc`处理`WM_NCLBUTTONDOWN`消息事件。 - -今天讲第二种方法: - -1. 需要了解 `SystemParametersInfo` API函数 -2. `SPI_GETDRAGFULLWINDOWS`:确定是否允许拖拉到最大窗口 -3. `SPI_SETDRAGFULLWINDOWS`:设置是否允许拖至最大窗口 - -效果就是这样的: - -![ShowFrameWhenDrag](/PyQt/Demo/ScreenShot/ShowFrameWhenDrag.gif) - -正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。 - -## 代码 - -https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py - -```python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2019年4月23日 -@author: Irony -@site: https://pyqt5.com https://github.com/892768447 -@email: 892768447@qq.com -@file: ShowFrameWhenDrag -@description: 调整窗口显示边框 -""" -from ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\ - c_ulonglong, WINFUNCTYPE, c_uint - -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel - - -__Author__ = 'Irony' -__Copyright__ = 'Copyright (c) 2019 Irony' -__Version__ = 1.0 - -if sizeof(c_long) == sizeof(c_void_p): - WPARAM = c_ulong - LPARAM = c_long -elif sizeof(c_longlong) == sizeof(c_void_p): - WPARAM = c_ulonglong - LPARAM = c_longlong - -WM_NCLBUTTONDOWN = 0x00a1 -GWL_WNDPROC = -4 -SPI_GETDRAGFULLWINDOWS = 38 -SPI_SETDRAGFULLWINDOWS = 37 -WNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM) - -try: - CallWindowProc = windll.user32.CallWindowProcW - SetWindowLong = windll.user32.SetWindowLongW - SystemParametersInfo = windll.user32.SystemParametersInfoW -except: - CallWindowProc = windll.user32.CallWindowProcA - SetWindowLong = windll.user32.SetWindowLongA - SystemParametersInfo = windll.user32.SystemParametersInfoA - - -def GetDragFullwindows(): - rv = c_int() - SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0) - return rv.value - - -def SetDragFullwindows(value): - SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0) - - -class Window(QWidget): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - layout = QVBoxLayout(self) - layout.addWidget(QLabel('拖动或者调整窗口试试看')) - - # 重点替换窗口处理过程 - self._newwndproc = WNDPROC(self._wndproc) - self._oldwndproc = SetWindowLong( - int(self.winId()), GWL_WNDPROC, self._newwndproc) - - def _wndproc(self, hwnd, msg, wparam, lparam): - if msg == WM_NCLBUTTONDOWN: - # 获取系统本身是否已经开启 - isDragFullWindow = GetDragFullwindows() - if isDragFullWindow != 0: - # 开启虚线框 - SetDragFullwindows(0) - # 系统本身处理 - ret = CallWindowProc( - self._oldwndproc, hwnd, msg, wparam, lparam) - # 关闭虚线框 - SetDragFullwindows(1) - return ret - return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam) - - -if __name__ == '__main__': - import sys - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - w = Window() - w.show() - sys.exit(app.exec_()) -``` - -## 片尾 - -替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。 \ No newline at end of file diff --git a/source/_posts/speedgithub.md b/source/_posts/speedgithub.md deleted file mode 100644 index ce7b64a0..00000000 --- a/source/_posts/speedgithub.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -author: Irony -title: 解决GitHub下载速度缓慢的问题 -date: 2019-04-18 08:59:06 -tags: - - Github -categories: 笔记 ---- - -由于Github的下载走的是AWS - 亚马逊的路线,,so slow,跟乌龟一样慢。。照着一些方法改了hosts文件,偶尔能提提速度。 - - -## Windows -Hosts文件的路径是: - -C:\Windows\System32\drivers\etc - -## Mac -终端内输入: - -sudo vim /etc/hosts - - -## 追加域名的IP地址 - -利用https://www.ipaddress.com/ 来获得以下两个GitHub域名的IP地址: - -(1) github.com - -(2) github.global.ssl.fastly.net - -打开网页后,利用输入框内分别查询两个域名 - -将以上两段IP写入Hosts文件中: - - - -``` -192.30.253.112               github.com -151.101.185.194              github.global.ssl.fastly.net -``` - -保存。 - -刷新 DNS 缓存 - -在终端或CMD中,执行以下命令: - -ipconfig /flushdns \ No newline at end of file diff --git a/source/_posts/studynotes.md b/source/_posts/studynotes.md deleted file mode 100644 index 242b15e5..00000000 --- a/source/_posts/studynotes.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -author: Irony -title: PyQt学习心得 -date: 2019-08-26 09:00:00 -sticky: 888 -tags: - - PyQt -categories: 笔记 ---- - -在学习PyQt的过程中由于资料的缺乏或者没有中文导致大多数人感叹资料太少,学习困难,又或者急于求进,赶鸭子上架的情况,此时有系统的学习方法很重要。每个人都需要有自己的学习方法,别人的学习方法并不一定适合自己但可以采纳一些。笔者在这里列举了一些当初自己自学的一些心得和方法,希望帮助大家建立一套自己的学习PyQt的方法,提高自身的学习能力。 - - -## Python基础 - -在学习和使用PyQt之前需要熟练使用Python,经过对QQ群里经常提问的问题的分析,发现大部分人对Python中的基础知识掌握不牢固导致很多基础问题,如果要想更好的使用Python以及它的扩展必需要进行系统的学习。这里列举一下常用的知识点。 - -1. 类         [参考资料](https://www.runoob.com/python3/python3-class.html) -2. 类的继承 -3. 类的多继承 -4. 类方法重写     [参考资料](https://www.runoob.com/w3cnote/python-extends-init.html) -5. 类中的super函数  [参考资料](https://www.runoob.com/python/python-func-super.html) -6. 函数调用/参数类型 -7. 对象调用(参考第1点) - -必须熟练掌握上面的知识点后入门PyQt才比较容易,如果初学者对上面的知识点还不是很了解,本文不适合继续往下阅读。 - -## 设计师 - -Qt 设计师除了方便快速设计一些简单的界面外,其实笔者觉得更大的作用在于帮助用户熟悉各类控件、属性、信号等 - -1. 这里建议初学者不要急于求成,打开设计师新建一个`Widget`的窗口,比如 - -![desiger_create](/images/studynotes/desiger_create.png) - -2. 然后把左侧的所有控件挨个拖动到中间的窗口中,比如这里拖动一个**Push Button**按钮 - -![desiger_drag](/images/studynotes/desiger_drag.png) - -3. 在设计师右下角的属性编辑器中列举了该控件的所有父类,意味着可以调用和重写父类的所有方法,建议初学者把这个属性编辑器的所有属性挨个调整看看效果,部分控件可能需要**Ctrl+R**预览界面才能看到,同时像**QListWidget,QTreeWidget,QTableWidget**等某些控件需要在控件上右键增加数据才可以 - -![desiger_property](/images/studynotes/desiger_property.png) -![desiger_property2](/images/studynotes/desiger_property2.png) - -4. 两个控件之间简单的信号槽关联可以通过设计师快速的设置 - -![desiger_signal](/images/studynotes/desiger_signal.png) -![desiger_signal2](/images/studynotes/desiger_signal2.png) - -5. 提高进阶的方法,当你需要手动写代码实现界面的时候,不妨把UI文件转出PY文件,看看是如何构造的(这里涉及到布局等知识见后文) - -## 布局 - -Qt界面提供了方便的4种基本布局,**QVboxLayout,QHboxLayout,QFormLayout,QGridLayout**,初学者需要数量掌握这4种布局外加2种拉伸器(占位挤压) - -首先需要知道Qt界面的中控件的层级顺序以及parent,parent的作用既作为子控件的父元素也可以自动管理Qt的对象(具体可以搜索下关于 Qt parent的资料) - -1. 在没有布局的情况下,在设计师中拖动摆放的控件是一层一层的叠加覆盖,此时每个添加的子控件的parent都是最外层的控件 - -![desiger_stack](/images/studynotes/desiger_stack.png) - -2. 如果需要界面中的控件自动适应高度宽度,此时则需要使用4种布局来包裹里面的子控件,注意的是:布局不是控件不能设置高度宽度和样式等,是一个抽象的东西,就好比是一根橡皮筋包裹几个矩形的物品;布局也可以设置一些属性(在设计师属性编辑器中),比如设置两者直接的间距,设置距离上下左右的间距,设置比例等 - -![desiger_layout](/images/studynotes/desiger_layout.png) - -3. 在没有布局或者有布局的时候。可以添加容器控件(**QWidget,QFrame,QGroupBox,QScrollArea,QToolBox,QTabWidget,QStackedWidget,QMidArea,QDockWidget**)这些容器可以放置子控件,从而循环嵌套。 - -## 例子 - -在PyQt5.5的时候自带了一个例子文件夹(后面的版本没有的话可以下载PyQt5源码,里面有个examples文件夹),想要熟练的掌握PyQt还需要从自带的例子中学习,必须要每个例子都运行一次然后看看这个例子实现了什么,这样才能记忆深刻。 -同时很多开发者在[https://github.com/PyQt5/PyQt](https://github.com/PyQt5/PyQt)分享了各类进阶例子,同时也欢迎大家共同完善该项目,提供更多更好的例子。另外也可以下载该项目的客户端[PyQtClient](https://github.com/PyQt5/PyQtClient/releases)软件,支持运行其中的例子 - -建议在更深入的学习PyQt之前多看看一些例子。 - -## 文档 - -接下来要说的就是Qt的api文档,[官网文档](https://doc.qt.io/qt-5/classes.html),这里其实不要害怕是英文就不想看,觉得看不懂了,其实官网的文档还是比较简洁的,而且函数名也比较直观就能知道意思。也可以用谷歌浏览器打开右键翻译,基本上都能看懂。笔者前期写过一篇[如何查阅Qt文档](/viewapi.html)的文档可以阅读学习一番。 - -这里就拿[QWebEngineView](https://doc.qt.io/qt-5/qwebengineview.html)举一个例子,首先初学者在使用这个浏览器控件时候,会有诸多的问题比如:Cookie,拦截器等就不知道如何去调用函数来设置 - -1. 首先打开官网文档 [https://doc.qt.io/qt-5/qwebengineview.html](https://doc.qt.io/qt-5/qwebengineview.html),可以看到只有少量的函数可以调用,寻找一番并没有发现和Cookie相关的东西,这个时候就需要把重点放在有特俗返回值的函数上,比如: -```c++ -QWebEngineHistory * history() const -QWebEnginePage * page() const -QWebEngineSettings * settings() const -``` - -这三个函数返回了一个类实例,就意味着可以调用其中的方法。 - -2. 点击**page()**打开 [https://doc.qt.io/qt-5/qwebenginepage.html](https://doc.qt.io/qt-5/qwebenginepage.html),发现没有cookie相关的东西,只有**QWebEngineProfile * profile() const**这个函数比较可疑。 - -3. 点击**profile()**打开 [https://doc.qt.io/qt-5/qwebengineprofile.html](https://doc.qt.io/qt-5/qwebengineprofile.html),在浏览器中搜索`cookie`发现这个类中包含大量和cookie相关的东西,比如:**QWebEngineCookieStore * cookieStore()`**从名字上可以猜测大概意思为cookie储存 - -4. 点击**cookieStore()**打开 [https://doc.qt.io/qt-5/qwebenginecookiestore.html](https://doc.qt.io/qt-5/qwebenginecookiestore.html),此时就会发现这个类里面包含了删除和设置cookie的方法。 - -5. 但是找到了这些方法后,面对初学者又一个问题来了,该如何去用?根据上面4点整理一下,把他们当做简单的Python对象,方法和操作方法和class一样的。 - -```python -self.webview = QWebEngineView() -# 得到page -page = self.webview.page() -# 得到profile -profile = page.profile() -# 得到cookieStore -cookieStore = profile.cookieStore() -# 清空cookie -cookieStore.deleteAllCookies() - -# 用简短代码来表达就是 -cookieStore = self.webview.page().profile().cookieStore() -cookieStore.deleteAllCookies() -``` - -## 异常调试 - -可能有时候由于粗心,或者调用了一些非法函数,参数错误等会导致程序出现一些异常,首先第一步复制最后一行的错误去百度或者谷歌搜索,大多时候能找到问题所在。其次如果搜索不到或者自己的异常可能是由于某个变量的值不对引起的,就需要在编辑器中打断点使用DEBUG模式调试变量值(如果不会可以采用麻烦一点的办法:用`print`打印出变量值) - -遇到问题后首先需要自己多调试排查问题,不要一遇到问题就去问,自己多尝试一个一个排查直到找到问题所在并解决,这也是一种提高自身能力的地方。 - -## 检索资料 - -作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。信息检索是每个程序猿必备的能力之一,其好处在于可以更快更准确的在茫茫网络海洋中找到自己所需要的东西,这个过程需要长期不断积累和练习。 - -1. 中文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt 拖拽 -2. 英文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt Drag Drop - -## 片尾 - -好了,笔者基本上的学习过程就整理如上,这并不是说每个人都适合这样的方法,但至少笔者是这样一步一步走过来的。当你养成了一个学习、发现和解决问题的好习惯时就会慢慢得心应手。 \ No newline at end of file diff --git a/source/_posts/suggesteditor.md b/source/_posts/suggesteditor.md deleted file mode 100644 index bf774005..00000000 --- a/source/_posts/suggesteditor.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -author: Irony -title: 推荐编辑器LiClipse -date: 2019-05-04 18:04:08 -tags: - - 编辑器 -categories: 随笔 ---- - -关于Python的开发编辑器有很多,每个人有每个人的喜好,经常看到很多在问什么编辑器好用,有人推荐Sublime,有人推荐Pycharm等等,这里就不去比较其它编辑器的优缺点了,只谈谈关于LiClipse这个编辑器在初级使用阶段的智能提示功能等。开箱即用,支持多种语言,RST,Markdown和HTML编辑器的HTML预览。 - - -其实LiClipse这个编辑器就是以前的PyDev插件的独立版本,基于Eclipse编辑器开发,去掉了Java的相关开发功能,关于软件的详细说明可以去官网查看: http://www.liclipse.com/ - -编辑器只需要少量的配置,打开即可使用,快速自动import,也可以根据需要安装自己所需的插件,比如json、svn、主题插件等。个人推荐:适合刚入门的新手使用 - -由于新版的PyQt和PyDev去掉了详细的函数提示,所以PyQt的智能提示只有函数和返回值,并没有英文注释,但是以前的比如PyQt4的智能提示应该是有详细的英文注释提示。 - -## 界面预览 - -1. 主界面 -![editor1](/images/editor1.png) -2. 鼠标悬停提示 -![editor2](/images/editor2.png) -3. 输入提示 -![editor3](/images/editor3.png) -4. Git面板 -![editor4](/images/editor4.png) -5. 全局搜索(Ctrl + H) -![editor5](/images/editor5.png) -![editor6](/images/editor6.png) - -## 自动导包 - -其实这个功能我是非常喜欢的,通过按下快捷键即可自动寻找包名导入,快捷键 Ctrl + Shift + O - -![editor_import](/images/editor_import.png) - -也可以在标红的代码上按下 Ctrl + F1进行导入 - -![editor_import2](/images/editor_import2.png) - -## 配置 - -打开编辑器后首先要配置【Window -> Preferences】的就是Python的环境变量,可以同时添加多个Python版本 - -![editor_env](/images/editor_env.png) - -## Tab等设置 - -1. Insert spaces for tabs tab转空格 -2. Show line numbers 显示行号 - -![editor_tab](/images/editor_tab.png) - -## 模版 - -这个功能可以快速插入自己定义好的模版代码,比如 `if __name__ == '__main__':`等等,比如我这里配置的创建文件的模版 - -![editor_tpl](/images/editor_tpl.png) - -## 常用快捷键 - -| | | -|:----------:|:----------------:| -| 格式化对齐 | Ctrl + Shift + F | -| 自动导包 | Ctrl + Shift + O | -| 快捷提示 | Alt + / | \ No newline at end of file diff --git a/source/_posts/use_pyuic_insteadof_pyside2uic.md b/source/_posts/use_pyuic_insteadof_pyside2uic.md deleted file mode 100644 index dd248c74..00000000 --- a/source/_posts/use_pyuic_insteadof_pyside2uic.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -author: 人间白头  -title: 修改pyuic代替pyside2-uic. -date: 2019-12-26 19:49:41 -tags: - - PyQt5 PySide2 - -categories: 随笔 ---- - -修改pyuic代替pyside2-uic - - -修改pyuic代替pyside2-uic. - -最近看到挺多人用pyside2的uic编译ui文件有问题 . -写个解决办法. - -首先 , -`pip install qtpy` , -这个是兼容pyqt5和pyside2的 , 无缝转换 . - -然后 , -修改 pyqt5 的 uic , - -![image.png](https://upload-images.jianshu.io/upload_images/10769157-2ed1053f322c26a6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -最后用pyuic5 , 生成Ui_XXX.py文件 . diff --git a/source/_posts/viewapi.md b/source/_posts/viewapi.md deleted file mode 100644 index 37d7ce11..00000000 --- a/source/_posts/viewapi.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -author: Irony -title: 如何查阅Qt文档 -date: 2019-05-04 20:50:20 -tags: - - PyQt -categories: 笔记 ---- - -很多网友在问有没有PyQt5的文档之类的问题,在PyQt4的时候PyQt官网有了英文版的文档,随后有网友翻译成了中文。不过现在PyQt5官方的文档都指向了C++的Qt文档,其实C++的Qt API文档结构很清晰,翻阅很容易的,需要注意几点。 - - -作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。 - -拿Qt C++文档来说,官网地址是:https://doc.qt.io/qt-5/qtwidgets-module.html 这里面记录了所有控件的详细函数文档。 - -比如拿 输入框 `QLineEdit` 来说,怎么去查询它的用法和信号槽等资料? - -https://doc.qt.io/qt-5/qlineedit.html - -## 左侧目录 - -在文档左侧目录中有如下几个: - -Properties - 控件里的属性(比如宽高等,通常需要当作函数调用) - -Public Slots - 这个是控件自己的槽函数(当作普通函数就行) - -Signals - 这个是输入框的包含的信号 - -Public Functions、Reimplemented Public Functions、Static Public Members、Protected Functions、Reimplemented Protected Functions - 这几个都是函数列表 - -![howtoviewapi1](/images/howtoviewapi1.png) - -## 类说明 - -![howtoviewapi2](/images/howtoviewapi2.png) - -这里有两个注意点 - -1. 红色方框内的表示该控件(输入框)继承于`QWidget`,所以该控件(输入框)拥有父类的所有方法和信号,当当前文档找不到相关资料和函数时,可以去父类找找看。 -2. 紫色方框内表示列举所有的方法(包括父类) - -## 函数列表 - -![howtoviewapi3](/images/howtoviewapi3.png) - -这里列举的就是该控件(输入框)的函数,同理点击上面的紫色方框是查看所有方法,一般这里主要用来查询你需要的功能函数,Qt的函数名比较容易理解,比如:只读ReadOnly,选择文字:setSelection。 - -所以再查下这部分资料的时候建议在浏览器中Ctrl + F打开浏览器的搜索框,并输入英文关键词来检索你所需要的函数在哪里。 - -![howtoviewapi8](/images/howtoviewapi8.png) - -## 槽函数 - -![howtoviewapi4](/images/howtoviewapi4.png) - -这部分列举的是槽函数,其实在PyQt中槽函数可以当作普通的函数。普通的函数也可以作为槽函数,直接通过信号连接即可,注意方框所示,还有很多函数是在父类里面。 - -## 信号 - -![howtoviewapi5](/images/howtoviewapi5.png) - -这部分列举了该控件(输入框)所定义的信号,主要还是看名字,大多都能知道是做什么的,比如: - -1. editingFinished - 编辑完成信号 -2. returnPressed - 回车键信号 -3. textChanged(const QString &text) - 内容改变信号 - -这里还有个问题就是参数问题,一般&后面的text作为参数传递到槽函数中 - -## 函数详细说明 - -当不明确这个函数是做什么的,可以点击该函数跳转到下面的说明,比如回车键信号`returnPressed` - -![howtoviewapi6](/images/howtoviewapi6.png) - -如图上所示,用翻译插件翻译,大部分就明白了,如下: - -![howtoviewapi7](/images/howtoviewapi7.png) - -## 关于如何搜索资料 - -比如当你要搜索输入框内容改变事件,一般建议两种搜索,且搜索的时候用空格把关键词分开搜索,而且直接用控件名 - -1. 中文搜索引擎:QLineEdit 内容 改变 -2. 英文搜索引擎:QLineEdit text change diff --git a/source/_posts/viewpyindesigner_625781186.md b/source/_posts/viewpyindesigner_625781186.md deleted file mode 100644 index 4333210a..00000000 --- a/source/_posts/viewpyindesigner_625781186.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -author: 不许人间见白头 -title: 如何和设计师中查看ui转换的py代码 -date: 2019-04-30 13:11:09 -tags: - - PyQt - - Designer - - 设计师 -categories: 教程 ---- - -通过 设计师  查看ui转换的py代码 - -当初我刚学pyqt的时候 , 也有很多疑惑 , 用什么属性把控件加到布局 , 改了这个属性会发生什么 , 为什么这个会这样, 那个会那样 。。。 。。。 - -后来就看ui 转成的py代码 , 注释一下 , 什么效果消失了 , 就是那个api引起的 。 - - -再来后发现了官方文档 , 查一查函数就行了 . - -但是有些api文档找起来麻烦 , 用设计师点几下就行了 , 然后把转换出来的代码拷贝一下就完事了. - -可是需要单独把ui转为py文件 , 之后再删除这个文件也是很烦的一件事 . - -好 , 话不多说 , 接下来手把手教你如何快速在ui中查看py代码 . - -官方也考虑过这种情况 , 所以 设计师中 是有这个功能的 , 但是qt的是没问题的 , pyqt的毕竟是绑定过来的 , 所以正常来说 你点击之后会弹出一个找不到应用程序的提示 . - -看到这个东西是不是很眼熟 , 我们用的命令pyuic5 和这个东西应该是一样的 . - -![viewpyindesigner1](/images/viewpyindesigner1.jpg) - -所以接下来 , 我们找找电脑上有没有这个东西 - -![viewpyindesigner2](/images/viewpyindesigner2.jpg) - -果然在pyqt5-toos文件夹下有这个东西 , - -我们根据第一张图的提示 , 把这个东西拷贝到相应的目录 (如果没有那个bin文件夹, 手动创建), - -![viewpyindesigner3](/images/viewpyindesigner3.jpg) - -好了 , 大功告成 ! diff --git a/source/_posts/virtualenvpy_625781186.md b/source/_posts/virtualenvpy_625781186.md deleted file mode 100644 index a5d4bb8d..00000000 --- a/source/_posts/virtualenvpy_625781186.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -author: 人间白头  -title: python 拷贝虚拟环境(一)   -date: 2019-05-02 15:21:01 -tags: - - Python - - virtualenvwrapper - - virtualenv -categories: 随笔 ---- - -通常来说 , 刚开始使用python的时候都是把包装到全局路径 , 随着各个项目安装的包越来越多 , 之后每开始一个项目 , pycharm创建索引的时间都越来越漫长 , 所以不可避免得开始使用虚拟环境。 -经过一番了解 ,虚拟环境的优点有这些: -- 改善 pycharm 索引时间; -- 各个项目的库不会冲突; -- 理论上虚拟环境可以给同版本的操作系统使用(未试验过); -- pip freeze > requestment.txt 导出的依赖清晰; -- 各个版本的python共存; -- ... - - -python虚拟环境库除了自带的venv , 还有三方库`virtualenv` , 此外 在`virtualenv`基础上又开发了`virtualenvwrapper(virtualenvwrapper_win)` 来管理 - -本文基于`virtualenvwrapper` 创建的虚拟环境来讲解. - - 以下是收集的一些virtualenvwrapper配置教程: - # linux平台 - https://www.cnblogs.com/netfoxman/p/5994697.html - # window平台 - https://blog.csdn.net/shaququ/article/details/54292043   - https://blog.csdn.net/iaau0908/article/details/54021518 -虚拟环境创建多了我们就会发现 , -有时候使用相同版本的环境,一些常用的库是需要重新安装的, -那么能不能创建一个基础环境, 默认拥有这些库, 然后在这个基础环境上继续安装三方库呢 ? - -本文经过试验发现是可行的: - -1. 创建基础虚拟环境 `mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径]`. 例如 `mkvirtualenv py34 -p c:\Python34\python.exe` - -2. 切换到虚拟环境`workon py34` , 然后安装一下三方库, 然后复制`py34`这个文件夹备份一下 ; -3. 接着复制这个`py34`文件夹, 把复制后的文件夹改名为我们需要需要的文件夹例如`new34` -4. 进入`new34文件夹`, 用任意编辑器全路径搜索`py34`(替换虚拟环境的路径) -5. 删除`new34/Scripts`下的`pip.exe, pip3.exe, pip3.x.exe, easy_install.exe`(因为安装路径硬编码到这里面了, 改不了, 需要重新安装) -6. https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二 , 源码安装 `setuptools` 后再用`easy_install pip` 安装pip后 , 完成 ; - 如果有问题, 就继续按照方式一的源码安装pip; -7. 在`new34`环境下 用`pip show 三方库` 来看一些库的位置, 确保正确. - - diff --git a/source/_posts/webviewnew.md b/source/_posts/webviewnew.md deleted file mode 100644 index 56badce2..00000000 --- a/source/_posts/webviewnew.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -author: Irony -title: PyQt5编译QWebView与QWebEngineView共存 -date: 2019-01-12 19:28:06 -tags: - - PyQt - - QWebView - - 浏览器 -categories: 教程 ---- - -在PyQt5.5过后移除了`QWebView`控件,改用`QWebEngineView`,但是这个刚开始用起来不是很方便,最近在整理一些例子的时候需要同时使用`QWebView`和`QWebEngineView`,故希望把`QWebView`重新加入到后面的PyQt5版本中,查看PyQt5.10.1的源码发现里面其实是有`QWebView`的,只是因为Qt5.10.1中没有编译好的dll等导致无法编译。 - - -## 准备工作 - -1. 安装VS2015 -2. 安装Qt5.10.1 -3. 前往 https://github.com/annulen/webkit/releases 下载对应的文件,比如:qtwebkit-5.212.0_alpha2-qt59-msvc2015-x86.zip -4. 下载PyQt5.10.1源码 -5. 下载对应版本的sip源码 - -## 编译 - -1. 设置环境变量`set PATH=D:\soft\Qt\Qt5.10.1\5.10.1\msvc2015\bin;%PATH%` -2. 首先进入vs2015命令行编译sip并安装,`python configure.py && nmake && nmake install` -3. 进入PyQt5.10.1源码编译安装即可 -4. 如果要减少PyQt5.10.1的编译可以试试以下代码 - -``` -D:\soft\Python35\python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc -``` \ No newline at end of file diff --git a/source/about/index.md b/source/about/index.md deleted file mode 100644 index 98a40b75..00000000 --- a/source/about/index.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: 关于社区 -layout: about -comments: false -date: 2019-04-30 11:38:36 -share: false -type: "about" ---- - -## 为什么要搭建这个社区 - -也许是因为喜爱PyQt,喜欢用它做很多东西吧,也想为大家提供更多关于PyQt的帮助。 - -内容主要是 记录 一些开发日志,分享些 我认为有趣的东西,还有 各种教程、例子 等等。 - -还有就是收集和整理记录下网友们平时遇到的问题,提出的需求等等,方便其他人搜索。 - -如需帮助可以前往 [issues](https://github.com/PyQt5/PyQt/issues/new) 留下您的问题 - -如需发表文章可以前往 [这里](https://github.com/PyQt5/blog/tree/dev/source/_posts) 发表新文章, 同时需要查看 [发文要求](https://github.com/PyQt5/blog/blob/dev/README.md) - -## 投稿 - -欢迎投稿: [https://github.com/PyQt5/blog/blob/dev/README.md](https://github.com/PyQt5/blog/blob/dev/README.md) - -## 关于我? - -信息很少... - -## 联系我们? - -[点击链接加入群聊【PyQt5 学习】:https://jq.qq.com/?_wv=1027&k=5Y29SHz](https://jq.qq.com/?_wv=1027&k=5Y29SHz) - -## 捐助支持 - -![微信](/images/weixin.png) or ![支付宝](/images/zhifubao.png) \ No newline at end of file diff --git a/source/categories/index.md b/source/categories/index.md deleted file mode 100644 index 9aa154d8..00000000 --- a/source/categories/index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: 分类 -comments: false -share: false -aside: false -top_img: false -type: "categories" -date: 2023-10-08 00:00:13 ---- diff --git a/source/faq/index.md b/source/faq/index.md deleted file mode 100644 index 3623022b..00000000 --- a/source/faq/index.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: 常见问题 -date: 2019-06-20 10:58:20 -share: false -type: "faq" ---- - -# 常见问题整理 - -这里会收集和整理各种常见问题,包括但不限于编辑器问题、编译打包问题、常见犯错。希望大家可以共同来完善此文档,编辑地址:[https://github.com/PyQt5/blog/blob/dev/source/faq/index.md](https://github.com/PyQt5/blog/blob/dev/source/faq/index.md) - -## 案例库和提问 - -有专门的项目收集例子、提交例子、和回答问题,同时也建议大家都在上面多多提问和提交例子,这样可以留下记录方便遇到同样问题的人快速找到答案。 - -项目地址:[https://github.com/PyQt5/PyQt/issues](https://github.com/PyQt5/PyQt/issues) - -如果你想写一写文章来帮助其他人少走弯路,也可以到 [https://github.com/PyQt5/blog/tree/dev](https://github.com/PyQt5/blog/tree/dev) 去提交博客文章,访问地址是:[https://pyqt5.com](https://pyqt5.com) - -## 文档查阅 - - - Qt 官方文档 [http://doc.qt.io/qt-5/classes.html](http://doc.qt.io/qt-5/classes.html) - - PySide2 文档 [https://doc.qt.io/qtforpython/index.html](https://doc.qt.io/qtforpython/index.html) - - 如何查阅Qt文档 [https://pyqt5.com/viewapi.html](https://pyqt5.com/viewapi.html) - -## 界面美化 - - - Qt 官网样式参考 [https://doc.qt.io/qt-5/stylesheet-examples.html](https://doc.qt.io/qt-5/stylesheet-examples.html) - - QSS语法参考 [https://www.cnblogs.com/wangqiguo/p/4960776.html](https://www.cnblogs.com/wangqiguo/p/4960776.html) - - CSS语法参考 [http://www.w3school.com.cn/css/index.asp](http://www.w3school.com.cn/css/index.asp) - - 使用字体图标 [https://github.com/PyQt5/PyQt/tree/master/QFont](https://github.com/PyQt5/PyQt/tree/master/QFont) - - 阿里云字体图标 [http://iconfont.cn/](http://iconfont.cn/) - - ## Pyinstaller - -1. 如果安装anaconda, 请别用这个环境的python -2. 设置pyqt5的环境变量 -3. 如果在pycharm中把文件夹设置为了根路径 , 请在终端(cmd)中 使用命令 python xxx.py 运行脚本来确认 模块导入无错误后在打包 -4. 这点很重要!! 如果需要打包成单文件, 先别用-w 命令, 最后打包无错误后再加上-w -5. 如果打包后的窗体一闪而过, 请在cmd中运行你的exe文件 - -错误处理: - -6. module PyQt5.sip not found: 确保在cmd模式下可以import这个模块后, 再在程序中手动import PyQt5.sip , 然后再打包尝试运行 -7. Failed to load platform plugin “windows”...: 百度有解决方法 , 拷贝 python目录下的\\PyQt5\\Qt\\plugins\\platforms到exe目录 -8. QPixmap处理/样式 问题 都是同5.一样都是dll丢失 , 到目录下找对应的文件件拷贝到exe目录 -9. --add-data 打包非python模块文件 , 可能出现的问题及办法: [https://github.com/pyinstaller/pyinstaller/issues/3749](https://github.com/pyinstaller/pyinstaller/issues/3749) - - 还是失败的话检查 电脑用户名是否是中文, 如果是中文 - - 对那个路径名进行编码 - - 则改变spec中 exe= EXE(.....)里的runtime_tmpdir指定为英文路径 -10. 如果需要打包成单文件 , 先别用-w 命令, 最后打包无错误后再加上-w - -## Pycharm - -1. PyQt5环境配置 [https://blog.csdn.net/px41834/article/details/79383985](https://blog.csdn.net/px41834/article/details/79383985) -2. 调试PyQt没有错误信息提示,原因以及解决办法: [https://www.jianshu.com/p/47b6e7ce4639](https://www.jianshu.com/p/47b6e7ce4639) -3. 不识别PyQt5模块: - - 新建的项目使用了新建的虚拟环境的python.exe解释器,更换已经安装过pyqt5的解释器再更新索引即可,设置python解释器路径在pycharm的菜单File->Settings->Project:->Project Interpreter - - 在尝试网上搜索的办法都没解决的情况下 ,一般就是pycharm的配置出问题了,找到C:\\Users\\XXX\\.PyCharm2018.1 路径, 删除之后重启pycharm ,重新配置 - -## Eric6 - -1. 环境配置 请参考 第一讲 [https://space.bilibili.com/1863103/#/](https://space.bilibili.com/1863103/#/) -2. 汉化:eric6汉化包只到17.12版本,但是可以兼容高版本,自行百度或群文件下载 -3. 双击无法打开设计师//pyuic5|pyrcc5无法编译ui|qrc文件 - - 检查是否安装pyqt-tools或者PyQt5Designer - - 检查是否加入环境变量 - - eric6 菜单栏 设置(settings) - 首选项(preference) - Qt - - Qt -> Tools -> Tools Directory: 配置designer.exe路径 - - PyQt -> Tools -> Tools Directory: 配置pyuic5.exe/pyrcc5.exe路径 - - !! 检查qrc路径中是否含有中文 !! 如果有则重命名 - -## 设计师 - -1. 通过pip install pyqt5-tools 或者 pip install PyQt5Designer 安装 -2. PyQt5Designer自带汉化包,执行在site-packages\PyQt5\Qt\bin\designer.exe - -## Matplotlib - -1. PyQt5 结合 matplotlib 时,如何显示其 NavigationToolbar:[http://www.cnblogs.com/hhh5460/p/5189843.html](http://www.cnblogs.com/hhh5460/p/5189843.html) -2. matplotlib绑定到PyQt5:[http://www.cnblogs.com/hhh5460/p/4322652.html](http://www.cnblogs.com/hhh5460/p/4322652.html) \ No newline at end of file diff --git a/source/guestbook/index.md b/source/guestbook/index.md deleted file mode 100644 index 1793a01a..00000000 --- a/source/guestbook/index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: 留言板 -date: 2019-04-30 17:38:29 -share: false -type: "guestbook" ---- - -
    \ No newline at end of file diff --git a/source/issues/index.md b/source/issues/index.md deleted file mode 100644 index 4b9bf4f3..00000000 --- a/source/issues/index.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: PyQt5讨论 -comments: false -date: 2019-10-20 23:24:25 -share: false -description: PyQt5 Issues 论坛 讨论 -home: true ---- - - - - - - - - -
    \ No newline at end of file diff --git a/source/json/music.json b/source/json/music.json deleted file mode 100644 index bc7f9258..00000000 --- a/source/json/music.json +++ /dev/null @@ -1,128 +0,0 @@ -[ - { - "name": "青花瓷", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.4/青花瓷/青花瓷.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000002eFUFm2XYZ7z_2.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.4/青花瓷/青花瓷.lrc" - }, - { - "name": "稻香", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.1/周杰伦/稻香/稻香.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000002Neh8l0uciQZ_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.1/周杰伦/稻香/稻香.lrc" - }, - { - "name": "晴天", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/晴天/晴天.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000000MkMni19ClKG_3.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/晴天/晴天.lrc" - }, - { - "name": "七里香", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/七里香/七里香.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000003DFRzD192KKD_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/七里香/七里香.lrc" - }, - { - "name": "花海", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music-jay@1.0.1/花海/花海.flac", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000002Neh8l0uciQZ_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music-jay@1.0.1/花海/花海.lrc" - }, - { - "name": "反方向的钟", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music-jay@1.0.1/反方向的钟/反方向的钟.flac", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000000f01724fd7TH_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music-jay@1.0.1/反方向的钟/反方向的钟.lrc" - }, - { - "name": "兰亭序", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.1/周杰伦/兰亭序/兰亭序.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000002Neh8l0uciQZ_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.1/周杰伦/兰亭序/兰亭序.lrc" - }, - { - "name": "说好的辛福呢", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/说好的辛福呢/说好的辛福呢.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000002Neh8l0uciQZ_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/说好的辛福呢/说好的幸福呢.lrc" - }, - { - "name": "等你下课 (with 杨瑞代)", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.1/周杰伦/等你下课/等你下课.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000003bSL0v4bpKAx_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.1/周杰伦/等你下课/等你下课.lrc" - }, - { - "name": "我落泪情绪零碎", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/我落泪情绪零碎/我落泪情绪零碎.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000000bviBl4FjTpO_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/我落泪情绪零碎/我落泪情绪零碎.lrc" - }, - { - "name": "听妈妈的话", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/听妈妈的话/听妈妈的话.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000002jLGWe16Tf1H_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.2/听妈妈的话/听妈妈的话.lrc" - }, - { - "name": "明明就", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music-jay@1.0.1/明明就/明明就.flac", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000003Ow85E3pnoqi_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music-jay@1.0.1/明明就/明明就.lrc" - }, - { - "name": "我是如此相信", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music-jay@1.0.1/我是如此相信/我是如此相信.flac", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000001hGx1Z0so1YX_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music-jay@1.0.1/我是如此相信/我是如此相信.lrc" - }, - { - "name": "发如雪", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.3/发如雪/发如雪.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M0000024bjiL2aocxT_3.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.3/发如雪/发如雪.lrc" - }, - { - "name": "以父之名", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.3/以父之名/以父之名.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000000MkMni19ClKG_3.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.3/以父之名/以父之名.lrc" - }, - { - "name": "园游会", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.3/园游会/园游会.flac", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000003DFRzD192KKD_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.3/园游会/园游会.lrc" - }, - { - "name": "本草纲目", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.4/本草纲目/本草纲目.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000002jLGWe16Tf1H_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.4/本草纲目/本草纲目.lrc" - }, - { - "name": "龙卷风", - "artist": "周杰伦", - "url": "https://npm.elemecdn.com/anzhiyu-music@1.0.4/龙卷风/龙卷风.mp3", - "cover": "https://y.qq.com/music/photo_new/T002R300x300M000000f01724fd7TH_1.jpg?max_age=2592000", - "lrc": "https://npm.elemecdn.com/anzhiyu-music@1.0.4/龙卷风/龙卷风.lrc" - } - ] \ No newline at end of file diff --git a/source/link/index.md b/source/link/index.md deleted file mode 100644 index 316ee074..00000000 --- a/source/link/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: 友情链接 -comments: false -share: false -type: "link" -date: 2023-10-08 00:01:32 ---- diff --git a/source/music/index.md b/source/music/index.md deleted file mode 100644 index d99628c5..00000000 --- a/source/music/index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: 音乐馆 -date: 2024-04-30 00:00:00 -type: music -aplayer: true -top_img: false -comments: false -aside: false ---- \ No newline at end of file diff --git a/source/tags/index.md b/source/tags/index.md deleted file mode 100644 index 5447e196..00000000 --- a/source/tags/index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: 标签 -comments: false -share: false -top_img: false -type: "tags" -date: 2019-04-30 11:36:17 ---- diff --git a/speedgithub.html b/speedgithub.html new file mode 100644 index 00000000..e9bd6af8 --- /dev/null +++ b/speedgithub.html @@ -0,0 +1,290 @@ +解决GitHub下载速度缓慢的问题 | PyQt + + + + + + + + + + + + + +

    解决GitHub下载速度缓慢的问题

    由于 Github 的下载走的是 AWS - 亚马逊的路线,,so slow,跟乌龟一样慢。。照着一些方法改了 hosts 文件,偶尔能提提速度。

    + +

    # Windows

    +

    Hosts 文件的路径是:

    +

    C:\Windows\System32\drivers\etc

    +

    # Mac

    +

    终端内输入:

    +

    sudo vim /etc/hosts

    +

    # 追加域名的 IP 地址

    +

    利用 https://www.ipaddress.com/ 来获得以下两个 GitHub 域名的 IP 地址:

    +

    (1) github.com

    +

    (2) github.global.ssl.fastly.net

    +

    打开网页后,利用输入框内分别查询两个域名

    +

    将以上两段 IP 写入 Hosts 文件中:

    +
    192.30.253.112               github.com
    +151.101.185.194              github.global.ssl.fastly.net
    +

    保存。

    +

    刷新 DNS 缓存

    +

    在终端或 CMD 中,执行以下命令:

    +

    ipconfig /flushdns

    +
    文章作者: Irony
    文章链接: https://pyqt5.com/speedgithub.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
    赞助
    • 微信付
      微信付
    • 支付宝
      支付宝
    \ No newline at end of file diff --git a/source/static/api/css/imgshare.css b/static/api/css/imgshare.css similarity index 100% rename from source/static/api/css/imgshare.css rename to static/api/css/imgshare.css diff --git a/source/static/api/css/like.css b/static/api/css/like.css similarity index 100% rename from source/static/api/css/like.css rename to static/api/css/like.css diff --git a/source/static/api/css/select_share.css b/static/api/css/select_share.css similarity index 100% rename from source/static/api/css/select_share.css rename to static/api/css/select_share.css diff --git a/source/static/api/css/share_popup.css b/static/api/css/share_popup.css similarity index 95% rename from source/static/api/css/share_popup.css rename to static/api/css/share_popup.css index 74410946..4e2c5a08 100644 --- a/source/static/api/css/share_popup.css +++ b/static/api/css/share_popup.css @@ -1 +1 @@ -.bdshare_dialog_bg{position:fixed;_position:absolute;width:312px;height:341px;z-index:9999;overflow:hidden;display:none}.bdshare_dialog_box{position:fixed;_position:absolute;width:300px;border:6px solid #8F8F8F;height:329px;z-index:10000;text-align:left;box-shadow:0 0 7px #aaa;-webkit-box-shadow:0 0 7px #aaa;-moz-box-shadow:0 0 7px #aaa;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;overflow:hidden;background:#f6f6f6;display:none}.bdshare_dialog_top,.bdshare_popup_top{height:28px;color:#626262;overflow:hidden;font-weight:bold;font-size:14px;line-height:28px;padding:0 5px}.bdshare_dialog_close{width:22px;height:23px;background:url(../img/share/pop_c.gif?v=2d7108c8.gif) no-repeat 0 0;float:right;display:block;margin-top:2px}.bdshare_dialog_list{margin:0;padding:10px 0;height:256px;background:#fff;overflow:auto;overflow-x:hidden}.bdshare_dialog_bottom{height:25px;line-height:25px;font-size:12px;text-align:right;padding:0 10px}.bdshare_dialog_bottom a{color:#999;text-decoration:none}.bdshare_dialog_bottom a:hover{color:#00a9e0}.bdshare_dialog_list li{float:left;width:130px;padding:2px;margin-left:6px;_margin-left:3px;height:28px;overflow:hidden;list-style:none}.bdshare_dialog_list a,.bdshare_popup_list a,.bdshare_popup_bottom a{color:#565656;font:12px '宋体';display:block;background-image:url(../img/share/icons_0_16.png?v=ba7acbd3.png);background-repeat:no-repeat;padding:5px 0 5px 28px;text-decoration:none;border:1px solid #fff;line-height:18px}.bdshare_dialog_list a:hover,.bdshare_popup_list a:hover{background-color:#f3f3f3;border:1px solid #eee;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px}.popup_qzone{background-position:4px -47px}.popup_tsina{background-position:4px -99px}.popup_renren{background-position:4px -203px}.popup_tqq{background-position:4px -255px}.popup_kaixin001{background-position:4px -307px}.popup_tqf{background-position:4px -359px}.popup_hi{background-position:4px -411px}.popup_douban{background-position:4px -463px}.popup_tieba{background-position:4px -723px}.popup_hx{background-position:4px -983px}.popup_fx{background-position:4px -1035px}.popup_ty{background-position:4px -1191px}.popup_fbook{background-position:4px -1347px}.popup_twi{background-position:4px -1399px}.popup_linkedin{background-position:4px -1659px}.popup_meilishuo{background-position:4px -1711px}.popup_mogujie{background-position:4px -1763px}.popup_diandian{background-position:4px -1815px}.popup_huaban{background-position:4px -1867px}.popup_duitang{background-position:4px -2023px}.popup_youdao{background-position:4px -2075px}.popup_wealink{background-position:4px -2179px}.popup_copy{background-position:4px -2283px}.popup_mail{background-position:4px -2335px}.popup_print{background-position:4px -2387px}.popup_mshare{background-position:4px -2439px}.popup_sqq{background-position:4px -2647px}.popup_sdo{background-position:4px -2699px}.popup_qingbiji{background-position:4px -2751px}.popup_people{background-position:4px -2803px}.popup_xinhua{background-position:4px -2907px}.popup_yaolan{background-position:4px -2959px}.popup_thx{background-position:4px -2491px}.popup_bdhome{background-position:4px -151px}.popup_bdxc{background-position:4px -2543px}.popup_more{background-position:4px 5px}.popup_bdysc{background-position:4px -3063px}.popup_isohu{background-position:4px -3011px}.popup_ibaidu{background-position:4px -3115px}.popup_weixin{background-position:4px -1607px}.popup_iguba{background-position:4px -1295px}.popup_h163{background-position:4px -3156px}.bdshare_popup_bg{position:absolute;display:none;z-index:9997}.bdshare_popup_box{position:absolute;display:none;z-index:9998;text-align:left;background:#f6f6f6;border:solid 1px #e9e9e9}.bdshare_popup_list{margin:0;padding:5px 0;background:#fff;overflow:auto;overflow-x:hidden;_zoom:1}.bdshare_popup_bottom{clear:both;height:30px;font-size:12px;text-align:right;padding:0 10px}.bdshare_popup_bottom a{color:#999;text-decoration:none;border:0;float:right}.bdshare_popup_bottom a:hover{color:#00a9e0}.bdshare_popup_list li{float:left;width:100px;padding:2px;margin-left:6px;_margin-left:3px;height:28px;overflow:hidden;list-style:none}.popup_tsohu,.popup_tfh,.popup_baidu,.popup_qq,.popup_msn,.popup_sohu,.popup_qy,.popup_leho,.popup_ifeng,.popup_ff,.popup_tuita,.popup_ms,.popup_deli,.popup_s51,.popup_t163,.popup_share189,.popup_xg,.popup_s139{display:none}.bdshare_dialog_box,.bdshare_dialog_list a:hover,.bdshare_popup_list a:hover{-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;}.popup_evernotecn{background-position:4px -3180px;} \ No newline at end of file +.bdshare_dialog_bg{position:fixed;_position:absolute;width:312px;height:341px;z-index:9999;overflow:hidden;display:none}.bdshare_dialog_box{position:fixed;_position:absolute;width:300px;border:6px solid #8F8F8F;height:329px;z-index:10000;text-align:left;box-shadow:0 0 7px #aaa;-webkit-box-shadow:0 0 7px #aaa;-moz-box-shadow:0 0 7px #aaa;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;overflow:hidden;background:#f6f6f6;display:none}.bdshare_dialog_top,.bdshare_popup_top{height:28px;color:#626262;overflow:hidden;font-weight:bold;font-size:14px;line-height:28px;padding:0 5px}.bdshare_dialog_close{width:22px;height:23px;background:url(../img/share/pop_c.gif?v=2d7108c8.gif) no-repeat 0 0;float:right;display:block;margin-top:2px}.bdshare_dialog_list{margin:0;padding:10px 0;height:256px;background:#fff;overflow:auto;overflow-x:hidden}.bdshare_dialog_bottom{height:25px;line-height:25px;font-size:12px;text-align:right;padding:0 10px}.bdshare_dialog_bottom a{color:#999;text-decoration:none}.bdshare_dialog_bottom a:hover{color:#00a9e0}.bdshare_dialog_list li{float:left;width:130px;padding:2px;margin-left:6px;_margin-left:3px;height:28px;overflow:hidden;list-style:none}.bdshare_dialog_list a,.bdshare_popup_list a,.bdshare_popup_bottom a{color:#565656;font:12px '宋体';display:block;background-image:url(../img/share/icons_0_16.png?v=ba7acbd3.png);background-repeat:no-repeat;padding:5px 0 5px 28px;text-decoration:none;border:1px solid #fff;line-height:18px}.bdshare_dialog_list a:hover,.bdshare_popup_list a:hover{background-color:#f3f3f3;border:1px solid #eee;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px}.popup_qzone{background-position:4px -47px}.popup_tsina{background-position:4px -99px}.popup_renren{background-position:4px -203px}.popup_tqq{background-position:4px -255px}.popup_kaixin001{background-position:4px -307px}.popup_tqf{background-position:4px -359px}.popup_hi{background-position:4px -411px}.popup_douban{background-position:4px -463px}.popup_tieba{background-position:4px -723px}.popup_hx{background-position:4px -983px}.popup_fx{background-position:4px -1035px}.popup_ty{background-position:4px -1191px}.popup_fbook{background-position:4px -1347px}.popup_twi{background-position:4px -1399px}.popup_linkedin{background-position:4px -1659px}.popup_meilishuo{background-position:4px -1711px}.popup_mogujie{background-position:4px -1763px}.popup_diandian{background-position:4px -1815px}.popup_huaban{background-position:4px -1867px}.popup_duitang{background-position:4px -2023px}.popup_youdao{background-position:4px -2075px}.popup_wealink{background-position:4px -2179px}.popup_copy{background-position:4px -2283px}.popup_mail{background-position:4px -2335px}.popup_print{background-position:4px -2387px}.popup_mshare{background-position:4px -2439px}.popup_sqq{background-position:4px -2647px}.popup_sdo{background-position:4px -2699px}.popup_qingbiji{background-position:4px -2751px}.popup_people{background-position:4px -2803px}.popup_xinhua{background-position:4px -2907px}.popup_yaolan{background-position:4px -2959px}.popup_thx{background-position:4px -2491px}.popup_bdhome{background-position:4px -151px}.popup_bdxc{background-position:4px -2543px}.popup_more{background-position:4px 5px}.popup_bdysc{background-position:4px -3063px}.popup_isohu{background-position:4px -3011px}.popup_ibaidu{background-position:4px -3115px}.popup_weixin{background-position:4px -1607px}.popup_iguba{background-position:4px -1295px}.popup_h163{background-position:4px -3156px}.bdshare_popup_bg{position:absolute;display:none;z-index:9997}.bdshare_popup_box{position:absolute;display:none;z-index:9998;text-align:left;background:#f6f6f6;border:solid 1px #e9e9e9}.bdshare_popup_list{margin:0;padding:5px 0;background:#fff;overflow:auto;overflow-x:hidden;_zoom:1}.bdshare_popup_bottom{clear:both;height:30px;font-size:12px;text-align:right;padding:0 10px}.bdshare_popup_bottom a{color:#999;text-decoration:none;border:0;float:right}.bdshare_popup_bottom a:hover{color:#00a9e0}.bdshare_popup_list li{float:left;width:100px;padding:2px;margin-left:6px;_margin-left:3px;height:28px;overflow:hidden;list-style:none}.popup_tsohu,.popup_tfh,.popup_baidu,.popup_qq,.popup_msn,.popup_sohu,.popup_qy,.popup_leho,.popup_ifeng,.popup_ff,.popup_tuita,.popup_ms,.popup_deli,.popup_s51,.popup_t163,.popup_share189,.popup_xg,.popup_s139{display:none}.bdshare_dialog_box,.bdshare_dialog_list a:hover,.bdshare_popup_list a:hover{background-clip:padding-box;}.popup_evernotecn{background-position:4px -3180px;} \ No newline at end of file diff --git a/source/static/api/css/share_style0_16.css b/static/api/css/share_style0_16.css similarity index 100% rename from source/static/api/css/share_style0_16.css rename to static/api/css/share_style0_16.css diff --git a/source/static/api/css/share_style0_24.css b/static/api/css/share_style0_24.css similarity index 100% rename from source/static/api/css/share_style0_24.css rename to static/api/css/share_style0_24.css diff --git a/source/static/api/css/share_style0_32.css b/static/api/css/share_style0_32.css similarity index 100% rename from source/static/api/css/share_style0_32.css rename to static/api/css/share_style0_32.css diff --git a/source/static/api/css/share_style1_16.css b/static/api/css/share_style1_16.css similarity index 100% rename from source/static/api/css/share_style1_16.css rename to static/api/css/share_style1_16.css diff --git a/source/static/api/css/share_style1_24.css b/static/api/css/share_style1_24.css similarity index 100% rename from source/static/api/css/share_style1_24.css rename to static/api/css/share_style1_24.css diff --git a/source/static/api/css/share_style1_32.css b/static/api/css/share_style1_32.css similarity index 100% rename from source/static/api/css/share_style1_32.css rename to static/api/css/share_style1_32.css diff --git a/source/static/api/css/share_style2.css b/static/api/css/share_style2.css similarity index 100% rename from source/static/api/css/share_style2.css rename to static/api/css/share_style2.css diff --git a/source/static/api/css/share_style2_16.css b/static/api/css/share_style2_16.css similarity index 100% rename from source/static/api/css/share_style2_16.css rename to static/api/css/share_style2_16.css diff --git a/source/static/api/css/share_style2_24.css b/static/api/css/share_style2_24.css similarity index 100% rename from source/static/api/css/share_style2_24.css rename to static/api/css/share_style2_24.css diff --git a/source/static/api/css/share_style2_32.css b/static/api/css/share_style2_32.css similarity index 100% rename from source/static/api/css/share_style2_32.css rename to static/api/css/share_style2_32.css diff --git a/source/static/api/css/share_style4.css b/static/api/css/share_style4.css similarity index 100% rename from source/static/api/css/share_style4.css rename to static/api/css/share_style4.css diff --git a/source/static/api/css/slide_share.css b/static/api/css/slide_share.css similarity index 97% rename from source/static/api/css/slide_share.css rename to static/api/css/slide_share.css index 34394f2b..5780e064 100644 --- a/source/static/api/css/slide_share.css +++ b/static/api/css/slide_share.css @@ -1 +1 @@ -.bdshare-slide-button-box{height:326px;position:fixed;overflow:visible}.bdshare-slide-button-box .bdshare-slide-button{width:24px;height:88px;display:block;position:absolute;top:58px}.bdshare-slide-style-r0 .bdshare-slide-button{background:url(../img/share/r0.gif?v=d9371706.gif) no-repeat 0 0}.bdshare-slide-style-r1 .bdshare-slide-button{background:url(../img/share/r1.gif?v=5668db67.gif) no-repeat 0 0}.bdshare-slide-style-r2 .bdshare-slide-button{background:url(../img/share/r2.gif?v=08b06973.gif) no-repeat 0 0}.bdshare-slide-style-r3 .bdshare-slide-button{background:url(../img/share/r3.gif?v=76e62e61.gif) no-repeat 0 0}.bdshare-slide-style-r4 .bdshare-slide-button{background:url(../img/share/r4.gif?v=1a3eaae1.gif) no-repeat 0 0}.bdshare-slide-style-r5 .bdshare-slide-button{background:url(../img/share/r5.gif?v=c90e5a12.gif) no-repeat 0 0}.bdshare-slide-style-r6 .bdshare-slide-button{background:url(../img/share/r6.gif?v=8af9306f.gif) no-repeat 0 0}.bdshare-slide-style-r7 .bdshare-slide-button{background:url(../img/share/r7.gif?v=053cdaac.gif) no-repeat 0 0}.bdshare-slide-style-r8 .bdshare-slide-button{background:url(../img/share/r8.gif?v=640a093b.gif) no-repeat 0 0}.bdshare-slide-style-l0 .bdshare-slide-button{background:url(../img/share/l0.gif?v=4e666e56.gif) no-repeat 0 0}.bdshare-slide-style-l1 .bdshare-slide-button{background:url(../img/share/l1.gif?v=3ffb4640.gif) no-repeat 0 0}.bdshare-slide-style-l2 .bdshare-slide-button{background:url(../img/share/l2.gif?v=47242a70.gif) no-repeat 0 0}.bdshare-slide-style-l3 .bdshare-slide-button{background:url(../img/share/l3.gif?v=78e2d043.gif) no-repeat 0 0}.bdshare-slide-style-l4 .bdshare-slide-button{background:url(../img/share/l4.gif?v=4afa38d2.gif) no-repeat 0 0}.bdshare-slide-style-l5 .bdshare-slide-button{background:url(../img/share/l5.gif?v=5e170970.gif) no-repeat 0 0}.bdshare-slide-style-l6 .bdshare-slide-button{background:url(../img/share/l6.gif?v=8759da8b.gif) no-repeat 0 0}.bdshare-slide-style-l7 .bdshare-slide-button{background:url(../img/share/l7.gif?v=df4c2738.gif) no-repeat 0 0}.bdshare-slide-style-l8 .bdshare-slide-button{background:url(../img/share/l8.gif?v=31ac73d4.gif) no-repeat 0 0}.bdshare-slide-list-box{border:solid 1px #e9e9e9;text-align:left;overflow:hidden;background:#f6f6f6}.bdshare-slide-top{height:28px;color:#626262;overflow:hidden;font-weight:bold;font-size:14px;line-height:28px;padding:0 5px}.bdshare-slide-list{background:#fff;overflow:auto;overflow-x:hidden;padding:5px 0;margin:0;background:#fff;overflow:auto;overflow-x:hidden;_zoom:1}.bdshare-slide-list-ul{padding:0;margin:0;border:0;list-style:none}.bdshare-slide-bottom{line-height:25px;font-size:12px;text-align:right;clear:both;height:30px}.bdshare-slide-bottom a{color:#999;text-decoration:none;border:0;float:right}.bdshare-slide-bottom a:hover{color:#00a9e0}.bdshare-slide-list li{float:left;padding:2px;margin-left:6px;_margin-left:3px;height:28px;overflow:hidden;list-style:none;width:100px}.bdshare-slide-list a,.bdshare-slide-bottom a{color:#565656;font:12px '宋体';display:block;background-image:url(../img/share/icons_0_16.png?v=91362611.png);background-repeat:no-repeat;padding:5px 0 5px 28px;text-decoration:none;border:1px solid #fff;line-height:18px}.bdshare-slide-list a:hover{background-color:#f3f3f3;border:1px solid #eee;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px}.slide-qzone{background-position:4px -47px}.slide-tsina{background-position:4px -99px}.slide-renren{background-position:4px -203px}.slide-tqq{background-position:4px -255px}.slide-kaixin001{background-position:4px -307px}.slide-tqf{background-position:4px -359px}.slide-hi{background-position:4px -411px}.slide-douban{background-position:4px -463px}.slide-tieba{background-position:4px -723px}.slide-hx{background-position:4px -983px}.slide-fx{background-position:4px -1035px}.slide-ty{background-position:4px -1191px}.slide-fbook{background-position:4px -1347px}.slide-twi{background-position:4px -1399px}.slide-zx{background-position:4px -1607px}.slide-linkedin{background-position:4px -1659px}.slide-meilishuo{background-position:4px -1711px}.slide-mogujie{background-position:4px -1763px}.slide-diandian{background-position:4px -1815px}.slide-huaban{background-position:4px -1867px}.slide-duitang{background-position:4px -2023px}.slide-youdao{background-position:4px -2075px}.slide-wealink{background-position:4px -2179px}.slide-copy{background-position:4px -2283px}.slide-mail{background-position:4px -2335px}.slide-print{background-position:4px -2387px}.slide-mshare{background-position:4px -2439px}.slide-sqq{background-position:4px -2647px}.slide-sdo{background-position:4px -2699px}.slide-qingbiji{background-position:4px -2751px}.slide-people{background-position:4px -2803px}.slide-xinhua{background-position:4px -2907px}.slide-yaolan{background-position:4px -2959px}.slide-thx{background-position:4px -2491px}.slide-bdhome{background-position:4px -151px}.slide-bdxc{background-position:4px -2543px}.slide-bdysc{background-position:4px -3063px}.slide-isohu{background-position:4px -3011px}.slide-more{background-position:4px 5px}.slide-ibaidu{background-position:4px -3115px}.slide-weixin{background-position:4px -1607px}.slide-iguba{background-position:4px -1295px}.slide-evernotecn{background-position:4px -3179px}.bdshare-slide-bottom .slide-more{border:none;margin-right:10px}.slide-tsohu,.slide-tfh,.slide-baidu,.slide-qq,.slide-msn,.slide-sohu,.slide-qy,.slide-leho,.slide-ifeng,.slide-ff,.slide-tuita,.slide-ms,.slide-deli,.slide-s51,.slide-t163,.slide-share189,.slide-xg,.slide-s139{display:none}.bdshare-slide-list a:hover{-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box} \ No newline at end of file +.bdshare-slide-button-box{height:326px;position:fixed;overflow:visible}.bdshare-slide-button-box .bdshare-slide-button{width:24px;height:88px;display:block;position:absolute;top:58px}.bdshare-slide-style-r0 .bdshare-slide-button{background:url(../img/share/r0.gif?v=d9371706.gif) no-repeat 0 0}.bdshare-slide-style-r1 .bdshare-slide-button{background:url(../img/share/r1.gif?v=5668db67.gif) no-repeat 0 0}.bdshare-slide-style-r2 .bdshare-slide-button{background:url(../img/share/r2.gif?v=08b06973.gif) no-repeat 0 0}.bdshare-slide-style-r3 .bdshare-slide-button{background:url(../img/share/r3.gif?v=76e62e61.gif) no-repeat 0 0}.bdshare-slide-style-r4 .bdshare-slide-button{background:url(../img/share/r4.gif?v=1a3eaae1.gif) no-repeat 0 0}.bdshare-slide-style-r5 .bdshare-slide-button{background:url(../img/share/r5.gif?v=c90e5a12.gif) no-repeat 0 0}.bdshare-slide-style-r6 .bdshare-slide-button{background:url(../img/share/r6.gif?v=8af9306f.gif) no-repeat 0 0}.bdshare-slide-style-r7 .bdshare-slide-button{background:url(../img/share/r7.gif?v=053cdaac.gif) no-repeat 0 0}.bdshare-slide-style-r8 .bdshare-slide-button{background:url(../img/share/r8.gif?v=640a093b.gif) no-repeat 0 0}.bdshare-slide-style-l0 .bdshare-slide-button{background:url(../img/share/l0.gif?v=4e666e56.gif) no-repeat 0 0}.bdshare-slide-style-l1 .bdshare-slide-button{background:url(../img/share/l1.gif?v=3ffb4640.gif) no-repeat 0 0}.bdshare-slide-style-l2 .bdshare-slide-button{background:url(../img/share/l2.gif?v=47242a70.gif) no-repeat 0 0}.bdshare-slide-style-l3 .bdshare-slide-button{background:url(../img/share/l3.gif?v=78e2d043.gif) no-repeat 0 0}.bdshare-slide-style-l4 .bdshare-slide-button{background:url(../img/share/l4.gif?v=4afa38d2.gif) no-repeat 0 0}.bdshare-slide-style-l5 .bdshare-slide-button{background:url(../img/share/l5.gif?v=5e170970.gif) no-repeat 0 0}.bdshare-slide-style-l6 .bdshare-slide-button{background:url(../img/share/l6.gif?v=8759da8b.gif) no-repeat 0 0}.bdshare-slide-style-l7 .bdshare-slide-button{background:url(../img/share/l7.gif?v=df4c2738.gif) no-repeat 0 0}.bdshare-slide-style-l8 .bdshare-slide-button{background:url(../img/share/l8.gif?v=31ac73d4.gif) no-repeat 0 0}.bdshare-slide-list-box{border:solid 1px #e9e9e9;text-align:left;overflow:hidden;background:#f6f6f6}.bdshare-slide-top{height:28px;color:#626262;overflow:hidden;font-weight:bold;font-size:14px;line-height:28px;padding:0 5px}.bdshare-slide-list{background:#fff;overflow:auto;overflow-x:hidden;padding:5px 0;margin:0;background:#fff;overflow:auto;overflow-x:hidden;_zoom:1}.bdshare-slide-list-ul{padding:0;margin:0;border:0;list-style:none}.bdshare-slide-bottom{line-height:25px;font-size:12px;text-align:right;clear:both;height:30px}.bdshare-slide-bottom a{color:#999;text-decoration:none;border:0;float:right}.bdshare-slide-bottom a:hover{color:#00a9e0}.bdshare-slide-list li{float:left;padding:2px;margin-left:6px;_margin-left:3px;height:28px;overflow:hidden;list-style:none;width:100px}.bdshare-slide-list a,.bdshare-slide-bottom a{color:#565656;font:12px '宋体';display:block;background-image:url(../img/share/icons_0_16.png?v=91362611.png);background-repeat:no-repeat;padding:5px 0 5px 28px;text-decoration:none;border:1px solid #fff;line-height:18px}.bdshare-slide-list a:hover{background-color:#f3f3f3;border:1px solid #eee;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px}.slide-qzone{background-position:4px -47px}.slide-tsina{background-position:4px -99px}.slide-renren{background-position:4px -203px}.slide-tqq{background-position:4px -255px}.slide-kaixin001{background-position:4px -307px}.slide-tqf{background-position:4px -359px}.slide-hi{background-position:4px -411px}.slide-douban{background-position:4px -463px}.slide-tieba{background-position:4px -723px}.slide-hx{background-position:4px -983px}.slide-fx{background-position:4px -1035px}.slide-ty{background-position:4px -1191px}.slide-fbook{background-position:4px -1347px}.slide-twi{background-position:4px -1399px}.slide-zx{background-position:4px -1607px}.slide-linkedin{background-position:4px -1659px}.slide-meilishuo{background-position:4px -1711px}.slide-mogujie{background-position:4px -1763px}.slide-diandian{background-position:4px -1815px}.slide-huaban{background-position:4px -1867px}.slide-duitang{background-position:4px -2023px}.slide-youdao{background-position:4px -2075px}.slide-wealink{background-position:4px -2179px}.slide-copy{background-position:4px -2283px}.slide-mail{background-position:4px -2335px}.slide-print{background-position:4px -2387px}.slide-mshare{background-position:4px -2439px}.slide-sqq{background-position:4px -2647px}.slide-sdo{background-position:4px -2699px}.slide-qingbiji{background-position:4px -2751px}.slide-people{background-position:4px -2803px}.slide-xinhua{background-position:4px -2907px}.slide-yaolan{background-position:4px -2959px}.slide-thx{background-position:4px -2491px}.slide-bdhome{background-position:4px -151px}.slide-bdxc{background-position:4px -2543px}.slide-bdysc{background-position:4px -3063px}.slide-isohu{background-position:4px -3011px}.slide-more{background-position:4px 5px}.slide-ibaidu{background-position:4px -3115px}.slide-weixin{background-position:4px -1607px}.slide-iguba{background-position:4px -1295px}.slide-evernotecn{background-position:4px -3179px}.bdshare-slide-bottom .slide-more{border:none;margin-right:10px}.slide-tsohu,.slide-tfh,.slide-baidu,.slide-qq,.slide-msn,.slide-sohu,.slide-qy,.slide-leho,.slide-ifeng,.slide-ff,.slide-tuita,.slide-ms,.slide-deli,.slide-s51,.slide-t163,.slide-share189,.slide-xg,.slide-s139{display:none}.bdshare-slide-list a:hover{background-clip:padding-box} \ No newline at end of file diff --git a/source/static/api/css/weixin_popup.css b/static/api/css/weixin_popup.css similarity index 100% rename from source/static/api/css/weixin_popup.css rename to static/api/css/weixin_popup.css diff --git a/source/static/api/img/share/icons_0_16.png b/static/api/img/share/icons_0_16.png similarity index 100% rename from source/static/api/img/share/icons_0_16.png rename to static/api/img/share/icons_0_16.png diff --git a/source/static/api/img/share/icons_0_24.png b/static/api/img/share/icons_0_24.png similarity index 100% rename from source/static/api/img/share/icons_0_24.png rename to static/api/img/share/icons_0_24.png diff --git a/source/static/api/img/share/icons_0_32.png b/static/api/img/share/icons_0_32.png similarity index 100% rename from source/static/api/img/share/icons_0_32.png rename to static/api/img/share/icons_0_32.png diff --git a/source/static/api/img/share/icons_1_16.png b/static/api/img/share/icons_1_16.png similarity index 100% rename from source/static/api/img/share/icons_1_16.png rename to static/api/img/share/icons_1_16.png diff --git a/source/static/api/img/share/icons_1_24.png b/static/api/img/share/icons_1_24.png similarity index 100% rename from source/static/api/img/share/icons_1_24.png rename to static/api/img/share/icons_1_24.png diff --git a/source/static/api/img/share/icons_1_32.png b/static/api/img/share/icons_1_32.png similarity index 100% rename from source/static/api/img/share/icons_1_32.png rename to static/api/img/share/icons_1_32.png diff --git a/source/static/api/img/share/icons_2_16.png b/static/api/img/share/icons_2_16.png similarity index 100% rename from source/static/api/img/share/icons_2_16.png rename to static/api/img/share/icons_2_16.png diff --git a/source/static/api/img/share/icons_2_24.png b/static/api/img/share/icons_2_24.png similarity index 100% rename from source/static/api/img/share/icons_2_24.png rename to static/api/img/share/icons_2_24.png diff --git a/source/static/api/img/share/icons_2_32.png b/static/api/img/share/icons_2_32.png similarity index 100% rename from source/static/api/img/share/icons_2_32.png rename to static/api/img/share/icons_2_32.png diff --git a/source/static/api/img/share/l0.gif b/static/api/img/share/l0.gif similarity index 100% rename from source/static/api/img/share/l0.gif rename to static/api/img/share/l0.gif diff --git a/source/static/api/img/share/l1.gif b/static/api/img/share/l1.gif similarity index 100% rename from source/static/api/img/share/l1.gif rename to static/api/img/share/l1.gif diff --git a/source/static/api/img/share/l2.gif b/static/api/img/share/l2.gif similarity index 100% rename from source/static/api/img/share/l2.gif rename to static/api/img/share/l2.gif diff --git a/source/static/api/img/share/l3.gif b/static/api/img/share/l3.gif similarity index 100% rename from source/static/api/img/share/l3.gif rename to static/api/img/share/l3.gif diff --git a/source/static/api/img/share/l4.gif b/static/api/img/share/l4.gif similarity index 100% rename from source/static/api/img/share/l4.gif rename to static/api/img/share/l4.gif diff --git a/source/static/api/img/share/l5.gif b/static/api/img/share/l5.gif similarity index 100% rename from source/static/api/img/share/l5.gif rename to static/api/img/share/l5.gif diff --git a/source/static/api/img/share/l6.gif b/static/api/img/share/l6.gif similarity index 100% rename from source/static/api/img/share/l6.gif rename to static/api/img/share/l6.gif diff --git a/source/static/api/img/share/l7.gif b/static/api/img/share/l7.gif similarity index 100% rename from source/static/api/img/share/l7.gif rename to static/api/img/share/l7.gif diff --git a/source/static/api/img/share/l8.gif b/static/api/img/share/l8.gif similarity index 100% rename from source/static/api/img/share/l8.gif rename to static/api/img/share/l8.gif diff --git a/source/static/api/img/share/pop_c.gif b/static/api/img/share/pop_c.gif similarity index 100% rename from source/static/api/img/share/pop_c.gif rename to static/api/img/share/pop_c.gif diff --git a/source/static/api/img/share/r0.gif b/static/api/img/share/r0.gif similarity index 100% rename from source/static/api/img/share/r0.gif rename to static/api/img/share/r0.gif diff --git a/source/static/api/img/share/r1.gif b/static/api/img/share/r1.gif similarity index 100% rename from source/static/api/img/share/r1.gif rename to static/api/img/share/r1.gif diff --git a/source/static/api/img/share/r2.gif b/static/api/img/share/r2.gif similarity index 100% rename from source/static/api/img/share/r2.gif rename to static/api/img/share/r2.gif diff --git a/source/static/api/img/share/r3.gif b/static/api/img/share/r3.gif similarity index 100% rename from source/static/api/img/share/r3.gif rename to static/api/img/share/r3.gif diff --git a/source/static/api/img/share/r4.gif b/static/api/img/share/r4.gif similarity index 100% rename from source/static/api/img/share/r4.gif rename to static/api/img/share/r4.gif diff --git a/source/static/api/img/share/r5.gif b/static/api/img/share/r5.gif similarity index 100% rename from source/static/api/img/share/r5.gif rename to static/api/img/share/r5.gif diff --git a/source/static/api/img/share/r6.gif b/static/api/img/share/r6.gif similarity index 100% rename from source/static/api/img/share/r6.gif rename to static/api/img/share/r6.gif diff --git a/source/static/api/img/share/r7.gif b/static/api/img/share/r7.gif similarity index 100% rename from source/static/api/img/share/r7.gif rename to static/api/img/share/r7.gif diff --git a/source/static/api/img/share/r8.gif b/static/api/img/share/r8.gif similarity index 100% rename from source/static/api/img/share/r8.gif rename to static/api/img/share/r8.gif diff --git a/source/static/api/img/share/sc.png b/static/api/img/share/sc.png similarity index 100% rename from source/static/api/img/share/sc.png rename to static/api/img/share/sc.png diff --git a/source/static/api/img/share/selectshare_close.png b/static/api/img/share/selectshare_close.png similarity index 100% rename from source/static/api/img/share/selectshare_close.png rename to static/api/img/share/selectshare_close.png diff --git a/source/static/api/img/share/share-search-icon.png b/static/api/img/share/share-search-icon.png similarity index 100% rename from source/static/api/img/share/share-search-icon.png rename to static/api/img/share/share-search-icon.png diff --git a/source/static/api/js/base/class.js b/static/api/js/base/class.js similarity index 100% rename from source/static/api/js/base/class.js rename to static/api/js/base/class.js diff --git a/source/static/api/js/base/tangram.js b/static/api/js/base/tangram.js similarity index 100% rename from source/static/api/js/base/tangram.js rename to static/api/js/base/tangram.js diff --git a/source/static/api/js/component/animate.js b/static/api/js/component/animate.js similarity index 100% rename from source/static/api/js/component/animate.js rename to static/api/js/component/animate.js diff --git a/source/static/api/js/component/anticheat.js b/static/api/js/component/anticheat.js similarity index 100% rename from source/static/api/js/component/anticheat.js rename to static/api/js/component/anticheat.js diff --git a/source/static/api/js/component/comm_tools.js b/static/api/js/component/comm_tools.js similarity index 100% rename from source/static/api/js/component/comm_tools.js rename to static/api/js/component/comm_tools.js diff --git a/source/static/api/js/component/partners.js b/static/api/js/component/partners.js similarity index 100% rename from source/static/api/js/component/partners.js rename to static/api/js/component/partners.js diff --git a/source/static/api/js/component/pop_base.js b/static/api/js/component/pop_base.js similarity index 100% rename from source/static/api/js/component/pop_base.js rename to static/api/js/component/pop_base.js diff --git a/source/static/api/js/component/pop_dialog.js b/static/api/js/component/pop_dialog.js similarity index 100% rename from source/static/api/js/component/pop_dialog.js rename to static/api/js/component/pop_dialog.js diff --git a/source/static/api/js/component/pop_popup.js b/static/api/js/component/pop_popup.js similarity index 100% rename from source/static/api/js/component/pop_popup.js rename to static/api/js/component/pop_popup.js diff --git a/source/static/api/js/component/pop_popup_slide.js b/static/api/js/component/pop_popup_slide.js similarity index 100% rename from source/static/api/js/component/pop_popup_slide.js rename to static/api/js/component/pop_popup_slide.js diff --git a/source/static/api/js/component/qrcode.js b/static/api/js/component/qrcode.js similarity index 100% rename from source/static/api/js/component/qrcode.js rename to static/api/js/component/qrcode.js diff --git a/source/static/api/js/conf/const.js b/static/api/js/conf/const.js similarity index 100% rename from source/static/api/js/conf/const.js rename to static/api/js/conf/const.js diff --git a/source/static/api/js/conf/define.js b/static/api/js/conf/define.js similarity index 100% rename from source/static/api/js/conf/define.js rename to static/api/js/conf/define.js diff --git a/source/static/api/js/share.js b/static/api/js/share.js similarity index 100% rename from source/static/api/js/share.js rename to static/api/js/share.js diff --git a/source/static/api/js/share/api_base.js b/static/api/js/share/api_base.js similarity index 100% rename from source/static/api/js/share/api_base.js rename to static/api/js/share/api_base.js diff --git a/source/static/api/js/share/combine_api.js b/static/api/js/share/combine_api.js similarity index 100% rename from source/static/api/js/share/combine_api.js rename to static/api/js/share/combine_api.js diff --git a/source/static/api/js/share/image_api.js b/static/api/js/share/image_api.js similarity index 100% rename from source/static/api/js/share/image_api.js rename to static/api/js/share/image_api.js diff --git a/source/static/api/js/share/like_api.js b/static/api/js/share/like_api.js similarity index 100% rename from source/static/api/js/share/like_api.js rename to static/api/js/share/like_api.js diff --git a/source/static/api/js/share/likeshare.js b/static/api/js/share/likeshare.js similarity index 100% rename from source/static/api/js/share/likeshare.js rename to static/api/js/share/likeshare.js diff --git a/source/static/api/js/share/select_api.js b/static/api/js/share/select_api.js similarity index 100% rename from source/static/api/js/share/select_api.js rename to static/api/js/share/select_api.js diff --git a/source/static/api/js/share/share_api.js b/static/api/js/share/share_api.js similarity index 100% rename from source/static/api/js/share/share_api.js rename to static/api/js/share/share_api.js diff --git a/source/static/api/js/share/slide_api.js b/static/api/js/share/slide_api.js similarity index 100% rename from source/static/api/js/share/slide_api.js rename to static/api/js/share/slide_api.js diff --git a/source/static/api/js/start/router.js b/static/api/js/start/router.js similarity index 100% rename from source/static/api/js/start/router.js rename to static/api/js/start/router.js diff --git a/static/api/js/trans/data.js b/static/api/js/trans/data.js new file mode 100644 index 00000000..e69de29b diff --git a/static/api/js/trans/logger.js b/static/api/js/trans/logger.js new file mode 100644 index 00000000..e69de29b diff --git a/source/static/api/js/trans/trans.js b/static/api/js/trans/trans.js similarity index 100% rename from source/static/api/js/trans/trans.js rename to static/api/js/trans/trans.js diff --git a/source/static/api/js/trans/trans_bdxc.js b/static/api/js/trans/trans_bdxc.js similarity index 100% rename from source/static/api/js/trans/trans_bdxc.js rename to static/api/js/trans/trans_bdxc.js diff --git a/source/static/api/js/trans/trans_bdysc.js b/static/api/js/trans/trans_bdysc.js similarity index 100% rename from source/static/api/js/trans/trans_bdysc.js rename to static/api/js/trans/trans_bdysc.js diff --git a/source/static/api/js/trans/trans_weixin.js b/static/api/js/trans/trans_weixin.js similarity index 100% rename from source/static/api/js/trans/trans_weixin.js rename to static/api/js/trans/trans_weixin.js diff --git a/source/static/api/js/view/image_view.js b/static/api/js/view/image_view.js similarity index 100% rename from source/static/api/js/view/image_view.js rename to static/api/js/view/image_view.js diff --git a/source/static/api/js/view/like_view.js b/static/api/js/view/like_view.js similarity index 100% rename from source/static/api/js/view/like_view.js rename to static/api/js/view/like_view.js diff --git a/source/static/api/js/view/select_view.js b/static/api/js/view/select_view.js similarity index 100% rename from source/static/api/js/view/select_view.js rename to static/api/js/view/select_view.js diff --git a/source/static/api/js/view/share_view.js b/static/api/js/view/share_view.js similarity index 100% rename from source/static/api/js/view/share_view.js rename to static/api/js/view/share_view.js diff --git a/source/static/api/js/view/slide_view.js b/static/api/js/view/slide_view.js similarity index 100% rename from source/static/api/js/view/slide_view.js rename to static/api/js/view/slide_view.js diff --git a/source/static/api/js/view/view_base.js b/static/api/js/view/view_base.js similarity index 100% rename from source/static/api/js/view/view_base.js rename to static/api/js/view/view_base.js diff --git a/studynotes.html b/studynotes.html new file mode 100644 index 00000000..3e0945b4 --- /dev/null +++ b/studynotes.html @@ -0,0 +1,373 @@ +PyQt学习心得 | PyQt + + + + + + + + + + + + + +

    PyQt学习心得

    在学习 PyQt 的过程中由于资料的缺乏或者没有中文导致大多数人感叹资料太少,学习困难,又或者急于求进,赶鸭子上架的情况,此时有系统的学习方法很重要。每个人都需要有自己的学习方法,别人的学习方法并不一定适合自己但可以采纳一些。笔者在这里列举了一些当初自己自学的一些心得和方法,希望帮助大家建立一套自己的学习 PyQt 的方法,提高自身的学习能力。

    + +

    # Python 基础

    +

    在学习和使用 PyQt 之前需要熟练使用 Python,经过对 QQ 群里经常提问的问题的分析,发现大部分人对 Python 中的基础知识掌握不牢固导致很多基础问题,如果要想更好的使用 Python 以及它的扩展必需要进行系统的学习。这里列举一下常用的知识点。

    +
      +
    1. 类         参考资料
    2. +
    3. 类的继承
    4. +
    5. 类的多继承
    6. +
    7. 类方法重写     参考资料
    8. +
    9. 类中的 super 函数  参考资料
    10. +
    11. 函数调用 / 参数类型
    12. +
    13. 对象调用 (参考第 1 点)
    14. +
    +

    必须熟练掌握上面的知识点后入门 PyQt 才比较容易,如果初学者对上面的知识点还不是很了解,本文不适合继续往下阅读。

    +

    # 设计师

    +

    Qt 设计师除了方便快速设计一些简单的界面外,其实笔者觉得更大的作用在于帮助用户熟悉各类控件、属性、信号等

    +
      +
    1. 这里建议初学者不要急于求成,打开设计师新建一个 Widget 的窗口,比如
    2. +
    +

    desiger_create

    +
      +
    1. 然后把左侧的所有控件挨个拖动到中间的窗口中,比如这里拖动一个 Push Button 按钮
    2. +
    +

    desiger_drag

    +
      +
    1. 在设计师右下角的属性编辑器中列举了该控件的所有父类,意味着可以调用和重写父类的所有方法,建议初学者把这个属性编辑器的所有属性挨个调整看看效果,部分控件可能需要 Ctrl+R 预览界面才能看到,同时像 QListWidget,QTreeWidget,QTableWidget 等某些控件需要在控件上右键增加数据才可以
    2. +
    +

    desiger_property
    +desiger_property2

    +
      +
    1. 两个控件之间简单的信号槽关联可以通过设计师快速的设置
    2. +
    +

    desiger_signal
    +desiger_signal2

    +
      +
    1. 提高进阶的方法,当你需要手动写代码实现界面的时候,不妨把 UI 文件转出 PY 文件,看看是如何构造的(这里涉及到布局等知识见后文)
    2. +
    +

    # 布局

    +

    Qt 界面提供了方便的 4 种基本布局,QVboxLayout,QHboxLayout,QFormLayout,QGridLayout,初学者需要数量掌握这 4 种布局外加 2 种拉伸器(占位挤压)

    +

    首先需要知道 Qt 界面的中控件的层级顺序以及 parent,parent 的作用既作为子控件的父元素也可以自动管理 Qt 的对象(具体可以搜索下关于 Qt parent 的资料)

    +
      +
    1. 在没有布局的情况下,在设计师中拖动摆放的控件是一层一层的叠加覆盖,此时每个添加的子控件的 parent 都是最外层的控件
    2. +
    +

    desiger_stack

    +
      +
    1. 如果需要界面中的控件自动适应高度宽度,此时则需要使用 4 种布局来包裹里面的子控件,注意的是:布局不是控件不能设置高度宽度和样式等,是一个抽象的东西,就好比是一根橡皮筋包裹几个矩形的物品;布局也可以设置一些属性(在设计师属性编辑器中),比如设置两者直接的间距,设置距离上下左右的间距,设置比例等
    2. +
    +

    desiger_layout

    +
      +
    1. 在没有布局或者有布局的时候。可以添加容器控件(QWidget,QFrame,QGroupBox,QScrollArea,QToolBox,QTabWidget,QStackedWidget,QMidArea,QDockWidget)这些容器可以放置子控件,从而循环嵌套。
    2. +
    +

    # 例子

    +

    在 PyQt5.5 的时候自带了一个例子文件夹(后面的版本没有的话可以下载 PyQt5 源码,里面有个 examples 文件夹),想要熟练的掌握 PyQt 还需要从自带的例子中学习,必须要每个例子都运行一次然后看看这个例子实现了什么,这样才能记忆深刻。
    +同时很多开发者在 https://github.com/PyQt5/PyQt 分享了各类进阶例子,同时也欢迎大家共同完善该项目,提供更多更好的例子。另外也可以下载该项目的客户端 PyQtClient 软件,支持运行其中的例子

    +

    建议在更深入的学习 PyQt 之前多看看一些例子。

    +

    # 文档

    +

    接下来要说的就是 Qt 的 api 文档,官网文档,这里其实不要害怕是英文就不想看,觉得看不懂了,其实官网的文档还是比较简洁的,而且函数名也比较直观就能知道意思。也可以用谷歌浏览器打开右键翻译,基本上都能看懂。笔者前期写过一篇如何查阅 Qt 文档的文档可以阅读学习一番。

    +

    这里就拿 QWebEngineView 举一个例子,首先初学者在使用这个浏览器控件时候,会有诸多的问题比如:Cookie,拦截器等就不知道如何去调用函数来设置

    +
      +
    1. 首先打开官网文档 https://doc.qt.io/qt-5/qwebengineview.html,可以看到只有少量的函数可以调用,寻找一番并没有发现和 Cookie 相关的东西,这个时候就需要把重点放在有特俗返回值的函数上,比如:
    2. +
    +
    QWebEngineHistory *	          history() const
    +QWebEnginePage *	          page() const
    +QWebEngineSettings *	      settings() const
    +

    这三个函数返回了一个类实例,就意味着可以调用其中的方法。

    +
      +
    1. +

      点击 page () 打开 https://doc.qt.io/qt-5/qwebenginepage.html,发现没有 cookie 相关的东西,只有 QWebEngineProfile * profile () const 这个函数比较可疑。

      +
    2. +
    3. +

      点击 **profile ()** 打开 https://doc.qt.io/qt-5/qwebengineprofile.html,在浏览器中搜索 cookie 发现这个类中包含大量和 cookie 相关的东西,比如:**QWebEngineCookieStore * cookieStore ()`** 从名字上可以猜测大概意思为 cookie 储存

      +
    4. +
    5. +

      点击 **cookieStore ()** 打开 https://doc.qt.io/qt-5/qwebenginecookiestore.html,此时就会发现这个类里面包含了删除和设置 cookie 的方法。

      +
    6. +
    7. +

      但是找到了这些方法后,面对初学者又一个问题来了,该如何去用?根据上面 4 点整理一下,把他们当做简单的 Python 对象,方法和操作方法和 class 一样的。

      +
    8. +
    +
    self.webview = QWebEngineView()
    +# 得到page
    +page = self.webview.page()
    +# 得到profile
    +profile = page.profile()
    +# 得到cookieStore
    +cookieStore = profile.cookieStore()
    +# 清空cookie
    +cookieStore.deleteAllCookies()
    +
    +# 用简短代码来表达就是
    +cookieStore = self.webview.page().profile().cookieStore()
    +cookieStore.deleteAllCookies()
    +

    # 异常调试

    +

    可能有时候由于粗心,或者调用了一些非法函数,参数错误等会导致程序出现一些异常,首先第一步复制最后一行的错误去百度或者谷歌搜索,大多时候能找到问题所在。其次如果搜索不到或者自己的异常可能是由于某个变量的值不对引起的,就需要在编辑器中打断点使用 DEBUG 模式调试变量值(如果不会可以采用麻烦一点的办法:用 print 打印出变量值)

    +

    遇到问题后首先需要自己多调试排查问题,不要一遇到问题就去问,自己多尝试一个一个排查直到找到问题所在并解决,这也是一种提高自身能力的地方。

    +

    # 检索资料

    +

    作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。信息检索是每个程序猿必备的能力之一,其好处在于可以更快更准确的在茫茫网络海洋中找到自己所需要的东西,这个过程需要长期不断积累和练习。

    +
      +
    1. 中文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt 拖拽
    2. +
    3. 英文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt Drag Drop
    4. +
    +

    # 片尾

    +

    好了,笔者基本上的学习过程就整理如上,这并不是说每个人都适合这样的方法,但至少笔者是这样一步一步走过来的。当你养成了一个学习、发现和解决问题的好习惯时就会慢慢得心应手。

    +
    文章作者: Irony
    文章链接: https://pyqt5.com/studynotes.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
    赞助
    • 微信付
      微信付
    • 支付宝
      支付宝
    \ No newline at end of file diff --git a/suggesteditor.html b/suggesteditor.html new file mode 100644 index 00000000..1f9cf8cf --- /dev/null +++ b/suggesteditor.html @@ -0,0 +1,329 @@ +推荐编辑器LiClipse | PyQt + + + + + + + + + + + + + +

    推荐编辑器LiClipse

    关于 Python 的开发编辑器有很多,每个人有每个人的喜好,经常看到很多在问什么编辑器好用,有人推荐 Sublime,有人推荐 Pycharm 等等,这里就不去比较其它编辑器的优缺点了,只谈谈关于 LiClipse 这个编辑器在初级使用阶段的智能提示功能等。开箱即用,支持多种语言,RST,Markdown 和 HTML 编辑器的 HTML 预览。

    + +

    其实 LiClipse 这个编辑器就是以前的 PyDev 插件的独立版本,基于 Eclipse 编辑器开发,去掉了 Java 的相关开发功能,关于软件的详细说明可以去官网查看: http://www.liclipse.com/

    +

    编辑器只需要少量的配置,打开即可使用,快速自动 import,也可以根据需要安装自己所需的插件,比如 json、svn、主题插件等。个人推荐:适合刚入门的新手使用

    +

    由于新版的 PyQt 和 PyDev 去掉了详细的函数提示,所以 PyQt 的智能提示只有函数和返回值,并没有英文注释,但是以前的比如 PyQt4 的智能提示应该是有详细的英文注释提示。

    +

    # 界面预览

    +
      +
    1. 主界面
      +editor1
    2. +
    3. 鼠标悬停提示
      +editor2
    4. +
    5. 输入提示
      +editor3
    6. +
    7. Git 面板
      +editor4
    8. +
    9. 全局搜索(Ctrl + H)
      +editor5
      +editor6
    10. +
    +

    # 自动导包

    +

    其实这个功能我是非常喜欢的,通过按下快捷键即可自动寻找包名导入,快捷键 Ctrl + Shift + O

    +

    editor_import

    +

    也可以在标红的代码上按下 Ctrl + F1 进行导入

    +

    editor_import2

    +

    # 配置

    +

    打开编辑器后首先要配置【Window -> Preferences】的就是 Python 的环境变量,可以同时添加多个 Python 版本

    +

    editor_env

    +

    # Tab 等设置

    +
      +
    1. Insert spaces for tabs tab 转空格
    2. +
    3. Show line numbers 显示行号
    4. +
    +

    editor_tab

    +

    # 模版

    +

    这个功能可以快速插入自己定义好的模版代码,比如 if __name__ == '__main__': 等等,比如我这里配置的创建文件的模版

    +

    editor_tpl

    +

    # 常用快捷键

    + + + + + + + + + + + + + + + + + + + + + +
    格式化对齐Ctrl + Shift + F
    自动导包Ctrl + Shift + O
    快捷提示Alt + /
    +
    文章作者: Irony
    文章链接: https://pyqt5.com/suggesteditor.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt
    赞助
    • 微信付
      微信付
    • 支付宝
      支付宝
    \ No newline at end of file diff --git a/tag/.9png/feed.json b/tag/.9png/feed.json new file mode 100644 index 00000000..49fdabd1 --- /dev/null +++ b/tag/.9png/feed.json @@ -0,0 +1,21 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \".9png\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/qtninepatch.html", + "url": "https://pyqt5.com/qtninepatch.html", + "title": "PyQt5显示.9格式的PNG图片", + "date_published": "2018-10-26T02:00:08.000Z", + "content_html": "

    做过安卓开发的和使用过 QQ 的都知道 .9.png 这种图片格式,效果就如 QQ 的聊天气泡一样可以拉伸,这种格式的图片允许开发人员定义可扩展区域,当需要延伸图片以填充比图片本身更大区域时,可扩展区的内容被延展;允许开发人员定义内容显示区,用于显示文字或其他内容。目前在 Github 上有两个 C++ 版本的,在这里我把它们都用 Python 实现了一遍。另外一个我也为 PyQt 提供了编译好的 pyd 文件。

    \n\n

    # C++ 版本

    \n

    在 Github 开源库中搜索到两个 C++ 版本的

    \n
      \n
    1. 一个是 NinePatchQt
    2. \n
    3. 一个是 QtNinePatch
    4. \n
    \n

    # PyQt5 版本

    \n

    这里也分为两个版本,都是基于上面的 C++ 源码翻译改写过来的,具体的例子见项目里面的测试代码吧。

    \n
      \n
    1. QtNinePatch 是参考第一个源码编写,用法是在 paintEvent 中调用
    2. \n
    3. QtNinePatch2 是参考第二个源码编写,用法是 pixmap = QtNinePatch.createPixmapFromNinePatchImage(self.image, self.width(), self.height()) 直接得到一个处理好的 QPixmap 对象来使用
    4. \n
    \n

    # 说明

    \n
      \n
    1. 建议优先使用 pyd 版本的(后续提供 Python3.4 3.5 3.6 3.7 编译好的 32 为库文件),也可以自行编译,编译步骤见下文。
    2. \n
    3. 其次可以使用纯 python 版本 2 的(个人觉得方便调用)
    4. \n
    5. 最后再考虑纯 python 版本 1 的吧
    6. \n
    7. 以上为个人意见,两个 C++ 版本的写法不一样,但是核心算法应该是类似的。
    8. \n
    \n

    # 自行编译

    \n
      \n
    1. 首先要安装好 Qt、PyQt5、编译安装对应的 sip、对应的 VC++ 编译工具
    2. \n
    3. 用 Qt Creator 打开 pro 文件进行编译
    4. \n
    5. 进入源码中的 sip 文件夹修改 configure.py 文件
    6. \n
    \n
    # 这里是你的VC版本和对应的Qt目录中的文件夹\nconfig.platform = "win32-msvc2010"\nqt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010'
    \n
      \n
    1. 最后执行 python configure.py 来编译
    2. \n
    \n

    # 下载

    \n

    https://github.com/PyQt5/PyQt/tree/master/QLabel

    \n

    # 效果图

    \n

    \"NinePatchImage\"

    \n", + "tags": [ + "PyQt", + "图片", + "气泡", + ".9png" + ] + } + ] +} \ No newline at end of file diff --git a/tag/.9png/rss.xml b/tag/.9png/rss.xml new file mode 100644 index 00000000..77130c1e --- /dev/null +++ b/tag/.9png/rss.xml @@ -0,0 +1,105 @@ + + + + PyQt • Posts by ".9png" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Fri, 26 Oct 2018 02:00:08 +0000 + Fri, 26 Oct 2018 02:00:08 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/qtninepatch.html + PyQt5显示.9格式的PNG图片 + https://pyqt5.com/qtninepatch.html + PyQt + 图片 + 气泡 + .9png + Fri, 26 Oct 2018 02:00:08 +0000 + + + + diff --git a/tag/asyncio/feed.json b/tag/asyncio/feed.json new file mode 100644 index 00000000..eaf94fda --- /dev/null +++ b/tag/asyncio/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"asyncio\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pyqt5asyncio.html", + "url": "https://pyqt5.com/pyqt5asyncio.html", + "title": "PyQt5结合Asyncio异步", + "date_published": "2018-10-24T06:32:26.000Z", + "content_html": "

    今天尝试了下 quamash 框架,该框架是一个 PyQt 的异步事件循环封装库,使用 Python3+ 的 asyncio 这个异步库。在看了该项目的内容后发现只有一个简单的进度条例子,故尝试用其来下载网络图片并显示。

    \n\n

    # 安装依赖

    \n
      \n
    1. pip install quamash
    2. \n
    3. pip install aiohttp
    4. \n
    5. Python3.5+ 和 PyQt5
    6. \n
    \n

    这里使用 aiohttp 是因为它基于 asyncio 封装的网络操作库,常见的 getpost 等方法,不过它只支持 Python3.5 及以上的版本,主要是它使用了 async def 这样的语法。

    \n

    # 说明

    \n
      \n
    1. 在创建 QApplication 后随即设置替换事件循环 loop
    2. \n
    \n
    app = QApplication(sys.argv)\nloop = QEventLoop(app)\nasyncio.set_event_loop(loop)\nw = Window()
    \n
      \n
    1. 通过 asyncio.ensure_future(func(), loop=loop) 来执行某个异步函数
    2. \n
    \n

    # 流程

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    WindowinitSession(初始化 session)
    下载按钮doDownload(执行_doDownload 方法)
    session.get(下载 json 数据进行解析)
    添加到界面_doDownloadImage(对单张图片进行下载)
    \n

    # 源码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月24日\n@author: Irony\n@site: https://github.com/892768447\n@email: 892768447@qq.com\n@file: AsyncioUiClient\n@description: \n\"\"\"\nimport asyncio\n\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QMovie\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton,\\\n    QApplication, QListWidget, QListWidgetItem, QLabel, QMessageBox\nimport aiohttp\nfrom quamash import QEventLoop\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\nUrl = 'https://www.doutula.com/api/search?keyword=%E6%9C%80%E6%96%B0%E8%A1%A8%E6%83%85&mime=0&page={}'\nHeaders = {\n    ':authority': 'www.doutula.com',\n    ':method': 'GET',\n    ':scheme': 'https',\n    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',\n    'accept-language': 'zh-CN,zh;q=0.9',\n    'cache-control': 'max-age=0',\n    'dnt': '1',\n    'upgrade-insecure-requests': '1',\n    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6756.400 QQBrowser/10.2.2498.400'\n}\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.listWidget = QListWidget(self)\n        self.listWidget.setSpacing(2)  # item直接的间隔\n        # 隐藏横向滚动条\n        self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        # 让list 从左到右排列\n        self.listWidget.setFlow(self.listWidget.LeftToRight)\n        # 自动换行\n        self.listWidget.setWrapping(True)\n        self.listWidget.setResizeMode(self.listWidget.Adjust)\n\n        self.buttonMsg = QPushButton('弹出提示框', self, clicked=self.showMessage)\n        self.buttonDown = QPushButton('下载图片', self, clicked=self.doDownload)\n        layout.addWidget(self.listWidget)\n        layout.addWidget(self.buttonMsg)\n        layout.addWidget(self.buttonDown)\n        self.currentPage = 0\n        self.initSession()  # 其实没必要,session主要用在需要登录的网站。缓存cookie用\n\n    def initSession(self):\n        async def _initSession():\n            # 初始化session\n            self.session = aiohttp.ClientSession(loop=loop)\n            print(self.session)\n        asyncio.ensure_future(_initSession(), loop=loop)\n\n    async def _doDownloadImage(self, url):\n        # 下载图片并添加到界面\n        async with self.session.get(url) as resp:\n            data = await resp.read()\n            if not data:\n                print('下载失败: ', url)\n                return\n            path = os.path.join('tmp', os.path.basename(url))\n            with open(path, 'wb') as fp:\n                fp.write(data)\n            item = QListWidgetItem(url, self.listWidget)\n            image = QPixmap(path)\n            item.setSizeHint(image.size())\n            label = QLabel(self.listWidget)\n            label.setPixmap(image)\n            if path.endswith('.gif'):  # 可能是动态图\n                label.setMovie(QMovie(path))\n            self.listWidget.setItemWidget(item, label)\n            self.listWidget.scrollToBottom()\n\n    async def _doDownload(self):\n        # 下载工作\n        if self.currentPage == -1:\n            QMessageBox.information(self, '提示', '已经没有更多了')\n            return\n        self.currentPage += 1\n        url = Url.format(self.currentPage)\n        print('get url: ', url)\n        async with self.session.get(url, headers=Headers) as resp:\n            data = await resp.json()\n            if not data:\n                return\n            data = data.get('data', None)\n            if not data:\n                self.currentPage = -1\n                print('已经是最后一页了')\n                return\n            # 解析json并生成item添加到界面中\n            for entity in data.get('list', []):\n                url = entity.get('image_url', None)\n                if not url:\n                    continue\n                await self._doDownloadImage(url)  # 下载图片\n\n    def doDownload(self):\n        # 响应按钮点击调用\n        asyncio.ensure_future(self._doDownload(), loop=loop)\n\n    def showMessage(self):\n        # 显示对话框\n        app.aboutQt()\n\n    def closeEvent(self, event):\n        if not self.session.closed:\n            asyncio.ensure_future(self.session.close(), loop=loop)\n        super(Window, self).closeEvent(event)\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    import os\n    os.makedirs('tmp', exist_ok=True)\n    sys.excepthook = cgitb.enable(1, None, 5, 'text')\n    app = QApplication(sys.argv)\n    loop = QEventLoop(app)\n    asyncio.set_event_loop(loop)\n    w = Window()\n    w.show()\n    with loop:\n        loop.run_forever()
    \n

    # 效果图

    \n

    \"pyqt5asyncio\"

    \n", + "tags": [ + "PyQt", + "Asyncio", + "异步" + ] + } + ] +} \ No newline at end of file diff --git a/tag/asyncio/rss.xml b/tag/asyncio/rss.xml new file mode 100644 index 00000000..75387e71 --- /dev/null +++ b/tag/asyncio/rss.xml @@ -0,0 +1,279 @@ + + + + PyQt • Posts by "asyncio" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 24 Oct 2018 06:32:26 +0000 + Wed, 24 Oct 2018 06:32:26 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pyqt5asyncio.html + PyQt5结合Asyncio异步 + https://pyqt5.com/pyqt5asyncio.html + PyQt + Asyncio + 异步 + Wed, 24 Oct 2018 06:32:26 +0000 + + + + diff --git a/tag/debug/feed.json b/tag/debug/feed.json new file mode 100644 index 00000000..77a91c1e --- /dev/null +++ b/tag/debug/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"debug\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/read_open_source.html", + "url": "https://pyqt5.com/read_open_source.html", + "title": "像读文章一样读源码", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

    使用 snoop, 像读文章一样读源码。

    \n\n

    不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
    \n\"image.png\"

    \n

    没有目的地概览 , 不知不觉就追究到细节里面去了。

    \n

    \"image.png\"

    \n

    所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。

    \n

    开始 :

    \n
      \n
    1. python DEBUG 模块介绍 :
      \n 前段时间看过挺多文章提到 pysoonper 这个调试模块,有兴趣的可以百度一下.
      \n 个人尝试了一下,篇幅过大的 DEBUG 不适合用 pysoonper , 因为没有缩进!
      \n 这几天偶然遇到一个二次封装的模块 snoop, 完美地解决了这个问题.
    2. \n
    3. 操作步骤 :
    4. \n
    \n\n

    发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。

    \n\n

    callreturn 给加进去.

    \n\n

    \"eric6启动阶段\"

    \n

    \"image.png\"

    \n

    #000 是为了方便搜索 。
    \n需要自己手动折叠 。
    \n可以发现 每个 splash.showMessage() 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。

    \n
    \n

    # ps: vscode 阅读 log 文件还是有一些不方便的地方,除了在 2. 中提到的,还有包括关闭文件再打开,折叠状态不会保留,有其他更好的方式 请留言告诉我,谢谢.

    \n", + "tags": [ + "Python", + "debug", + "snoop" + ] + } + ] +} \ No newline at end of file diff --git a/tag/debug/rss.xml b/tag/debug/rss.xml new file mode 100644 index 00000000..55b4dc51 --- /dev/null +++ b/tag/debug/rss.xml @@ -0,0 +1,107 @@ + + + + PyQt • Posts by "debug" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 06 Jul 2019 17:37:22 +0000 + Sat, 06 Jul 2019 17:37:22 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/read_open_source.html + 像读文章一样读源码 + https://pyqt5.com/read_open_source.html + Python + debug + snoop + Sat, 06 Jul 2019 17:37:22 +0000 + + + + diff --git a/tag/designer/feed.json b/tag/designer/feed.json new file mode 100644 index 00000000..abb4f06c --- /dev/null +++ b/tag/designer/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"designer\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/viewpyindesigner_625781186.html", + "url": "https://pyqt5.com/viewpyindesigner_625781186.html", + "title": "如何和设计师中查看ui转换的py代码", + "date_published": "2019-04-30T05:11:09.000Z", + "content_html": "

    通过 设计师  查看 ui 转换的 py 代码

    \n

    当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。

    \n

    后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。

    \n\n

    再来后发现了官方文档,查一查函数就行了 .

    \n

    但是有些 api 文档找起来麻烦,用设计师点几下就行了,然后把转换出来的代码拷贝一下就完事了.

    \n

    可是需要单独把 ui 转为 py 文件,之后再删除这个文件也是很烦的一件事 .

    \n

    好,话不多说,接下来手把手教你如何快速在 ui 中查看 py 代码 .

    \n

    官方也考虑过这种情况,所以 设计师中 是有这个功能的,但是 qt 的是没问题的,pyqt 的毕竟是绑定过来的,所以正常来说 你点击之后会弹出一个找不到应用程序的提示 .

    \n

    看到这个东西是不是很眼熟,我们用的命令 pyuic5 和这个东西应该是一样的 .

    \n

    \"viewpyindesigner1\"

    \n

    所以接下来,我们找找电脑上有没有这个东西

    \n

    \"viewpyindesigner2\"

    \n

    果然在 pyqt5-toos 文件夹下有这个东西,

    \n

    我们根据第一张图的提示,把这个东西拷贝到相应的目录 (如果没有那个 bin 文件夹,手动创建),

    \n

    \"viewpyindesigner3\"

    \n

    好了,大功告成!

    \n", + "tags": [ + "PyQt", + "Designer", + "设计师" + ] + } + ] +} \ No newline at end of file diff --git a/tag/designer/rss.xml b/tag/designer/rss.xml new file mode 100644 index 00000000..f7e2edb1 --- /dev/null +++ b/tag/designer/rss.xml @@ -0,0 +1,84 @@ + + + + PyQt • Posts by "designer" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 30 Apr 2019 05:11:09 +0000 + Tue, 30 Apr 2019 05:11:09 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/viewpyindesigner_625781186.html + 如何和设计师中查看ui转换的py代码 + https://pyqt5.com/viewpyindesigner_625781186.html + PyQt + Designer + 设计师 + Tue, 30 Apr 2019 05:11:09 +0000 + + + + diff --git a/tag/ffmpeg/feed.json b/tag/ffmpeg/feed.json new file mode 100644 index 00000000..1eaea087 --- /dev/null +++ b/tag/ffmpeg/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"ffmpeg\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/ffmpeghls.html", + "url": "https://pyqt5.com/ffmpeghls.html", + "title": "FFmpeg合成加密HLS记录", + "date_published": "2019-01-12T11:28:06.000Z", + "content_html": "

    记录在某个需求中要求截图并合成加密视频文件,这里采用 FFmpeg 的管道流来实现生成 HLS 加密文件。

    \n\n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年3月4日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\n\nfrom pathlib import Path\nfrom subprocess import Popen, PIPE\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\n# p = Popen([r'D:\\soft\\ffmpeg\\bin\\ffmpeg.exe', '-y',\n#            '-threads', '2',\n#            '-f', 'image2pipe',\n#            '-vcodec', 'mjpeg', '-r', '24', '-i', '-',\n#            '-vcodec', 'h264', '-r', '24',\n#            #            '-encryption_scheme', 'cenc-aes-ctr',\n#            #            '-encryption_key', '617D8A125A284DF48E3C6B1866348A3F',\n#            #            '-encryption_kid', 'B326F895B6A24CC5A4DC70995728059C',\n#            r'F:\\Workspace\\Test\\videos\\video.mp4'], stdin=PIPE)\n\np = Popen([r'D:\\soft\\ffmpeg\\bin\\ffmpeg.exe',\n           '-re',   # 按照实际帧率读取输入文件\n           '-y',        # 覆盖已存在文件\n           '-threads', '2',  # 线程数量\n           '-f', 'image2pipe',  # PIPE图片流\n           '-vcodec', 'mjpeg',  # 图片编码\n           '-r', '24',  # 帧率\n           '-i', '-',  # 指定输入流为PIPE\n           '-vcodec', 'h264',  # 输出编码\n           '-r', '24',  # 帧率\n           '-map', '0',\n#            '-crf','20',     # 降低质量\n           '-b', '720k',        # 码率\n           '-f', 'hls',\n           '-codec:v', 'libx264',\n           '-vbsf', 'h264_mp4toannexb',\n           # 指定加密密匙文件\n           '-hls_key_info_file', r'F:\\Workspace\\Test\\videokey.info',\n           '-hls_time', '20',\n           '-hls_list_size', '0',\n           '-hls_wrap', '0',\n#            '-hls_flags', 'single_file',  # 生成单个文件(有bug)\n           r'F:\\Workspace\\Test\\videos\\playlist.m3u8'], stdin=PIPE)\nprint(p)\n\nt = 1 / 24\nfor i, path in enumerate(Path('frames').rglob('*.jpg')):\n    #     print(i, path)\n    p.stdin.write(open(str(path), 'rb').read())\n\np.stdin.close()\np.wait()\nprint('ok')
    ", + "tags": [ + "Python", + "FFmpeg", + "HLS" + ] + } + ] +} \ No newline at end of file diff --git a/tag/ffmpeg/rss.xml b/tag/ffmpeg/rss.xml new file mode 100644 index 00000000..f24bc464 --- /dev/null +++ b/tag/ffmpeg/rss.xml @@ -0,0 +1,134 @@ + + + + PyQt • Posts by "ffmpeg" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 12 Jan 2019 11:28:06 +0000 + Sat, 12 Jan 2019 11:28:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/ffmpeghls.html + FFmpeg合成加密HLS记录 + https://pyqt5.com/ffmpeghls.html + Python + FFmpeg + HLS + Sat, 12 Jan 2019 11:28:06 +0000 + + + + diff --git a/tag/github/feed.json b/tag/github/feed.json new file mode 100644 index 00000000..8810848d --- /dev/null +++ b/tag/github/feed.json @@ -0,0 +1,18 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"github\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/speedgithub.html", + "url": "https://pyqt5.com/speedgithub.html", + "title": "解决GitHub下载速度缓慢的问题", + "date_published": "2019-04-18T00:59:06.000Z", + "content_html": "

    由于 Github 的下载走的是 AWS - 亚马逊的路线,,so slow,跟乌龟一样慢。。照着一些方法改了 hosts 文件,偶尔能提提速度。

    \n\n

    # Windows

    \n

    Hosts 文件的路径是:

    \n

    C:\\Windows\\System32\\drivers\\etc

    \n

    # Mac

    \n

    终端内输入:

    \n

    sudo vim /etc/hosts

    \n

    # 追加域名的 IP 地址

    \n

    利用 https://www.ipaddress.com/ 来获得以下两个 GitHub 域名的 IP 地址:

    \n

    (1) github.com

    \n

    (2) github.global.ssl.fastly.net

    \n

    打开网页后,利用输入框内分别查询两个域名

    \n

    将以上两段 IP 写入 Hosts 文件中:

    \n
    192.30.253.112               github.com\n151.101.185.194              github.global.ssl.fastly.net
    \n

    保存。

    \n

    刷新 DNS 缓存

    \n

    在终端或 CMD 中,执行以下命令:

    \n

    ipconfig /flushdns

    \n", + "tags": [ + "Github" + ] + } + ] +} \ No newline at end of file diff --git a/tag/github/rss.xml b/tag/github/rss.xml new file mode 100644 index 00000000..a0b50dc1 --- /dev/null +++ b/tag/github/rss.xml @@ -0,0 +1,85 @@ + + + + PyQt • Posts by "github" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 18 Apr 2019 00:59:06 +0000 + Thu, 18 Apr 2019 00:59:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/speedgithub.html + 解决GitHub下载速度缓慢的问题 + https://pyqt5.com/speedgithub.html + Github + Thu, 18 Apr 2019 00:59:06 +0000 + + + + diff --git a/tag/hls/feed.json b/tag/hls/feed.json new file mode 100644 index 00000000..3dc739b1 --- /dev/null +++ b/tag/hls/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"hls\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/ffmpeghls.html", + "url": "https://pyqt5.com/ffmpeghls.html", + "title": "FFmpeg合成加密HLS记录", + "date_published": "2019-01-12T11:28:06.000Z", + "content_html": "

    记录在某个需求中要求截图并合成加密视频文件,这里采用 FFmpeg 的管道流来实现生成 HLS 加密文件。

    \n\n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年3月4日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\n\nfrom pathlib import Path\nfrom subprocess import Popen, PIPE\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\n# p = Popen([r'D:\\soft\\ffmpeg\\bin\\ffmpeg.exe', '-y',\n#            '-threads', '2',\n#            '-f', 'image2pipe',\n#            '-vcodec', 'mjpeg', '-r', '24', '-i', '-',\n#            '-vcodec', 'h264', '-r', '24',\n#            #            '-encryption_scheme', 'cenc-aes-ctr',\n#            #            '-encryption_key', '617D8A125A284DF48E3C6B1866348A3F',\n#            #            '-encryption_kid', 'B326F895B6A24CC5A4DC70995728059C',\n#            r'F:\\Workspace\\Test\\videos\\video.mp4'], stdin=PIPE)\n\np = Popen([r'D:\\soft\\ffmpeg\\bin\\ffmpeg.exe',\n           '-re',   # 按照实际帧率读取输入文件\n           '-y',        # 覆盖已存在文件\n           '-threads', '2',  # 线程数量\n           '-f', 'image2pipe',  # PIPE图片流\n           '-vcodec', 'mjpeg',  # 图片编码\n           '-r', '24',  # 帧率\n           '-i', '-',  # 指定输入流为PIPE\n           '-vcodec', 'h264',  # 输出编码\n           '-r', '24',  # 帧率\n           '-map', '0',\n#            '-crf','20',     # 降低质量\n           '-b', '720k',        # 码率\n           '-f', 'hls',\n           '-codec:v', 'libx264',\n           '-vbsf', 'h264_mp4toannexb',\n           # 指定加密密匙文件\n           '-hls_key_info_file', r'F:\\Workspace\\Test\\videokey.info',\n           '-hls_time', '20',\n           '-hls_list_size', '0',\n           '-hls_wrap', '0',\n#            '-hls_flags', 'single_file',  # 生成单个文件(有bug)\n           r'F:\\Workspace\\Test\\videos\\playlist.m3u8'], stdin=PIPE)\nprint(p)\n\nt = 1 / 24\nfor i, path in enumerate(Path('frames').rglob('*.jpg')):\n    #     print(i, path)\n    p.stdin.write(open(str(path), 'rb').read())\n\np.stdin.close()\np.wait()\nprint('ok')
    ", + "tags": [ + "Python", + "FFmpeg", + "HLS" + ] + } + ] +} \ No newline at end of file diff --git a/tag/hls/rss.xml b/tag/hls/rss.xml new file mode 100644 index 00000000..96d5a134 --- /dev/null +++ b/tag/hls/rss.xml @@ -0,0 +1,134 @@ + + + + PyQt • Posts by "hls" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 12 Jan 2019 11:28:06 +0000 + Sat, 12 Jan 2019 11:28:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/ffmpeghls.html + FFmpeg合成加密HLS记录 + https://pyqt5.com/ffmpeghls.html + Python + FFmpeg + HLS + Sat, 12 Jan 2019 11:28:06 +0000 + + + + diff --git a/tag/m1/feed.json b/tag/m1/feed.json new file mode 100644 index 00000000..ecd2faf3 --- /dev/null +++ b/tag/m1/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"m1\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/macm1pyqt.html", + "url": "https://pyqt5.com/macm1pyqt.html", + "title": "如何在Mac M1上快速安装PyQt5", + "date_published": "2023-10-07T06:08:06.000Z", + "content_html": "

    由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。
    \nM1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。

    \n\n

    1. 安装 Python
    \npython-3.9.13-macosx10.9.pkg

    \n

    2. 勾选自定义同时只勾选安装 pip

    \n

    \"step1.png\"

    \n

    \"step1.png\"

    \n

    3. 设置 pip 源

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi\n/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna
    \n

    4. 安装 PyQt5

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5
    \n

    5. 测试

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/python3
    \n

    \"step3.png\"

    \n

    📢📢📢

    \n

    也可以直接安装 Miniconda

    \n

    然后:conda install -c conda-forge pyqt

    \n", + "tags": [ + "PyQt", + "Mac", + "M1" + ] + } + ] +} \ No newline at end of file diff --git a/tag/m1/rss.xml b/tag/m1/rss.xml new file mode 100644 index 00000000..c8e8c299 --- /dev/null +++ b/tag/m1/rss.xml @@ -0,0 +1,86 @@ + + + + PyQt • Posts by "m1" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 07 Oct 2023 06:08:06 +0000 + Sat, 07 Oct 2023 06:08:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/macm1pyqt.html + 如何在Mac M1上快速安装PyQt5 + https://pyqt5.com/macm1pyqt.html + PyQt + Mac + M1 + Sat, 07 Oct 2023 06:08:06 +0000 + + + + diff --git a/tag/mac/feed.json b/tag/mac/feed.json new file mode 100644 index 00000000..0a32af76 --- /dev/null +++ b/tag/mac/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"mac\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/macm1pyqt.html", + "url": "https://pyqt5.com/macm1pyqt.html", + "title": "如何在Mac M1上快速安装PyQt5", + "date_published": "2023-10-07T06:08:06.000Z", + "content_html": "

    由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。
    \nM1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。

    \n\n

    1. 安装 Python
    \npython-3.9.13-macosx10.9.pkg

    \n

    2. 勾选自定义同时只勾选安装 pip

    \n

    \"step1.png\"

    \n

    \"step1.png\"

    \n

    3. 设置 pip 源

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi\n/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna
    \n

    4. 安装 PyQt5

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5
    \n

    5. 测试

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/python3
    \n

    \"step3.png\"

    \n

    📢📢📢

    \n

    也可以直接安装 Miniconda

    \n

    然后:conda install -c conda-forge pyqt

    \n", + "tags": [ + "PyQt", + "Mac", + "M1" + ] + } + ] +} \ No newline at end of file diff --git a/tag/mac/rss.xml b/tag/mac/rss.xml new file mode 100644 index 00000000..8a4e9c6b --- /dev/null +++ b/tag/mac/rss.xml @@ -0,0 +1,86 @@ + + + + PyQt • Posts by "mac" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 07 Oct 2023 06:08:06 +0000 + Sat, 07 Oct 2023 06:08:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/macm1pyqt.html + 如何在Mac M1上快速安装PyQt5 + https://pyqt5.com/macm1pyqt.html + PyQt + Mac + M1 + Sat, 07 Oct 2023 06:08:06 +0000 + + + + diff --git a/tag/model/feed.json b/tag/model/feed.json new file mode 100644 index 00000000..2ed7c55a --- /dev/null +++ b/tag/model/feed.json @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"model\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/datawidgetmapper_625781186.html", + "url": "https://pyqt5.com/datawidgetmapper_625781186.html", + "title": "QDataWidgetMapper 数据库绑定 QLineEdit控件", + "date_published": "2018-10-29T08:17:59.000Z", + "content_html": "

    qt 为操作数据库提供了一个 model+view 的模式,这样简单的出入库逻辑就不需要自己编写。

    \n

    QDataWidgetMapper 可以 将数据库的数据 映射到其他控件 。

    \n

    注意:表格里的数据修改 还没有提交到数据库,需要点击提交按钮才生效。

    \n\n

    https://github.com/PyQt5/PyQt/tree/master/Test/partner_625781186/16_sqlModel/01_mapper

    \n

    # 代码

    \n
    #-*- coding: utf-8 -*-\n\nfrom PyQt5 import  QtWidgets, QtGui, QtCore\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nfrom PyQt5.QtSql import *\n\nimport sys\n\nsys.path.append('./ui')\nfrom Ui_MainWindow import Ui_MainWindow\n\n\nclass MainWindow(QMainWindow, Ui_MainWindow):\n    def __init__(self, parent=None, *args):\n\n        super(MainWindow, self).__init__(parent,  *args)\n        self.setupUi(self)\n        self.resize(800,600)\n        \n        #===============================   db   ======================================#\n        # self.db = QSqlDatabase.addDatabase('QMYSQL')\n        # self.db.setHostName(\"127.0.0.1\")  # set address\n        # self.db.setUserName(\"root\")  # set user name\n        # self.db.setPassword('123456')  # set user pwd   \n        # self.db.setDatabaseName(\"database\")\n        \n        self.db = QSqlDatabase.addDatabase('QSQLITE')\n        self.db.setDatabaseName('./db/database.db')\n\n        #================================= codemodel =====================================#\n        # 实例化model\n        self.codeModel = QSqlRelationalTableModel()\n        # model设置表\n        self.initializeModel(self.codeModel, 'Mongo')\n        # 设置编辑策略\n        # self.codeModel.setEditStrategy(QSqlTableModel.OnFieldChange)\n        # !!! 这里要注意 , 只能用这个策略 , 才可以实现自动提交\n        self.codeModel.setEditStrategy(QSqlTableModel.OnManualSubmit)\n\n        self.codeView = self.createView(\"code_View\", self.codeModel)\n        self.verticalLayout.addWidget(self.codeView)  \n\n        #================================ initData ==================================#\n        # 数据映射\n        self.mapper = QDataWidgetMapper()\n        # 提交策略\n        self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)\n        # 映射的模型源\n        self.mapper.setModel(self.codeModel)\n        self.mapper.addMapping(self.l1,0)\n        self.mapper.addMapping(self.l2,1)\n        self.mapper.addMapping(self.l3,2)\n        self.mapper.addMapping(self.l4,3)\n        self.mapper.addMapping(self.l5,4)\n\n        self.mapper.toFirst()\n        \n        #================================ pushButton ==================================#\n        self.sub_btn.clicked.connect(self.mapper.submit)\n        self.sub_btn.clicked.connect(self.codeModel.submitAll)\n        self.pre_btn.clicked.connect(self.mapper.toPrevious)\n        self.next_btn.clicked.connect(self.mapper.toNext)\n        \n    def initializeModel(self, model, tablename):\n        '''重关联。'''\n        model.setTable(tablename)\n#        model.setEditStrategy(QSqlTableModel.OnRowChange)\n        model.select()\n        \n    def createView(self, title, model):\n        '''创建TableView视图'''\n        view =  QTableView()\n        view.setModel(model)\n        view.setWindowTitle(title)\n        #列宽设置\n        view.horizontalHeader().setSectionResizeMode(3)\n        #行高设置\n        view.verticalHeader().setSectionResizeMode(1)\n        #充满列宽\n        view.horizontalHeader().setStretchLastSection(True) \n#        view.verticalHeader().setVisible(False)#隐藏行标题\n        #标题左对齐\n        view.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)\n        #标题左对齐\n        view.verticalHeader().setDefaultAlignment(Qt.AlignLeft)\n        \n        return view  \n    \n\nif __name__ == \"__main__\":\n    import sys\n    \n    app = QApplication(sys.argv)\n    app.setStyle(QStyleFactory.create(\"Fusion\"))\n    ui = MainWindow()\n    ui.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"datawidgetmapper\"

    \n", + "tags": [ + "PyQt", + "Model" + ] + } + ] +} \ No newline at end of file diff --git a/tag/model/rss.xml b/tag/model/rss.xml new file mode 100644 index 00000000..989ea551 --- /dev/null +++ b/tag/model/rss.xml @@ -0,0 +1,173 @@ + + + + PyQt • Posts by "model" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Mon, 29 Oct 2018 08:17:59 +0000 + Mon, 29 Oct 2018 08:17:59 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/datawidgetmapper_625781186.html + QDataWidgetMapper 数据库绑定 QLineEdit控件 + https://pyqt5.com/datawidgetmapper_625781186.html + PyQt + Model + Mon, 29 Oct 2018 08:17:59 +0000 + + + + diff --git a/tag/pyqt hook key/feed.json b/tag/pyqt hook key/feed.json new file mode 100644 index 00000000..dbe4001f --- /dev/null +++ b/tag/pyqt hook key/feed.json @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"pyqt hook key\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pyqt5_hook_key_625781186.html", + "url": "https://pyqt5.com/pyqt5_hook_key_625781186.html", + "title": "在pyqt中使用python全局钩子模块", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

    在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像 QQ 一样可以全局热键截图。这里介绍几个方法实现在 PyQt 中使用 Python 全局钩子模块实现全局热键功能。

    \n\n
      \n
    1. pyHook3
    2. \n
    \n

    安装命令 : pip install pyhook3

    \n

    https://blog.csdn.net/q871063970/article/details/86648386

    \n

    似乎将 pyhook 支持 py3 版本的了?没有太多研究.

    \n

    缺点:只支持 win 平台.

    \n

    2. keyboard & mouse

    \n

    安装命令: pip install keyboard mouse

    \n
    \nfrom PyQt5 import  QtGui, QtWidgets, QtCore\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nimport keyboard\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.testBtn = QPushButton(self)\n        layout.addWidget(self.testBtn)\n\n        keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey'))\n        keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc"))\n\n    def abc(self,a,b,c):\n        print(a,b,c)\n        \nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    更详细例子 : pyqt 中使用 keyboard 全局热键

    \n

    优点:跨平台;

    \n

    缺点:模块名字取得太差,不容易被发现.

    \n", + "tags": [ + "Python", + "pyqt hook key" + ] + } + ] +} \ No newline at end of file diff --git a/tag/pyqt hook key/rss.xml b/tag/pyqt hook key/rss.xml new file mode 100644 index 00000000..af4a271e --- /dev/null +++ b/tag/pyqt hook key/rss.xml @@ -0,0 +1,107 @@ + + + + PyQt • Posts by "pyqt hook key" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 06 Jul 2019 17:37:22 +0000 + Sat, 06 Jul 2019 17:37:22 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pyqt5_hook_key_625781186.html + 在pyqt中使用python全局钩子模块 + https://pyqt5.com/pyqt5_hook_key_625781186.html + Python + pyqt hook key + Sat, 06 Jul 2019 17:37:22 +0000 + + + + diff --git a/tag/pyqt/feed.json b/tag/pyqt/feed.json new file mode 100644 index 00000000..a0a7fc23 --- /dev/null +++ b/tag/pyqt/feed.json @@ -0,0 +1,237 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"pyqt\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/macm1pyqt.html", + "url": "https://pyqt5.com/macm1pyqt.html", + "title": "如何在Mac M1上快速安装PyQt5", + "date_published": "2023-10-07T06:08:06.000Z", + "content_html": "

    由于官方并没有在 M1 上编译 PyQt 导致安装存在一些问题。
    \nM1 上的 Python 不能直接使用 x64 的 PyQt5。但是 M1 上可以运行 x64 的 Python。所以通过安装 x64 的 Python 然后再安装 PyQt5 即可。

    \n\n

    1. 安装 Python
    \npython-3.9.13-macosx10.9.pkg

    \n

    2. 勾选自定义同时只勾选安装 pip

    \n

    \"step1.png\"

    \n

    \"step1.png\"

    \n

    3. 设置 pip 源

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install pqi\n/Library/Frameworks/Python.framework/Versions/3.9/bin/pqi use tuna
    \n

    4. 安装 PyQt5

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install PyQt5
    \n

    5. 测试

    \n
    /Library/Frameworks/Python.framework/Versions/3.9/bin/python3
    \n

    \"step3.png\"

    \n

    📢📢📢

    \n

    也可以直接安装 Miniconda

    \n

    然后:conda install -c conda-forge pyqt

    \n", + "tags": [ + "PyQt", + "Mac", + "M1" + ] + }, + { + "id": "https://pyqt5.com/studynotes.html", + "url": "https://pyqt5.com/studynotes.html", + "title": "PyQt学习心得", + "date_published": "2019-08-26T01:00:00.000Z", + "content_html": "

    在学习 PyQt 的过程中由于资料的缺乏或者没有中文导致大多数人感叹资料太少,学习困难,又或者急于求进,赶鸭子上架的情况,此时有系统的学习方法很重要。每个人都需要有自己的学习方法,别人的学习方法并不一定适合自己但可以采纳一些。笔者在这里列举了一些当初自己自学的一些心得和方法,希望帮助大家建立一套自己的学习 PyQt 的方法,提高自身的学习能力。

    \n\n

    # Python 基础

    \n

    在学习和使用 PyQt 之前需要熟练使用 Python,经过对 QQ 群里经常提问的问题的分析,发现大部分人对 Python 中的基础知识掌握不牢固导致很多基础问题,如果要想更好的使用 Python 以及它的扩展必需要进行系统的学习。这里列举一下常用的知识点。

    \n
      \n
    1. 类         参考资料
    2. \n
    3. 类的继承
    4. \n
    5. 类的多继承
    6. \n
    7. 类方法重写     参考资料
    8. \n
    9. 类中的 super 函数  参考资料
    10. \n
    11. 函数调用 / 参数类型
    12. \n
    13. 对象调用 (参考第 1 点)
    14. \n
    \n

    必须熟练掌握上面的知识点后入门 PyQt 才比较容易,如果初学者对上面的知识点还不是很了解,本文不适合继续往下阅读。

    \n

    # 设计师

    \n

    Qt 设计师除了方便快速设计一些简单的界面外,其实笔者觉得更大的作用在于帮助用户熟悉各类控件、属性、信号等

    \n
      \n
    1. 这里建议初学者不要急于求成,打开设计师新建一个 Widget 的窗口,比如
    2. \n
    \n

    \"desiger_create\"

    \n
      \n
    1. 然后把左侧的所有控件挨个拖动到中间的窗口中,比如这里拖动一个 Push Button 按钮
    2. \n
    \n

    \"desiger_drag\"

    \n
      \n
    1. 在设计师右下角的属性编辑器中列举了该控件的所有父类,意味着可以调用和重写父类的所有方法,建议初学者把这个属性编辑器的所有属性挨个调整看看效果,部分控件可能需要 Ctrl+R 预览界面才能看到,同时像 QListWidget,QTreeWidget,QTableWidget 等某些控件需要在控件上右键增加数据才可以
    2. \n
    \n

    \"desiger_property\"
    \n\"desiger_property2\"

    \n
      \n
    1. 两个控件之间简单的信号槽关联可以通过设计师快速的设置
    2. \n
    \n

    \"desiger_signal\"
    \n\"desiger_signal2\"

    \n
      \n
    1. 提高进阶的方法,当你需要手动写代码实现界面的时候,不妨把 UI 文件转出 PY 文件,看看是如何构造的(这里涉及到布局等知识见后文)
    2. \n
    \n

    # 布局

    \n

    Qt 界面提供了方便的 4 种基本布局,QVboxLayout,QHboxLayout,QFormLayout,QGridLayout,初学者需要数量掌握这 4 种布局外加 2 种拉伸器(占位挤压)

    \n

    首先需要知道 Qt 界面的中控件的层级顺序以及 parent,parent 的作用既作为子控件的父元素也可以自动管理 Qt 的对象(具体可以搜索下关于 Qt parent 的资料)

    \n
      \n
    1. 在没有布局的情况下,在设计师中拖动摆放的控件是一层一层的叠加覆盖,此时每个添加的子控件的 parent 都是最外层的控件
    2. \n
    \n

    \"desiger_stack\"

    \n
      \n
    1. 如果需要界面中的控件自动适应高度宽度,此时则需要使用 4 种布局来包裹里面的子控件,注意的是:布局不是控件不能设置高度宽度和样式等,是一个抽象的东西,就好比是一根橡皮筋包裹几个矩形的物品;布局也可以设置一些属性(在设计师属性编辑器中),比如设置两者直接的间距,设置距离上下左右的间距,设置比例等
    2. \n
    \n

    \"desiger_layout\"

    \n
      \n
    1. 在没有布局或者有布局的时候。可以添加容器控件(QWidget,QFrame,QGroupBox,QScrollArea,QToolBox,QTabWidget,QStackedWidget,QMidArea,QDockWidget)这些容器可以放置子控件,从而循环嵌套。
    2. \n
    \n

    # 例子

    \n

    在 PyQt5.5 的时候自带了一个例子文件夹(后面的版本没有的话可以下载 PyQt5 源码,里面有个 examples 文件夹),想要熟练的掌握 PyQt 还需要从自带的例子中学习,必须要每个例子都运行一次然后看看这个例子实现了什么,这样才能记忆深刻。
    \n同时很多开发者在 https://github.com/PyQt5/PyQt 分享了各类进阶例子,同时也欢迎大家共同完善该项目,提供更多更好的例子。另外也可以下载该项目的客户端 PyQtClient 软件,支持运行其中的例子

    \n

    建议在更深入的学习 PyQt 之前多看看一些例子。

    \n

    # 文档

    \n

    接下来要说的就是 Qt 的 api 文档,官网文档,这里其实不要害怕是英文就不想看,觉得看不懂了,其实官网的文档还是比较简洁的,而且函数名也比较直观就能知道意思。也可以用谷歌浏览器打开右键翻译,基本上都能看懂。笔者前期写过一篇如何查阅 Qt 文档的文档可以阅读学习一番。

    \n

    这里就拿 QWebEngineView 举一个例子,首先初学者在使用这个浏览器控件时候,会有诸多的问题比如:Cookie,拦截器等就不知道如何去调用函数来设置

    \n
      \n
    1. 首先打开官网文档 https://doc.qt.io/qt-5/qwebengineview.html,可以看到只有少量的函数可以调用,寻找一番并没有发现和 Cookie 相关的东西,这个时候就需要把重点放在有特俗返回值的函数上,比如:
    2. \n
    \n
    QWebEngineHistory *\t          history() const\nQWebEnginePage *\t          page() const\nQWebEngineSettings *\t      settings() const
    \n

    这三个函数返回了一个类实例,就意味着可以调用其中的方法。

    \n
      \n
    1. \n

      点击 page () 打开 https://doc.qt.io/qt-5/qwebenginepage.html,发现没有 cookie 相关的东西,只有 QWebEngineProfile *\tprofile () const 这个函数比较可疑。

      \n
    2. \n
    3. \n

      点击 **profile ()** 打开 https://doc.qt.io/qt-5/qwebengineprofile.html,在浏览器中搜索 cookie 发现这个类中包含大量和 cookie 相关的东西,比如:**QWebEngineCookieStore *\tcookieStore ()`** 从名字上可以猜测大概意思为 cookie 储存

      \n
    4. \n
    5. \n

      点击 **cookieStore ()** 打开 https://doc.qt.io/qt-5/qwebenginecookiestore.html,此时就会发现这个类里面包含了删除和设置 cookie 的方法。

      \n
    6. \n
    7. \n

      但是找到了这些方法后,面对初学者又一个问题来了,该如何去用?根据上面 4 点整理一下,把他们当做简单的 Python 对象,方法和操作方法和 class 一样的。

      \n
    8. \n
    \n
    self.webview = QWebEngineView()\n# 得到page\npage = self.webview.page()\n# 得到profile\nprofile = page.profile()\n# 得到cookieStore\ncookieStore = profile.cookieStore()\n# 清空cookie\ncookieStore.deleteAllCookies()\n\n# 用简短代码来表达就是\ncookieStore = self.webview.page().profile().cookieStore()\ncookieStore.deleteAllCookies()
    \n

    # 异常调试

    \n

    可能有时候由于粗心,或者调用了一些非法函数,参数错误等会导致程序出现一些异常,首先第一步复制最后一行的错误去百度或者谷歌搜索,大多时候能找到问题所在。其次如果搜索不到或者自己的异常可能是由于某个变量的值不对引起的,就需要在编辑器中打断点使用 DEBUG 模式调试变量值(如果不会可以采用麻烦一点的办法:用 print 打印出变量值)

    \n

    遇到问题后首先需要自己多调试排查问题,不要一遇到问题就去问,自己多尝试一个一个排查直到找到问题所在并解决,这也是一种提高自身能力的地方。

    \n

    # 检索资料

    \n

    作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。信息检索是每个程序猿必备的能力之一,其好处在于可以更快更准确的在茫茫网络海洋中找到自己所需要的东西,这个过程需要长期不断积累和练习。

    \n
      \n
    1. 中文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt 拖拽
    2. \n
    3. 英文搜索引擎:采用多个关键词 以空格分开搜索,如:PyQt Drag Drop
    4. \n
    \n

    # 片尾

    \n

    好了,笔者基本上的学习过程就整理如上,这并不是说每个人都适合这样的方法,但至少笔者是这样一步一步走过来的。当你养成了一个学习、发现和解决问题的好习惯时就会慢慢得心应手。

    \n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/qtwebjs.html", + "url": "https://pyqt5.com/qtwebjs.html", + "title": "QtWebkit和QWebEngineView与Javascript交互", + "date_published": "2019-05-22T03:30:36.000Z", + "content_html": "

    以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

    \n\n

    # 说明

    \n
      \n
    1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
    2. \n
    3. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
    4. \n
    5. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
    6. \n
    \n
    @pyqtSlot(str)\ndef callFromJs(self, text):\n    QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))
    \n
      \n
    1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
    2. \n
    \n
    // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\nBridge.windowTitleChanged.connect({fun: function(title) {\n    showLog(\"标题被修改为:\" + title);\n}}, \"fun\");\n\n// 绑定自定义的信号customSignal\nBridge.customSignal.connect({fun: function(text) {\n    showLog(\"收到自定义信号内容:\" + text);\n}}, \"fun\");
    \n
      \n
    1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
    2. \n
    \n
    new QWebChannel(qt.webChannelTransport,\n    function(channel) {\n        window.Bridge = channel.objects.Bridge;\n        \n        // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\n        Bridge.windowTitleChanged.connect(function(title) {\n            showLog(\"标题被修改为:\" + title);\n        });\n        \n        // 绑定自定义的信号customSignal\n        Bridge.customSignal.connect(function(text) {\n           showLog(\"收到自定义信号内容:\" + text);\n        });\n    }\n);
    \n

    # 代码

    \n

    QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

    \n

    QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

    \n
      \n
    1. 针对 QWebView 的核心实现
    2. \n
    \n
    class WebView(QWebView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebView, self).__init__(*args, **kwargs)\n        self.initSettings()\n        # 暴露接口对象\n        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)\n\n    def _exposeInterface(self):\n        \"\"\"向Js暴露调用本地方法接口\n        \"\"\"\n        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
    \n
      \n
    1. 针对 QWebEngineView 的核心实现
    2. \n
    \n
    class WebEngineView(QWebEngineView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebEngineView, self).__init__(*args, **kwargs)\n        self.channel = QWebChannel(self)\n        # 把自身对象传递进去\n        self.channel.registerObject('Bridge', self)\n        # 设置交互接口\n        self.page().setWebChannel(self.channel)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
    \n

    # 效果图

    \n

    \"JsSignals\"

    \n", + "tags": [ + "PyQt", + "QWebView", + "QWebEngineView", + "浏览器" + ] + }, + { + "id": "https://pyqt5.com/flipwidgetanimation.html", + "url": "https://pyqt5.com/flipwidgetanimation.html", + "title": "PyQt5窗口翻转动画", + "date_published": "2019-05-15T14:48:00.000Z", + "content_html": "

    QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 QStackedWidget 包含两个控件做切换,同时单独使用一个窗口做动画绘制。

    \n\n

    # 原理说明

    \n
      \n
    1. 用了两个 QLabel 来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
    2. \n
    3. 用了 QStackedWidget 来存放上面的两个界面 QLabel
    4. \n
    5. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口
    6. \n
    7. 通过 setWindowOpacity 控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
    8. \n
    9. 动画窗口 FlipWidget.py 主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画
    10. \n
    \n

    # 核心实现

    \n
      \n
    1. 主要是在 paintEvent 方法中使用 QTransformQPainter 进行圆心变换以及 rotate 设置翻转角度
    2. \n
    3. 同时根据翻转的角度范围对图片进行切换和缩放
    4. \n
    \n
    def paintEvent(self, event):\n    super(FlipWidget, self).paintEvent(event)\n\n    if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible():\n\n        painter = QPainter(self)\n        painter.setRenderHint(QPainter.Antialiasing, True)\n        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n        # 变换\n        transform = QTransform()\n        # 把圆心设置为矩形中心\n        transform.translate(self.width() / 2, self.height() / 2)\n\n        if self._angle >= -90 and self._angle <= 90:\n            # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程\n            painter.save()\n            # 设置翻转角度\n            transform.rotate(self._angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image1.width() / 2\n            height = int(self.image1.height() *\n                         (1 - abs(self._angle / self.Scale) / 100))\n            image = self.image1.scaled(\n                self.image1.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()\n        else:\n            # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程\n            painter.save()\n            if self._angle > 0:\n                angle = 180 + self._angle\n            else:\n                angle = self._angle - 180\n            # 设置翻转角度, 注意这里角度有差异\n            transform.rotate(angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image2.width() / 2\n            height = int(self.image2.height() *\n                         (1 - ((360 - abs(angle)) / self.Scale / 100)))\n            image = self.image2.scaled(\n                self.image2.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py

    \n

    # 效果图

    \n

    \"FlipWidgetAnimation\"

    \n", + "tags": [ + "PyQt", + "动画", + "翻转" + ] + }, + { + "id": "https://pyqt5.com/QPropertyAnimation.html", + "url": "https://pyqt5.com/QPropertyAnimation.html", + "title": "PyQt属性动画(QPropertyAnimation)", + "date_published": "2019-05-08T07:43:06.000Z", + "content_html": "

    QPropertyAnimation 继承自 QVariantAnimation ,其作为 Qt 的属性动画用于针对控件的属性或者继承自 QObject 的对象中定义的属性做修改,
    \n简单来说就是基类是 QObject 且定义了属性变量,就可以用 QPropertyAnimation 来做属性动画。同时也可以通过 pyqtProperty 来增加自定义属性。

    \n\n

    首先,通过构造函数 QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None) 创建一个对象,其中

    \n
      \n
    1. 第一个参数是动画作用的对象,也可以通过 setTargetObject 设置
    2. \n
    3. 第二个参数是属性名,在 py3 中类型是 bytes,也可以通过 setPropertyName 设置
    4. \n
    \n

    # 函数

    \n

    一些常见的设置函数

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    setPropertyName设置属性名
    setTargetObject设置动画作用对象
    setDuration设置动画持续时间(毫秒)
    setStartValue设置开始值
    setEndValue设置结束值
    setEasingCurve设置动画曲线
    setKeyValueAt插入线性值
    setLoopCount设置循环次数(-1 为永久)
    \n

    # 示例

    \n

    比如这个例子:

    \n
      \n
    1. 修改控件的 geometry 大小
    2. \n
    3. 修改自定义属性
    4. \n
    5. 修改进度条的 value 值
    6. \n
    \n

    \"QPropertyAnimation\"

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年5月8日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\nfrom PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurve\nfrom PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\\\n    QLabel, QProgressBar, QSpacerItem, QSizePolicy\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.resize(400, 400)\n        self._value = 0\n        self.button = QPushButton('属性动画测试', self)\n        self.button.clicked.connect(self.doStart)\n        self.button.setGeometry(0, 0, 80, 40)\n\n        self.buttonc = QPushButton('自定义属性 测试', self)\n        self.buttonc.clicked.connect(self.doStartCustom)\n\n        self.label = QLabel('', self)\n\n        self.progressbar = QProgressBar(self)\n        self.progressbar.setRange(0, 99)\n\n        layout = QVBoxLayout(self)\n        layout.addItem(QSpacerItem(\n            20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed))\n        layout.addWidget(self.buttonc)\n        layout.addWidget(self.label)\n        layout.addWidget(self.progressbar)\n\n        # 进度条动画\n        self.progressStart()\n\n    # 此处是自定义属性,并通过动画修改后,设置QLabel的值\n    @pyqtProperty(int)\n    def value(self):\n        return self._value\n\n    @value.setter\n    def value(self, v):\n        self._value = v\n        self.label.setText('当前值:{}'.format(v))\n\n    def doStart(self):\n        # 第一个参数是要执行的对象\n        animation = QPropertyAnimation(self.button, b'geometry', self)\n        animation.setDuration(2000)  # 持续时间\n        # 缓和曲线风格,加了曲线动画会很大程度影响\n        animation.setEasingCurve(QEasingCurve.OutBounce)\n        animation.setStartValue(QRect(0, 0, 40, 40))\n        animation.setEndValue(QRect(250, 250, 80, 80))\n        animation.start(animation.DeleteWhenStopped)\n\n    def doStartCustom(self):\n        # 自定义属性动画\n        # 由于定义的属性是在继承的QWidget, 所以第一个参数是self\n        # 第二个参数就是 value\n        animation = QPropertyAnimation(self, b'value', self)\n        animation.setDuration(2000)  # 持续时间\n        animation.setStartValue(0)\n        animation.setEndValue(100)\n        animation.start(animation.DeleteWhenStopped)\n\n    def progressStart(self):\n        # 进度条动画\n        # 这里 value是QProgressBar自带的属性,具体可以看文档\n        # https://doc.qt.io/qt-5/qprogressbar.html#properties\n        animation = QPropertyAnimation(self.progressbar, b'value', self)\n        animation.setDuration(2000)  # 持续时间\n        animation.setLoopCount(-1)\n        # 这里采用插入线性值,第一个参数的范围是(0-1)\n        # 第二个参数的范围是进度(最小值-最大值)\n        animation.setKeyValueAt(0, self.progressbar.minimum())\n        animation.setKeyValueAt(0.1, 10)\n        animation.setKeyValueAt(0.2, 30)\n        animation.setKeyValueAt(0.5, 60)\n        animation.setKeyValueAt(0.7, 80)\n        animation.setKeyValueAt(1, self.progressbar.maximum())\n        animation.start(animation.DeleteWhenStopped)\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())\n
    ", + "tags": [ + "PyQt", + "动画" + ] + }, + { + "id": "https://pyqt5.com/viewapi.html", + "url": "https://pyqt5.com/viewapi.html", + "title": "如何查阅Qt文档", + "date_published": "2019-05-04T12:50:20.000Z", + "content_html": "

    很多网友在问有没有 PyQt5 的文档之类的问题,在 PyQt4 的时候 PyQt 官网有了英文版的文档,随后有网友翻译成了中文。不过现在 PyQt5 官方的文档都指向了 C 的 Qt 文档,其实 C 的 Qt API 文档结构很清晰,翻阅很容易的,需要注意几点。

    \n\n

    作为一个开发人员确实需要具备查阅文档、查询资料等基础技能,会为自己的开发带来很大的帮助,要善于搜索,通过不同的方式去搜索才能找到自己需要的东西。

    \n

    拿 Qt C++ 文档来说,官网地址是:https://doc.qt.io/qt-5/qtwidgets-module.html 这里面记录了所有控件的详细函数文档。

    \n

    比如拿 输入框 QLineEdit 来说,怎么去查询它的用法和信号槽等资料?

    \n

    https://doc.qt.io/qt-5/qlineedit.html

    \n

    # 左侧目录

    \n

    在文档左侧目录中有如下几个:

    \n

    Properties - 控件里的属性(比如宽高等,通常需要当作函数调用)

    \n

    Public Slots - 这个是控件自己的槽函数(当作普通函数就行)

    \n

    Signals - 这个是输入框的包含的信号

    \n

    Public Functions、Reimplemented Public Functions、Static Public Members、Protected Functions、Reimplemented Protected Functions - 这几个都是函数列表

    \n

    \"howtoviewapi1\"

    \n

    # 类说明

    \n

    \"howtoviewapi2\"

    \n

    这里有两个注意点

    \n
      \n
    1. 红色方框内的表示该控件(输入框)继承于 QWidget ,所以该控件(输入框)拥有父类的所有方法和信号,当当前文档找不到相关资料和函数时,可以去父类找找看。
    2. \n
    3. 紫色方框内表示列举所有的方法(包括父类)
    4. \n
    \n

    # 函数列表

    \n

    \"howtoviewapi3\"

    \n

    这里列举的就是该控件(输入框)的函数,同理点击上面的紫色方框是查看所有方法,一般这里主要用来查询你需要的功能函数,Qt 的函数名比较容易理解,比如:只读 ReadOnly,选择文字:setSelection。

    \n

    所以再查下这部分资料的时候建议在浏览器中 Ctrl + F 打开浏览器的搜索框,并输入英文关键词来检索你所需要的函数在哪里。

    \n

    \"howtoviewapi8\"

    \n

    # 槽函数

    \n

    \"howtoviewapi4\"

    \n

    这部分列举的是槽函数,其实在 PyQt 中槽函数可以当作普通的函数。普通的函数也可以作为槽函数,直接通过信号连接即可,注意方框所示,还有很多函数是在父类里面。

    \n

    # 信号

    \n

    \"howtoviewapi5\"

    \n

    这部分列举了该控件(输入框)所定义的信号,主要还是看名字,大多都能知道是做什么的,比如:

    \n
      \n
    1. editingFinished - 编辑完成信号
    2. \n
    3. returnPressed - 回车键信号
    4. \n
    5. textChanged (const QString &text) - 内容改变信号
    6. \n
    \n

    这里还有个问题就是参数问题,一般 & 后面的 text 作为参数传递到槽函数中

    \n

    # 函数详细说明

    \n

    当不明确这个函数是做什么的,可以点击该函数跳转到下面的说明,比如回车键信号 returnPressed

    \n

    \"howtoviewapi6\"

    \n

    如图上所示,用翻译插件翻译,大部分就明白了,如下:

    \n

    \"howtoviewapi7\"

    \n

    # 关于如何搜索资料

    \n

    比如当你要搜索输入框内容改变事件,一般建议两种搜索,且搜索的时候用空格把关键词分开搜索,而且直接用控件名

    \n
      \n
    1. 中文搜索引擎:QLineEdit 内容 改变
    2. \n
    3. 英文搜索引擎:QLineEdit text change
    4. \n
    \n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/bindsignals.html", + "url": "https://pyqt5.com/bindsignals.html", + "title": "三种方式绑定信号槽", + "date_published": "2019-05-04T08:07:06.000Z", + "content_html": "

    网上关于 PyQt5 的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在 PyQt5 中无法使用,这里归纳总结下已有的几种绑定信号槽的方式,
    \n这几种方式各有各的优点和缺点。

    \n\n

    # 方式一

    \n

    这个方式是最开始接触设计师的时候知道的,主要是通过控件的 objectNameQtCore.QMetaObject.connectSlotsByName(Form) 提供的连接函数来自动完成注册,
    \n比如带有按钮的界面 ui 文件转成 py 文件后会发现如下代码:

    \n
    self.pushButton = QtWidgets.QPushButton(Form)\nself.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28))\nself.pushButton.setObjectName(\"pushButton\")\n\n# 通过这里自动完成连接信号槽\nQtCore.QMetaObject.connectSlotsByName(Form)
    \n

    此时只需要继承该 UI 文件类然后增加如下方法:

    \n
    \n@pyqtSlot()\ndef on_pushButton_clicked(self):\n    print('button clicked')
    \n

    这里解释一下, @pyqtSlot() 装饰器把函数 on_pushButton_clicked 包装为一个槽函数,
    \n而 QtCore.QMetaObject.connectSlotsByName(Form) 这句代码的意思就是自动去寻找满足的槽函数

    \n

    注意:这里有个规范(on_xxxx_clicked),这里必须要满足 on_控件的objectName_控件的信号 这样下划线连接起来的函数名才能被识别,
    \n比如按钮的点击: on_pushButton_clicked 、勾选框的选中: on_checkbox_toggled(self, checked)

    \n

    # 方式二

    \n

    这种方式则直接通过代码里调用控件的信号的 connect 方法来进行绑定,比如:

    \n
    # 按钮点击函数\ndef doClicked(self):\n    print(self.sender(), 'clicked')\n\n# 绑定点击信号\nself.pushButton.clicked.connect(self.doClicked)
    \n

    注意: connect 的是函数名字self.sender() 这句代码是获取信号发送者(比如这里就是得到这个按钮对象),
    \n用处在于有时候要循环创建一堆按钮

    \n

    # 方式三

    \n

    通过参数这种方式其实比较特殊,在 PyQt 中大部分存在,但是在 PySide 中则很少,原因是两者的封装方式不同。

    \n

    同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如:

    \n
    \n# 按钮点击函数\ndef doClicked(self):\n    print(self.sender(), 'clicked')\n\npushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40)
    \n

    这里可以通过参数(信号名字) = 函数来绑定信号

    \n

    同时也可以设置其它参数,比如
    \n button.setMinimumHeight(40) 也可以像参数里那样设置 minimumHeight=40

    \n", + "tags": [ + "PyQt", + "信号" + ] + }, + { + "id": "https://pyqt5.com/runnablesignal_625781186.html", + "url": "https://pyqt5.com/runnablesignal_625781186.html", + "title": "QRunnable线程池发信号", + "date_published": "2019-04-30T07:58:09.000Z", + "content_html": "

    因为只有继承 QObject 的类才能有信号和自定义信号,而 QRunnable 并不是继承自 QObject ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。

    \n\n

    pools 是 QThreadPool 实例

    \n

    # 看图说话

    \n
      \n
    1. \"runnablesignal1\"
    2. \n
    3. 定义一个全局信号类
      \n\"runnablesignal2\"
    4. \n
    5. 在 QRunnable 中发送
      \n\"runnablesignal3\"
    6. \n
    \n", + "tags": [ + "PyQt", + "信号", + "线程" + ] + }, + { + "id": "https://pyqt5.com/viewpyindesigner_625781186.html", + "url": "https://pyqt5.com/viewpyindesigner_625781186.html", + "title": "如何和设计师中查看ui转换的py代码", + "date_published": "2019-04-30T05:11:09.000Z", + "content_html": "

    通过 设计师  查看 ui 转换的 py 代码

    \n

    当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。

    \n

    后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。

    \n\n

    再来后发现了官方文档,查一查函数就行了 .

    \n

    但是有些 api 文档找起来麻烦,用设计师点几下就行了,然后把转换出来的代码拷贝一下就完事了.

    \n

    可是需要单独把 ui 转为 py 文件,之后再删除这个文件也是很烦的一件事 .

    \n

    好,话不多说,接下来手把手教你如何快速在 ui 中查看 py 代码 .

    \n

    官方也考虑过这种情况,所以 设计师中 是有这个功能的,但是 qt 的是没问题的,pyqt 的毕竟是绑定过来的,所以正常来说 你点击之后会弹出一个找不到应用程序的提示 .

    \n

    看到这个东西是不是很眼熟,我们用的命令 pyuic5 和这个东西应该是一样的 .

    \n

    \"viewpyindesigner1\"

    \n

    所以接下来,我们找找电脑上有没有这个东西

    \n

    \"viewpyindesigner2\"

    \n

    果然在 pyqt5-toos 文件夹下有这个东西,

    \n

    我们根据第一张图的提示,把这个东西拷贝到相应的目录 (如果没有那个 bin 文件夹,手动创建),

    \n

    \"viewpyindesigner3\"

    \n

    好了,大功告成!

    \n", + "tags": [ + "PyQt", + "Designer", + "设计师" + ] + }, + { + "id": "https://pyqt5.com/showframe.html", + "url": "https://pyqt5.com/showframe.html", + "title": "PyQt5调整窗口显示边框", + "date_published": "2019-04-26T14:19:26.000Z", + "content_html": "

    windows 某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 windows 在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -> 调整 Windows 的外观和性能 -> 去掉勾选 拖动时显示窗口内容。

    \n\n

    由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。

    \n
      \n
    1. 一种是自己绘制一个边框效果,放开鼠标时才操作真正的窗口。
    2. \n
    3. 二是替换窗口的处理过程函数 wndproc 处理 WM_NCLBUTTONDOWN 消息事件。
    4. \n
    \n

    今天讲第二种方法:

    \n
      \n
    1. 需要了解 SystemParametersInfo  API 函数
    2. \n
    3. SPI_GETDRAGFULLWINDOWS :确定是否允许拖拉到最大窗口
    4. \n
    5. SPI_SETDRAGFULLWINDOWS :设置是否允许拖至最大窗口
    6. \n
    \n

    效果就是这样的:

    \n

    \"ShowFrameWhenDrag\"

    \n

    正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。

    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月23日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: ShowFrameWhenDrag\n@description: 调整窗口显示边框\n\"\"\"\nfrom ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\\\n    c_ulonglong, WINFUNCTYPE, c_uint\n\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\nif sizeof(c_long) == sizeof(c_void_p):\n    WPARAM = c_ulong\n    LPARAM = c_long\nelif sizeof(c_longlong) == sizeof(c_void_p):\n    WPARAM = c_ulonglong\n    LPARAM = c_longlong\n\nWM_NCLBUTTONDOWN = 0x00a1\nGWL_WNDPROC = -4\nSPI_GETDRAGFULLWINDOWS = 38\nSPI_SETDRAGFULLWINDOWS = 37\nWNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM)\n\ntry:\n    CallWindowProc = windll.user32.CallWindowProcW\n    SetWindowLong = windll.user32.SetWindowLongW\n    SystemParametersInfo = windll.user32.SystemParametersInfoW\nexcept:\n    CallWindowProc = windll.user32.CallWindowProcA\n    SetWindowLong = windll.user32.SetWindowLongA\n    SystemParametersInfo = windll.user32.SystemParametersInfoA\n\n\ndef GetDragFullwindows():\n    rv = c_int()\n    SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0)\n    return rv.value\n\n\ndef SetDragFullwindows(value):\n    SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0)\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        layout.addWidget(QLabel('拖动或者调整窗口试试看'))\n\n        # 重点替换窗口处理过程\n        self._newwndproc = WNDPROC(self._wndproc)\n        self._oldwndproc = SetWindowLong(\n            int(self.winId()), GWL_WNDPROC, self._newwndproc)\n\n    def _wndproc(self, hwnd, msg, wparam, lparam):\n        if msg == WM_NCLBUTTONDOWN:\n            # 获取系统本身是否已经开启\n            isDragFullWindow = GetDragFullwindows()\n            if isDragFullWindow != 0:\n                # 开启虚线框\n                SetDragFullwindows(0)\n                # 系统本身处理\n                ret = CallWindowProc(\n                    self._oldwndproc, hwnd, msg, wparam, lparam)\n                # 关闭虚线框\n                SetDragFullwindows(1)\n                return ret\n        return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 片尾

    \n

    替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。

    \n", + "tags": [ + "PyQt", + "边框" + ] + }, + { + "id": "https://pyqt5.com/issignalconnected.html", + "url": "https://pyqt5.com/issignalconnected.html", + "title": "PyQt5判断信号是否连接", + "date_published": "2019-04-26T14:06:26.000Z", + "content_html": "

    PyQt 中某些情况下需要取消原来的信号连接,此时需要使用 disconnect 方法,但是在逻辑不严谨的情况下可能会导致多次调用 disconnect 方法而导致报错,当然可以通过 try except 来包裹代码。这里通过  isSignalConnected  来判断信号是否连接。

    \n\n

    在 QOjbect 文档中这样写到:

    \n
    static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged);\nif (isSignalConnected(valueChangedSignal)) {\n    QByteArray data;\n    data = get_the_value();       // expensive operation\n    emit valueChanged(data);\n}
    \n

    通过直接传入信号就行了,但是这在 PyQt 中不可行。需要这么做

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年2月24日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: IsSignalConnected\n@description: 判断信号是否连接\n\"\"\"\n\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.button1 = QPushButton('已连接', self, clicked=self.doTest)\n        self.button2 = QPushButton('未连接', self)\n        self.retView = QTextBrowser(self)\n        layout.addWidget(self.button1)\n        layout.addWidget(self.button2)\n        layout.addWidget(self.retView)\n\n    def doTest(self):\n        self.retView.append(\"\"\"\n        # button1 clicked 是否连接: %s\n        # button2 clicked 是否连接: %s\n        \"\"\" % (\n            self.isSignalConnected(self.button1, 'clicked()'),\n            self.isSignalConnected(self.button2, 'clicked()')\n        ))\n\n    def isSignalConnected(self, obj, name):\n        \"\"\"判断信号是否连接\n        :param obj:        对象\n        :param name:       信号名,如 clicked()\n        \"\"\"\n        index = obj.metaObject().indexOfMethod(name)\n        if index > -1:\n            method = obj.metaObject().method(index)\n            if method:\n                return obj.isSignalConnected(method)\n        return False\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"IsSignalConnected\"

    \n", + "tags": [ + "PyQt", + "信号" + ] + }, + { + "id": "https://pyqt5.com/shadowradius.html", + "url": "https://pyqt5.com/shadowradius.html", + "title": "PyQt5无边框圆角阴影", + "date_published": "2019-04-25T16:06:26.000Z", + "content_html": "

    在做 PyQt 窗口开发中经常会遇到要做一些无边框不规则的窗口,可能还会带有阴影效果,这里演示做一个简单的无边框圆角的窗口,原理就在于背景窗口的透明和一层有色背景控件的叠加。

    \n\n

    # 原理说明

    \n
      \n
    1. 黑色(方便说明)的 QDialog 或者 QWidget 作为全透明无边框窗口。
    2. \n
    3. 其中白色的 QWidget 才是主要显示圆角和阴影的窗口,用于承载其它控件的显示。
    4. \n
    5. 注意红色和紫色的方框内的层次。
    6. \n
    7. 另:如果要熟悉纯代码编写请看 FramelessDialog.py
    8. \n
    \n

    如图:

    \n

    \"FramelessDialog1\"

    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月25日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: FramelessWidget\n@description: 无边框圆角带阴影窗口 \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect\nfrom frameless import Ui_Dialog\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019'\n\n\nclass Window(QDialog, Ui_Dialog):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.mPos = None\n        self.setupUi(self)\n        self.closeButton.clicked.connect(self.close)\n        # 重点\n        # 无边框\n        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)\n        # 背景透明(就是ui中黑色背景的那个控件)\n        self.setAttribute(Qt.WA_TranslucentBackground, True)\n\n        # 添加阴影\n        effect = QGraphicsDropShadowEffect(self)\n        effect.setBlurRadius(12)\n        effect.setOffset(0, 0)\n        effect.setColor(Qt.gray)\n        self.setGraphicsEffect(effect)\n\n    # 加上简单的移动功能\n\n    def mousePressEvent(self, event):\n        \"\"\"鼠标点击事件\"\"\"\n        if event.button() == Qt.LeftButton:\n            self.mPos = event.pos()\n        event.accept()\n\n    def mouseReleaseEvent(self, event):\n        '''鼠标弹起事件'''\n        self.mPos = None\n        event.accept()\n\n    def mouseMoveEvent(self, event):\n        if event.buttons() == Qt.LeftButton and self.mPos:\n            self.move(self.mapToGlobal(event.pos() - self.mPos))\n        event.accept()\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"FramelessDialog\"

    \n

    # 下载

    \n

    无边框圆角阴影.zip

    \n", + "tags": [ + "PyQt", + "阴影", + "无边框", + "圆角" + ] + }, + { + "id": "https://pyqt5.com/pyqtclientmac.html", + "url": "https://pyqt5.com/pyqtclientmac.html", + "title": "在Mac上以正确的姿势使用PyQtClient看Demo", + "date_published": "2019-04-02T09:18:43.000Z", + "content_html": "

    由于 PyQtClient 只提供了 Windows 的版本,这里记录下编译后在 Mac 上运行。

    \n\n

    # 下载项目

    \n

    安装 git 略。没有的东西可以都先去试试 brew install xxx。没安装 homebrew 的建议使用搜索引擎

    \n

    git clone https://github.com/PyQt5/PyQtClient.git

    \n

    # 配置环境

    \n
      \n
    1. 打开 IDE 配置 python 环境,使用 anaconda 比较方便
    2. \n
    3. 推荐用 pycharm,我是习惯了用 idea。anaconda 安装可以去官网下载。
    4. \n
    5. 环境原因,选择新建一个 python 3.6 p.s. 我取的环境名字就是 3.6 所以后面的 3.6 其实是这个原因
    6. \n
    \n

    # conda 源

    \n

    最好是加环境变量,不加也可以,就是以后用到的都需要指定路径,不太常用,我就没加

    \n
    ~/anaconda3/bin/conda config --add channels conda-forge\n~/anaconda3/bin/conda config --add channels defaults\n~/anaconda3/bin/conda config --add channels r\n~/anaconda3/bin/conda config --add channels bioconda\n~/anaconda3/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ 
    \n

    # pip 源

    \n
    mkdir ~/.pip && vim ~/.pip/pip.conf
    \n
    [global]\nindex-url = http://mirrors.aliyun.com/pypi/simple/\n[install]\ntrusted-host = mirrors.aliyun.com
    \n

    # 安装编译依赖

    \n
    ~/.conda/envs/3.6/bin/pip install -r PyQtClient/requirements.txt
    \n

    运行提示没有 webkit,开始手动编译

    \n
      \n
    1. wget http://download.qt.io/archive/qt/5.9/5.9.0/qt-opensource-mac-x64-5.9.0.dmg
    2. \n
    3. wget https://github.com/annulen/webkit/releases/download/qtwebkit-5.212.0-alpha2/qtwebkit-5.212.0_alpha2-qt59-darwin-x64.tar.xz
    4. \n
    5. wget https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.10.1/PyQt5_gpl-5.10.1.zip
    6. \n
    7. wget https://www.riverbankcomputing.com/static/Downloads/sip/4.19.8/sip-4.19.8.tar.gz
    8. \n
    9. 编译 sip: ~/.conda/envs/3.6/bin/python configure.py --platform macx-g++ && make && sudo make install
    10. \n
    11. 编译 Webkit.so 没有 qmake 和 sip 的环境变量, 所以后面都是手动指定的
    12. \n
    \n
    ~/.conda/envs/3.6/bin/python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc --qmake=~/Qt5.9.0/5.9/clang_64/bin/qmake --sip=~/.conda/3.6/bin/sip && make && sudo make install
    \n

    # 插曲

    \n
      \n
    1. libcurl 版本要求 10.0,而我的是 9.0,原因是前面我自己摸索,乱装依赖,所以遇到了
    2. \n
    \n
    ~/anaconda3/bin/conda install -n 3.6 -c conda-forge libcurl
    \n
      \n
    1. 结果这个 libcurl 10.0.0 是装上了,可是 pygit2 版本不对了,conda 给升级了,PyQtClient 里 requirements.txt 要求这个包的版本(pygit2==0.27.2)几乎决定了其他的环境版本。后来还是老实的用 conda 去装了。这个连 python 版本什么的都会跟着变的。最后降级的结果是 python 3.6.7
    2. \n
    \n
    ~/anaconda3/bin/conda install -n 3.6 -c conda-forge libgit2==0.27.2
    \n

    至此总算是启动正常了。

    \n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/pyqtclient.html", + "url": "https://pyqt5.com/pyqtclient.html", + "title": "PyQtClient例子客户端", + "date_published": "2019-02-02T07:15:06.000Z", + "content_html": "

    \"1.gif\" 对本博客所写的项目 PyQt 例子进行一个客户端的编写,客户端主要实现一些动画效果,更换皮肤,运行例子等功能。\"2.gif\"

    \n\n

    # 项目地址

    \n
    \n

    # Windows 客户端下载

    \n
      \n
    1. 包含部分例子
    2. \n
    3. 不包含例子
    4. \n
    5. 百度网盘 提取码: nadv
    6. \n
    \n

    # 效果图

    \n

    \"PyQtClient\"

    \n", + "tags": [ + "PyQt" + ] + }, + { + "id": "https://pyqt5.com/webviewnew.html", + "url": "https://pyqt5.com/webviewnew.html", + "title": "PyQt5编译QWebView与QWebEngineView共存", + "date_published": "2019-01-12T11:28:06.000Z", + "content_html": "

    在 PyQt5.5 过后移除了 QWebView 控件,改用 QWebEngineView ,但是这个刚开始用起来不是很方便,最近在整理一些例子的时候需要同时使用 QWebViewQWebEngineView ,故希望把 QWebView 重新加入到后面的 PyQt5 版本中,查看 PyQt5.10.1 的源码发现里面其实是有 QWebView 的,只是因为 Qt5.10.1 中没有编译好的 dll 等导致无法编译。

    \n\n

    # 准备工作

    \n
      \n
    1. 安装 VS2015
    2. \n
    3. 安装 Qt5.10.1
    4. \n
    5. 前往 https://github.com/annulen/webkit/releases 下载对应的文件,比如:qtwebkit-5.212.0_alpha2-qt59-msvc2015-x86.zip
    6. \n
    7. 下载 PyQt5.10.1 源码
    8. \n
    9. 下载对应版本的 sip 源码
    10. \n
    \n

    # 编译

    \n
      \n
    1. 设置环境变量 set PATH=D:\\soft\\Qt\\Qt5.10.1\\5.10.1\\msvc2015\\bin;%PATH%
    2. \n
    3. 首先进入 vs2015 命令行编译 sip 并安装, python configure.py && nmake && nmake install
    4. \n
    5. 进入 PyQt5.10.1 源码编译安装即可
    6. \n
    7. 如果要减少 PyQt5.10.1 的编译可以试试以下代码
    8. \n
    \n
    D:\\soft\\Python35\\python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc
    ", + "tags": [ + "PyQt", + "QWebView", + "浏览器" + ] + }, + { + "id": "https://pyqt5.com/pageswitching.html", + "url": "https://pyqt5.com/pageswitching.html", + "title": "PyQt5之图片轮播", + "date_published": "2018-11-24T13:45:06.000Z", + "content_html": "

    之前看到了 QStackedWidget 做切换动画,让界面不那么生硬,于是参考了 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt 做了一个 QStackedWidget 的切换动画,然后利用 QStackedWidget 结合多个 QLabel 显示图片来做一个轮播效果。

    \n

    其实在写之前也在网上找了很多例子,参看过后发现大多例子都是利用到了 paintEvent 去绘制,这样其实还是比较麻烦,个人觉得更好的方式是使用 QPropertyAnimation 属性动画修改控件中 QLabel 图片控件的 pos 位置属性就可以达到移动效果了。

    \n\n
      \n
    1. 比较核心的算法就是要计算当前页面和下一个页面的位置偏移量,比如:
    2. \n
    \n
    # 计算偏移量\noffsetX = self.frameRect().width()\noffsetY = self.frameRect().height()\nw_next.setGeometry(0, 0, offsetX, offsetY)\n\nif direction == self.BOTTOM2TOP:\n    offsetX = 0\n    offsetY = -offsetY\nelif direction == self.TOP2BOTTOM:\n    offsetX = 0\nelif direction == self.RIGHT2LEFT:\n    offsetX = -offsetX\n    offsetY = 0\nelif direction == self.LEFT2RIGHT:\n    offsetY = 0\n\n# 重新定位显示区域外部/旁边的下一个窗口小部件\npnext = w_next.pos()\npnow = w_now.pos()\nself._pnow = pnow\n\n# 移动到指定位置并显示\nw_next.move(pnext.x() - offsetX, pnext.y() - offsetY)\nw_next.show()\nw_next.raise_()
    \n
      \n
    1. \n

      其次是对这两个页面增加关联 pos 属性的 QPropertyAnimation 动画,然后加入到并行动画组 QParallelAnimationGroup 中再启动即可。

      \n
    2. \n
    3. \n

      QStackedWidgetsetCurrentIndexsetCurrentWidget 这两个函数进行了覆盖重写达到及时手动调用这两个函数也会产生动画效果的目的。

      \n
    4. \n
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/PageSwitching.py

    \n

    # 效果图

    \n

    \"PageSwitching\"

    \n", + "tags": [ + "PyQt", + "动画", + "轮播" + ] + }, + { + "id": "https://pyqt5.com/jumpslider.html", + "url": "https://pyqt5.com/jumpslider.html", + "title": "PyQt5之QSlider滑动条点击定位", + "date_published": "2018-11-05T15:12:26.000Z", + "content_html": "

    QSlider 在通常情况下支持鼠标点击可以任意拖动,或者鼠标点击则往鼠标点击的方向移动一小格,这种移动一小格通常情况下用起来很不方便,比如我要做一个播放器的播放进度条,肯定是点击某个位置就直接跳到该位置,为此需要对 QSlider 的鼠标事件 mousePressEvent 进行重写。

    \n\n

    # 实现方法

    \n

    一般的想法就是重写 mousePressEvent 后,得到鼠标点击的 x 和 y 点然后进行比例换算,再通过 setValue 来设置值,其实 QSliderstyle 里面是有一个 sliderValueFromPosition 方法来计算值的。直接调用这个方法即可。

    \n
      \n
    1. 首先通过 QSlider.style().subControlRect 方法计算得到滑块的区域,当鼠标点击区域在此次时则交给系统自己处理(比如按住不放拖动)
    2. \n
    3. 通过 orientation 判断滑动条的方向(横竖)
    4. \n
    5. 通过 invertedAppearance 判断滑动条是否反向(左右、上下)
    6. \n
    7. 通过 QSlider.style().sliderValueFromPosition(最小值, 最大值, x或者y坐标, 宽度或者高度) 来计算得到值
    8. \n
    9. 最后通过 setValue 来设置值
    10. \n
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QSlider/ClickJumpSlider.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年11月5日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: JumpSlider\n@description: \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle, QWidget,\\\n    QFormLayout, QLabel\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass JumpSlider(QSlider):\n\n    def mousePressEvent(self, event):\n        # 获取上面的拉动块位置\n        option = QStyleOptionSlider()\n        self.initStyleOption(option)\n        rect = self.style().subControlRect(\n            QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)\n        if rect.contains(event.pos()):\n            # 如果鼠标点击的位置在滑块上则交给Qt自行处理\n            super(JumpSlider, self).mousePressEvent(event)\n            return\n        if self.orientation() == Qt.Horizontal:\n            # 横向,要考虑invertedAppearance是否反向显示的问题\n            self.setValue(self.style().sliderValueFromPosition(\n                self.minimum(), self.maximum(),\n                event.x() if not self.invertedAppearance() else (self.width(\n                ) - event.x()), self.width()))\n        else:\n            # 纵向\n            self.setValue(self.style().sliderValueFromPosition(\n                self.minimum(), self.maximum(),\n                (self.height() - event.y()) if not self.invertedAppearance(\n                ) else event.y(), self.height()))\n\n\nclass TestWindow(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(TestWindow, self).__init__(*args, **kwargs)\n        layout = QFormLayout(self)\n\n        self.label1 = QLabel('0', self)\n        layout.addRow(self.label1, JumpSlider(\n            Qt.Horizontal, valueChanged=lambda v: self.label1.setText(str(v))))\n\n        # 横向-反向显示\n        self.label2 = QLabel('0', self)\n        layout.addRow(self.label2, JumpSlider(\n            Qt.Horizontal, invertedAppearance=True,\n            valueChanged=lambda v: self.label2.setText(str(v))))\n\n        self.label3 = QLabel('0', self)\n        layout.addRow(self.label3, JumpSlider(\n            Qt.Vertical, minimumHeight=200, valueChanged=lambda v: self.label3.setText(str(v))))\n\n        # 纵向反向显示\n        self.label4 = QLabel('0', self)\n        layout.addRow(self.label4, JumpSlider(\n            Qt.Vertical, invertedAppearance=True,\n            minimumHeight=200, valueChanged=lambda v: self.label4.setText(str(v))))\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, '')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = TestWindow()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"ClickJumpSlider\"

    \n", + "tags": [ + "PyQt", + "滑动条" + ] + }, + { + "id": "https://pyqt5.com/rlatticeeffect.html", + "url": "https://pyqt5.com/rlatticeeffect.html", + "title": "PyQt5仿网页鼠标移动点阵特效", + "date_published": "2018-10-29T08:49:10.000Z", + "content_html": "

    Orz,前段时间有个 zz 需求,就是要做一个类似网页上很多个多点连线、鼠标移动跟随的那种炫酷特效,然后花了点时间在网上找了 js 做的,刚开始打算是嵌入 QWebView 来显示网页,后来研究了下 js 的算法代码,遂改用 QWidgetpaintEvent 直接绘制。

    \n\n

    # 大概思路

    \n
      \n
    1. 先根据窗口大小随机创建一些点
    2. \n
    3. 遍历这些点并找到与之相关联的点
    4. \n
    5. 在动画过程中绘制圆点和画两点之间的连线
    6. \n
    7. 属性动画 QPropertyAnimation 改变颜色的透明度
    8. \n
    \n

    # 题外

    \n
      \n
    1. 这里没有仔细去研究 js 里的算法优化,在浏览器里嗖嗖的就生成了,在 py 里好慢…
    2. \n
    3. 尽量在 py 里优化了循环操作,也简单的做了个 cython 加速也才提高了 1s ? 1 倍?..
    4. \n
    5. 不要只是为了好看用这玩意儿,和网页的效果一样,占 CPU !!! 没有任何意义
    6. \n
    7. 如果有更好的优化算法请告知,3Q
    8. \n
    9. pyd 是 python3.4 生成的,删掉 pyd 也能运行
    10. \n
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/RlatticeEffect.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年11月22日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\nfrom random import random\nfrom time import time\n\nfrom PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\\\n    Qt, QRectF, pyqtSignal\nfrom PyQt5.QtGui import QColor, QPainterPath, QPainter\nfrom PyQt5.QtWidgets import QWidget\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\ntry:\n    import pointtool  # @UnusedImport @UnresolvedImport\n    getDistance = pointtool.getDistance\n    findClose = pointtool.findClose\nexcept:\n    import math\n\n    def getDistance(p1, p2):\n        return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2)\n\n    def findClose(points):\n        plen = len(points)\n        for i in range(plen):\n            closest = [None, None, None, None, None]\n            p1 = points[i]\n            for j in range(plen):\n                p2 = points[j]\n                dte1 = getDistance(p1, p2)\n                if p1 != p2:\n                    placed = False\n                    for k in range(5):\n                        if not placed:\n                            if not closest[k]:\n                                closest[k] = p2\n                                placed = True\n                    for k in range(5):\n                        if not placed:\n                            if dte1 < getDistance(p1, closest[k]):\n                                closest[k] = p2\n                                placed = True\n            p1.closest = closest\n\n\nclass Target:\n\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n\nclass Point(QObject):\n\n    valueChanged = pyqtSignal()\n\n    def __init__(self, x, ox, y, oy, *args, **kwargs):\n        super(Point, self).__init__(*args, **kwargs)\n        self.__x = x\n        self._x = x\n        self.originX = ox\n        self._y = y\n        self.__y = y\n        self.originY = oy\n        # 5个闭合点\n        self.closest = [0, 0, 0, 0, 0]\n        # 圆半径\n        self.radius = 2 + random() * 2\n        # 连线颜色\n        self.lineColor = QColor(156, 217, 249)\n        # 圆颜色\n        self.circleColor = QColor(156, 217, 249)\n\n    def initAnimation(self):\n        # 属性动画\n        if not hasattr(self, 'xanimation'):\n            self.xanimation = QPropertyAnimation(\n                self, b'x', self, valueChanged=self.valueChanged.emit,\n                easingCurve=QEasingCurve.InOutSine)\n            self.yanimation = QPropertyAnimation(\n                self, b'y', self, valueChanged=self.valueChanged.emit,\n                easingCurve=QEasingCurve.InOutSine,\n                finished=self.updateAnimation)\n            self.updateAnimation()\n\n    def updateAnimation(self):\n        self.xanimation.stop()\n        self.yanimation.stop()\n        duration = (1 + random()) * 1000\n        self.xanimation.setDuration(duration)\n        self.yanimation.setDuration(duration)\n        self.xanimation.setStartValue(self.__x)\n        self.xanimation.setEndValue(self.originX - 50 + random() * 100)\n        self.yanimation.setStartValue(self.__y)\n        self.yanimation.setEndValue(self.originY - 50 + random() * 100)\n        self.xanimation.start()\n        self.yanimation.start()\n\n    @pyqtProperty(float)\n    def x(self):\n        return self._x\n\n    @x.setter\n    def x(self, x):\n        self._x = x\n\n    @pyqtProperty(float)\n    def y(self):\n        return self._y\n\n    @y.setter\n    def y(self, y):\n        self._y = y\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.setMouseTracking(True)\n        self.resize(800, 600)\n        self.points = []\n        self.target = Target(self.width() / 2, self.height() / 2)\n        self.initPoints()\n\n    def paintEvent(self, event):\n        super(Window, self).paintEvent(event)\n        painter = QPainter()\n        painter.begin(self)\n        painter.setRenderHint(QPainter.Antialiasing)\n        painter.fillRect(self.rect(), Qt.black)\n        self.animate(painter)\n        painter.end()\n\n    def mouseMoveEvent(self, event):\n        super(Window, self).mouseMoveEvent(event)\n        # 鼠标移动时更新xy坐标\n        self.target.x = event.x()\n        self.target.y = event.y()\n        self.update()\n\n    def initPoints(self):\n        t = time()\n        self.points.clear()\n        # 创建点\n        stepX = self.width() / 20\n        stepY = self.height() / 20\n        for x in range(0, self.width(), int(stepX)):\n            for y in range(0, self.height(), int(stepY)):\n                ox = x + random() * stepX\n                oy = y + random() * stepY\n                point = Point(ox, ox, oy, oy)\n                point.valueChanged.connect(self.update)\n                self.points.append(point)\n        print(time() - t)\n\n        t = time()\n        # 每个点寻找5个闭合点\n        findClose(self.points)\n        print(time() - t)\n\n    def animate(self, painter):\n        for p in self.points:\n            # 检测点的范围\n            value = abs(getDistance(self.target, p))\n            if value < 4000:\n                # 其实就是修改颜色透明度\n                p.lineColor.setAlphaF(0.3)\n                p.circleColor.setAlphaF(0.6)\n            elif value < 20000:\n                p.lineColor.setAlphaF(0.1)\n                p.circleColor.setAlphaF(0.3)\n            elif value < 40000:\n                p.lineColor.setAlphaF(0.02)\n                p.circleColor.setAlphaF(0.1)\n            else:\n                p.lineColor.setAlphaF(0)\n                p.circleColor.setAlphaF(0)\n\n            # 画线条\n            if p.lineColor.alpha():\n                for pc in p.closest:\n                    if not pc:\n                        continue\n                    path = QPainterPath()\n                    path.moveTo(p.x, p.y)\n                    path.lineTo(pc.x, pc.y)\n                    painter.save()\n                    painter.setPen(p.lineColor)\n                    painter.drawPath(path)\n                    painter.restore()\n\n            # 画圆\n            painter.save()\n            painter.setPen(Qt.NoPen)\n            painter.setBrush(p.circleColor)\n            painter.drawRoundedRect(QRectF(\n                p.x - p.radius, p.y - p.radius, 2 * p.radius, 2 * p.radius), p.radius, p.radius)\n            painter.restore()\n\n            # 开启动画\n            p.initAnimation()\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, '')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"RlatticeEffect\"

    \n", + "tags": [ + "PyQt", + "动画", + "特效" + ] + }, + { + "id": "https://pyqt5.com/datawidgetmapper_625781186.html", + "url": "https://pyqt5.com/datawidgetmapper_625781186.html", + "title": "QDataWidgetMapper 数据库绑定 QLineEdit控件", + "date_published": "2018-10-29T08:17:59.000Z", + "content_html": "

    qt 为操作数据库提供了一个 model+view 的模式,这样简单的出入库逻辑就不需要自己编写。

    \n

    QDataWidgetMapper 可以 将数据库的数据 映射到其他控件 。

    \n

    注意:表格里的数据修改 还没有提交到数据库,需要点击提交按钮才生效。

    \n\n

    https://github.com/PyQt5/PyQt/tree/master/Test/partner_625781186/16_sqlModel/01_mapper

    \n

    # 代码

    \n
    #-*- coding: utf-8 -*-\n\nfrom PyQt5 import  QtWidgets, QtGui, QtCore\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nfrom PyQt5.QtSql import *\n\nimport sys\n\nsys.path.append('./ui')\nfrom Ui_MainWindow import Ui_MainWindow\n\n\nclass MainWindow(QMainWindow, Ui_MainWindow):\n    def __init__(self, parent=None, *args):\n\n        super(MainWindow, self).__init__(parent,  *args)\n        self.setupUi(self)\n        self.resize(800,600)\n        \n        #===============================   db   ======================================#\n        # self.db = QSqlDatabase.addDatabase('QMYSQL')\n        # self.db.setHostName(\"127.0.0.1\")  # set address\n        # self.db.setUserName(\"root\")  # set user name\n        # self.db.setPassword('123456')  # set user pwd   \n        # self.db.setDatabaseName(\"database\")\n        \n        self.db = QSqlDatabase.addDatabase('QSQLITE')\n        self.db.setDatabaseName('./db/database.db')\n\n        #================================= codemodel =====================================#\n        # 实例化model\n        self.codeModel = QSqlRelationalTableModel()\n        # model设置表\n        self.initializeModel(self.codeModel, 'Mongo')\n        # 设置编辑策略\n        # self.codeModel.setEditStrategy(QSqlTableModel.OnFieldChange)\n        # !!! 这里要注意 , 只能用这个策略 , 才可以实现自动提交\n        self.codeModel.setEditStrategy(QSqlTableModel.OnManualSubmit)\n\n        self.codeView = self.createView(\"code_View\", self.codeModel)\n        self.verticalLayout.addWidget(self.codeView)  \n\n        #================================ initData ==================================#\n        # 数据映射\n        self.mapper = QDataWidgetMapper()\n        # 提交策略\n        self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit)\n        # 映射的模型源\n        self.mapper.setModel(self.codeModel)\n        self.mapper.addMapping(self.l1,0)\n        self.mapper.addMapping(self.l2,1)\n        self.mapper.addMapping(self.l3,2)\n        self.mapper.addMapping(self.l4,3)\n        self.mapper.addMapping(self.l5,4)\n\n        self.mapper.toFirst()\n        \n        #================================ pushButton ==================================#\n        self.sub_btn.clicked.connect(self.mapper.submit)\n        self.sub_btn.clicked.connect(self.codeModel.submitAll)\n        self.pre_btn.clicked.connect(self.mapper.toPrevious)\n        self.next_btn.clicked.connect(self.mapper.toNext)\n        \n    def initializeModel(self, model, tablename):\n        '''重关联。'''\n        model.setTable(tablename)\n#        model.setEditStrategy(QSqlTableModel.OnRowChange)\n        model.select()\n        \n    def createView(self, title, model):\n        '''创建TableView视图'''\n        view =  QTableView()\n        view.setModel(model)\n        view.setWindowTitle(title)\n        #列宽设置\n        view.horizontalHeader().setSectionResizeMode(3)\n        #行高设置\n        view.verticalHeader().setSectionResizeMode(1)\n        #充满列宽\n        view.horizontalHeader().setStretchLastSection(True) \n#        view.verticalHeader().setVisible(False)#隐藏行标题\n        #标题左对齐\n        view.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)\n        #标题左对齐\n        view.verticalHeader().setDefaultAlignment(Qt.AlignLeft)\n        \n        return view  \n    \n\nif __name__ == \"__main__\":\n    import sys\n    \n    app = QApplication(sys.argv)\n    app.setStyle(QStyleFactory.create(\"Fusion\"))\n    ui = MainWindow()\n    ui.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"datawidgetmapper\"

    \n", + "tags": [ + "PyQt", + "Model" + ] + }, + { + "id": "https://pyqt5.com/qtninepatch.html", + "url": "https://pyqt5.com/qtninepatch.html", + "title": "PyQt5显示.9格式的PNG图片", + "date_published": "2018-10-26T02:00:08.000Z", + "content_html": "

    做过安卓开发的和使用过 QQ 的都知道 .9.png 这种图片格式,效果就如 QQ 的聊天气泡一样可以拉伸,这种格式的图片允许开发人员定义可扩展区域,当需要延伸图片以填充比图片本身更大区域时,可扩展区的内容被延展;允许开发人员定义内容显示区,用于显示文字或其他内容。目前在 Github 上有两个 C++ 版本的,在这里我把它们都用 Python 实现了一遍。另外一个我也为 PyQt 提供了编译好的 pyd 文件。

    \n\n

    # C++ 版本

    \n

    在 Github 开源库中搜索到两个 C++ 版本的

    \n
      \n
    1. 一个是 NinePatchQt
    2. \n
    3. 一个是 QtNinePatch
    4. \n
    \n

    # PyQt5 版本

    \n

    这里也分为两个版本,都是基于上面的 C++ 源码翻译改写过来的,具体的例子见项目里面的测试代码吧。

    \n
      \n
    1. QtNinePatch 是参考第一个源码编写,用法是在 paintEvent 中调用
    2. \n
    3. QtNinePatch2 是参考第二个源码编写,用法是 pixmap = QtNinePatch.createPixmapFromNinePatchImage(self.image, self.width(), self.height()) 直接得到一个处理好的 QPixmap 对象来使用
    4. \n
    \n

    # 说明

    \n
      \n
    1. 建议优先使用 pyd 版本的(后续提供 Python3.4 3.5 3.6 3.7 编译好的 32 为库文件),也可以自行编译,编译步骤见下文。
    2. \n
    3. 其次可以使用纯 python 版本 2 的(个人觉得方便调用)
    4. \n
    5. 最后再考虑纯 python 版本 1 的吧
    6. \n
    7. 以上为个人意见,两个 C++ 版本的写法不一样,但是核心算法应该是类似的。
    8. \n
    \n

    # 自行编译

    \n
      \n
    1. 首先要安装好 Qt、PyQt5、编译安装对应的 sip、对应的 VC++ 编译工具
    2. \n
    3. 用 Qt Creator 打开 pro 文件进行编译
    4. \n
    5. 进入源码中的 sip 文件夹修改 configure.py 文件
    6. \n
    \n
    # 这里是你的VC版本和对应的Qt目录中的文件夹\nconfig.platform = "win32-msvc2010"\nqt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010'
    \n
      \n
    1. 最后执行 python configure.py 来编译
    2. \n
    \n

    # 下载

    \n

    https://github.com/PyQt5/PyQt/tree/master/QLabel

    \n

    # 效果图

    \n

    \"NinePatchImage\"

    \n", + "tags": [ + "PyQt", + "图片", + "气泡", + ".9png" + ] + } + ] +} \ No newline at end of file diff --git a/tag/pyqt/rss.xml b/tag/pyqt/rss.xml new file mode 100644 index 00000000..8e754f93 --- /dev/null +++ b/tag/pyqt/rss.xml @@ -0,0 +1,1693 @@ + + + + PyQt • Posts by "pyqt" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 07 Oct 2023 06:08:06 +0000 + Sat, 07 Oct 2023 06:08:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/macm1pyqt.html + 如何在Mac M1上快速安装PyQt5 + https://pyqt5.com/macm1pyqt.html + PyQt + Mac + M1 + Sat, 07 Oct 2023 06:08:06 +0000 + + + + https://pyqt5.com/studynotes.html + PyQt学习心得 + https://pyqt5.com/studynotes.html + PyQt + Mon, 26 Aug 2019 01:00:00 +0000 + + + + https://pyqt5.com/qtwebjs.html + QtWebkit和QWebEngineView与Javascript交互 + https://pyqt5.com/qtwebjs.html + PyQt + QWebView + QWebEngineView + 浏览器 + Wed, 22 May 2019 03:30:36 +0000 + + + + https://pyqt5.com/flipwidgetanimation.html + PyQt5窗口翻转动画 + https://pyqt5.com/flipwidgetanimation.html + PyQt + 动画 + 翻转 + Wed, 15 May 2019 14:48:00 +0000 + + + + https://pyqt5.com/QPropertyAnimation.html + PyQt属性动画(QPropertyAnimation) + https://pyqt5.com/QPropertyAnimation.html + PyQt + 动画 + Wed, 08 May 2019 07:43:06 +0000 + + + + https://pyqt5.com/viewapi.html + 如何查阅Qt文档 + https://pyqt5.com/viewapi.html + PyQt + Sat, 04 May 2019 12:50:20 +0000 + + + + https://pyqt5.com/bindsignals.html + 三种方式绑定信号槽 + https://pyqt5.com/bindsignals.html + PyQt + 信号 + Sat, 04 May 2019 08:07:06 +0000 + + + + https://pyqt5.com/runnablesignal_625781186.html + QRunnable线程池发信号 + https://pyqt5.com/runnablesignal_625781186.html + PyQt + 信号 + 线程 + Tue, 30 Apr 2019 07:58:09 +0000 + + + + https://pyqt5.com/viewpyindesigner_625781186.html + 如何和设计师中查看ui转换的py代码 + https://pyqt5.com/viewpyindesigner_625781186.html + PyQt + Designer + 设计师 + Tue, 30 Apr 2019 05:11:09 +0000 + + + + https://pyqt5.com/showframe.html + PyQt5调整窗口显示边框 + https://pyqt5.com/showframe.html + PyQt + 边框 + Fri, 26 Apr 2019 14:19:26 +0000 + + + + https://pyqt5.com/issignalconnected.html + PyQt5判断信号是否连接 + https://pyqt5.com/issignalconnected.html + PyQt + 信号 + Fri, 26 Apr 2019 14:06:26 +0000 + + + + https://pyqt5.com/shadowradius.html + PyQt5无边框圆角阴影 + https://pyqt5.com/shadowradius.html + PyQt + 阴影 + 无边框 + 圆角 + Thu, 25 Apr 2019 16:06:26 +0000 + + + + https://pyqt5.com/pyqtclientmac.html + 在Mac上以正确的姿势使用PyQtClient看Demo + https://pyqt5.com/pyqtclientmac.html + PyQt + Tue, 02 Apr 2019 09:18:43 +0000 + + + + https://pyqt5.com/pyqtclient.html + PyQtClient例子客户端 + https://pyqt5.com/pyqtclient.html + PyQt + Sat, 02 Feb 2019 07:15:06 +0000 + + + + https://pyqt5.com/webviewnew.html + PyQt5编译QWebView与QWebEngineView共存 + https://pyqt5.com/webviewnew.html + PyQt + QWebView + 浏览器 + Sat, 12 Jan 2019 11:28:06 +0000 + + + + https://pyqt5.com/pageswitching.html + PyQt5之图片轮播 + https://pyqt5.com/pageswitching.html + PyQt + 动画 + 轮播 + Sat, 24 Nov 2018 13:45:06 +0000 + + + + https://pyqt5.com/jumpslider.html + PyQt5之QSlider滑动条点击定位 + https://pyqt5.com/jumpslider.html + PyQt + 滑动条 + Mon, 05 Nov 2018 15:12:26 +0000 + + + + https://pyqt5.com/rlatticeeffect.html + PyQt5仿网页鼠标移动点阵特效 + https://pyqt5.com/rlatticeeffect.html + PyQt + 动画 + 特效 + Mon, 29 Oct 2018 08:49:10 +0000 + + + + https://pyqt5.com/datawidgetmapper_625781186.html + QDataWidgetMapper 数据库绑定 QLineEdit控件 + https://pyqt5.com/datawidgetmapper_625781186.html + PyQt + Model + Mon, 29 Oct 2018 08:17:59 +0000 + + + + https://pyqt5.com/qtninepatch.html + PyQt5显示.9格式的PNG图片 + https://pyqt5.com/qtninepatch.html + PyQt + 图片 + 气泡 + .9png + Fri, 26 Oct 2018 02:00:08 +0000 + + + + diff --git a/tag/pyqt5 pyside2/feed.json b/tag/pyqt5 pyside2/feed.json new file mode 100644 index 00000000..79dc6395 --- /dev/null +++ b/tag/pyqt5 pyside2/feed.json @@ -0,0 +1,18 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"pyqt5 pyside2\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html", + "url": "https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html", + "title": "修改pyuic代替pyside2-uic.", + "date_published": "2019-12-26T11:49:41.000Z", + "content_html": "

    修改 pyuic 代替 pyside2-uic

    \n\n

    修改 pyuic 代替 pyside2-uic.

    \n

    最近看到挺多人用 pyside2 的 uic 编译 ui 文件有问题 .
    \n 写个解决办法.

    \n

    首先,
    \n pip install qtpy ,
    \n 这个是兼容 pyqt5 和 pyside2 的,无缝转换 .

    \n

    然后,
    \n 修改 pyqt5 的 uic ,

    \n

    \"image.png\"

    \n

    最后用 pyuic5 , 生成 Ui_XXX.py 文件 .

    \n", + "tags": [ + "PyQt5 PySide2" + ] + } + ] +} \ No newline at end of file diff --git a/tag/pyqt5 pyside2/rss.xml b/tag/pyqt5 pyside2/rss.xml new file mode 100644 index 00000000..f0d62a41 --- /dev/null +++ b/tag/pyqt5 pyside2/rss.xml @@ -0,0 +1,77 @@ + + + + PyQt • Posts by "pyqt5 pyside2" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 26 Dec 2019 11:49:41 +0000 + Thu, 26 Dec 2019 11:49:41 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + 修改pyuic代替pyside2-uic. + https://pyqt5.com/use_pyuic_insteadof_pyside2uic.html + PyQt5 PySide2 + Thu, 26 Dec 2019 11:49:41 +0000 + + + + diff --git a/tag/pytest-qt/feed.json b/tag/pytest-qt/feed.json new file mode 100644 index 00000000..c8c5f218 --- /dev/null +++ b/tag/pytest-qt/feed.json @@ -0,0 +1,18 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"pytest-qt\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pytest_qt_modal_625781186.html", + "url": "https://pyqt5.com/pytest_qt_modal_625781186.html", + "title": "pytest-qt 测试模态窗体.", + "date_published": "2024-04-30T01:46:52.392Z", + "content_html": "

    步骤分别是 :

    \n
      \n
    1. \n

      点击 开始扫描 弹出 选择路径窗口;

      \n
    2. \n
    3. \n

      勾选路基;

      \n
    4. \n
    \n

    3. 点击确定;

    \n\n

    大概想测一下这个界面 :

    \n

    \"image.png\"

    \n

    步骤分别是 :

    \n
      \n
    1. \n

      点击 开始扫描 弹出 选择路径窗口;

      \n
    2. \n
    3. \n

      勾选路基;

      \n
    4. \n
    \n

    3. 点击确定;

    \n

    需要测试的函数 :

    \n

    \"image.png\"

    \n

    测试函数 :

    \n

    \"image.png\"

    \n

    可以发现断言失败 .

    \n

    \"image.png\"

    \n

    官方文档:测试模态窗体.

    \n

    https://pytest-qt.readthedocs.io/en/latest/note_dialogs.html

    \n

    用的是官方的 monkeypatch 方式 .

    \n

    大致意思就是替换 FileSelectPathDialog 类的 exec 函数.

    \n", + "tags": [ + "pytest-qt" + ] + } + ] +} \ No newline at end of file diff --git a/tag/pytest-qt/rss.xml b/tag/pytest-qt/rss.xml new file mode 100644 index 00000000..41b44cd7 --- /dev/null +++ b/tag/pytest-qt/rss.xml @@ -0,0 +1,98 @@ + + + + PyQt • Posts by "pytest-qt" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 30 Apr 2024 01:46:52 +0000 + Tue, 30 Apr 2024 01:46:52 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pytest_qt_modal_625781186.html + pytest-qt 测试模态窗体. + https://pyqt5.com/pytest_qt_modal_625781186.html + pytest-qt + Tue, 30 Apr 2024 01:46:52 +0000 + + + + diff --git "a/tag/python \347\212\266\346\200\201\346\234\272/feed.json" "b/tag/python \347\212\266\346\200\201\346\234\272/feed.json" new file mode 100644 index 00000000..291ee26e --- /dev/null +++ "b/tag/python \347\212\266\346\200\201\346\234\272/feed.json" @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"python 状态机\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/python_statemachine_625781186.html", + "url": "https://pyqt5.com/python_statemachine_625781186.html", + "title": "python 状态机模块  ", + "date_published": "2019-07-17T09:03:33.000Z", + "content_html": "

    用状态来取代 if…else 判断。

    \n\n

    GUI 涉及到挺多的状态改变,以前一直用 if…else 来判断,最近读了设计模式,发现有个状态模式,随后发现了状态机这个东西 .

    \n

    python 的状态机模块挺多的,不过好像很多都不更新了.
    \n 推荐 2 个状态机模块,但是也没有太深入的使用经验,就跑跑例子,以后有更详细的 pyqt 例子再补上 .

    \n

    1: pip install python-statemachine

    \n

    官方例子 : https://github.com/fgmacedo/python-statemachine

    \n

    2. pip install state_machine

    \n

    官方例子 : https://github.com/jtushman/state_machine

    \n

    1 的 最近一次更新在 6 个月以前,使用 类继承mixin 方式,不过有些地方不如 2 个人性化;

    \n

    2 的设计更人性化一些,包括状态改变 beforeafter , 不过由于是装饰器实现的动态增加属性,有些地方编辑器智能提示可能就靠不上了.

    \n

    两者实现实现方式不一样,有兴趣可以读读源码 .

    \n
      \n
    1. qt 内置状态机框架
    2. \n
    \n

    https://blog.csdn.net/amnes1a/article/details/62418196

    \n

    https://blog.csdn.net/dongfenghuojian/article/details/78187131

    \n

    http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程)

    \n", + "tags": [ + "Python", + "python 状态机" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/python \347\212\266\346\200\201\346\234\272/rss.xml" "b/tag/python \347\212\266\346\200\201\346\234\272/rss.xml" new file mode 100644 index 00000000..569d11ec --- /dev/null +++ "b/tag/python \347\212\266\346\200\201\346\234\272/rss.xml" @@ -0,0 +1,84 @@ + + + + PyQt • Posts by "python 状态机" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 17 Jul 2019 09:03:33 +0000 + Wed, 17 Jul 2019 09:03:33 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/python_statemachine_625781186.html + python 状态机模块   + https://pyqt5.com/python_statemachine_625781186.html + Python + python 状态机 + Wed, 17 Jul 2019 09:03:33 +0000 + + + + diff --git a/tag/python/feed.json b/tag/python/feed.json new file mode 100644 index 00000000..3be06539 --- /dev/null +++ b/tag/python/feed.json @@ -0,0 +1,121 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"python\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/equal_str_width_625781186.html", + "url": "https://pyqt5.com/equal_str_width_625781186.html", + "title": "python 判断屏幕等宽字符串的长度  ", + "date_published": "2019-12-26T11:49:41.000Z", + "content_html": "

    判断屏幕等宽字符串的长度?

    \n\n

    判断屏幕等宽字符串的长度?

    \n

    \"image.png\"

    \n

    【新手】重庆 - 搬砖 - NoWait 22:41:50 @北京 - BUG 开发 - 黑择明 求指点
    \n【专家】北京 - BUG 开发 - 黑择明 22:43:04 fontMetrics
    \n【专家】 https://pyqt.site (892768447) 22:43:54 QFontMetrics
    \n【专家】 https://pyqt.site (892768447) 22:44:09 通过 QLabel.font ().fontMetrics () 得到

    \n

    【新手】重庆 - 搬砖 - NoWait 22:52:00
    \nhttps://stackoverflow.com/questions/35771863/how-to-calculate-length-of-string-in-pixels-for-specific-font-and-size
    \n\"image.png\"

    \n

    【新手】重庆 - 搬砖 - NoWait 22:53:15 感觉和 fontMetrics 应该是差不多的

    \n

    \"image.png\"

    \n
    \n

    【专家】北京 - BUG 开发 - 黑择明 (996742224) 11:29:04
    \nfm = QFontMetrics(QFont())
    \nfm.width(“qweqwe”)

    \n", + "tags": [ + "Python" + ] + }, + { + "id": "https://pyqt5.com/python_statemachine_625781186.html", + "url": "https://pyqt5.com/python_statemachine_625781186.html", + "title": "python 状态机模块  ", + "date_published": "2019-07-17T09:03:33.000Z", + "content_html": "

    用状态来取代 if…else 判断。

    \n\n

    GUI 涉及到挺多的状态改变,以前一直用 if…else 来判断,最近读了设计模式,发现有个状态模式,随后发现了状态机这个东西 .

    \n

    python 的状态机模块挺多的,不过好像很多都不更新了.
    \n 推荐 2 个状态机模块,但是也没有太深入的使用经验,就跑跑例子,以后有更详细的 pyqt 例子再补上 .

    \n

    1: pip install python-statemachine

    \n

    官方例子 : https://github.com/fgmacedo/python-statemachine

    \n

    2. pip install state_machine

    \n

    官方例子 : https://github.com/jtushman/state_machine

    \n

    1 的 最近一次更新在 6 个月以前,使用 类继承mixin 方式,不过有些地方不如 2 个人性化;

    \n

    2 的设计更人性化一些,包括状态改变 beforeafter , 不过由于是装饰器实现的动态增加属性,有些地方编辑器智能提示可能就靠不上了.

    \n

    两者实现实现方式不一样,有兴趣可以读读源码 .

    \n
      \n
    1. qt 内置状态机框架
    2. \n
    \n

    https://blog.csdn.net/amnes1a/article/details/62418196

    \n

    https://blog.csdn.net/dongfenghuojian/article/details/78187131

    \n

    http://blog.sina.com.cn/s/articlelist_3284623693_0_1.html (系列教程)

    \n", + "tags": [ + "Python", + "python 状态机" + ] + }, + { + "id": "https://pyqt5.com/pyqt5_hook_key_625781186.html", + "url": "https://pyqt5.com/pyqt5_hook_key_625781186.html", + "title": "在pyqt中使用python全局钩子模块", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

    在某些时候需要为自己的软件增加全局键盘监听,比如软件最小化隐藏后可以通过热键唤醒,又或者比如像 QQ 一样可以全局热键截图。这里介绍几个方法实现在 PyQt 中使用 Python 全局钩子模块实现全局热键功能。

    \n\n
      \n
    1. pyHook3
    2. \n
    \n

    安装命令 : pip install pyhook3

    \n

    https://blog.csdn.net/q871063970/article/details/86648386

    \n

    似乎将 pyhook 支持 py3 版本的了?没有太多研究.

    \n

    缺点:只支持 win 平台.

    \n

    2. keyboard & mouse

    \n

    安装命令: pip install keyboard mouse

    \n
    \nfrom PyQt5 import  QtGui, QtWidgets, QtCore\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nimport keyboard\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.testBtn = QPushButton(self)\n        layout.addWidget(self.testBtn)\n\n        keyboard.add_hotkey('ctrl+shift+x', lambda:print('triggered', 'hotkey'))\n        keyboard.add_hotkey('ctrl+shift+c', self.abc,args=('aa',"bb","cc"))\n\n    def abc(self,a,b,c):\n        print(a,b,c)\n        \nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    更详细例子 : pyqt 中使用 keyboard 全局热键

    \n

    优点:跨平台;

    \n

    缺点:模块名字取得太差,不容易被发现.

    \n", + "tags": [ + "Python", + "pyqt hook key" + ] + }, + { + "id": "https://pyqt5.com/read_open_source.html", + "url": "https://pyqt5.com/read_open_source.html", + "title": "像读文章一样读源码", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

    使用 snoop, 像读文章一样读源码。

    \n\n

    不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
    \n\"image.png\"

    \n

    没有目的地概览 , 不知不觉就追究到细节里面去了。

    \n

    \"image.png\"

    \n

    所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。

    \n

    开始 :

    \n
      \n
    1. python DEBUG 模块介绍 :
      \n 前段时间看过挺多文章提到 pysoonper 这个调试模块,有兴趣的可以百度一下.
      \n 个人尝试了一下,篇幅过大的 DEBUG 不适合用 pysoonper , 因为没有缩进!
      \n 这几天偶然遇到一个二次封装的模块 snoop, 完美地解决了这个问题.
    2. \n
    3. 操作步骤 :
    4. \n
    \n\n

    发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。

    \n\n

    callreturn 给加进去.

    \n\n

    \"eric6启动阶段\"

    \n

    \"image.png\"

    \n

    #000 是为了方便搜索 。
    \n需要自己手动折叠 。
    \n可以发现 每个 splash.showMessage() 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。

    \n
    \n

    # ps: vscode 阅读 log 文件还是有一些不方便的地方,除了在 2. 中提到的,还有包括关闭文件再打开,折叠状态不会保留,有其他更好的方式 请留言告诉我,谢谢.

    \n", + "tags": [ + "Python", + "debug", + "snoop" + ] + }, + { + "id": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "url": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "title": "python 获取子进程print信息  ", + "date_published": "2019-05-24T06:39:44.000Z", + "content_html": "

    在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。

    \n\n

    问题所在:

    \n

    \"image.png\"

    \n

    如果模块都由自己开发, 正常操作

    \n

    \"image.png\"

    \n

    但是因为不能改,所以只能拦截:
    \n代码:

    \n
    pythonPath = self.pythonPath_cb.currentText()\n\nif suffix == \"py\":\n    # 首次\n    self.pyCommand = [pythonPath, path]\n    self.modifiedReloadPython(path)\ndef modifiedReloadPython(self, path_):\n    os.chdir(os.path.dirname(path_))\n    # 子进程调用\n    self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n    # self.stdoutWorker.p = self.p\n    self.stdoutWorker = Worker(self.p)\n    self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append(\"PYDEBUG:\\n\" + x))\n    self.stdoutWorker.start()\nclass Worker(QThread):\n    stdout_signal = pyqtSignal(str)\n\n    def __init__(self, p, parent=None):\n        super().__init__(parent)\n        self.p = p\n\n    def run(self):\n        while True:\n            QApplication.processEvents()\n            if self.p is not None:\n                line = self.p.stdout.readline()\n                # line = line.strip()\n                if line != b'':\n                    try:\n                        info = line.decode()\n                        self.stdout_signal.emit(info)\n                    except:\n                        self.stdout_signal.emit(repr(line))\n    
    \n", + "tags": [ + "Python", + "subprocess.Popen", + "拦截print" + ] + }, + { + "id": "https://pyqt5.com/virtualenvpy_625781186.html", + "url": "https://pyqt5.com/virtualenvpy_625781186.html", + "title": "python 拷贝虚拟环境(一)  ", + "date_published": "2019-05-02T07:21:01.000Z", + "content_html": "

    通常来说,刚开始使用 python 的时候都是把包装到全局路径,随着各个项目安装的包越来越多,之后每开始一个项目,pycharm 创建索引的时间都越来越漫长,所以不可避免得开始使用虚拟环境。
    \n经过一番了解 ,虚拟环境的优点有这些:

    \n\n\n

    python 虚拟环境库除了自带的 venv , 还有三方库 virtualenv , 此外 在 virtualenv 基础上又开发了 virtualenvwrapper(virtualenvwrapper_win) 来管理

    \n

    本文基于 virtualenvwrapper 创建的虚拟环境来讲解.

    \n
    以下是收集的一些virtualenvwrapper配置教程:\n# linux平台\nhttps://www.cnblogs.com/netfoxman/p/5994697.html\n# window平台\nhttps://blog.csdn.net/shaququ/article/details/54292043   \nhttps://blog.csdn.net/iaau0908/article/details/54021518\n
    \n

    虚拟环境创建多了我们就会发现,
    \n 有时候使用相同版本的环境,一些常用的库是需要重新安装的,
    \n 那么能不能创建一个基础环境,默认拥有这些库,然后在这个基础环境上继续安装三方库呢?

    \n

    本文经过试验发现是可行的:

    \n
      \n
    1. \n

      创建基础虚拟环境 mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径] . 例如 mkvirtualenv py34 -p c:\\Python34\\python.exe

      \n
    2. \n
    3. \n

      切换到虚拟环境 workon py34 , 然后安装一下三方库,然后复制 py34 这个文件夹备份一下;

      \n
    4. \n
    5. \n

      接着复制这个 py34 文件夹,把复制后的文件夹改名为我们需要需要的文件夹例如 new34

      \n
    6. \n
    7. \n

      进入 new34文件夹 ,用任意编辑器全路径搜索 py34 (替换虚拟环境的路径)

      \n
    8. \n
    9. \n

      删除 new34/Scripts 下的 pip.exe, pip3.exe, pip3.x.exe, easy_install.exe (因为安装路径硬编码到这里面了,改不了,需要重新安装)

      \n
    10. \n
    11. \n

      https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二,源码安装 setuptools 后再用 easy_install pip 安装 pip 后,完成;
      \n 如果有问题,就继续按照方式一的源码安装 pip;

      \n
    12. \n
    13. \n

      new34 环境下 用 pip show 三方库 来看一些库的位置,确保正确.

      \n
    14. \n
    \n", + "tags": [ + "Python", + "virtualenvwrapper", + "virtualenv" + ] + }, + { + "id": "https://pyqt5.com/calljava.html", + "url": "https://pyqt5.com/calljava.html", + "title": "Python调用Java对Excel截图", + "date_published": "2019-03-12T13:15:06.000Z", + "content_html": "

    有的时候会遇到一些奇葩的需求,就是用 Excel 做报表,但是需要对里面的数据进行填充并生成报表图片,发送出去。这里记录用 python 调用 jar 包对 excel 文件进行公式计算和截图,数据填充可以用 xlrd 或者 openpyxl

    \n\n

    利用 jpype 模块初始化 java 虚拟机加载 jar 包然后执行其中的功能。

    \n

    # 代码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年3月12日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: CallJava\n@description: \n\"\"\"\nimport os\n\nimport jpype\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019'\n\n\ndef convertToImage():\n    Workbook = jpype.JClass('com.aspose.cells.Workbook')\n    ImageFormat = jpype.JClass('com.aspose.cells.ImageFormat')\n    ImageOrPrintOptions = jpype.JClass(\n        'com.aspose.cells.ImageOrPrintOptions')\n    SheetRender = jpype.JClass('com.aspose.cells.SheetRender')\n\n    book = Workbook(os.path.abspath('data/test.xlsx').replace('\\\\', '/'))\n    # 保存为html\n    book.save('data/index.html', 12)\n    # 保存为pdf\n    book.save('data/test.pdf')\n\n    # 截图\n    imgOptions = ImageOrPrintOptions()\n    # imgOptions.setQuality(100)\n    imgOptions.setOnePagePerSheet(True)\n\n    # 输出图片格式\n#     imgOptions.setImageFormat(ImageFormat.getJpeg())\n    imgOptions.setImageFormat(ImageFormat.getPng())\n\n    # 计算\n    CalculationOptions = jpype.JClass(\n        'com.aspose.cells.CalculationOptions')\n    opt = CalculationOptions()\n    # 对Sheet1中的公式进行计算\n    sheet = book.getWorksheets().get('Sheet1')\n    sheet.calculateFormula(opt, True)\n\n    # 设置区域\n    pageSetup = sheet.getPageSetup()\n    # 去掉边距\n    pageSetup.setBottomMargin(0.)\n    pageSetup.setLeftMargin(0.)\n    pageSetup.setRightMargin(0.)\n    pageSetup.setTopMargin(0.)\n    # 设置要截图的区域(对角线)\n    pageSetup.setPrintArea('A0:C2')\n    # Create a SheetRender object for the target sheet\n    sr = SheetRender(sheet, imgOptions)\n    for page in range(sr.getPageCount()):\n        # Generate an image for the worksheet\n        sr.toImage(\n            page, os.path.join('data', '%d.png' % (page + 1)))\n\n\ndef test():\n    # emm这里不知道什么用绝对路径就报错\n    libs = '{};{}'.format(\n        'libs/bcprov-jdk16-146.jar',\n        'libs/aspose-cells-19.2.jar'\n    )\n    command = (jpype.getDefaultJVMPath(),\n                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',\n                   '-Djava.class.path={0}'.format(libs))\n    print(command)\n    jpype.startJVM(jpype.getDefaultJVMPath(),\n                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',\n                   '-Djava.class.path={0}'.format(libs)\n                   )\n    # 解决多线程问题\n    jpype.attachThreadToJVM()\n    # 对excel截图\n    convertToImage()\n    # 关闭虚拟机\n    jpype.shutdownJVM()\n    print('截图完成')\n\n\nif __name__ == '__main__':\n    test()
    \n

    # 附件

    \n

    调用 java 生成报表.7z

    \n

    解压后进入 whls 文件夹安装对应版本的 jpype 包

    \n

    # 效果图

    \n

    \"calljava\"

    \n", + "tags": [ + "Python", + "截图" + ] + }, + { + "id": "https://pyqt5.com/ffmpeghls.html", + "url": "https://pyqt5.com/ffmpeghls.html", + "title": "FFmpeg合成加密HLS记录", + "date_published": "2019-01-12T11:28:06.000Z", + "content_html": "

    记录在某个需求中要求截图并合成加密视频文件,这里采用 FFmpeg 的管道流来实现生成 HLS 加密文件。

    \n\n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年3月4日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\n\nfrom pathlib import Path\nfrom subprocess import Popen, PIPE\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\n# p = Popen([r'D:\\soft\\ffmpeg\\bin\\ffmpeg.exe', '-y',\n#            '-threads', '2',\n#            '-f', 'image2pipe',\n#            '-vcodec', 'mjpeg', '-r', '24', '-i', '-',\n#            '-vcodec', 'h264', '-r', '24',\n#            #            '-encryption_scheme', 'cenc-aes-ctr',\n#            #            '-encryption_key', '617D8A125A284DF48E3C6B1866348A3F',\n#            #            '-encryption_kid', 'B326F895B6A24CC5A4DC70995728059C',\n#            r'F:\\Workspace\\Test\\videos\\video.mp4'], stdin=PIPE)\n\np = Popen([r'D:\\soft\\ffmpeg\\bin\\ffmpeg.exe',\n           '-re',   # 按照实际帧率读取输入文件\n           '-y',        # 覆盖已存在文件\n           '-threads', '2',  # 线程数量\n           '-f', 'image2pipe',  # PIPE图片流\n           '-vcodec', 'mjpeg',  # 图片编码\n           '-r', '24',  # 帧率\n           '-i', '-',  # 指定输入流为PIPE\n           '-vcodec', 'h264',  # 输出编码\n           '-r', '24',  # 帧率\n           '-map', '0',\n#            '-crf','20',     # 降低质量\n           '-b', '720k',        # 码率\n           '-f', 'hls',\n           '-codec:v', 'libx264',\n           '-vbsf', 'h264_mp4toannexb',\n           # 指定加密密匙文件\n           '-hls_key_info_file', r'F:\\Workspace\\Test\\videokey.info',\n           '-hls_time', '20',\n           '-hls_list_size', '0',\n           '-hls_wrap', '0',\n#            '-hls_flags', 'single_file',  # 生成单个文件(有bug)\n           r'F:\\Workspace\\Test\\videos\\playlist.m3u8'], stdin=PIPE)\nprint(p)\n\nt = 1 / 24\nfor i, path in enumerate(Path('frames').rglob('*.jpg')):\n    #     print(i, path)\n    p.stdin.write(open(str(path), 'rb').read())\n\np.stdin.close()\np.wait()\nprint('ok')
    ", + "tags": [ + "Python", + "FFmpeg", + "HLS" + ] + }, + { + "id": "https://pyqt5.com/daemonthread.html", + "url": "https://pyqt5.com/daemonthread.html", + "title": "多线程之守护线程和阻塞线程", + "date_published": "2018-10-24T07:51:15.000Z", + "content_html": "

    如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的 daemon 属性。

    \n\n

    即在线程开始(thread.start ())之前,调用 setDeamon()函数,设定线程的 daemon 标志。

    \n

    (thread.setDaemon (True))就表示这个线程 “不重要”。

    \n

    如果你想等待子线程完成再退出,那就什么都不用做,或者显示地调用 thread.setDaemon (False),设置 daemon 的值为 false。新的子线程会继承父线程的 daemon 标志。

    \n

    整个 Python 会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

    \n

    setDaemon () 函数要放在 start 之前设置才行。

    \n
    import threading\nimport time\n\ndef func():\n    print(\"子线程开启:\", time.localtime())\n    time.sleep(2)\n    print(\"子线程结束:\", time.localtime())\n\n\nprint(\"主线程开启:\", time.localtime())\nt = threading.Thread(target=func, args=())\n# t.setDaemon(True)\nt.start()\nprint(\"主线程关闭:\", time.localtime())
    \n

    在 Python 的多线程编程中,在实例代码中经常有 thread1.join () 这样的代码。那么今天咱们用实际代码来解释一下 join 函数的作用。

    \n

    join 的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的 join 函数。

    \n

    先看看这个:

    \n
      \n
    1. 阻塞主进程,专注于执行多线程中的程序。
    2. \n
    3. 多线程多 join 的情况下,依次执行各线程的 join 方法,前头一个结束了才能执行后面一个。
    4. \n
    5. 无参数,则等待到该线程结束,才开始执行下一个线程的 join。
    6. \n
    7. 参数 timeout 为线程的阻塞时间,如 timeout=2 就是罩着这个线程 2s 以后,就不管他了,继续执行下面的代码。
    8. \n
    9. 下面的例子是一次阻塞子线程,每个子线程都会等上个子线程 join 结束才会执行,如果注释掉 t.join 则会同时执行 5 个子线程,多线程在做网络访问的时候可以减少等待时间,那么在一个工作流程中可以将访问网络接口的情况做成多线程。
    10. \n
    \n
    import threading, time\n\ndef func():\n    print(\"hello world!\")\n    time.sleep(1)\n\nprint(\"hello main start\")\nfor i in range(5):\n    t = threading.Thread(target=func, args=())\n    print(t.getName())\n    t.start()\n    t.join()
    ", + "tags": [ + "Python", + "线程" + ] + }, + { + "id": "https://pyqt5.com/cgitb.html", + "url": "https://pyqt5.com/cgitb.html", + "title": "异常捕获之cgitb模块", + "date_published": "2018-09-17T15:17:06.000Z", + "content_html": "

    cgitb 模块为 Python 脚本提供了一个特殊的异常管理器。名字有点误导人,它最初设计是为了以 HTML 格式展示 cgi 脚本的大量异常信息。后来,他扩展为也可以展示纯文本信息。该模块激活后,如果发生了未捕获的异常,将会展示格式化的输出报告。该报告包括源代码每一层的回溯,以及当前执行程序的参数和局部变量。以及,你可以选择将这些信息存到一个文件里,而不是发送到浏览器。

    \n\n

    # 用途

    \n

    当编辑器中无法显示错误信息时,尤其是 PyQt ,可以尝试在 cmd 中运行代码,或者使用此模块来得到错误信息。

    \n

    # 介绍

    \n

    # cgitb.enable

    \n
    cgitb.enable(display=1, logdir=None, context=5, format=\"html\")
    \n

    参数说明

    \n
      \n
    1. display 1,发送至浏览器;0, 不发送
    2. \n
    3. logdir 如果有的话,写到该目录下
    4. \n
    5. context 显示错误代码周围的代码行数
    6. \n
    7. format 是否显示为 HTML,除了’html’之外的所有值,都会显示为纯文本
    8. \n
    \n

    # cgitb.handle

    \n
    cgitb.handle(info=None)
    \n

    参数说明

    \n
      \n
    1. 如果你想用 cgitb 处理异常,你可以调用这个函数。
    2. \n
    3. info 应当是含有异常类型、异常值和 traceback 对象的三元组
    4. \n
    5. 如同 sys.exc_info () 返回的那样。如果不提供 info,则从 sys.exc_info 中获取。
    6. \n
    \n

    # 如何使用

    \n

    以下代码放在最开始执行

    \n
    import cgitb\nimport sys\nsys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
    \n", + "tags": [ + "Python", + "异常" + ] + } + ] +} \ No newline at end of file diff --git a/tag/python/rss.xml b/tag/python/rss.xml new file mode 100644 index 00000000..79fe7344 --- /dev/null +++ b/tag/python/rss.xml @@ -0,0 +1,593 @@ + + + + PyQt • Posts by "python" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 26 Dec 2019 11:49:41 +0000 + Thu, 26 Dec 2019 11:49:41 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/equal_str_width_625781186.html + python 判断屏幕等宽字符串的长度   + https://pyqt5.com/equal_str_width_625781186.html + Python + Thu, 26 Dec 2019 11:49:41 +0000 + + + + https://pyqt5.com/python_statemachine_625781186.html + python 状态机模块   + https://pyqt5.com/python_statemachine_625781186.html + Python + python 状态机 + Wed, 17 Jul 2019 09:03:33 +0000 + + + + https://pyqt5.com/pyqt5_hook_key_625781186.html + 在pyqt中使用python全局钩子模块 + https://pyqt5.com/pyqt5_hook_key_625781186.html + Python + pyqt hook key + Sat, 06 Jul 2019 17:37:22 +0000 + + + + https://pyqt5.com/read_open_source.html + 像读文章一样读源码 + https://pyqt5.com/read_open_source.html + Python + debug + snoop + Sat, 06 Jul 2019 17:37:22 +0000 + + + + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + python 获取子进程print信息   + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + Python + subprocess.Popen + 拦截print + Fri, 24 May 2019 06:39:44 +0000 + + + + https://pyqt5.com/virtualenvpy_625781186.html + python 拷贝虚拟环境(一)   + https://pyqt5.com/virtualenvpy_625781186.html + Python + virtualenvwrapper + virtualenv + Thu, 02 May 2019 07:21:01 +0000 + + + + https://pyqt5.com/calljava.html + Python调用Java对Excel截图 + https://pyqt5.com/calljava.html + Python + 截图 + Tue, 12 Mar 2019 13:15:06 +0000 + + + + https://pyqt5.com/ffmpeghls.html + FFmpeg合成加密HLS记录 + https://pyqt5.com/ffmpeghls.html + Python + FFmpeg + HLS + Sat, 12 Jan 2019 11:28:06 +0000 + + + + https://pyqt5.com/daemonthread.html + 多线程之守护线程和阻塞线程 + https://pyqt5.com/daemonthread.html + Python + 线程 + Wed, 24 Oct 2018 07:51:15 +0000 + + + + https://pyqt5.com/cgitb.html + 异常捕获之cgitb模块 + https://pyqt5.com/cgitb.html + Python + 异常 + Mon, 17 Sep 2018 15:17:06 +0000 + + + + diff --git a/tag/qwebengineview/feed.json b/tag/qwebengineview/feed.json new file mode 100644 index 00000000..b36ccdf6 --- /dev/null +++ b/tag/qwebengineview/feed.json @@ -0,0 +1,21 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"qwebengineview\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/qtwebjs.html", + "url": "https://pyqt5.com/qtwebjs.html", + "title": "QtWebkit和QWebEngineView与Javascript交互", + "date_published": "2019-05-22T03:30:36.000Z", + "content_html": "

    以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

    \n\n

    # 说明

    \n
      \n
    1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
    2. \n
    3. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
    4. \n
    5. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
    6. \n
    \n
    @pyqtSlot(str)\ndef callFromJs(self, text):\n    QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))
    \n
      \n
    1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
    2. \n
    \n
    // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\nBridge.windowTitleChanged.connect({fun: function(title) {\n    showLog(\"标题被修改为:\" + title);\n}}, \"fun\");\n\n// 绑定自定义的信号customSignal\nBridge.customSignal.connect({fun: function(text) {\n    showLog(\"收到自定义信号内容:\" + text);\n}}, \"fun\");
    \n
      \n
    1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
    2. \n
    \n
    new QWebChannel(qt.webChannelTransport,\n    function(channel) {\n        window.Bridge = channel.objects.Bridge;\n        \n        // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\n        Bridge.windowTitleChanged.connect(function(title) {\n            showLog(\"标题被修改为:\" + title);\n        });\n        \n        // 绑定自定义的信号customSignal\n        Bridge.customSignal.connect(function(text) {\n           showLog(\"收到自定义信号内容:\" + text);\n        });\n    }\n);
    \n

    # 代码

    \n

    QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

    \n

    QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

    \n
      \n
    1. 针对 QWebView 的核心实现
    2. \n
    \n
    class WebView(QWebView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebView, self).__init__(*args, **kwargs)\n        self.initSettings()\n        # 暴露接口对象\n        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)\n\n    def _exposeInterface(self):\n        \"\"\"向Js暴露调用本地方法接口\n        \"\"\"\n        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
    \n
      \n
    1. 针对 QWebEngineView 的核心实现
    2. \n
    \n
    class WebEngineView(QWebEngineView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebEngineView, self).__init__(*args, **kwargs)\n        self.channel = QWebChannel(self)\n        # 把自身对象传递进去\n        self.channel.registerObject('Bridge', self)\n        # 设置交互接口\n        self.page().setWebChannel(self.channel)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
    \n

    # 效果图

    \n

    \"JsSignals\"

    \n", + "tags": [ + "PyQt", + "QWebView", + "QWebEngineView", + "浏览器" + ] + } + ] +} \ No newline at end of file diff --git a/tag/qwebengineview/rss.xml b/tag/qwebengineview/rss.xml new file mode 100644 index 00000000..36b55dbb --- /dev/null +++ b/tag/qwebengineview/rss.xml @@ -0,0 +1,163 @@ + + + + PyQt • Posts by "qwebengineview" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 22 May 2019 03:30:36 +0000 + Wed, 22 May 2019 03:30:36 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/qtwebjs.html + QtWebkit和QWebEngineView与Javascript交互 + https://pyqt5.com/qtwebjs.html + PyQt + QWebView + QWebEngineView + 浏览器 + Wed, 22 May 2019 03:30:36 +0000 + + + + diff --git a/tag/qwebview/feed.json b/tag/qwebview/feed.json new file mode 100644 index 00000000..2aa58d89 --- /dev/null +++ b/tag/qwebview/feed.json @@ -0,0 +1,33 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"qwebview\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/qtwebjs.html", + "url": "https://pyqt5.com/qtwebjs.html", + "title": "QtWebkit和QWebEngineView与Javascript交互", + "date_published": "2019-05-22T03:30:36.000Z", + "content_html": "

    以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

    \n\n

    # 说明

    \n
      \n
    1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
    2. \n
    3. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
    4. \n
    5. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
    6. \n
    \n
    @pyqtSlot(str)\ndef callFromJs(self, text):\n    QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))
    \n
      \n
    1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
    2. \n
    \n
    // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\nBridge.windowTitleChanged.connect({fun: function(title) {\n    showLog(\"标题被修改为:\" + title);\n}}, \"fun\");\n\n// 绑定自定义的信号customSignal\nBridge.customSignal.connect({fun: function(text) {\n    showLog(\"收到自定义信号内容:\" + text);\n}}, \"fun\");
    \n
      \n
    1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
    2. \n
    \n
    new QWebChannel(qt.webChannelTransport,\n    function(channel) {\n        window.Bridge = channel.objects.Bridge;\n        \n        // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\n        Bridge.windowTitleChanged.connect(function(title) {\n            showLog(\"标题被修改为:\" + title);\n        });\n        \n        // 绑定自定义的信号customSignal\n        Bridge.customSignal.connect(function(text) {\n           showLog(\"收到自定义信号内容:\" + text);\n        });\n    }\n);
    \n

    # 代码

    \n

    QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

    \n

    QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

    \n
      \n
    1. 针对 QWebView 的核心实现
    2. \n
    \n
    class WebView(QWebView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebView, self).__init__(*args, **kwargs)\n        self.initSettings()\n        # 暴露接口对象\n        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)\n\n    def _exposeInterface(self):\n        \"\"\"向Js暴露调用本地方法接口\n        \"\"\"\n        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
    \n
      \n
    1. 针对 QWebEngineView 的核心实现
    2. \n
    \n
    class WebEngineView(QWebEngineView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebEngineView, self).__init__(*args, **kwargs)\n        self.channel = QWebChannel(self)\n        # 把自身对象传递进去\n        self.channel.registerObject('Bridge', self)\n        # 设置交互接口\n        self.page().setWebChannel(self.channel)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
    \n

    # 效果图

    \n

    \"JsSignals\"

    \n", + "tags": [ + "PyQt", + "QWebView", + "QWebEngineView", + "浏览器" + ] + }, + { + "id": "https://pyqt5.com/webviewnew.html", + "url": "https://pyqt5.com/webviewnew.html", + "title": "PyQt5编译QWebView与QWebEngineView共存", + "date_published": "2019-01-12T11:28:06.000Z", + "content_html": "

    在 PyQt5.5 过后移除了 QWebView 控件,改用 QWebEngineView ,但是这个刚开始用起来不是很方便,最近在整理一些例子的时候需要同时使用 QWebViewQWebEngineView ,故希望把 QWebView 重新加入到后面的 PyQt5 版本中,查看 PyQt5.10.1 的源码发现里面其实是有 QWebView 的,只是因为 Qt5.10.1 中没有编译好的 dll 等导致无法编译。

    \n\n

    # 准备工作

    \n
      \n
    1. 安装 VS2015
    2. \n
    3. 安装 Qt5.10.1
    4. \n
    5. 前往 https://github.com/annulen/webkit/releases 下载对应的文件,比如:qtwebkit-5.212.0_alpha2-qt59-msvc2015-x86.zip
    6. \n
    7. 下载 PyQt5.10.1 源码
    8. \n
    9. 下载对应版本的 sip 源码
    10. \n
    \n

    # 编译

    \n
      \n
    1. 设置环境变量 set PATH=D:\\soft\\Qt\\Qt5.10.1\\5.10.1\\msvc2015\\bin;%PATH%
    2. \n
    3. 首先进入 vs2015 命令行编译 sip 并安装, python configure.py && nmake && nmake install
    4. \n
    5. 进入 PyQt5.10.1 源码编译安装即可
    6. \n
    7. 如果要减少 PyQt5.10.1 的编译可以试试以下代码
    8. \n
    \n
    D:\\soft\\Python35\\python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc
    ", + "tags": [ + "PyQt", + "QWebView", + "浏览器" + ] + } + ] +} \ No newline at end of file diff --git a/tag/qwebview/rss.xml b/tag/qwebview/rss.xml new file mode 100644 index 00000000..f3c8fc7c --- /dev/null +++ b/tag/qwebview/rss.xml @@ -0,0 +1,190 @@ + + + + PyQt • Posts by "qwebview" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 22 May 2019 03:30:36 +0000 + Wed, 22 May 2019 03:30:36 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/qtwebjs.html + QtWebkit和QWebEngineView与Javascript交互 + https://pyqt5.com/qtwebjs.html + PyQt + QWebView + QWebEngineView + 浏览器 + Wed, 22 May 2019 03:30:36 +0000 + + + + https://pyqt5.com/webviewnew.html + PyQt5编译QWebView与QWebEngineView共存 + https://pyqt5.com/webviewnew.html + PyQt + QWebView + 浏览器 + Sat, 12 Jan 2019 11:28:06 +0000 + + + + diff --git a/tag/snoop/feed.json b/tag/snoop/feed.json new file mode 100644 index 00000000..906211ff --- /dev/null +++ b/tag/snoop/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"snoop\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/read_open_source.html", + "url": "https://pyqt5.com/read_open_source.html", + "title": "像读文章一样读源码", + "date_published": "2019-07-06T17:37:22.000Z", + "content_html": "

    使用 snoop, 像读文章一样读源码。

    \n\n

    不得不说 开源项目没有一个提纲 , 看起来太操蛋了。问了作者, 作者说 , 你运行下主函数, 然后慢慢跟 。。。
    \n\"image.png\"

    \n

    没有目的地概览 , 不知不觉就追究到细节里面去了。

    \n

    \"image.png\"

    \n

    所以这一篇文章的目地就是 , 如何在没有提纲的情况下 , 能更好的只关注流程 , 而不是细节 。

    \n

    开始 :

    \n
      \n
    1. python DEBUG 模块介绍 :
      \n 前段时间看过挺多文章提到 pysoonper 这个调试模块,有兴趣的可以百度一下.
      \n 个人尝试了一下,篇幅过大的 DEBUG 不适合用 pysoonper , 因为没有缩进!
      \n 这几天偶然遇到一个二次封装的模块 snoop, 完美地解决了这个问题.
    2. \n
    3. 操作步骤 :
    4. \n
    \n\n

    发现可以折叠 , 但是最大可折叠等级只到 5 级 , 而且无法对对应等级折叠 , 有点遗憾 。也许是.log 格式选得不太好, 不知道是否有更好的后缀格式。

    \n\n

    callreturn 给加进去.

    \n\n

    \"eric6启动阶段\"

    \n

    \"image.png\"

    \n

    #000 是为了方便搜索 。
    \n需要自己手动折叠 。
    \n可以发现 每个 splash.showMessage() 都是一个阶段 , 展开折叠之后就是每个阶段具体执行细节 。

    \n
    \n

    # ps: vscode 阅读 log 文件还是有一些不方便的地方,除了在 2. 中提到的,还有包括关闭文件再打开,折叠状态不会保留,有其他更好的方式 请留言告诉我,谢谢.

    \n", + "tags": [ + "Python", + "debug", + "snoop" + ] + } + ] +} \ No newline at end of file diff --git a/tag/snoop/rss.xml b/tag/snoop/rss.xml new file mode 100644 index 00000000..aeacd37c --- /dev/null +++ b/tag/snoop/rss.xml @@ -0,0 +1,107 @@ + + + + PyQt • Posts by "snoop" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 06 Jul 2019 17:37:22 +0000 + Sat, 06 Jul 2019 17:37:22 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/read_open_source.html + 像读文章一样读源码 + https://pyqt5.com/read_open_source.html + Python + debug + snoop + Sat, 06 Jul 2019 17:37:22 +0000 + + + + diff --git a/tag/subprocess.popen/feed.json b/tag/subprocess.popen/feed.json new file mode 100644 index 00000000..adb7712f --- /dev/null +++ b/tag/subprocess.popen/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"subprocess.popen\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "url": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "title": "python 获取子进程print信息  ", + "date_published": "2019-05-24T06:39:44.000Z", + "content_html": "

    在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。

    \n\n

    问题所在:

    \n

    \"image.png\"

    \n

    如果模块都由自己开发, 正常操作

    \n

    \"image.png\"

    \n

    但是因为不能改,所以只能拦截:
    \n代码:

    \n
    pythonPath = self.pythonPath_cb.currentText()\n\nif suffix == \"py\":\n    # 首次\n    self.pyCommand = [pythonPath, path]\n    self.modifiedReloadPython(path)\ndef modifiedReloadPython(self, path_):\n    os.chdir(os.path.dirname(path_))\n    # 子进程调用\n    self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n    # self.stdoutWorker.p = self.p\n    self.stdoutWorker = Worker(self.p)\n    self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append(\"PYDEBUG:\\n\" + x))\n    self.stdoutWorker.start()\nclass Worker(QThread):\n    stdout_signal = pyqtSignal(str)\n\n    def __init__(self, p, parent=None):\n        super().__init__(parent)\n        self.p = p\n\n    def run(self):\n        while True:\n            QApplication.processEvents()\n            if self.p is not None:\n                line = self.p.stdout.readline()\n                # line = line.strip()\n                if line != b'':\n                    try:\n                        info = line.decode()\n                        self.stdout_signal.emit(info)\n                    except:\n                        self.stdout_signal.emit(repr(line))\n    
    \n", + "tags": [ + "Python", + "subprocess.Popen", + "拦截print" + ] + } + ] +} \ No newline at end of file diff --git a/tag/subprocess.popen/rss.xml b/tag/subprocess.popen/rss.xml new file mode 100644 index 00000000..fec90fea --- /dev/null +++ b/tag/subprocess.popen/rss.xml @@ -0,0 +1,109 @@ + + + + PyQt • Posts by "subprocess.popen" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Fri, 24 May 2019 06:39:44 +0000 + Fri, 24 May 2019 06:39:44 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + python 获取子进程print信息   + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + Python + subprocess.Popen + 拦截print + Fri, 24 May 2019 06:39:44 +0000 + + + + diff --git a/tag/virtualenv/feed.json b/tag/virtualenv/feed.json new file mode 100644 index 00000000..486ddffe --- /dev/null +++ b/tag/virtualenv/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"virtualenv\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/virtualenvpy_625781186.html", + "url": "https://pyqt5.com/virtualenvpy_625781186.html", + "title": "python 拷贝虚拟环境(一)  ", + "date_published": "2019-05-02T07:21:01.000Z", + "content_html": "

    通常来说,刚开始使用 python 的时候都是把包装到全局路径,随着各个项目安装的包越来越多,之后每开始一个项目,pycharm 创建索引的时间都越来越漫长,所以不可避免得开始使用虚拟环境。
    \n经过一番了解 ,虚拟环境的优点有这些:

    \n\n\n

    python 虚拟环境库除了自带的 venv , 还有三方库 virtualenv , 此外 在 virtualenv 基础上又开发了 virtualenvwrapper(virtualenvwrapper_win) 来管理

    \n

    本文基于 virtualenvwrapper 创建的虚拟环境来讲解.

    \n
    以下是收集的一些virtualenvwrapper配置教程:\n# linux平台\nhttps://www.cnblogs.com/netfoxman/p/5994697.html\n# window平台\nhttps://blog.csdn.net/shaququ/article/details/54292043   \nhttps://blog.csdn.net/iaau0908/article/details/54021518\n
    \n

    虚拟环境创建多了我们就会发现,
    \n 有时候使用相同版本的环境,一些常用的库是需要重新安装的,
    \n 那么能不能创建一个基础环境,默认拥有这些库,然后在这个基础环境上继续安装三方库呢?

    \n

    本文经过试验发现是可行的:

    \n
      \n
    1. \n

      创建基础虚拟环境 mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径] . 例如 mkvirtualenv py34 -p c:\\Python34\\python.exe

      \n
    2. \n
    3. \n

      切换到虚拟环境 workon py34 , 然后安装一下三方库,然后复制 py34 这个文件夹备份一下;

      \n
    4. \n
    5. \n

      接着复制这个 py34 文件夹,把复制后的文件夹改名为我们需要需要的文件夹例如 new34

      \n
    6. \n
    7. \n

      进入 new34文件夹 ,用任意编辑器全路径搜索 py34 (替换虚拟环境的路径)

      \n
    8. \n
    9. \n

      删除 new34/Scripts 下的 pip.exe, pip3.exe, pip3.x.exe, easy_install.exe (因为安装路径硬编码到这里面了,改不了,需要重新安装)

      \n
    10. \n
    11. \n

      https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二,源码安装 setuptools 后再用 easy_install pip 安装 pip 后,完成;
      \n 如果有问题,就继续按照方式一的源码安装 pip;

      \n
    12. \n
    13. \n

      new34 环境下 用 pip show 三方库 来看一些库的位置,确保正确.

      \n
    14. \n
    \n", + "tags": [ + "Python", + "virtualenvwrapper", + "virtualenv" + ] + } + ] +} \ No newline at end of file diff --git a/tag/virtualenv/rss.xml b/tag/virtualenv/rss.xml new file mode 100644 index 00000000..7f7ca6b6 --- /dev/null +++ b/tag/virtualenv/rss.xml @@ -0,0 +1,115 @@ + + + + PyQt • Posts by "virtualenv" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 02 May 2019 07:21:01 +0000 + Thu, 02 May 2019 07:21:01 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/virtualenvpy_625781186.html + python 拷贝虚拟环境(一)   + https://pyqt5.com/virtualenvpy_625781186.html + Python + virtualenvwrapper + virtualenv + Thu, 02 May 2019 07:21:01 +0000 + + + + diff --git a/tag/virtualenvwrapper/feed.json b/tag/virtualenvwrapper/feed.json new file mode 100644 index 00000000..f601d837 --- /dev/null +++ b/tag/virtualenvwrapper/feed.json @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"virtualenvwrapper\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/virtualenvpy_625781186.html", + "url": "https://pyqt5.com/virtualenvpy_625781186.html", + "title": "python 拷贝虚拟环境(一)  ", + "date_published": "2019-05-02T07:21:01.000Z", + "content_html": "

    通常来说,刚开始使用 python 的时候都是把包装到全局路径,随着各个项目安装的包越来越多,之后每开始一个项目,pycharm 创建索引的时间都越来越漫长,所以不可避免得开始使用虚拟环境。
    \n经过一番了解 ,虚拟环境的优点有这些:

    \n\n\n

    python 虚拟环境库除了自带的 venv , 还有三方库 virtualenv , 此外 在 virtualenv 基础上又开发了 virtualenvwrapper(virtualenvwrapper_win) 来管理

    \n

    本文基于 virtualenvwrapper 创建的虚拟环境来讲解.

    \n
    以下是收集的一些virtualenvwrapper配置教程:\n# linux平台\nhttps://www.cnblogs.com/netfoxman/p/5994697.html\n# window平台\nhttps://blog.csdn.net/shaququ/article/details/54292043   \nhttps://blog.csdn.net/iaau0908/article/details/54021518\n
    \n

    虚拟环境创建多了我们就会发现,
    \n 有时候使用相同版本的环境,一些常用的库是需要重新安装的,
    \n 那么能不能创建一个基础环境,默认拥有这些库,然后在这个基础环境上继续安装三方库呢?

    \n

    本文经过试验发现是可行的:

    \n
      \n
    1. \n

      创建基础虚拟环境 mkvirtualenv <环境名称> [-p空格python其他版本的解释器路径] . 例如 mkvirtualenv py34 -p c:\\Python34\\python.exe

      \n
    2. \n
    3. \n

      切换到虚拟环境 workon py34 , 然后安装一下三方库,然后复制 py34 这个文件夹备份一下;

      \n
    4. \n
    5. \n

      接着复制这个 py34 文件夹,把复制后的文件夹改名为我们需要需要的文件夹例如 new34

      \n
    6. \n
    7. \n

      进入 new34文件夹 ,用任意编辑器全路径搜索 py34 (替换虚拟环境的路径)

      \n
    8. \n
    9. \n

      删除 new34/Scripts 下的 pip.exe, pip3.exe, pip3.x.exe, easy_install.exe (因为安装路径硬编码到这里面了,改不了,需要重新安装)

      \n
    10. \n
    11. \n

      https://blog.csdn.net/douniwan007009/article/details/81463958 按方式二,源码安装 setuptools 后再用 easy_install pip 安装 pip 后,完成;
      \n 如果有问题,就继续按照方式一的源码安装 pip;

      \n
    12. \n
    13. \n

      new34 环境下 用 pip show 三方库 来看一些库的位置,确保正确.

      \n
    14. \n
    \n", + "tags": [ + "Python", + "virtualenvwrapper", + "virtualenv" + ] + } + ] +} \ No newline at end of file diff --git a/tag/virtualenvwrapper/rss.xml b/tag/virtualenvwrapper/rss.xml new file mode 100644 index 00000000..cbbd43b4 --- /dev/null +++ b/tag/virtualenvwrapper/rss.xml @@ -0,0 +1,115 @@ + + + + PyQt • Posts by "virtualenvwrapper" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 02 May 2019 07:21:01 +0000 + Thu, 02 May 2019 07:21:01 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/virtualenvpy_625781186.html + python 拷贝虚拟环境(一)   + https://pyqt5.com/virtualenvpy_625781186.html + Python + virtualenvwrapper + virtualenv + Thu, 02 May 2019 07:21:01 +0000 + + + + diff --git "a/tag/\344\277\241\345\217\267/feed.json" "b/tag/\344\277\241\345\217\267/feed.json" new file mode 100644 index 00000000..56e903c5 --- /dev/null +++ "b/tag/\344\277\241\345\217\267/feed.json" @@ -0,0 +1,42 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"信号\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/bindsignals.html", + "url": "https://pyqt5.com/bindsignals.html", + "title": "三种方式绑定信号槽", + "date_published": "2019-05-04T08:07:06.000Z", + "content_html": "

    网上关于 PyQt5 的信号绑定使用的教程比较上,很多还是以前的绑定方式,导致在 PyQt5 中无法使用,这里归纳总结下已有的几种绑定信号槽的方式,
    \n这几种方式各有各的优点和缺点。

    \n\n

    # 方式一

    \n

    这个方式是最开始接触设计师的时候知道的,主要是通过控件的 objectNameQtCore.QMetaObject.connectSlotsByName(Form) 提供的连接函数来自动完成注册,
    \n比如带有按钮的界面 ui 文件转成 py 文件后会发现如下代码:

    \n
    self.pushButton = QtWidgets.QPushButton(Form)\nself.pushButton.setGeometry(QtCore.QRect(60, 40, 93, 28))\nself.pushButton.setObjectName(\"pushButton\")\n\n# 通过这里自动完成连接信号槽\nQtCore.QMetaObject.connectSlotsByName(Form)
    \n

    此时只需要继承该 UI 文件类然后增加如下方法:

    \n
    \n@pyqtSlot()\ndef on_pushButton_clicked(self):\n    print('button clicked')
    \n

    这里解释一下, @pyqtSlot() 装饰器把函数 on_pushButton_clicked 包装为一个槽函数,
    \n而 QtCore.QMetaObject.connectSlotsByName(Form) 这句代码的意思就是自动去寻找满足的槽函数

    \n

    注意:这里有个规范(on_xxxx_clicked),这里必须要满足 on_控件的objectName_控件的信号 这样下划线连接起来的函数名才能被识别,
    \n比如按钮的点击: on_pushButton_clicked 、勾选框的选中: on_checkbox_toggled(self, checked)

    \n

    # 方式二

    \n

    这种方式则直接通过代码里调用控件的信号的 connect 方法来进行绑定,比如:

    \n
    # 按钮点击函数\ndef doClicked(self):\n    print(self.sender(), 'clicked')\n\n# 绑定点击信号\nself.pushButton.clicked.connect(self.doClicked)
    \n

    注意: connect 的是函数名字self.sender() 这句代码是获取信号发送者(比如这里就是得到这个按钮对象),
    \n用处在于有时候要循环创建一堆按钮

    \n

    # 方式三

    \n

    通过参数这种方式其实比较特殊,在 PyQt 中大部分存在,但是在 PySide 中则很少,原因是两者的封装方式不同。

    \n

    同时该方式用于在纯代码中比较常见,而且需要对该控件有那些信号可以用要很熟习,比如:

    \n
    \n# 按钮点击函数\ndef doClicked(self):\n    print(self.sender(), 'clicked')\n\npushButton = QPushButton('按钮', self, clicked=self.doClicked, minimumHeight=40)
    \n

    这里可以通过参数(信号名字) = 函数来绑定信号

    \n

    同时也可以设置其它参数,比如
    \n button.setMinimumHeight(40) 也可以像参数里那样设置 minimumHeight=40

    \n", + "tags": [ + "PyQt", + "信号" + ] + }, + { + "id": "https://pyqt5.com/runnablesignal_625781186.html", + "url": "https://pyqt5.com/runnablesignal_625781186.html", + "title": "QRunnable线程池发信号", + "date_published": "2019-04-30T07:58:09.000Z", + "content_html": "

    因为只有继承 QObject 的类才能有信号和自定义信号,而 QRunnable 并不是继承自 QObject ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。

    \n\n

    pools 是 QThreadPool 实例

    \n

    # 看图说话

    \n
      \n
    1. \"runnablesignal1\"
    2. \n
    3. 定义一个全局信号类
      \n\"runnablesignal2\"
    4. \n
    5. 在 QRunnable 中发送
      \n\"runnablesignal3\"
    6. \n
    \n", + "tags": [ + "PyQt", + "信号", + "线程" + ] + }, + { + "id": "https://pyqt5.com/issignalconnected.html", + "url": "https://pyqt5.com/issignalconnected.html", + "title": "PyQt5判断信号是否连接", + "date_published": "2019-04-26T14:06:26.000Z", + "content_html": "

    PyQt 中某些情况下需要取消原来的信号连接,此时需要使用 disconnect 方法,但是在逻辑不严谨的情况下可能会导致多次调用 disconnect 方法而导致报错,当然可以通过 try except 来包裹代码。这里通过  isSignalConnected  来判断信号是否连接。

    \n\n

    在 QOjbect 文档中这样写到:

    \n
    static const QMetaMethod valueChangedSignal = QMetaMethod::fromSignal(&MyObject::valueChanged);\nif (isSignalConnected(valueChangedSignal)) {\n    QByteArray data;\n    data = get_the_value();       // expensive operation\n    emit valueChanged(data);\n}
    \n

    通过直接传入信号就行了,但是这在 PyQt 中不可行。需要这么做

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年2月24日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: IsSignalConnected\n@description: 判断信号是否连接\n\"\"\"\n\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextBrowser\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.button1 = QPushButton('已连接', self, clicked=self.doTest)\n        self.button2 = QPushButton('未连接', self)\n        self.retView = QTextBrowser(self)\n        layout.addWidget(self.button1)\n        layout.addWidget(self.button2)\n        layout.addWidget(self.retView)\n\n    def doTest(self):\n        self.retView.append(\"\"\"\n        # button1 clicked 是否连接: %s\n        # button2 clicked 是否连接: %s\n        \"\"\" % (\n            self.isSignalConnected(self.button1, 'clicked()'),\n            self.isSignalConnected(self.button2, 'clicked()')\n        ))\n\n    def isSignalConnected(self, obj, name):\n        \"\"\"判断信号是否连接\n        :param obj:        对象\n        :param name:       信号名,如 clicked()\n        \"\"\"\n        index = obj.metaObject().indexOfMethod(name)\n        if index > -1:\n            method = obj.metaObject().method(index)\n            if method:\n                return obj.isSignalConnected(method)\n        return False\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"IsSignalConnected\"

    \n", + "tags": [ + "PyQt", + "信号" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\344\277\241\345\217\267/rss.xml" "b/tag/\344\277\241\345\217\267/rss.xml" new file mode 100644 index 00000000..6a904013 --- /dev/null +++ "b/tag/\344\277\241\345\217\267/rss.xml" @@ -0,0 +1,214 @@ + + + + PyQt • Posts by "信号" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 04 May 2019 08:07:06 +0000 + Sat, 04 May 2019 08:07:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/bindsignals.html + 三种方式绑定信号槽 + https://pyqt5.com/bindsignals.html + PyQt + 信号 + Sat, 04 May 2019 08:07:06 +0000 + + + + https://pyqt5.com/runnablesignal_625781186.html + QRunnable线程池发信号 + https://pyqt5.com/runnablesignal_625781186.html + PyQt + 信号 + 线程 + Tue, 30 Apr 2019 07:58:09 +0000 + + + + https://pyqt5.com/issignalconnected.html + PyQt5判断信号是否连接 + https://pyqt5.com/issignalconnected.html + PyQt + 信号 + Fri, 26 Apr 2019 14:06:26 +0000 + + + + diff --git "a/tag/\345\212\250\347\224\273/feed.json" "b/tag/\345\212\250\347\224\273/feed.json" new file mode 100644 index 00000000..4904e3cc --- /dev/null +++ "b/tag/\345\212\250\347\224\273/feed.json" @@ -0,0 +1,67 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"动画\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/flipwidgetanimation.html", + "url": "https://pyqt5.com/flipwidgetanimation.html", + "title": "PyQt5窗口翻转动画", + "date_published": "2019-05-15T14:48:00.000Z", + "content_html": "

    QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 QStackedWidget 包含两个控件做切换,同时单独使用一个窗口做动画绘制。

    \n\n

    # 原理说明

    \n
      \n
    1. 用了两个 QLabel 来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
    2. \n
    3. 用了 QStackedWidget 来存放上面的两个界面 QLabel
    4. \n
    5. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口
    6. \n
    7. 通过 setWindowOpacity 控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
    8. \n
    9. 动画窗口 FlipWidget.py 主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画
    10. \n
    \n

    # 核心实现

    \n
      \n
    1. 主要是在 paintEvent 方法中使用 QTransformQPainter 进行圆心变换以及 rotate 设置翻转角度
    2. \n
    3. 同时根据翻转的角度范围对图片进行切换和缩放
    4. \n
    \n
    def paintEvent(self, event):\n    super(FlipWidget, self).paintEvent(event)\n\n    if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible():\n\n        painter = QPainter(self)\n        painter.setRenderHint(QPainter.Antialiasing, True)\n        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n        # 变换\n        transform = QTransform()\n        # 把圆心设置为矩形中心\n        transform.translate(self.width() / 2, self.height() / 2)\n\n        if self._angle >= -90 and self._angle <= 90:\n            # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程\n            painter.save()\n            # 设置翻转角度\n            transform.rotate(self._angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image1.width() / 2\n            height = int(self.image1.height() *\n                         (1 - abs(self._angle / self.Scale) / 100))\n            image = self.image1.scaled(\n                self.image1.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()\n        else:\n            # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程\n            painter.save()\n            if self._angle > 0:\n                angle = 180 + self._angle\n            else:\n                angle = self._angle - 180\n            # 设置翻转角度, 注意这里角度有差异\n            transform.rotate(angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image2.width() / 2\n            height = int(self.image2.height() *\n                         (1 - ((360 - abs(angle)) / self.Scale / 100)))\n            image = self.image2.scaled(\n                self.image2.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py

    \n

    # 效果图

    \n

    \"FlipWidgetAnimation\"

    \n", + "tags": [ + "PyQt", + "动画", + "翻转" + ] + }, + { + "id": "https://pyqt5.com/QPropertyAnimation.html", + "url": "https://pyqt5.com/QPropertyAnimation.html", + "title": "PyQt属性动画(QPropertyAnimation)", + "date_published": "2019-05-08T07:43:06.000Z", + "content_html": "

    QPropertyAnimation 继承自 QVariantAnimation ,其作为 Qt 的属性动画用于针对控件的属性或者继承自 QObject 的对象中定义的属性做修改,
    \n简单来说就是基类是 QObject 且定义了属性变量,就可以用 QPropertyAnimation 来做属性动画。同时也可以通过 pyqtProperty 来增加自定义属性。

    \n\n

    首先,通过构造函数 QPropertyAnimation(QObject, Union[QByteArray, bytes, bytearray], parent: QObject = None) 创建一个对象,其中

    \n
      \n
    1. 第一个参数是动画作用的对象,也可以通过 setTargetObject 设置
    2. \n
    3. 第二个参数是属性名,在 py3 中类型是 bytes,也可以通过 setPropertyName 设置
    4. \n
    \n

    # 函数

    \n

    一些常见的设置函数

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    setPropertyName设置属性名
    setTargetObject设置动画作用对象
    setDuration设置动画持续时间(毫秒)
    setStartValue设置开始值
    setEndValue设置结束值
    setEasingCurve设置动画曲线
    setKeyValueAt插入线性值
    setLoopCount设置循环次数(-1 为永久)
    \n

    # 示例

    \n

    比如这个例子:

    \n
      \n
    1. 修改控件的 geometry 大小
    2. \n
    3. 修改自定义属性
    4. \n
    5. 修改进度条的 value 值
    6. \n
    \n

    \"QPropertyAnimation\"

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年5月8日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\nfrom PyQt5.QtCore import QPropertyAnimation, QRect, pyqtProperty, QEasingCurve\nfrom PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout,\\\n    QLabel, QProgressBar, QSpacerItem, QSizePolicy\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.resize(400, 400)\n        self._value = 0\n        self.button = QPushButton('属性动画测试', self)\n        self.button.clicked.connect(self.doStart)\n        self.button.setGeometry(0, 0, 80, 40)\n\n        self.buttonc = QPushButton('自定义属性 测试', self)\n        self.buttonc.clicked.connect(self.doStartCustom)\n\n        self.label = QLabel('', self)\n\n        self.progressbar = QProgressBar(self)\n        self.progressbar.setRange(0, 99)\n\n        layout = QVBoxLayout(self)\n        layout.addItem(QSpacerItem(\n            20, 60, QSizePolicy.Fixed, QSizePolicy.Fixed))\n        layout.addWidget(self.buttonc)\n        layout.addWidget(self.label)\n        layout.addWidget(self.progressbar)\n\n        # 进度条动画\n        self.progressStart()\n\n    # 此处是自定义属性,并通过动画修改后,设置QLabel的值\n    @pyqtProperty(int)\n    def value(self):\n        return self._value\n\n    @value.setter\n    def value(self, v):\n        self._value = v\n        self.label.setText('当前值:{}'.format(v))\n\n    def doStart(self):\n        # 第一个参数是要执行的对象\n        animation = QPropertyAnimation(self.button, b'geometry', self)\n        animation.setDuration(2000)  # 持续时间\n        # 缓和曲线风格,加了曲线动画会很大程度影响\n        animation.setEasingCurve(QEasingCurve.OutBounce)\n        animation.setStartValue(QRect(0, 0, 40, 40))\n        animation.setEndValue(QRect(250, 250, 80, 80))\n        animation.start(animation.DeleteWhenStopped)\n\n    def doStartCustom(self):\n        # 自定义属性动画\n        # 由于定义的属性是在继承的QWidget, 所以第一个参数是self\n        # 第二个参数就是 value\n        animation = QPropertyAnimation(self, b'value', self)\n        animation.setDuration(2000)  # 持续时间\n        animation.setStartValue(0)\n        animation.setEndValue(100)\n        animation.start(animation.DeleteWhenStopped)\n\n    def progressStart(self):\n        # 进度条动画\n        # 这里 value是QProgressBar自带的属性,具体可以看文档\n        # https://doc.qt.io/qt-5/qprogressbar.html#properties\n        animation = QPropertyAnimation(self.progressbar, b'value', self)\n        animation.setDuration(2000)  # 持续时间\n        animation.setLoopCount(-1)\n        # 这里采用插入线性值,第一个参数的范围是(0-1)\n        # 第二个参数的范围是进度(最小值-最大值)\n        animation.setKeyValueAt(0, self.progressbar.minimum())\n        animation.setKeyValueAt(0.1, 10)\n        animation.setKeyValueAt(0.2, 30)\n        animation.setKeyValueAt(0.5, 60)\n        animation.setKeyValueAt(0.7, 80)\n        animation.setKeyValueAt(1, self.progressbar.maximum())\n        animation.start(animation.DeleteWhenStopped)\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())\n
    ", + "tags": [ + "PyQt", + "动画" + ] + }, + { + "id": "https://pyqt5.com/pageswitching.html", + "url": "https://pyqt5.com/pageswitching.html", + "title": "PyQt5之图片轮播", + "date_published": "2018-11-24T13:45:06.000Z", + "content_html": "

    之前看到了 QStackedWidget 做切换动画,让界面不那么生硬,于是参考了 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt 做了一个 QStackedWidget 的切换动画,然后利用 QStackedWidget 结合多个 QLabel 显示图片来做一个轮播效果。

    \n

    其实在写之前也在网上找了很多例子,参看过后发现大多例子都是利用到了 paintEvent 去绘制,这样其实还是比较麻烦,个人觉得更好的方式是使用 QPropertyAnimation 属性动画修改控件中 QLabel 图片控件的 pos 位置属性就可以达到移动效果了。

    \n\n
      \n
    1. 比较核心的算法就是要计算当前页面和下一个页面的位置偏移量,比如:
    2. \n
    \n
    # 计算偏移量\noffsetX = self.frameRect().width()\noffsetY = self.frameRect().height()\nw_next.setGeometry(0, 0, offsetX, offsetY)\n\nif direction == self.BOTTOM2TOP:\n    offsetX = 0\n    offsetY = -offsetY\nelif direction == self.TOP2BOTTOM:\n    offsetX = 0\nelif direction == self.RIGHT2LEFT:\n    offsetX = -offsetX\n    offsetY = 0\nelif direction == self.LEFT2RIGHT:\n    offsetY = 0\n\n# 重新定位显示区域外部/旁边的下一个窗口小部件\npnext = w_next.pos()\npnow = w_now.pos()\nself._pnow = pnow\n\n# 移动到指定位置并显示\nw_next.move(pnext.x() - offsetX, pnext.y() - offsetY)\nw_next.show()\nw_next.raise_()
    \n
      \n
    1. \n

      其次是对这两个页面增加关联 pos 属性的 QPropertyAnimation 动画,然后加入到并行动画组 QParallelAnimationGroup 中再启动即可。

      \n
    2. \n
    3. \n

      QStackedWidgetsetCurrentIndexsetCurrentWidget 这两个函数进行了覆盖重写达到及时手动调用这两个函数也会产生动画效果的目的。

      \n
    4. \n
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/PageSwitching.py

    \n

    # 效果图

    \n

    \"PageSwitching\"

    \n", + "tags": [ + "PyQt", + "动画", + "轮播" + ] + }, + { + "id": "https://pyqt5.com/rlatticeeffect.html", + "url": "https://pyqt5.com/rlatticeeffect.html", + "title": "PyQt5仿网页鼠标移动点阵特效", + "date_published": "2018-10-29T08:49:10.000Z", + "content_html": "

    Orz,前段时间有个 zz 需求,就是要做一个类似网页上很多个多点连线、鼠标移动跟随的那种炫酷特效,然后花了点时间在网上找了 js 做的,刚开始打算是嵌入 QWebView 来显示网页,后来研究了下 js 的算法代码,遂改用 QWidgetpaintEvent 直接绘制。

    \n\n

    # 大概思路

    \n
      \n
    1. 先根据窗口大小随机创建一些点
    2. \n
    3. 遍历这些点并找到与之相关联的点
    4. \n
    5. 在动画过程中绘制圆点和画两点之间的连线
    6. \n
    7. 属性动画 QPropertyAnimation 改变颜色的透明度
    8. \n
    \n

    # 题外

    \n
      \n
    1. 这里没有仔细去研究 js 里的算法优化,在浏览器里嗖嗖的就生成了,在 py 里好慢…
    2. \n
    3. 尽量在 py 里优化了循环操作,也简单的做了个 cython 加速也才提高了 1s ? 1 倍?..
    4. \n
    5. 不要只是为了好看用这玩意儿,和网页的效果一样,占 CPU !!! 没有任何意义
    6. \n
    7. 如果有更好的优化算法请告知,3Q
    8. \n
    9. pyd 是 python3.4 生成的,删掉 pyd 也能运行
    10. \n
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/RlatticeEffect.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年11月22日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\nfrom random import random\nfrom time import time\n\nfrom PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\\\n    Qt, QRectF, pyqtSignal\nfrom PyQt5.QtGui import QColor, QPainterPath, QPainter\nfrom PyQt5.QtWidgets import QWidget\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\ntry:\n    import pointtool  # @UnusedImport @UnresolvedImport\n    getDistance = pointtool.getDistance\n    findClose = pointtool.findClose\nexcept:\n    import math\n\n    def getDistance(p1, p2):\n        return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2)\n\n    def findClose(points):\n        plen = len(points)\n        for i in range(plen):\n            closest = [None, None, None, None, None]\n            p1 = points[i]\n            for j in range(plen):\n                p2 = points[j]\n                dte1 = getDistance(p1, p2)\n                if p1 != p2:\n                    placed = False\n                    for k in range(5):\n                        if not placed:\n                            if not closest[k]:\n                                closest[k] = p2\n                                placed = True\n                    for k in range(5):\n                        if not placed:\n                            if dte1 < getDistance(p1, closest[k]):\n                                closest[k] = p2\n                                placed = True\n            p1.closest = closest\n\n\nclass Target:\n\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n\nclass Point(QObject):\n\n    valueChanged = pyqtSignal()\n\n    def __init__(self, x, ox, y, oy, *args, **kwargs):\n        super(Point, self).__init__(*args, **kwargs)\n        self.__x = x\n        self._x = x\n        self.originX = ox\n        self._y = y\n        self.__y = y\n        self.originY = oy\n        # 5个闭合点\n        self.closest = [0, 0, 0, 0, 0]\n        # 圆半径\n        self.radius = 2 + random() * 2\n        # 连线颜色\n        self.lineColor = QColor(156, 217, 249)\n        # 圆颜色\n        self.circleColor = QColor(156, 217, 249)\n\n    def initAnimation(self):\n        # 属性动画\n        if not hasattr(self, 'xanimation'):\n            self.xanimation = QPropertyAnimation(\n                self, b'x', self, valueChanged=self.valueChanged.emit,\n                easingCurve=QEasingCurve.InOutSine)\n            self.yanimation = QPropertyAnimation(\n                self, b'y', self, valueChanged=self.valueChanged.emit,\n                easingCurve=QEasingCurve.InOutSine,\n                finished=self.updateAnimation)\n            self.updateAnimation()\n\n    def updateAnimation(self):\n        self.xanimation.stop()\n        self.yanimation.stop()\n        duration = (1 + random()) * 1000\n        self.xanimation.setDuration(duration)\n        self.yanimation.setDuration(duration)\n        self.xanimation.setStartValue(self.__x)\n        self.xanimation.setEndValue(self.originX - 50 + random() * 100)\n        self.yanimation.setStartValue(self.__y)\n        self.yanimation.setEndValue(self.originY - 50 + random() * 100)\n        self.xanimation.start()\n        self.yanimation.start()\n\n    @pyqtProperty(float)\n    def x(self):\n        return self._x\n\n    @x.setter\n    def x(self, x):\n        self._x = x\n\n    @pyqtProperty(float)\n    def y(self):\n        return self._y\n\n    @y.setter\n    def y(self, y):\n        self._y = y\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.setMouseTracking(True)\n        self.resize(800, 600)\n        self.points = []\n        self.target = Target(self.width() / 2, self.height() / 2)\n        self.initPoints()\n\n    def paintEvent(self, event):\n        super(Window, self).paintEvent(event)\n        painter = QPainter()\n        painter.begin(self)\n        painter.setRenderHint(QPainter.Antialiasing)\n        painter.fillRect(self.rect(), Qt.black)\n        self.animate(painter)\n        painter.end()\n\n    def mouseMoveEvent(self, event):\n        super(Window, self).mouseMoveEvent(event)\n        # 鼠标移动时更新xy坐标\n        self.target.x = event.x()\n        self.target.y = event.y()\n        self.update()\n\n    def initPoints(self):\n        t = time()\n        self.points.clear()\n        # 创建点\n        stepX = self.width() / 20\n        stepY = self.height() / 20\n        for x in range(0, self.width(), int(stepX)):\n            for y in range(0, self.height(), int(stepY)):\n                ox = x + random() * stepX\n                oy = y + random() * stepY\n                point = Point(ox, ox, oy, oy)\n                point.valueChanged.connect(self.update)\n                self.points.append(point)\n        print(time() - t)\n\n        t = time()\n        # 每个点寻找5个闭合点\n        findClose(self.points)\n        print(time() - t)\n\n    def animate(self, painter):\n        for p in self.points:\n            # 检测点的范围\n            value = abs(getDistance(self.target, p))\n            if value < 4000:\n                # 其实就是修改颜色透明度\n                p.lineColor.setAlphaF(0.3)\n                p.circleColor.setAlphaF(0.6)\n            elif value < 20000:\n                p.lineColor.setAlphaF(0.1)\n                p.circleColor.setAlphaF(0.3)\n            elif value < 40000:\n                p.lineColor.setAlphaF(0.02)\n                p.circleColor.setAlphaF(0.1)\n            else:\n                p.lineColor.setAlphaF(0)\n                p.circleColor.setAlphaF(0)\n\n            # 画线条\n            if p.lineColor.alpha():\n                for pc in p.closest:\n                    if not pc:\n                        continue\n                    path = QPainterPath()\n                    path.moveTo(p.x, p.y)\n                    path.lineTo(pc.x, pc.y)\n                    painter.save()\n                    painter.setPen(p.lineColor)\n                    painter.drawPath(path)\n                    painter.restore()\n\n            # 画圆\n            painter.save()\n            painter.setPen(Qt.NoPen)\n            painter.setBrush(p.circleColor)\n            painter.drawRoundedRect(QRectF(\n                p.x - p.radius, p.y - p.radius, 2 * p.radius, 2 * p.radius), p.radius, p.radius)\n            painter.restore()\n\n            # 开启动画\n            p.initAnimation()\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, '')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"RlatticeEffect\"

    \n", + "tags": [ + "PyQt", + "动画", + "特效" + ] + }, + { + "id": "https://pyqt5.com/animateshadow.html", + "url": "https://pyqt5.com/animateshadow.html", + "title": "PyQt5动画边框阴影", + "date_published": "2018-09-25T15:38:12.000Z", + "content_html": "

    为子控件增加动画阴影效果,结合 QGraphicsDropShadowEffectQPropertyAnimation 动态改变阴影半径达到效果,在旧版本的 Qt 中 QGraphicsDropShadowEffect 可能会有点问题(父控件会影响子控件)

    \n\n

    # 原理

    \n

    原理是利用 QGraphicsDropShadowEffect 添加边框阴影,然后使用动画不停改变阴影的模糊半径来达到效果,如图:

    \n

    \"ShadowEffect\"

    \n

    # 简单说明

    \n
      \n
    1. 继承 QGraphicsDropShadowEffect 增加动态属性 radius
    2. \n
    3. 通过 setGraphicsEffect 方法设置控件的边框阴影
    4. \n
    5. 通过 QPropertyAnimation 属性动画不断改变 radius 的值并调用 setBlurRadius 更新半径值
    6. \n
    \n

    https://github.com/PyQt5/PyQt/blob/master/QGraphicsDropShadowEffect/ShadowEffect.py

    \n

    # 自定义类

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年9月25日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: AnimationShadowEffect\n@description: 边框动画阴影动画\n\"\"\"\nfrom PyQt5.QtCore import QPropertyAnimation, pyqtProperty\nfrom PyQt5.QtWidgets import QGraphicsDropShadowEffect\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\nclass AnimationShadowEffect(QGraphicsDropShadowEffect):\n\n    def __init__(self, color, *args, **kwargs):\n        super(AnimationShadowEffect, self).__init__(*args, **kwargs)\n        self.setColor(color)\n        self.setOffset(0, 0)\n        self.setBlurRadius(0)\n        self._radius = 0\n        self.animation = QPropertyAnimation(self)\n        self.animation.setTargetObject(self)\n        self.animation.setDuration(2000)  # 一次循环时间\n        self.animation.setLoopCount(-1)  # 永久循环\n        self.animation.setPropertyName(b'radius')\n        # 插入线行值\n        self.animation.setKeyValueAt(0, 1)\n        self.animation.setKeyValueAt(0.5, 30)\n        self.animation.setKeyValueAt(1, 1)\n\n    def start(self):\n        self.animation.start()\n\n    @pyqtProperty(int)\n    def radius(self):\n        return self._radius\n\n    @radius.setter\n    def radius(self, r):\n        self._radius = r\n        self.setBlurRadius(r)
    \n

    # 测试代码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年9月25日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: Test\n@description: \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap\nfrom PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit\n\nfrom AnimationShadowEffect import AnimationShadowEffect  # @UnresolvedImport\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n\n        # 绿色边框\n        labelGreen = QLabel(self, pixmap=QPixmap('1.jpg').scaled(100, 100))\n        layout.addWidget(labelGreen)\n        aniGreen = AnimationShadowEffect(Qt.darkGreen, labelGreen)\n        labelGreen.setGraphicsEffect(aniGreen)\n        aniGreen.start()\n\n        # 红色边框,圆形图片\n        labelRed = QLabel(self)\n        labelRed.setMinimumSize(100, 100)\n        labelRed.setMaximumSize(100, 100)\n        labelRed.setStyleSheet('border-image: url(1.jpg);border-radius: 50px;')\n        layout.addWidget(labelRed)\n        aniRed = AnimationShadowEffect(Qt.red, labelGreen)\n        labelRed.setGraphicsEffect(aniRed)\n        aniRed.start()\n\n        # 蓝色边框按钮\n        button = QPushButton('按钮', self)\n        aniButton = AnimationShadowEffect(Qt.blue, button)\n        layout.addWidget(button)\n        button.setGraphicsEffect(aniButton)\n        aniButton.start()\n\n        # 青色边框输入框\n        lineedit = QLineEdit(self)\n        aniEdit = AnimationShadowEffect(Qt.cyan, lineedit)\n        layout.addWidget(lineedit)\n        lineedit.setGraphicsEffect(aniEdit)\n        aniEdit.start()\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    ", + "tags": [ + "PyQt", + "动画", + "阴影" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\345\212\250\347\224\273/rss.xml" "b/tag/\345\212\250\347\224\273/rss.xml" new file mode 100644 index 00000000..c69103ab --- /dev/null +++ "b/tag/\345\212\250\347\224\273/rss.xml" @@ -0,0 +1,765 @@ + + + + PyQt • Posts by "动画" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 15 May 2019 14:48:00 +0000 + Wed, 15 May 2019 14:48:00 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/flipwidgetanimation.html + PyQt5窗口翻转动画 + https://pyqt5.com/flipwidgetanimation.html + PyQt + 动画 + 翻转 + Wed, 15 May 2019 14:48:00 +0000 + + + + https://pyqt5.com/QPropertyAnimation.html + PyQt属性动画(QPropertyAnimation) + https://pyqt5.com/QPropertyAnimation.html + PyQt + 动画 + Wed, 08 May 2019 07:43:06 +0000 + + + + https://pyqt5.com/pageswitching.html + PyQt5之图片轮播 + https://pyqt5.com/pageswitching.html + PyQt + 动画 + 轮播 + Sat, 24 Nov 2018 13:45:06 +0000 + + + + https://pyqt5.com/rlatticeeffect.html + PyQt5仿网页鼠标移动点阵特效 + https://pyqt5.com/rlatticeeffect.html + PyQt + 动画 + 特效 + Mon, 29 Oct 2018 08:49:10 +0000 + + + + https://pyqt5.com/animateshadow.html + PyQt5动画边框阴影 + https://pyqt5.com/animateshadow.html + PyQt + 动画 + 阴影 + Tue, 25 Sep 2018 15:38:12 +0000 + + + + diff --git "a/tag/\345\233\276\347\211\207/feed.json" "b/tag/\345\233\276\347\211\207/feed.json" new file mode 100644 index 00000000..5e6ebefb --- /dev/null +++ "b/tag/\345\233\276\347\211\207/feed.json" @@ -0,0 +1,33 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"图片\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/qtninepatch.html", + "url": "https://pyqt5.com/qtninepatch.html", + "title": "PyQt5显示.9格式的PNG图片", + "date_published": "2018-10-26T02:00:08.000Z", + "content_html": "

    做过安卓开发的和使用过 QQ 的都知道 .9.png 这种图片格式,效果就如 QQ 的聊天气泡一样可以拉伸,这种格式的图片允许开发人员定义可扩展区域,当需要延伸图片以填充比图片本身更大区域时,可扩展区的内容被延展;允许开发人员定义内容显示区,用于显示文字或其他内容。目前在 Github 上有两个 C++ 版本的,在这里我把它们都用 Python 实现了一遍。另外一个我也为 PyQt 提供了编译好的 pyd 文件。

    \n\n

    # C++ 版本

    \n

    在 Github 开源库中搜索到两个 C++ 版本的

    \n
      \n
    1. 一个是 NinePatchQt
    2. \n
    3. 一个是 QtNinePatch
    4. \n
    \n

    # PyQt5 版本

    \n

    这里也分为两个版本,都是基于上面的 C++ 源码翻译改写过来的,具体的例子见项目里面的测试代码吧。

    \n
      \n
    1. QtNinePatch 是参考第一个源码编写,用法是在 paintEvent 中调用
    2. \n
    3. QtNinePatch2 是参考第二个源码编写,用法是 pixmap = QtNinePatch.createPixmapFromNinePatchImage(self.image, self.width(), self.height()) 直接得到一个处理好的 QPixmap 对象来使用
    4. \n
    \n

    # 说明

    \n
      \n
    1. 建议优先使用 pyd 版本的(后续提供 Python3.4 3.5 3.6 3.7 编译好的 32 为库文件),也可以自行编译,编译步骤见下文。
    2. \n
    3. 其次可以使用纯 python 版本 2 的(个人觉得方便调用)
    4. \n
    5. 最后再考虑纯 python 版本 1 的吧
    6. \n
    7. 以上为个人意见,两个 C++ 版本的写法不一样,但是核心算法应该是类似的。
    8. \n
    \n

    # 自行编译

    \n
      \n
    1. 首先要安装好 Qt、PyQt5、编译安装对应的 sip、对应的 VC++ 编译工具
    2. \n
    3. 用 Qt Creator 打开 pro 文件进行编译
    4. \n
    5. 进入源码中的 sip 文件夹修改 configure.py 文件
    6. \n
    \n
    # 这里是你的VC版本和对应的Qt目录中的文件夹\nconfig.platform = "win32-msvc2010"\nqt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010'
    \n
      \n
    1. 最后执行 python configure.py 来编译
    2. \n
    \n

    # 下载

    \n

    https://github.com/PyQt5/PyQt/tree/master/QLabel

    \n

    # 效果图

    \n

    \"NinePatchImage\"

    \n", + "tags": [ + "PyQt", + "图片", + "气泡", + ".9png" + ] + }, + { + "id": "https://pyqt5.com/circleimage.html", + "url": "https://pyqt5.com/circleimage.html", + "title": "PyQt5圆形图片", + "date_published": "2018-09-25T14:13:26.000Z", + "content_html": "

    实现圆形图片的方法有很多,比如用遮罩(mask), 裁切等等。这里比较几种实现方式,选出个人认为最优的方案。

    \n\n

    https://github.com/PyQt5/PyQt/blob/master/QLabel/CircleImage.py

    \n

    # 采用 mask 方式

    \n

    具体参考 【Qt】QLabel 实现的圆形图像 - 米罗西 - 博客园

    \n

    # 画圆形遮盖(适合纯色背景)

    \n

    原理是在原图片上画一个 4 角有颜色,中间圆形镂空的图片。

    \n

    \"circleimage1\"

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n'''\nCreated on 2017年8月25日\n@author: Irony.\"[讽刺]\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@description: \n'''\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QPainter, QPainterPath\nfrom PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout\n\n\n__Author__ = \"By: Irony.\\\"[讽刺]\\nQQ: 892768447\\nEmail: 892768447@qq.com\"\n__Copyright__ = \"Copyright (c) 2017 Irony.\\\"[讽刺]\"\n__Version__ = \"Version 1.0\"\n\n\nclass Label(QLabel):\n\n    def __init__(self, *args, antialiasing=True, **kwargs):\n        super(Label, self).__init__(*args, **kwargs)\n        self.Antialiasing = antialiasing\n        self.setMaximumSize(200, 200)\n        self.setMinimumSize(200, 200)\n        self.radius = 100\n\n        #加载图片并缩放\n        self.image = QPixmap(\"head.jpg\").scaled(\n            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)\n\n        painter = QPainter(self.image)\n        if self.Antialiasing:\n            painter.setRenderHint(QPainter.Antialiasing, True)\n            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)\n            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n        path = QPainterPath()\n        path.addRoundedRect(\n            0, 0, self.width(), self.height(), self.radius, self.radius)\n        path.addRect(0,0,self.width(),self.height())\n        painter.setPen(Qt.NoPen)\n        painter.setBrush(Qt.green)\n        painter.drawPath(path)\n        self.setPixmap(self.image)\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n        layout.addWidget(Label(self))\n        layout.addWidget(Label(self, antialiasing=False))\n        self.setStyleSheet(\"background: black;\")\n\nif __name__ == \"__main__\":\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 使用 QPainter 的 setCompositionMode

    \n

    具体参考 Qt 圆形头像制作工具 抗锯齿 可缩放编辑

    \n
    //result_avatar_size 是我们最后生成的图片的长宽,可以是QSize(200, 200)的正圆\ndestination_image = QImage(result_avatar_size, QImage::Format_ARGB32_Premultiplied);\n//在黑色的正方形中间画一个透明的圆,作为头像遮罩\nQPainter painter(&destination_image);\npainter.setRenderHint(QPainter::Antialiasing);\n//全涂黑\npainter.fillRect(destination_image.rect(), QBrush(Qt::black, Qt::SolidPattern));\npainter.setCompositionMode(QPainter::CompositionMode_SourceOut);\npainter.setPen(Qt::NoPen);\npainter.setBrush(QBrush(Qt::transparent, Qt::SolidPattern));\n//画透明区域\npainter.drawEllipse(destination_image.rect());
    \n

    # 使用 QPainter 的切割方法(推荐)

    \n

    利用 QPainter.setClipPath 方法切割一个圆形的 QPainterPath

    \n

    \"circleimage2\"

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n'''\nCreated on 2017年8月25日\n@author: Irony.\"[讽刺]\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n'''\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QPainter, QPainterPath, QPen\nfrom PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout\n\n\n__Author__ = \"By: Irony.\\\"[讽刺]\\nQQ: 892768447\\nEmail: 892768447@qq.com\"\n__Copyright__ = \"Copyright (c) 2017 Irony.\\\"[讽刺]\"\n__Version__ = \"Version 1.0\"\n\n\nclass Label(QLabel):\n\n    def __init__(self, *args, antialiasing=True, **kwargs):\n        super(Label, self).__init__(*args, **kwargs)\n        self.Antialiasing = antialiasing\n        self.setMaximumSize(200, 200)\n        self.setMinimumSize(200, 200)\n        self.radius = 100\n\n        #####################核心实现#########################\n        self.target = QPixmap(self.size())  # 大小和控件一样\n        self.target.fill(Qt.transparent)  # 填充背景为透明\n\n        p = QPixmap(\"head.jpg\").scaled(  # 加载图片并缩放和控件一样大\n            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)\n\n        painter = QPainter(self.target)\n        if self.Antialiasing:\n            # 抗锯齿\n            painter.setRenderHint(QPainter.Antialiasing, True)\n            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)\n            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n#         painter.setPen(# 测试黑色圆圈\n#             QPen(Qt.black, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))\n        path = QPainterPath()\n        path.addRoundedRect(\n            0, 0, self.width(), self.height(), self.radius, self.radius)\n        #**** 切割为圆形 ****#\n        painter.setClipPath(path)\n#         painter.drawPath(path)  # 测试黑色圆圈\n\n        painter.drawPixmap(0, 0, p)\n        self.setPixmap(self.target)\n        #####################核心实现#########################\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n        layout.addWidget(Label(self))\n        layout.addWidget(Label(self, antialiasing=False))\n        self.setStyleSheet(\"background: black;\")\n\nif __name__ == \"__main__\":\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    ", + "tags": [ + "PyQt", + "圆形", + "图片" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\345\233\276\347\211\207/rss.xml" "b/tag/\345\233\276\347\211\207/rss.xml" new file mode 100644 index 00000000..fb8e1862 --- /dev/null +++ "b/tag/\345\233\276\347\211\207/rss.xml" @@ -0,0 +1,277 @@ + + + + PyQt • Posts by "图片" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Fri, 26 Oct 2018 02:00:08 +0000 + Fri, 26 Oct 2018 02:00:08 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/qtninepatch.html + PyQt5显示.9格式的PNG图片 + https://pyqt5.com/qtninepatch.html + PyQt + 图片 + 气泡 + .9png + Fri, 26 Oct 2018 02:00:08 +0000 + + + + https://pyqt5.com/circleimage.html + PyQt5圆形图片 + https://pyqt5.com/circleimage.html + PyQt + 圆形 + 图片 + Tue, 25 Sep 2018 14:13:26 +0000 + + + + diff --git "a/tag/\345\234\206\345\275\242/feed.json" "b/tag/\345\234\206\345\275\242/feed.json" new file mode 100644 index 00000000..6b79e93d --- /dev/null +++ "b/tag/\345\234\206\345\275\242/feed.json" @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"圆形\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/circleimage.html", + "url": "https://pyqt5.com/circleimage.html", + "title": "PyQt5圆形图片", + "date_published": "2018-09-25T14:13:26.000Z", + "content_html": "

    实现圆形图片的方法有很多,比如用遮罩(mask), 裁切等等。这里比较几种实现方式,选出个人认为最优的方案。

    \n\n

    https://github.com/PyQt5/PyQt/blob/master/QLabel/CircleImage.py

    \n

    # 采用 mask 方式

    \n

    具体参考 【Qt】QLabel 实现的圆形图像 - 米罗西 - 博客园

    \n

    # 画圆形遮盖(适合纯色背景)

    \n

    原理是在原图片上画一个 4 角有颜色,中间圆形镂空的图片。

    \n

    \"circleimage1\"

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n'''\nCreated on 2017年8月25日\n@author: Irony.\"[讽刺]\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@description: \n'''\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QPainter, QPainterPath\nfrom PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout\n\n\n__Author__ = \"By: Irony.\\\"[讽刺]\\nQQ: 892768447\\nEmail: 892768447@qq.com\"\n__Copyright__ = \"Copyright (c) 2017 Irony.\\\"[讽刺]\"\n__Version__ = \"Version 1.0\"\n\n\nclass Label(QLabel):\n\n    def __init__(self, *args, antialiasing=True, **kwargs):\n        super(Label, self).__init__(*args, **kwargs)\n        self.Antialiasing = antialiasing\n        self.setMaximumSize(200, 200)\n        self.setMinimumSize(200, 200)\n        self.radius = 100\n\n        #加载图片并缩放\n        self.image = QPixmap(\"head.jpg\").scaled(\n            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)\n\n        painter = QPainter(self.image)\n        if self.Antialiasing:\n            painter.setRenderHint(QPainter.Antialiasing, True)\n            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)\n            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n        path = QPainterPath()\n        path.addRoundedRect(\n            0, 0, self.width(), self.height(), self.radius, self.radius)\n        path.addRect(0,0,self.width(),self.height())\n        painter.setPen(Qt.NoPen)\n        painter.setBrush(Qt.green)\n        painter.drawPath(path)\n        self.setPixmap(self.image)\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n        layout.addWidget(Label(self))\n        layout.addWidget(Label(self, antialiasing=False))\n        self.setStyleSheet(\"background: black;\")\n\nif __name__ == \"__main__\":\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 使用 QPainter 的 setCompositionMode

    \n

    具体参考 Qt 圆形头像制作工具 抗锯齿 可缩放编辑

    \n
    //result_avatar_size 是我们最后生成的图片的长宽,可以是QSize(200, 200)的正圆\ndestination_image = QImage(result_avatar_size, QImage::Format_ARGB32_Premultiplied);\n//在黑色的正方形中间画一个透明的圆,作为头像遮罩\nQPainter painter(&destination_image);\npainter.setRenderHint(QPainter::Antialiasing);\n//全涂黑\npainter.fillRect(destination_image.rect(), QBrush(Qt::black, Qt::SolidPattern));\npainter.setCompositionMode(QPainter::CompositionMode_SourceOut);\npainter.setPen(Qt::NoPen);\npainter.setBrush(QBrush(Qt::transparent, Qt::SolidPattern));\n//画透明区域\npainter.drawEllipse(destination_image.rect());
    \n

    # 使用 QPainter 的切割方法(推荐)

    \n

    利用 QPainter.setClipPath 方法切割一个圆形的 QPainterPath

    \n

    \"circleimage2\"

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n'''\nCreated on 2017年8月25日\n@author: Irony.\"[讽刺]\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n'''\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QPainter, QPainterPath, QPen\nfrom PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout\n\n\n__Author__ = \"By: Irony.\\\"[讽刺]\\nQQ: 892768447\\nEmail: 892768447@qq.com\"\n__Copyright__ = \"Copyright (c) 2017 Irony.\\\"[讽刺]\"\n__Version__ = \"Version 1.0\"\n\n\nclass Label(QLabel):\n\n    def __init__(self, *args, antialiasing=True, **kwargs):\n        super(Label, self).__init__(*args, **kwargs)\n        self.Antialiasing = antialiasing\n        self.setMaximumSize(200, 200)\n        self.setMinimumSize(200, 200)\n        self.radius = 100\n\n        #####################核心实现#########################\n        self.target = QPixmap(self.size())  # 大小和控件一样\n        self.target.fill(Qt.transparent)  # 填充背景为透明\n\n        p = QPixmap(\"head.jpg\").scaled(  # 加载图片并缩放和控件一样大\n            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)\n\n        painter = QPainter(self.target)\n        if self.Antialiasing:\n            # 抗锯齿\n            painter.setRenderHint(QPainter.Antialiasing, True)\n            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)\n            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n#         painter.setPen(# 测试黑色圆圈\n#             QPen(Qt.black, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))\n        path = QPainterPath()\n        path.addRoundedRect(\n            0, 0, self.width(), self.height(), self.radius, self.radius)\n        #**** 切割为圆形 ****#\n        painter.setClipPath(path)\n#         painter.drawPath(path)  # 测试黑色圆圈\n\n        painter.drawPixmap(0, 0, p)\n        self.setPixmap(self.target)\n        #####################核心实现#########################\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n        layout.addWidget(Label(self))\n        layout.addWidget(Label(self, antialiasing=False))\n        self.setStyleSheet(\"background: black;\")\n\nif __name__ == \"__main__\":\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    ", + "tags": [ + "PyQt", + "圆形", + "图片" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\345\234\206\345\275\242/rss.xml" "b/tag/\345\234\206\345\275\242/rss.xml" new file mode 100644 index 00000000..ab230b93 --- /dev/null +++ "b/tag/\345\234\206\345\275\242/rss.xml" @@ -0,0 +1,229 @@ + + + + PyQt • Posts by "圆形" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 25 Sep 2018 14:13:26 +0000 + Tue, 25 Sep 2018 14:13:26 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/circleimage.html + PyQt5圆形图片 + https://pyqt5.com/circleimage.html + PyQt + 圆形 + 图片 + Tue, 25 Sep 2018 14:13:26 +0000 + + + + diff --git "a/tag/\345\234\206\350\247\222/feed.json" "b/tag/\345\234\206\350\247\222/feed.json" new file mode 100644 index 00000000..b44ba713 --- /dev/null +++ "b/tag/\345\234\206\350\247\222/feed.json" @@ -0,0 +1,21 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"圆角\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/shadowradius.html", + "url": "https://pyqt5.com/shadowradius.html", + "title": "PyQt5无边框圆角阴影", + "date_published": "2019-04-25T16:06:26.000Z", + "content_html": "

    在做 PyQt 窗口开发中经常会遇到要做一些无边框不规则的窗口,可能还会带有阴影效果,这里演示做一个简单的无边框圆角的窗口,原理就在于背景窗口的透明和一层有色背景控件的叠加。

    \n\n

    # 原理说明

    \n
      \n
    1. 黑色(方便说明)的 QDialog 或者 QWidget 作为全透明无边框窗口。
    2. \n
    3. 其中白色的 QWidget 才是主要显示圆角和阴影的窗口,用于承载其它控件的显示。
    4. \n
    5. 注意红色和紫色的方框内的层次。
    6. \n
    7. 另:如果要熟悉纯代码编写请看 FramelessDialog.py
    8. \n
    \n

    如图:

    \n

    \"FramelessDialog1\"

    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月25日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: FramelessWidget\n@description: 无边框圆角带阴影窗口 \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect\nfrom frameless import Ui_Dialog\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019'\n\n\nclass Window(QDialog, Ui_Dialog):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.mPos = None\n        self.setupUi(self)\n        self.closeButton.clicked.connect(self.close)\n        # 重点\n        # 无边框\n        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)\n        # 背景透明(就是ui中黑色背景的那个控件)\n        self.setAttribute(Qt.WA_TranslucentBackground, True)\n\n        # 添加阴影\n        effect = QGraphicsDropShadowEffect(self)\n        effect.setBlurRadius(12)\n        effect.setOffset(0, 0)\n        effect.setColor(Qt.gray)\n        self.setGraphicsEffect(effect)\n\n    # 加上简单的移动功能\n\n    def mousePressEvent(self, event):\n        \"\"\"鼠标点击事件\"\"\"\n        if event.button() == Qt.LeftButton:\n            self.mPos = event.pos()\n        event.accept()\n\n    def mouseReleaseEvent(self, event):\n        '''鼠标弹起事件'''\n        self.mPos = None\n        event.accept()\n\n    def mouseMoveEvent(self, event):\n        if event.buttons() == Qt.LeftButton and self.mPos:\n            self.move(self.mapToGlobal(event.pos() - self.mPos))\n        event.accept()\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"FramelessDialog\"

    \n

    # 下载

    \n

    无边框圆角阴影.zip

    \n", + "tags": [ + "PyQt", + "阴影", + "无边框", + "圆角" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\345\234\206\350\247\222/rss.xml" "b/tag/\345\234\206\350\247\222/rss.xml" new file mode 100644 index 00000000..0da4b647 --- /dev/null +++ "b/tag/\345\234\206\350\247\222/rss.xml" @@ -0,0 +1,151 @@ + + + + PyQt • Posts by "圆角" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 25 Apr 2019 16:06:26 +0000 + Thu, 25 Apr 2019 16:06:26 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/shadowradius.html + PyQt5无边框圆角阴影 + https://pyqt5.com/shadowradius.html + PyQt + 阴影 + 无边框 + 圆角 + Thu, 25 Apr 2019 16:06:26 +0000 + + + + diff --git "a/tag/\345\274\202\345\270\270/feed.json" "b/tag/\345\274\202\345\270\270/feed.json" new file mode 100644 index 00000000..b67019d1 --- /dev/null +++ "b/tag/\345\274\202\345\270\270/feed.json" @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"异常\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/cgitb.html", + "url": "https://pyqt5.com/cgitb.html", + "title": "异常捕获之cgitb模块", + "date_published": "2018-09-17T15:17:06.000Z", + "content_html": "

    cgitb 模块为 Python 脚本提供了一个特殊的异常管理器。名字有点误导人,它最初设计是为了以 HTML 格式展示 cgi 脚本的大量异常信息。后来,他扩展为也可以展示纯文本信息。该模块激活后,如果发生了未捕获的异常,将会展示格式化的输出报告。该报告包括源代码每一层的回溯,以及当前执行程序的参数和局部变量。以及,你可以选择将这些信息存到一个文件里,而不是发送到浏览器。

    \n\n

    # 用途

    \n

    当编辑器中无法显示错误信息时,尤其是 PyQt ,可以尝试在 cmd 中运行代码,或者使用此模块来得到错误信息。

    \n

    # 介绍

    \n

    # cgitb.enable

    \n
    cgitb.enable(display=1, logdir=None, context=5, format=\"html\")
    \n

    参数说明

    \n
      \n
    1. display 1,发送至浏览器;0, 不发送
    2. \n
    3. logdir 如果有的话,写到该目录下
    4. \n
    5. context 显示错误代码周围的代码行数
    6. \n
    7. format 是否显示为 HTML,除了’html’之外的所有值,都会显示为纯文本
    8. \n
    \n

    # cgitb.handle

    \n
    cgitb.handle(info=None)
    \n

    参数说明

    \n
      \n
    1. 如果你想用 cgitb 处理异常,你可以调用这个函数。
    2. \n
    3. info 应当是含有异常类型、异常值和 traceback 对象的三元组
    4. \n
    5. 如同 sys.exc_info () 返回的那样。如果不提供 info,则从 sys.exc_info 中获取。
    6. \n
    \n

    # 如何使用

    \n

    以下代码放在最开始执行

    \n
    import cgitb\nimport sys\nsys.excepthook = cgitb.Hook(1, None, 5, sys.stderr, 'text')
    \n", + "tags": [ + "Python", + "异常" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\345\274\202\345\270\270/rss.xml" "b/tag/\345\274\202\345\270\270/rss.xml" new file mode 100644 index 00000000..8d1e3206 --- /dev/null +++ "b/tag/\345\274\202\345\270\270/rss.xml" @@ -0,0 +1,93 @@ + + + + PyQt • Posts by "异常" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Mon, 17 Sep 2018 15:17:06 +0000 + Mon, 17 Sep 2018 15:17:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/cgitb.html + 异常捕获之cgitb模块 + https://pyqt5.com/cgitb.html + Python + 异常 + Mon, 17 Sep 2018 15:17:06 +0000 + + + + diff --git "a/tag/\345\274\202\346\255\245/feed.json" "b/tag/\345\274\202\346\255\245/feed.json" new file mode 100644 index 00000000..d0755c71 --- /dev/null +++ "b/tag/\345\274\202\346\255\245/feed.json" @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"异步\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pyqt5asyncio.html", + "url": "https://pyqt5.com/pyqt5asyncio.html", + "title": "PyQt5结合Asyncio异步", + "date_published": "2018-10-24T06:32:26.000Z", + "content_html": "

    今天尝试了下 quamash 框架,该框架是一个 PyQt 的异步事件循环封装库,使用 Python3+ 的 asyncio 这个异步库。在看了该项目的内容后发现只有一个简单的进度条例子,故尝试用其来下载网络图片并显示。

    \n\n

    # 安装依赖

    \n
      \n
    1. pip install quamash
    2. \n
    3. pip install aiohttp
    4. \n
    5. Python3.5+ 和 PyQt5
    6. \n
    \n

    这里使用 aiohttp 是因为它基于 asyncio 封装的网络操作库,常见的 getpost 等方法,不过它只支持 Python3.5 及以上的版本,主要是它使用了 async def 这样的语法。

    \n

    # 说明

    \n
      \n
    1. 在创建 QApplication 后随即设置替换事件循环 loop
    2. \n
    \n
    app = QApplication(sys.argv)\nloop = QEventLoop(app)\nasyncio.set_event_loop(loop)\nw = Window()
    \n
      \n
    1. 通过 asyncio.ensure_future(func(), loop=loop) 来执行某个异步函数
    2. \n
    \n

    # 流程

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    WindowinitSession(初始化 session)
    下载按钮doDownload(执行_doDownload 方法)
    session.get(下载 json 数据进行解析)
    添加到界面_doDownloadImage(对单张图片进行下载)
    \n

    # 源码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月24日\n@author: Irony\n@site: https://github.com/892768447\n@email: 892768447@qq.com\n@file: AsyncioUiClient\n@description: \n\"\"\"\nimport asyncio\n\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap, QMovie\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton,\\\n    QApplication, QListWidget, QListWidgetItem, QLabel, QMessageBox\nimport aiohttp\nfrom quamash import QEventLoop\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\nUrl = 'https://www.doutula.com/api/search?keyword=%E6%9C%80%E6%96%B0%E8%A1%A8%E6%83%85&mime=0&page={}'\nHeaders = {\n    ':authority': 'www.doutula.com',\n    ':method': 'GET',\n    ':scheme': 'https',\n    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',\n    'accept-language': 'zh-CN,zh;q=0.9',\n    'cache-control': 'max-age=0',\n    'dnt': '1',\n    'upgrade-insecure-requests': '1',\n    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6756.400 QQBrowser/10.2.2498.400'\n}\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.listWidget = QListWidget(self)\n        self.listWidget.setSpacing(2)  # item直接的间隔\n        # 隐藏横向滚动条\n        self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        # 让list 从左到右排列\n        self.listWidget.setFlow(self.listWidget.LeftToRight)\n        # 自动换行\n        self.listWidget.setWrapping(True)\n        self.listWidget.setResizeMode(self.listWidget.Adjust)\n\n        self.buttonMsg = QPushButton('弹出提示框', self, clicked=self.showMessage)\n        self.buttonDown = QPushButton('下载图片', self, clicked=self.doDownload)\n        layout.addWidget(self.listWidget)\n        layout.addWidget(self.buttonMsg)\n        layout.addWidget(self.buttonDown)\n        self.currentPage = 0\n        self.initSession()  # 其实没必要,session主要用在需要登录的网站。缓存cookie用\n\n    def initSession(self):\n        async def _initSession():\n            # 初始化session\n            self.session = aiohttp.ClientSession(loop=loop)\n            print(self.session)\n        asyncio.ensure_future(_initSession(), loop=loop)\n\n    async def _doDownloadImage(self, url):\n        # 下载图片并添加到界面\n        async with self.session.get(url) as resp:\n            data = await resp.read()\n            if not data:\n                print('下载失败: ', url)\n                return\n            path = os.path.join('tmp', os.path.basename(url))\n            with open(path, 'wb') as fp:\n                fp.write(data)\n            item = QListWidgetItem(url, self.listWidget)\n            image = QPixmap(path)\n            item.setSizeHint(image.size())\n            label = QLabel(self.listWidget)\n            label.setPixmap(image)\n            if path.endswith('.gif'):  # 可能是动态图\n                label.setMovie(QMovie(path))\n            self.listWidget.setItemWidget(item, label)\n            self.listWidget.scrollToBottom()\n\n    async def _doDownload(self):\n        # 下载工作\n        if self.currentPage == -1:\n            QMessageBox.information(self, '提示', '已经没有更多了')\n            return\n        self.currentPage += 1\n        url = Url.format(self.currentPage)\n        print('get url: ', url)\n        async with self.session.get(url, headers=Headers) as resp:\n            data = await resp.json()\n            if not data:\n                return\n            data = data.get('data', None)\n            if not data:\n                self.currentPage = -1\n                print('已经是最后一页了')\n                return\n            # 解析json并生成item添加到界面中\n            for entity in data.get('list', []):\n                url = entity.get('image_url', None)\n                if not url:\n                    continue\n                await self._doDownloadImage(url)  # 下载图片\n\n    def doDownload(self):\n        # 响应按钮点击调用\n        asyncio.ensure_future(self._doDownload(), loop=loop)\n\n    def showMessage(self):\n        # 显示对话框\n        app.aboutQt()\n\n    def closeEvent(self, event):\n        if not self.session.closed:\n            asyncio.ensure_future(self.session.close(), loop=loop)\n        super(Window, self).closeEvent(event)\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    import os\n    os.makedirs('tmp', exist_ok=True)\n    sys.excepthook = cgitb.enable(1, None, 5, 'text')\n    app = QApplication(sys.argv)\n    loop = QEventLoop(app)\n    asyncio.set_event_loop(loop)\n    w = Window()\n    w.show()\n    with loop:\n        loop.run_forever()
    \n

    # 效果图

    \n

    \"pyqt5asyncio\"

    \n", + "tags": [ + "PyQt", + "Asyncio", + "异步" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\345\274\202\346\255\245/rss.xml" "b/tag/\345\274\202\346\255\245/rss.xml" new file mode 100644 index 00000000..c5f41ba5 --- /dev/null +++ "b/tag/\345\274\202\346\255\245/rss.xml" @@ -0,0 +1,279 @@ + + + + PyQt • Posts by "异步" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 24 Oct 2018 06:32:26 +0000 + Wed, 24 Oct 2018 06:32:26 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pyqt5asyncio.html + PyQt5结合Asyncio异步 + https://pyqt5.com/pyqt5asyncio.html + PyQt + Asyncio + 异步 + Wed, 24 Oct 2018 06:32:26 +0000 + + + + diff --git "a/tag/\346\210\252\345\233\276/feed.json" "b/tag/\346\210\252\345\233\276/feed.json" new file mode 100644 index 00000000..b3ef5f6d --- /dev/null +++ "b/tag/\346\210\252\345\233\276/feed.json" @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"截图\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/calljava.html", + "url": "https://pyqt5.com/calljava.html", + "title": "Python调用Java对Excel截图", + "date_published": "2019-03-12T13:15:06.000Z", + "content_html": "

    有的时候会遇到一些奇葩的需求,就是用 Excel 做报表,但是需要对里面的数据进行填充并生成报表图片,发送出去。这里记录用 python 调用 jar 包对 excel 文件进行公式计算和截图,数据填充可以用 xlrd 或者 openpyxl

    \n\n

    利用 jpype 模块初始化 java 虚拟机加载 jar 包然后执行其中的功能。

    \n

    # 代码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年3月12日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: CallJava\n@description: \n\"\"\"\nimport os\n\nimport jpype\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019'\n\n\ndef convertToImage():\n    Workbook = jpype.JClass('com.aspose.cells.Workbook')\n    ImageFormat = jpype.JClass('com.aspose.cells.ImageFormat')\n    ImageOrPrintOptions = jpype.JClass(\n        'com.aspose.cells.ImageOrPrintOptions')\n    SheetRender = jpype.JClass('com.aspose.cells.SheetRender')\n\n    book = Workbook(os.path.abspath('data/test.xlsx').replace('\\\\', '/'))\n    # 保存为html\n    book.save('data/index.html', 12)\n    # 保存为pdf\n    book.save('data/test.pdf')\n\n    # 截图\n    imgOptions = ImageOrPrintOptions()\n    # imgOptions.setQuality(100)\n    imgOptions.setOnePagePerSheet(True)\n\n    # 输出图片格式\n#     imgOptions.setImageFormat(ImageFormat.getJpeg())\n    imgOptions.setImageFormat(ImageFormat.getPng())\n\n    # 计算\n    CalculationOptions = jpype.JClass(\n        'com.aspose.cells.CalculationOptions')\n    opt = CalculationOptions()\n    # 对Sheet1中的公式进行计算\n    sheet = book.getWorksheets().get('Sheet1')\n    sheet.calculateFormula(opt, True)\n\n    # 设置区域\n    pageSetup = sheet.getPageSetup()\n    # 去掉边距\n    pageSetup.setBottomMargin(0.)\n    pageSetup.setLeftMargin(0.)\n    pageSetup.setRightMargin(0.)\n    pageSetup.setTopMargin(0.)\n    # 设置要截图的区域(对角线)\n    pageSetup.setPrintArea('A0:C2')\n    # Create a SheetRender object for the target sheet\n    sr = SheetRender(sheet, imgOptions)\n    for page in range(sr.getPageCount()):\n        # Generate an image for the worksheet\n        sr.toImage(\n            page, os.path.join('data', '%d.png' % (page + 1)))\n\n\ndef test():\n    # emm这里不知道什么用绝对路径就报错\n    libs = '{};{}'.format(\n        'libs/bcprov-jdk16-146.jar',\n        'libs/aspose-cells-19.2.jar'\n    )\n    command = (jpype.getDefaultJVMPath(),\n                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',\n                   '-Djava.class.path={0}'.format(libs))\n    print(command)\n    jpype.startJVM(jpype.getDefaultJVMPath(),\n                   '-ea', '-Xmn128m', '-Xms512M', '-Xmx512M',\n                   '-Djava.class.path={0}'.format(libs)\n                   )\n    # 解决多线程问题\n    jpype.attachThreadToJVM()\n    # 对excel截图\n    convertToImage()\n    # 关闭虚拟机\n    jpype.shutdownJVM()\n    print('截图完成')\n\n\nif __name__ == '__main__':\n    test()
    \n

    # 附件

    \n

    调用 java 生成报表.7z

    \n

    解压后进入 whls 文件夹安装对应版本的 jpype 包

    \n

    # 效果图

    \n

    \"calljava\"

    \n", + "tags": [ + "Python", + "截图" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\346\210\252\345\233\276/rss.xml" "b/tag/\346\210\252\345\233\276/rss.xml" new file mode 100644 index 00000000..b682a1a8 --- /dev/null +++ "b/tag/\346\210\252\345\233\276/rss.xml" @@ -0,0 +1,167 @@ + + + + PyQt • Posts by "截图" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 12 Mar 2019 13:15:06 +0000 + Tue, 12 Mar 2019 13:15:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/calljava.html + Python调用Java对Excel截图 + https://pyqt5.com/calljava.html + Python + 截图 + Tue, 12 Mar 2019 13:15:06 +0000 + + + + diff --git "a/tag/\346\213\246\346\210\252print/feed.json" "b/tag/\346\213\246\346\210\252print/feed.json" new file mode 100644 index 00000000..103fda56 --- /dev/null +++ "b/tag/\346\213\246\346\210\252print/feed.json" @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"拦截print\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "url": "https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html", + "title": "python 获取子进程print信息  ", + "date_published": "2019-05-24T06:39:44.000Z", + "content_html": "

    在 PyQt 中使用子线程读取子进程 Python 脚本的 print 输出流内容。

    \n\n

    问题所在:

    \n

    \"image.png\"

    \n

    如果模块都由自己开发, 正常操作

    \n

    \"image.png\"

    \n

    但是因为不能改,所以只能拦截:
    \n代码:

    \n
    pythonPath = self.pythonPath_cb.currentText()\n\nif suffix == \"py\":\n    # 首次\n    self.pyCommand = [pythonPath, path]\n    self.modifiedReloadPython(path)\ndef modifiedReloadPython(self, path_):\n    os.chdir(os.path.dirname(path_))\n    # 子进程调用\n    self.p = subprocess.Popen(self.pyCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n    # self.stdoutWorker.p = self.p\n    self.stdoutWorker = Worker(self.p)\n    self.stdoutWorker.stdout_signal.connect(lambda x: self.error_te.append(\"PYDEBUG:\\n\" + x))\n    self.stdoutWorker.start()\nclass Worker(QThread):\n    stdout_signal = pyqtSignal(str)\n\n    def __init__(self, p, parent=None):\n        super().__init__(parent)\n        self.p = p\n\n    def run(self):\n        while True:\n            QApplication.processEvents()\n            if self.p is not None:\n                line = self.p.stdout.readline()\n                # line = line.strip()\n                if line != b'':\n                    try:\n                        info = line.decode()\n                        self.stdout_signal.emit(info)\n                    except:\n                        self.stdout_signal.emit(repr(line))\n    
    \n", + "tags": [ + "Python", + "subprocess.Popen", + "拦截print" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\346\213\246\346\210\252print/rss.xml" "b/tag/\346\213\246\346\210\252print/rss.xml" new file mode 100644 index 00000000..cac69ca0 --- /dev/null +++ "b/tag/\346\213\246\346\210\252print/rss.xml" @@ -0,0 +1,109 @@ + + + + PyQt • Posts by "拦截print" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Fri, 24 May 2019 06:39:44 +0000 + Fri, 24 May 2019 06:39:44 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + python 获取子进程print信息   + https://pyqt5.com/pyqt_get_subprocess_pipeline_625781186.html + Python + subprocess.Popen + 拦截print + Fri, 24 May 2019 06:39:44 +0000 + + + + diff --git "a/tag/\346\227\240\350\276\271\346\241\206/feed.json" "b/tag/\346\227\240\350\276\271\346\241\206/feed.json" new file mode 100644 index 00000000..ad177a24 --- /dev/null +++ "b/tag/\346\227\240\350\276\271\346\241\206/feed.json" @@ -0,0 +1,21 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"无边框\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/shadowradius.html", + "url": "https://pyqt5.com/shadowradius.html", + "title": "PyQt5无边框圆角阴影", + "date_published": "2019-04-25T16:06:26.000Z", + "content_html": "

    在做 PyQt 窗口开发中经常会遇到要做一些无边框不规则的窗口,可能还会带有阴影效果,这里演示做一个简单的无边框圆角的窗口,原理就在于背景窗口的透明和一层有色背景控件的叠加。

    \n\n

    # 原理说明

    \n
      \n
    1. 黑色(方便说明)的 QDialog 或者 QWidget 作为全透明无边框窗口。
    2. \n
    3. 其中白色的 QWidget 才是主要显示圆角和阴影的窗口,用于承载其它控件的显示。
    4. \n
    5. 注意红色和紫色的方框内的层次。
    6. \n
    7. 另:如果要熟悉纯代码编写请看 FramelessDialog.py
    8. \n
    \n

    如图:

    \n

    \"FramelessDialog1\"

    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月25日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: FramelessWidget\n@description: 无边框圆角带阴影窗口 \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect\nfrom frameless import Ui_Dialog\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019'\n\n\nclass Window(QDialog, Ui_Dialog):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.mPos = None\n        self.setupUi(self)\n        self.closeButton.clicked.connect(self.close)\n        # 重点\n        # 无边框\n        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)\n        # 背景透明(就是ui中黑色背景的那个控件)\n        self.setAttribute(Qt.WA_TranslucentBackground, True)\n\n        # 添加阴影\n        effect = QGraphicsDropShadowEffect(self)\n        effect.setBlurRadius(12)\n        effect.setOffset(0, 0)\n        effect.setColor(Qt.gray)\n        self.setGraphicsEffect(effect)\n\n    # 加上简单的移动功能\n\n    def mousePressEvent(self, event):\n        \"\"\"鼠标点击事件\"\"\"\n        if event.button() == Qt.LeftButton:\n            self.mPos = event.pos()\n        event.accept()\n\n    def mouseReleaseEvent(self, event):\n        '''鼠标弹起事件'''\n        self.mPos = None\n        event.accept()\n\n    def mouseMoveEvent(self, event):\n        if event.buttons() == Qt.LeftButton and self.mPos:\n            self.move(self.mapToGlobal(event.pos() - self.mPos))\n        event.accept()\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"FramelessDialog\"

    \n

    # 下载

    \n

    无边框圆角阴影.zip

    \n", + "tags": [ + "PyQt", + "阴影", + "无边框", + "圆角" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\346\227\240\350\276\271\346\241\206/rss.xml" "b/tag/\346\227\240\350\276\271\346\241\206/rss.xml" new file mode 100644 index 00000000..96c48fcd --- /dev/null +++ "b/tag/\346\227\240\350\276\271\346\241\206/rss.xml" @@ -0,0 +1,151 @@ + + + + PyQt • Posts by "无边框" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 25 Apr 2019 16:06:26 +0000 + Thu, 25 Apr 2019 16:06:26 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/shadowradius.html + PyQt5无边框圆角阴影 + https://pyqt5.com/shadowradius.html + PyQt + 阴影 + 无边框 + 圆角 + Thu, 25 Apr 2019 16:06:26 +0000 + + + + diff --git "a/tag/\346\260\224\346\263\241/feed.json" "b/tag/\346\260\224\346\263\241/feed.json" new file mode 100644 index 00000000..0e45c090 --- /dev/null +++ "b/tag/\346\260\224\346\263\241/feed.json" @@ -0,0 +1,21 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"气泡\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/qtninepatch.html", + "url": "https://pyqt5.com/qtninepatch.html", + "title": "PyQt5显示.9格式的PNG图片", + "date_published": "2018-10-26T02:00:08.000Z", + "content_html": "

    做过安卓开发的和使用过 QQ 的都知道 .9.png 这种图片格式,效果就如 QQ 的聊天气泡一样可以拉伸,这种格式的图片允许开发人员定义可扩展区域,当需要延伸图片以填充比图片本身更大区域时,可扩展区的内容被延展;允许开发人员定义内容显示区,用于显示文字或其他内容。目前在 Github 上有两个 C++ 版本的,在这里我把它们都用 Python 实现了一遍。另外一个我也为 PyQt 提供了编译好的 pyd 文件。

    \n\n

    # C++ 版本

    \n

    在 Github 开源库中搜索到两个 C++ 版本的

    \n
      \n
    1. 一个是 NinePatchQt
    2. \n
    3. 一个是 QtNinePatch
    4. \n
    \n

    # PyQt5 版本

    \n

    这里也分为两个版本,都是基于上面的 C++ 源码翻译改写过来的,具体的例子见项目里面的测试代码吧。

    \n
      \n
    1. QtNinePatch 是参考第一个源码编写,用法是在 paintEvent 中调用
    2. \n
    3. QtNinePatch2 是参考第二个源码编写,用法是 pixmap = QtNinePatch.createPixmapFromNinePatchImage(self.image, self.width(), self.height()) 直接得到一个处理好的 QPixmap 对象来使用
    4. \n
    \n

    # 说明

    \n
      \n
    1. 建议优先使用 pyd 版本的(后续提供 Python3.4 3.5 3.6 3.7 编译好的 32 为库文件),也可以自行编译,编译步骤见下文。
    2. \n
    3. 其次可以使用纯 python 版本 2 的(个人觉得方便调用)
    4. \n
    5. 最后再考虑纯 python 版本 1 的吧
    6. \n
    7. 以上为个人意见,两个 C++ 版本的写法不一样,但是核心算法应该是类似的。
    8. \n
    \n

    # 自行编译

    \n
      \n
    1. 首先要安装好 Qt、PyQt5、编译安装对应的 sip、对应的 VC++ 编译工具
    2. \n
    3. 用 Qt Creator 打开 pro 文件进行编译
    4. \n
    5. 进入源码中的 sip 文件夹修改 configure.py 文件
    6. \n
    \n
    # 这里是你的VC版本和对应的Qt目录中的文件夹\nconfig.platform = "win32-msvc2010"\nqt_path = 'D:/soft/Qt/Qt5.5.1/5.5/msvc2010'
    \n
      \n
    1. 最后执行 python configure.py 来编译
    2. \n
    \n

    # 下载

    \n

    https://github.com/PyQt5/PyQt/tree/master/QLabel

    \n

    # 效果图

    \n

    \"NinePatchImage\"

    \n", + "tags": [ + "PyQt", + "图片", + "气泡", + ".9png" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\346\260\224\346\263\241/rss.xml" "b/tag/\346\260\224\346\263\241/rss.xml" new file mode 100644 index 00000000..3c1938c9 --- /dev/null +++ "b/tag/\346\260\224\346\263\241/rss.xml" @@ -0,0 +1,105 @@ + + + + PyQt • Posts by "气泡" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Fri, 26 Oct 2018 02:00:08 +0000 + Fri, 26 Oct 2018 02:00:08 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/qtninepatch.html + PyQt5显示.9格式的PNG图片 + https://pyqt5.com/qtninepatch.html + PyQt + 图片 + 气泡 + .9png + Fri, 26 Oct 2018 02:00:08 +0000 + + + + diff --git "a/tag/\346\265\217\350\247\210\345\231\250/feed.json" "b/tag/\346\265\217\350\247\210\345\231\250/feed.json" new file mode 100644 index 00000000..ef11d5ed --- /dev/null +++ "b/tag/\346\265\217\350\247\210\345\231\250/feed.json" @@ -0,0 +1,33 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"浏览器\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/qtwebjs.html", + "url": "https://pyqt5.com/qtwebjs.html", + "title": "QtWebkit和QWebEngineView与Javascript交互", + "date_published": "2019-05-22T03:30:36.000Z", + "content_html": "

    以前还是 QWebView 的时候和 Javascript 交互起来很方便,但是到了 Qt5.6 以后改用了 QWebEngineView ,并通过其提供的 qwebchannel.js 来进行交互。可能是由于刚出来的原因,这玩意儿有个 bug 就是必须在每次加载页面的时候手动注入,跳转页面后就失效了,需要手动注入,目前有没有修复具体未测试。这里对 QWebViewQWebEngineView 与 Js 交互都做了一个示例。

    \n\n

    # 说明

    \n
      \n
    1. 针对 QWebView 通过 QWebFrameaddToJavaScriptWindowObject 把对象传递到 Javascript
    2. \n
    3. 针对 QWebEngineView 通过 QWebChannel.registerObject('Bridge', QObject) 把对象传递到 Javascript
    4. \n
    5. 可以通过 @pyqtSlot 装饰器来申明该方法可以暴露给 Javascript 调用
    6. \n
    \n
    @pyqtSlot(str)\ndef callFromJs(self, text):\n    QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))
    \n
      \n
    1. 针对 QWebViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
    2. \n
    \n
    // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\nBridge.windowTitleChanged.connect({fun: function(title) {\n    showLog(\"标题被修改为:\" + title);\n}}, \"fun\");\n\n// 绑定自定义的信号customSignal\nBridge.customSignal.connect({fun: function(text) {\n    showLog(\"收到自定义信号内容:\" + text);\n}}, \"fun\");
    \n
      \n
    1. 针对 QWebEngineViewJavascript 中获取该对象,可以通过该对象对窗口属性以及信号和暴露出的方法进行调用
    2. \n
    \n
    new QWebChannel(qt.webChannelTransport,\n    function(channel) {\n        window.Bridge = channel.objects.Bridge;\n        \n        // 这里绑定窗口的标题变化信号(这个信号是由QWidget内部的)\n        Bridge.windowTitleChanged.connect(function(title) {\n            showLog(\"标题被修改为:\" + title);\n        });\n        \n        // 绑定自定义的信号customSignal\n        Bridge.customSignal.connect(function(text) {\n           showLog(\"收到自定义信号内容:\" + text);\n        });\n    }\n);
    \n

    # 代码

    \n

    QWebViewhttps://github.com/PyQt5/PyQt/blob/master/QWebView/JsSignals.py

    \n

    QWebEngineViewhttps://github.com/PyQt5/PyQt/blob/master/QWebEngineView/JsSignals.py

    \n
      \n
    1. 针对 QWebView 的核心实现
    2. \n
    \n
    class WebView(QWebView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebView, self).__init__(*args, **kwargs)\n        self.initSettings()\n        # 暴露接口对象\n        self.page().mainFrame().javaScriptWindowObjectCleared.connect(self._exposeInterface)\n\n    def _exposeInterface(self):\n        \"\"\"向Js暴露调用本地方法接口\n        \"\"\"\n        self.page().mainFrame().addToJavaScriptWindowObject('Bridge', self)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
    \n
      \n
    1. 针对 QWebEngineView 的核心实现
    2. \n
    \n
    class WebEngineView(QWebEngineView):\n\n    customSignal = pyqtSignal(str)\n\n    def __init__(self, *args, **kwargs):\n        super(WebEngineView, self).__init__(*args, **kwargs)\n        self.channel = QWebChannel(self)\n        # 把自身对象传递进去\n        self.channel.registerObject('Bridge', self)\n        # 设置交互接口\n        self.page().setWebChannel(self.channel)\n\n    # 注意pyqtSlot用于把该函数暴露给js可以调用\n    @pyqtSlot(str)\n    def callFromJs(self, text):\n        QMessageBox.information(self, \"提示\", \"来自js调用:{}\".format(text))\n\n    def sendCustomSignal(self):\n        # 发送自定义信号\n        self.customSignal.emit('当前时间: ' + str(time()))
    \n

    # 效果图

    \n

    \"JsSignals\"

    \n", + "tags": [ + "PyQt", + "QWebView", + "QWebEngineView", + "浏览器" + ] + }, + { + "id": "https://pyqt5.com/webviewnew.html", + "url": "https://pyqt5.com/webviewnew.html", + "title": "PyQt5编译QWebView与QWebEngineView共存", + "date_published": "2019-01-12T11:28:06.000Z", + "content_html": "

    在 PyQt5.5 过后移除了 QWebView 控件,改用 QWebEngineView ,但是这个刚开始用起来不是很方便,最近在整理一些例子的时候需要同时使用 QWebViewQWebEngineView ,故希望把 QWebView 重新加入到后面的 PyQt5 版本中,查看 PyQt5.10.1 的源码发现里面其实是有 QWebView 的,只是因为 Qt5.10.1 中没有编译好的 dll 等导致无法编译。

    \n\n

    # 准备工作

    \n
      \n
    1. 安装 VS2015
    2. \n
    3. 安装 Qt5.10.1
    4. \n
    5. 前往 https://github.com/annulen/webkit/releases 下载对应的文件,比如:qtwebkit-5.212.0_alpha2-qt59-msvc2015-x86.zip
    6. \n
    7. 下载 PyQt5.10.1 源码
    8. \n
    9. 下载对应版本的 sip 源码
    10. \n
    \n

    # 编译

    \n
      \n
    1. 设置环境变量 set PATH=D:\\soft\\Qt\\Qt5.10.1\\5.10.1\\msvc2015\\bin;%PATH%
    2. \n
    3. 首先进入 vs2015 命令行编译 sip 并安装, python configure.py && nmake && nmake install
    4. \n
    5. 进入 PyQt5.10.1 源码编译安装即可
    6. \n
    7. 如果要减少 PyQt5.10.1 的编译可以试试以下代码
    8. \n
    \n
    D:\\soft\\Python35\\python configure.py --confirm-license --no-designer-plugin --no-qml-plugin --disable=dbus --disable=QAxContainer --disable=QtAndroidExtras --disable=QtBluetooth --disable=QtDBus --disable=QtDesigner --disable=Enginio --disable=QtLocation --disable=QtMacExtras --disable=QtMultimedia --disable=QtMultimediaWidgets --disable=QtNfc --disable=QtSerialPort --disable=QtSql --disable=QtSvg --disable=QtTest --disable=QtWinExtras --disable=QtX11Extras --disable=QtXml --disable=QtXmlPatterns --disable=pylupdate --disable=pyrcc
    ", + "tags": [ + "PyQt", + "QWebView", + "浏览器" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\346\265\217\350\247\210\345\231\250/rss.xml" "b/tag/\346\265\217\350\247\210\345\231\250/rss.xml" new file mode 100644 index 00000000..12a65af3 --- /dev/null +++ "b/tag/\346\265\217\350\247\210\345\231\250/rss.xml" @@ -0,0 +1,190 @@ + + + + PyQt • Posts by "浏览器" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 22 May 2019 03:30:36 +0000 + Wed, 22 May 2019 03:30:36 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/qtwebjs.html + QtWebkit和QWebEngineView与Javascript交互 + https://pyqt5.com/qtwebjs.html + PyQt + QWebView + QWebEngineView + 浏览器 + Wed, 22 May 2019 03:30:36 +0000 + + + + https://pyqt5.com/webviewnew.html + PyQt5编译QWebView与QWebEngineView共存 + https://pyqt5.com/webviewnew.html + PyQt + QWebView + 浏览器 + Sat, 12 Jan 2019 11:28:06 +0000 + + + + diff --git "a/tag/\346\273\221\345\212\250\346\235\241/feed.json" "b/tag/\346\273\221\345\212\250\346\235\241/feed.json" new file mode 100644 index 00000000..d6b664d4 --- /dev/null +++ "b/tag/\346\273\221\345\212\250\346\235\241/feed.json" @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"滑动条\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/jumpslider.html", + "url": "https://pyqt5.com/jumpslider.html", + "title": "PyQt5之QSlider滑动条点击定位", + "date_published": "2018-11-05T15:12:26.000Z", + "content_html": "

    QSlider 在通常情况下支持鼠标点击可以任意拖动,或者鼠标点击则往鼠标点击的方向移动一小格,这种移动一小格通常情况下用起来很不方便,比如我要做一个播放器的播放进度条,肯定是点击某个位置就直接跳到该位置,为此需要对 QSlider 的鼠标事件 mousePressEvent 进行重写。

    \n\n

    # 实现方法

    \n

    一般的想法就是重写 mousePressEvent 后,得到鼠标点击的 x 和 y 点然后进行比例换算,再通过 setValue 来设置值,其实 QSliderstyle 里面是有一个 sliderValueFromPosition 方法来计算值的。直接调用这个方法即可。

    \n
      \n
    1. 首先通过 QSlider.style().subControlRect 方法计算得到滑块的区域,当鼠标点击区域在此次时则交给系统自己处理(比如按住不放拖动)
    2. \n
    3. 通过 orientation 判断滑动条的方向(横竖)
    4. \n
    5. 通过 invertedAppearance 判断滑动条是否反向(左右、上下)
    6. \n
    7. 通过 QSlider.style().sliderValueFromPosition(最小值, 最大值, x或者y坐标, 宽度或者高度) 来计算得到值
    8. \n
    9. 最后通过 setValue 来设置值
    10. \n
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QSlider/ClickJumpSlider.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年11月5日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: JumpSlider\n@description: \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtWidgets import QSlider, QStyleOptionSlider, QStyle, QWidget,\\\n    QFormLayout, QLabel\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass JumpSlider(QSlider):\n\n    def mousePressEvent(self, event):\n        # 获取上面的拉动块位置\n        option = QStyleOptionSlider()\n        self.initStyleOption(option)\n        rect = self.style().subControlRect(\n            QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)\n        if rect.contains(event.pos()):\n            # 如果鼠标点击的位置在滑块上则交给Qt自行处理\n            super(JumpSlider, self).mousePressEvent(event)\n            return\n        if self.orientation() == Qt.Horizontal:\n            # 横向,要考虑invertedAppearance是否反向显示的问题\n            self.setValue(self.style().sliderValueFromPosition(\n                self.minimum(), self.maximum(),\n                event.x() if not self.invertedAppearance() else (self.width(\n                ) - event.x()), self.width()))\n        else:\n            # 纵向\n            self.setValue(self.style().sliderValueFromPosition(\n                self.minimum(), self.maximum(),\n                (self.height() - event.y()) if not self.invertedAppearance(\n                ) else event.y(), self.height()))\n\n\nclass TestWindow(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(TestWindow, self).__init__(*args, **kwargs)\n        layout = QFormLayout(self)\n\n        self.label1 = QLabel('0', self)\n        layout.addRow(self.label1, JumpSlider(\n            Qt.Horizontal, valueChanged=lambda v: self.label1.setText(str(v))))\n\n        # 横向-反向显示\n        self.label2 = QLabel('0', self)\n        layout.addRow(self.label2, JumpSlider(\n            Qt.Horizontal, invertedAppearance=True,\n            valueChanged=lambda v: self.label2.setText(str(v))))\n\n        self.label3 = QLabel('0', self)\n        layout.addRow(self.label3, JumpSlider(\n            Qt.Vertical, minimumHeight=200, valueChanged=lambda v: self.label3.setText(str(v))))\n\n        # 纵向反向显示\n        self.label4 = QLabel('0', self)\n        layout.addRow(self.label4, JumpSlider(\n            Qt.Vertical, invertedAppearance=True,\n            minimumHeight=200, valueChanged=lambda v: self.label4.setText(str(v))))\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, '')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = TestWindow()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"ClickJumpSlider\"

    \n", + "tags": [ + "PyQt", + "滑动条" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\346\273\221\345\212\250\346\235\241/rss.xml" "b/tag/\346\273\221\345\212\250\346\235\241/rss.xml" new file mode 100644 index 00000000..a631f859 --- /dev/null +++ "b/tag/\346\273\221\345\212\250\346\235\241/rss.xml" @@ -0,0 +1,166 @@ + + + + PyQt • Posts by "滑动条" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Mon, 05 Nov 2018 15:12:26 +0000 + Mon, 05 Nov 2018 15:12:26 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/jumpslider.html + PyQt5之QSlider滑动条点击定位 + https://pyqt5.com/jumpslider.html + PyQt + 滑动条 + Mon, 05 Nov 2018 15:12:26 +0000 + + + + diff --git "a/tag/\347\211\271\346\225\210/feed.json" "b/tag/\347\211\271\346\225\210/feed.json" new file mode 100644 index 00000000..6bfc5b3a --- /dev/null +++ "b/tag/\347\211\271\346\225\210/feed.json" @@ -0,0 +1,31 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"特效\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/rlatticeeffect.html", + "url": "https://pyqt5.com/rlatticeeffect.html", + "title": "PyQt5仿网页鼠标移动点阵特效", + "date_published": "2018-10-29T08:49:10.000Z", + "content_html": "

    Orz,前段时间有个 zz 需求,就是要做一个类似网页上很多个多点连线、鼠标移动跟随的那种炫酷特效,然后花了点时间在网上找了 js 做的,刚开始打算是嵌入 QWebView 来显示网页,后来研究了下 js 的算法代码,遂改用 QWidgetpaintEvent 直接绘制。

    \n\n

    # 大概思路

    \n
      \n
    1. 先根据窗口大小随机创建一些点
    2. \n
    3. 遍历这些点并找到与之相关联的点
    4. \n
    5. 在动画过程中绘制圆点和画两点之间的连线
    6. \n
    7. 属性动画 QPropertyAnimation 改变颜色的透明度
    8. \n
    \n

    # 题外

    \n
      \n
    1. 这里没有仔细去研究 js 里的算法优化,在浏览器里嗖嗖的就生成了,在 py 里好慢…
    2. \n
    3. 尽量在 py 里优化了循环操作,也简单的做了个 cython 加速也才提高了 1s ? 1 倍?..
    4. \n
    5. 不要只是为了好看用这玩意儿,和网页的效果一样,占 CPU !!! 没有任何意义
    6. \n
    7. 如果有更好的优化算法请告知,3Q
    8. \n
    9. pyd 是 python3.4 生成的,删掉 pyd 也能运行
    10. \n
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/RlatticeEffect.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年11月22日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: \n@description: \n\"\"\"\nfrom random import random\nfrom time import time\n\nfrom PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\\\n    Qt, QRectF, pyqtSignal\nfrom PyQt5.QtGui import QColor, QPainterPath, QPainter\nfrom PyQt5.QtWidgets import QWidget\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\ntry:\n    import pointtool  # @UnusedImport @UnresolvedImport\n    getDistance = pointtool.getDistance\n    findClose = pointtool.findClose\nexcept:\n    import math\n\n    def getDistance(p1, p2):\n        return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2)\n\n    def findClose(points):\n        plen = len(points)\n        for i in range(plen):\n            closest = [None, None, None, None, None]\n            p1 = points[i]\n            for j in range(plen):\n                p2 = points[j]\n                dte1 = getDistance(p1, p2)\n                if p1 != p2:\n                    placed = False\n                    for k in range(5):\n                        if not placed:\n                            if not closest[k]:\n                                closest[k] = p2\n                                placed = True\n                    for k in range(5):\n                        if not placed:\n                            if dte1 < getDistance(p1, closest[k]):\n                                closest[k] = p2\n                                placed = True\n            p1.closest = closest\n\n\nclass Target:\n\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n\nclass Point(QObject):\n\n    valueChanged = pyqtSignal()\n\n    def __init__(self, x, ox, y, oy, *args, **kwargs):\n        super(Point, self).__init__(*args, **kwargs)\n        self.__x = x\n        self._x = x\n        self.originX = ox\n        self._y = y\n        self.__y = y\n        self.originY = oy\n        # 5个闭合点\n        self.closest = [0, 0, 0, 0, 0]\n        # 圆半径\n        self.radius = 2 + random() * 2\n        # 连线颜色\n        self.lineColor = QColor(156, 217, 249)\n        # 圆颜色\n        self.circleColor = QColor(156, 217, 249)\n\n    def initAnimation(self):\n        # 属性动画\n        if not hasattr(self, 'xanimation'):\n            self.xanimation = QPropertyAnimation(\n                self, b'x', self, valueChanged=self.valueChanged.emit,\n                easingCurve=QEasingCurve.InOutSine)\n            self.yanimation = QPropertyAnimation(\n                self, b'y', self, valueChanged=self.valueChanged.emit,\n                easingCurve=QEasingCurve.InOutSine,\n                finished=self.updateAnimation)\n            self.updateAnimation()\n\n    def updateAnimation(self):\n        self.xanimation.stop()\n        self.yanimation.stop()\n        duration = (1 + random()) * 1000\n        self.xanimation.setDuration(duration)\n        self.yanimation.setDuration(duration)\n        self.xanimation.setStartValue(self.__x)\n        self.xanimation.setEndValue(self.originX - 50 + random() * 100)\n        self.yanimation.setStartValue(self.__y)\n        self.yanimation.setEndValue(self.originY - 50 + random() * 100)\n        self.xanimation.start()\n        self.yanimation.start()\n\n    @pyqtProperty(float)\n    def x(self):\n        return self._x\n\n    @x.setter\n    def x(self, x):\n        self._x = x\n\n    @pyqtProperty(float)\n    def y(self):\n        return self._y\n\n    @y.setter\n    def y(self, y):\n        self._y = y\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.setMouseTracking(True)\n        self.resize(800, 600)\n        self.points = []\n        self.target = Target(self.width() / 2, self.height() / 2)\n        self.initPoints()\n\n    def paintEvent(self, event):\n        super(Window, self).paintEvent(event)\n        painter = QPainter()\n        painter.begin(self)\n        painter.setRenderHint(QPainter.Antialiasing)\n        painter.fillRect(self.rect(), Qt.black)\n        self.animate(painter)\n        painter.end()\n\n    def mouseMoveEvent(self, event):\n        super(Window, self).mouseMoveEvent(event)\n        # 鼠标移动时更新xy坐标\n        self.target.x = event.x()\n        self.target.y = event.y()\n        self.update()\n\n    def initPoints(self):\n        t = time()\n        self.points.clear()\n        # 创建点\n        stepX = self.width() / 20\n        stepY = self.height() / 20\n        for x in range(0, self.width(), int(stepX)):\n            for y in range(0, self.height(), int(stepY)):\n                ox = x + random() * stepX\n                oy = y + random() * stepY\n                point = Point(ox, ox, oy, oy)\n                point.valueChanged.connect(self.update)\n                self.points.append(point)\n        print(time() - t)\n\n        t = time()\n        # 每个点寻找5个闭合点\n        findClose(self.points)\n        print(time() - t)\n\n    def animate(self, painter):\n        for p in self.points:\n            # 检测点的范围\n            value = abs(getDistance(self.target, p))\n            if value < 4000:\n                # 其实就是修改颜色透明度\n                p.lineColor.setAlphaF(0.3)\n                p.circleColor.setAlphaF(0.6)\n            elif value < 20000:\n                p.lineColor.setAlphaF(0.1)\n                p.circleColor.setAlphaF(0.3)\n            elif value < 40000:\n                p.lineColor.setAlphaF(0.02)\n                p.circleColor.setAlphaF(0.1)\n            else:\n                p.lineColor.setAlphaF(0)\n                p.circleColor.setAlphaF(0)\n\n            # 画线条\n            if p.lineColor.alpha():\n                for pc in p.closest:\n                    if not pc:\n                        continue\n                    path = QPainterPath()\n                    path.moveTo(p.x, p.y)\n                    path.lineTo(pc.x, pc.y)\n                    painter.save()\n                    painter.setPen(p.lineColor)\n                    painter.drawPath(path)\n                    painter.restore()\n\n            # 画圆\n            painter.save()\n            painter.setPen(Qt.NoPen)\n            painter.setBrush(p.circleColor)\n            painter.drawRoundedRect(QRectF(\n                p.x - p.radius, p.y - p.radius, 2 * p.radius, 2 * p.radius), p.radius, p.radius)\n            painter.restore()\n\n            # 开启动画\n            p.initAnimation()\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, '')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"RlatticeEffect\"

    \n", + "tags": [ + "PyQt", + "动画", + "特效" + ] + }, + { + "id": "https://pyqt5.com/likehtmleffect.html", + "url": "https://pyqt5.com/likehtmleffect.html", + "title": "PyQt5仿网页图片鼠标移动特效", + "date_published": "2018-10-23T09:57:03.000Z", + "content_html": "

    em,就是类似于那种游戏官网首页的图片,鼠标放上去后来回移动,图片的前景和背景错位移动。

    \n\n

    # 原理分析

    \n
      \n
    1. 2 张一样大小的透明图片,1 张作为背景,一张作为前景(比如说人物)。
    2. \n
    3. 当鼠标往左移动时,前景人物跟着往左移动,背景往右移动
    4. \n
    5. 计算好偏移量(见代码中)
    6. \n
    \n

    https://github.com/PyQt5/PyQt/blob/master/QLabel/ImageSlipped.py

    \n

    # 关键代码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月18日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: ImageSlipped\n@description: \n\"\"\"\nfrom PyQt5.QtGui import QPixmap, QPainter\nfrom PyQt5.QtWidgets import QWidget\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass SlippedImgWidget(QWidget):\n\n    def __init__(self, bg, fg, *args, **kwargs):\n        super(SlippedImgWidget, self).__init__(*args, **kwargs)\n        # 开启鼠标跟踪\n        self.setMouseTracking(True)\n        # 背景\n        self.bgPixmap = QPixmap(bg)\n        # 前景\n        self.pePixmap = QPixmap(fg)\n        # 最小尺寸(背景右边和下方隐藏10个像素)\n        size = self.bgPixmap.size()\n        self.setMinimumSize(size.width() - 10, size.height() - 10)\n        self.setMaximumSize(size.width() - 10, size.height() - 10)\n        # 分成10份用于鼠标移动判断\n        self.stepX = size.width() / 10\n        self.stepY = size.height() / 10\n        # 偏移量\n        self._offsets = [-4, -4, -4, -4]  # 背景(-4,-4),前景(-4,-4)\n\n    def mouseMoveEvent(self, event):\n        super(SlippedImgWidget, self).mouseMoveEvent(event)\n        pos = event.pos()\n\n        # 偏移量\n        offsetX = 5 - int(pos.x() / self.stepX)\n        offsetY = 5 - int(pos.y() / self.stepY)\n        self._offsets[0] = offsetX\n        self._offsets[1] = offsetY\n        self._offsets[2] = offsetX\n        self._offsets[3] = offsetY\n        # 刷新\n        self.update()\n\n    def paintEvent(self, event):\n        super(SlippedImgWidget, self).paintEvent(event)\n        # 绘制图形\n        painter = QPainter(self)\n        painter.setRenderHint(QPainter.Antialiasing)\n        # 左上角偏移5个像素画背景图片\n        painter.drawPixmap(\n            -5 + self._offsets[0],\n            -5 + self._offsets[1], self.bgPixmap)\n        # 右下角偏移5个像素画前景图片\n        painter.drawPixmap(\n            self.width() - self.pePixmap.width() + 5 - self._offsets[2],\n            self.height() - self.pePixmap.height() + 5 - self._offsets[3],\n            self.pePixmap\n        )\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = SlippedImgWidget('images/bg.png', 'images/fg.png')\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"ImageSlipped\"

    \n", + "tags": [ + "PyQt", + "特效" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\347\211\271\346\225\210/rss.xml" "b/tag/\347\211\271\346\225\210/rss.xml" new file mode 100644 index 00000000..f1ecce93 --- /dev/null +++ "b/tag/\347\211\271\346\225\210/rss.xml" @@ -0,0 +1,417 @@ + + + + PyQt • Posts by "特效" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Mon, 29 Oct 2018 08:49:10 +0000 + Mon, 29 Oct 2018 08:49:10 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/rlatticeeffect.html + PyQt5仿网页鼠标移动点阵特效 + https://pyqt5.com/rlatticeeffect.html + PyQt + 动画 + 特效 + Mon, 29 Oct 2018 08:49:10 +0000 + + + + https://pyqt5.com/likehtmleffect.html + PyQt5仿网页图片鼠标移动特效 + https://pyqt5.com/likehtmleffect.html + PyQt + 特效 + Tue, 23 Oct 2018 09:57:03 +0000 + + + + diff --git "a/tag/\347\252\227\345\217\243/feed.json" "b/tag/\347\252\227\345\217\243/feed.json" new file mode 100644 index 00000000..c9562330 --- /dev/null +++ "b/tag/\347\252\227\345\217\243/feed.json" @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"窗口\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/followwindow.html", + "url": "https://pyqt5.com/followwindow.html", + "title": "PyQt5窗口跟随其它窗口", + "date_published": "2018-10-23T07:08:56.000Z", + "content_html": "

    要实现 PyQt 窗口跟随其它外部的窗口,能想到两点办法,一个是 hook 系统事件得到目标窗口的位置和大小以及是否关闭等,二是通过循环检测窗口的位置来实现。

    \n\n

    # 基于 Windows 定时检测目标窗口

    \n
      \n
    1. 利用 win32gui 模块获取目标窗口的句柄
    2. \n
    3. 通过句柄获取目标窗口的大小位置,并设置自己的位置
    4. \n
    5. 主要是检测时间,在 10 毫秒以下很流畅
    6. \n
    7. 窗口关闭是根据目标句柄无效来判断
    8. \n
    \n

    https://github.com/PyQt5/PyQt/blob/master/Demo/FollowWindow.py

    \n

    # 代码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月22日\n@author: Irony\n@site: https://github.com/892768447\n@email: 892768447@qq.com\n@file: FollowWindow\n@description: \n\"\"\"\nimport os\n\nfrom PyQt5.QtCore import QTimer\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton\nimport win32gui\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        layout.addWidget(QPushButton('test', self))\n        self.tmpHwnd = None\n        # 启动定时器检测记事本的位置大小和是否关闭\n        self.checkTimer = QTimer(self, timeout=self.checkWindow)\n        self.checkTimer.start(10)  # 10毫秒比较流畅\n\n    def checkWindow(self):\n        # 查找\n        hwnd = win32gui.FindWindow('Notepad', None)\n        if self.tmpHwnd and not hwnd:\n            # 表示记事本关闭了\n            self.checkTimer.stop()\n            self.close()  # 关闭自己\n            return\n        if not hwnd:\n            return\n        self.tmpHwnd = hwnd\n        # 获取位置\n        rect = win32gui.GetWindowRect(hwnd)\n        print(rect)\n        self.move(rect[2], rect[1])\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    # 先检测是否已有记事本打开\n    hwnd = win32gui.FindWindow('Notepad', None)\n    print('hwnd', hwnd)\n    if not hwnd:\n        # 启动记事本\n        os.startfile('notepad')\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"FollowWindow\"

    \n", + "tags": [ + "PyQt", + "窗口" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\347\252\227\345\217\243/rss.xml" "b/tag/\347\252\227\345\217\243/rss.xml" new file mode 100644 index 00000000..dc8df7f9 --- /dev/null +++ "b/tag/\347\252\227\345\217\243/rss.xml" @@ -0,0 +1,145 @@ + + + + PyQt • Posts by "窗口" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 23 Oct 2018 07:08:56 +0000 + Tue, 23 Oct 2018 07:08:56 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/followwindow.html + PyQt5窗口跟随其它窗口 + https://pyqt5.com/followwindow.html + PyQt + 窗口 + Tue, 23 Oct 2018 07:08:56 +0000 + + + + diff --git "a/tag/\347\272\277\347\250\213/feed.json" "b/tag/\347\272\277\347\250\213/feed.json" new file mode 100644 index 00000000..5dc6881e --- /dev/null +++ "b/tag/\347\272\277\347\250\213/feed.json" @@ -0,0 +1,31 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"线程\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/runnablesignal_625781186.html", + "url": "https://pyqt5.com/runnablesignal_625781186.html", + "title": "QRunnable线程池发信号", + "date_published": "2019-04-30T07:58:09.000Z", + "content_html": "

    因为只有继承 QObject 的类才能有信号和自定义信号,而 QRunnable 并不是继承自 QObject ,也不能用多继承的方式,这里考虑定义个全局的 QObject 变量用来存放一些定义好的可复用的信号。

    \n\n

    pools 是 QThreadPool 实例

    \n

    # 看图说话

    \n
      \n
    1. \"runnablesignal1\"
    2. \n
    3. 定义一个全局信号类
      \n\"runnablesignal2\"
    4. \n
    5. 在 QRunnable 中发送
      \n\"runnablesignal3\"
    6. \n
    \n", + "tags": [ + "PyQt", + "信号", + "线程" + ] + }, + { + "id": "https://pyqt5.com/daemonthread.html", + "url": "https://pyqt5.com/daemonthread.html", + "title": "多线程之守护线程和阻塞线程", + "date_published": "2018-10-24T07:51:15.000Z", + "content_html": "

    如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的 daemon 属性。

    \n\n

    即在线程开始(thread.start ())之前,调用 setDeamon()函数,设定线程的 daemon 标志。

    \n

    (thread.setDaemon (True))就表示这个线程 “不重要”。

    \n

    如果你想等待子线程完成再退出,那就什么都不用做,或者显示地调用 thread.setDaemon (False),设置 daemon 的值为 false。新的子线程会继承父线程的 daemon 标志。

    \n

    整个 Python 会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

    \n

    setDaemon () 函数要放在 start 之前设置才行。

    \n
    import threading\nimport time\n\ndef func():\n    print(\"子线程开启:\", time.localtime())\n    time.sleep(2)\n    print(\"子线程结束:\", time.localtime())\n\n\nprint(\"主线程开启:\", time.localtime())\nt = threading.Thread(target=func, args=())\n# t.setDaemon(True)\nt.start()\nprint(\"主线程关闭:\", time.localtime())
    \n

    在 Python 的多线程编程中,在实例代码中经常有 thread1.join () 这样的代码。那么今天咱们用实际代码来解释一下 join 函数的作用。

    \n

    join 的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的 join 函数。

    \n

    先看看这个:

    \n
      \n
    1. 阻塞主进程,专注于执行多线程中的程序。
    2. \n
    3. 多线程多 join 的情况下,依次执行各线程的 join 方法,前头一个结束了才能执行后面一个。
    4. \n
    5. 无参数,则等待到该线程结束,才开始执行下一个线程的 join。
    6. \n
    7. 参数 timeout 为线程的阻塞时间,如 timeout=2 就是罩着这个线程 2s 以后,就不管他了,继续执行下面的代码。
    8. \n
    9. 下面的例子是一次阻塞子线程,每个子线程都会等上个子线程 join 结束才会执行,如果注释掉 t.join 则会同时执行 5 个子线程,多线程在做网络访问的时候可以减少等待时间,那么在一个工作流程中可以将访问网络接口的情况做成多线程。
    10. \n
    \n
    import threading, time\n\ndef func():\n    print(\"hello world!\")\n    time.sleep(1)\n\nprint(\"hello main start\")\nfor i in range(5):\n    t = threading.Thread(target=func, args=())\n    print(t.getName())\n    t.start()\n    t.join()
    ", + "tags": [ + "Python", + "线程" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\347\272\277\347\250\213/rss.xml" "b/tag/\347\272\277\347\250\213/rss.xml" new file mode 100644 index 00000000..16bb7152 --- /dev/null +++ "b/tag/\347\272\277\347\250\213/rss.xml" @@ -0,0 +1,129 @@ + + + + PyQt • Posts by "线程" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 30 Apr 2019 07:58:09 +0000 + Tue, 30 Apr 2019 07:58:09 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/runnablesignal_625781186.html + QRunnable线程池发信号 + https://pyqt5.com/runnablesignal_625781186.html + PyQt + 信号 + 线程 + Tue, 30 Apr 2019 07:58:09 +0000 + + + + https://pyqt5.com/daemonthread.html + 多线程之守护线程和阻塞线程 + https://pyqt5.com/daemonthread.html + Python + 线程 + Wed, 24 Oct 2018 07:51:15 +0000 + + + + diff --git "a/tag/\347\274\226\350\276\221\345\231\250/feed.json" "b/tag/\347\274\226\350\276\221\345\231\250/feed.json" new file mode 100644 index 00000000..66cd996f --- /dev/null +++ "b/tag/\347\274\226\350\276\221\345\231\250/feed.json" @@ -0,0 +1,18 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"编辑器\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/suggesteditor.html", + "url": "https://pyqt5.com/suggesteditor.html", + "title": "推荐编辑器LiClipse", + "date_published": "2019-05-04T10:04:08.000Z", + "content_html": "

    关于 Python 的开发编辑器有很多,每个人有每个人的喜好,经常看到很多在问什么编辑器好用,有人推荐 Sublime,有人推荐 Pycharm 等等,这里就不去比较其它编辑器的优缺点了,只谈谈关于 LiClipse 这个编辑器在初级使用阶段的智能提示功能等。开箱即用,支持多种语言,RST,Markdown 和 HTML 编辑器的 HTML 预览。

    \n\n

    其实 LiClipse 这个编辑器就是以前的 PyDev 插件的独立版本,基于 Eclipse 编辑器开发,去掉了 Java 的相关开发功能,关于软件的详细说明可以去官网查看: http://www.liclipse.com/

    \n

    编辑器只需要少量的配置,打开即可使用,快速自动 import,也可以根据需要安装自己所需的插件,比如 json、svn、主题插件等。个人推荐:适合刚入门的新手使用

    \n

    由于新版的 PyQt 和 PyDev 去掉了详细的函数提示,所以 PyQt 的智能提示只有函数和返回值,并没有英文注释,但是以前的比如 PyQt4 的智能提示应该是有详细的英文注释提示。

    \n

    # 界面预览

    \n
      \n
    1. 主界面
      \n\"editor1\"
    2. \n
    3. 鼠标悬停提示
      \n\"editor2\"
    4. \n
    5. 输入提示
      \n\"editor3\"
    6. \n
    7. Git 面板
      \n\"editor4\"
    8. \n
    9. 全局搜索(Ctrl + H)
      \n\"editor5\"
      \n\"editor6\"
    10. \n
    \n

    # 自动导包

    \n

    其实这个功能我是非常喜欢的,通过按下快捷键即可自动寻找包名导入,快捷键 Ctrl + Shift + O

    \n

    \"editor_import\"

    \n

    也可以在标红的代码上按下 Ctrl + F1 进行导入

    \n

    \"editor_import2\"

    \n

    # 配置

    \n

    打开编辑器后首先要配置【Window -> Preferences】的就是 Python 的环境变量,可以同时添加多个 Python 版本

    \n

    \"editor_env\"

    \n

    # Tab 等设置

    \n
      \n
    1. Insert spaces for tabs tab 转空格
    2. \n
    3. Show line numbers 显示行号
    4. \n
    \n

    \"editor_tab\"

    \n

    # 模版

    \n

    这个功能可以快速插入自己定义好的模版代码,比如 if __name__ == '__main__': 等等,比如我这里配置的创建文件的模版

    \n

    \"editor_tpl\"

    \n

    # 常用快捷键

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    格式化对齐Ctrl + Shift + F
    自动导包Ctrl + Shift + O
    快捷提示Alt + /
    \n", + "tags": [ + "编辑器" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\347\274\226\350\276\221\345\231\250/rss.xml" "b/tag/\347\274\226\350\276\221\345\231\250/rss.xml" new file mode 100644 index 00000000..9a725e57 --- /dev/null +++ "b/tag/\347\274\226\350\276\221\345\231\250/rss.xml" @@ -0,0 +1,124 @@ + + + + PyQt • Posts by "编辑器" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 04 May 2019 10:04:08 +0000 + Sat, 04 May 2019 10:04:08 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/suggesteditor.html + 推荐编辑器LiClipse + https://pyqt5.com/suggesteditor.html + 编辑器 + Sat, 04 May 2019 10:04:08 +0000 + + + + diff --git "a/tag/\347\277\273\350\275\254/feed.json" "b/tag/\347\277\273\350\275\254/feed.json" new file mode 100644 index 00000000..45ec6ba9 --- /dev/null +++ "b/tag/\347\277\273\350\275\254/feed.json" @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"翻转\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/flipwidgetanimation.html", + "url": "https://pyqt5.com/flipwidgetanimation.html", + "title": "PyQt5窗口翻转动画", + "date_published": "2019-05-15T14:48:00.000Z", + "content_html": "

    QQ 的界面一直是用来模仿练习做界面的好东西,这里就有一个类似 QQ 登录界面的实现翻转效果,当然这里并没有用两个窗口去做,而是用了 QStackedWidget 包含两个控件做切换,同时单独使用一个窗口做动画绘制。

    \n\n

    # 原理说明

    \n
      \n
    1. 用了两个 QLabel 来显示模拟的图片界面,并实现鼠标点击模拟真实的窗口对应位置点击
    2. \n
    3. 用了 QStackedWidget 来存放上面的两个界面 QLabel
    4. \n
    5. 点击切换时主要是对上面的两个界面进行截图并传递给翻转动画窗口
    6. \n
    7. 通过 setWindowOpacity 控制主窗口的显示隐藏(保留任务栏),当然也可以用 hide
    8. \n
    9. 动画窗口 FlipWidget.py 主要实现两张图片的翻转显示,考虑到 0-90 和 90-180 之前的情况,以及图片的缩放动画
    10. \n
    \n

    # 核心实现

    \n
      \n
    1. 主要是在 paintEvent 方法中使用 QTransformQPainter 进行圆心变换以及 rotate 设置翻转角度
    2. \n
    3. 同时根据翻转的角度范围对图片进行切换和缩放
    4. \n
    \n
    def paintEvent(self, event):\n    super(FlipWidget, self).paintEvent(event)\n\n    if hasattr(self, 'image1') and hasattr(self, 'image2') and self.isVisible():\n\n        painter = QPainter(self)\n        painter.setRenderHint(QPainter.Antialiasing, True)\n        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)\n\n        # 变换\n        transform = QTransform()\n        # 把圆心设置为矩形中心\n        transform.translate(self.width() / 2, self.height() / 2)\n\n        if self._angle >= -90 and self._angle <= 90:\n            # 当翻转角度在90范围内显示第一张图,且从大图缩放到小图的过程\n            painter.save()\n            # 设置翻转角度\n            transform.rotate(self._angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image1.width() / 2\n            height = int(self.image1.height() *\n                         (1 - abs(self._angle / self.Scale) / 100))\n            image = self.image1.scaled(\n                self.image1.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()\n        else:\n            # 当翻转角度在90范围内显示第二张图,且从小图缩放到原图的过程\n            painter.save()\n            if self._angle > 0:\n                angle = 180 + self._angle\n            else:\n                angle = self._angle - 180\n            # 设置翻转角度, 注意这里角度有差异\n            transform.rotate(angle, Qt.YAxis)\n            painter.setTransform(transform)\n            # 缩放图片高度\n            width = self.image2.width() / 2\n            height = int(self.image2.height() *\n                         (1 - ((360 - abs(angle)) / self.Scale / 100)))\n            image = self.image2.scaled(\n                self.image2.width(), height,\n                Qt.IgnoreAspectRatio, Qt.SmoothTransformation)\n            painter.drawPixmap(\n                QPointF(-width, -height / 2), image)\n            painter.restore()
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/FlipWidgetAnimation.py

    \n

    # 效果图

    \n

    \"FlipWidgetAnimation\"

    \n", + "tags": [ + "PyQt", + "动画", + "翻转" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\347\277\273\350\275\254/rss.xml" "b/tag/\347\277\273\350\275\254/rss.xml" new file mode 100644 index 00000000..8ca0bfe6 --- /dev/null +++ "b/tag/\347\277\273\350\275\254/rss.xml" @@ -0,0 +1,136 @@ + + + + PyQt • Posts by "翻转" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Wed, 15 May 2019 14:48:00 +0000 + Wed, 15 May 2019 14:48:00 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/flipwidgetanimation.html + PyQt5窗口翻转动画 + https://pyqt5.com/flipwidgetanimation.html + PyQt + 动画 + 翻转 + Wed, 15 May 2019 14:48:00 +0000 + + + + diff --git "a/tag/\350\217\234\345\215\225/feed.json" "b/tag/\350\217\234\345\215\225/feed.json" new file mode 100644 index 00000000..fbafc1fa --- /dev/null +++ "b/tag/\350\217\234\345\215\225/feed.json" @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"菜单\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/mselectmenu.html", + "url": "https://pyqt5.com/mselectmenu.html", + "title": "PyQt5菜单之多选功能", + "date_published": "2018-10-25T01:53:34.000Z", + "content_html": "

    有时候会遇到这种需求:在界面某个位置弹出一个菜单,其中里面的菜单项可以多选(类似配置选项),此时用 QMenu 会遇到点击一个菜单项就会自动关闭,当然可以通过其他方式实现该功能,不过这里就采用 QMenu 通过特殊的方式来实现该需求。

    \n\n

    # 需求

    \n

    要实现的效果:

    \n
      \n
    1. 菜单 1
    2. \n
    3. 菜单 2
    4. \n
    5. 菜单 3
    6. \n
    7. 菜单 4
    8. \n
    \n

    点击菜单 1、2、3 可以多选不关闭菜单

    \n

    点击菜单 4 可以勾选,并且关闭菜单

    \n

    # 原理

    \n
      \n
    1. 设置菜单项可勾选:通过 QAction.setCheckable(True) 方法实现
    2. \n
    3. 设置菜单不可关闭:通过覆盖 QMenu 的鼠标释放 mouseReleaseEvent 方法(可直接替换或者通过 installEventFilter 安装事件过滤器实现)
    4. \n
    5. 在菜单的鼠标释放事件中,当点击菜单项后是通过点击点坐标来查找是否有 QAction ,然后触发对应的 QAction
    6. \n
    7. 故在没有 QAction 的地方则直接交还给 QMenu 自行处理逻辑,在有 QAction 的地方可以根据自己的需求进行处理(如上所提)
    8. \n
    \n

    # 代码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年10月24日\n@author: Irony\n@site: https://github.com/892768447\n@email: 892768447@qq.com\n@file: 菜单多选不关闭\n@description: \n\"\"\"\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QMenu,\\\n    QAction\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = \"Copyright (c) 2018 Irony\"\n__Version__ = \"Version 1.0\"\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        self.labelInfo = QLabel(self)\n        self.button = QPushButton('带按钮的菜单', self)\n        layout.addWidget(self.labelInfo)\n        layout.addWidget(self.button)\n\n        # 添加菜单\n        self._initMenu()\n\n    def _initMenu(self):\n        # 创建菜单\n        self._menu = QMenu(self.button)\n        # 替换menu的鼠标释放事件达到选择性不关闭菜单\n        self._menu.mouseReleaseEvent = self._menu_mouseReleaseEvent\n        self._menu.addAction('菜单1', self._checkAction)\n        self._menu.addAction('菜单2', self._checkAction)\n        self._menu.addAction(\n            QAction('菜单3', self._menu, triggered=self._checkAction))\n        action = QAction('菜单4', self._menu, triggered=self._checkAction)\n        # 添加自定义的属性,判断该属性可以关闭菜单\n        action.setProperty('canHide', True)\n        self._menu.addAction(action)\n        for action in self._menu.actions():\n            # 循环设置可勾选\n            action.setCheckable(True)\n        self.button.setMenu(self._menu)\n\n    def _menu_mouseReleaseEvent(self, event):\n        action = self._menu.actionAt(event.pos())\n        if not action:\n            # 没有找到action就交给QMenu自己处理\n            return QMenu.mouseReleaseEvent(self._menu, event)\n        if action.property('canHide'):  # 如果有该属性则给菜单自己处理\n            return QMenu.mouseReleaseEvent(self._menu, event)\n        # 找到了QAction则只触发Action\n        action.activate(action.Trigger)\n\n    def _checkAction(self):\n        # 三个action都响应该函数\n        self.labelInfo.setText('\\n'.join(['{}\\t选中:{}'.format(\n            action.text(), action.isChecked()) for action in self._menu.actions()]))\n\n\nif __name__ == '__main__':\n    import sys\n    import cgitb\n    sys.excepthook = cgitb.enable(1, None, 5, 'text')\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.resize(400, 400)\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"MultiSelect\"

    \n", + "tags": [ + "PyQt", + "菜单" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\350\217\234\345\215\225/rss.xml" "b/tag/\350\217\234\345\215\225/rss.xml" new file mode 100644 index 00000000..ff5405f2 --- /dev/null +++ "b/tag/\350\217\234\345\215\225/rss.xml" @@ -0,0 +1,167 @@ + + + + PyQt • Posts by "菜单" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 25 Oct 2018 01:53:34 +0000 + Thu, 25 Oct 2018 01:53:34 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/mselectmenu.html + PyQt5菜单之多选功能 + https://pyqt5.com/mselectmenu.html + PyQt + 菜单 + Thu, 25 Oct 2018 01:53:34 +0000 + + + + diff --git "a/tag/\350\256\276\350\256\241\345\270\210/feed.json" "b/tag/\350\256\276\350\256\241\345\270\210/feed.json" new file mode 100644 index 00000000..96ae72ae --- /dev/null +++ "b/tag/\350\256\276\350\256\241\345\270\210/feed.json" @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"设计师\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/viewpyindesigner_625781186.html", + "url": "https://pyqt5.com/viewpyindesigner_625781186.html", + "title": "如何和设计师中查看ui转换的py代码", + "date_published": "2019-04-30T05:11:09.000Z", + "content_html": "

    通过 设计师  查看 ui 转换的 py 代码

    \n

    当初我刚学 pyqt 的时候,也有很多疑惑,用什么属性把控件加到布局,改了这个属性会发生什么,为什么这个会这样,那个会那样 。。。 。。。

    \n

    后来就看 ui 转成的 py 代码,注释一下,什么效果消失了,就是那个 api 引起的 。

    \n\n

    再来后发现了官方文档,查一查函数就行了 .

    \n

    但是有些 api 文档找起来麻烦,用设计师点几下就行了,然后把转换出来的代码拷贝一下就完事了.

    \n

    可是需要单独把 ui 转为 py 文件,之后再删除这个文件也是很烦的一件事 .

    \n

    好,话不多说,接下来手把手教你如何快速在 ui 中查看 py 代码 .

    \n

    官方也考虑过这种情况,所以 设计师中 是有这个功能的,但是 qt 的是没问题的,pyqt 的毕竟是绑定过来的,所以正常来说 你点击之后会弹出一个找不到应用程序的提示 .

    \n

    看到这个东西是不是很眼熟,我们用的命令 pyuic5 和这个东西应该是一样的 .

    \n

    \"viewpyindesigner1\"

    \n

    所以接下来,我们找找电脑上有没有这个东西

    \n

    \"viewpyindesigner2\"

    \n

    果然在 pyqt5-toos 文件夹下有这个东西,

    \n

    我们根据第一张图的提示,把这个东西拷贝到相应的目录 (如果没有那个 bin 文件夹,手动创建),

    \n

    \"viewpyindesigner3\"

    \n

    好了,大功告成!

    \n", + "tags": [ + "PyQt", + "Designer", + "设计师" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\350\256\276\350\256\241\345\270\210/rss.xml" "b/tag/\350\256\276\350\256\241\345\270\210/rss.xml" new file mode 100644 index 00000000..be0c465f --- /dev/null +++ "b/tag/\350\256\276\350\256\241\345\270\210/rss.xml" @@ -0,0 +1,84 @@ + + + + PyQt • Posts by "设计师" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Tue, 30 Apr 2019 05:11:09 +0000 + Tue, 30 Apr 2019 05:11:09 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/viewpyindesigner_625781186.html + 如何和设计师中查看ui转换的py代码 + https://pyqt5.com/viewpyindesigner_625781186.html + PyQt + Designer + 设计师 + Tue, 30 Apr 2019 05:11:09 +0000 + + + + diff --git "a/tag/\350\275\256\346\222\255/feed.json" "b/tag/\350\275\256\346\222\255/feed.json" new file mode 100644 index 00000000..1f157838 --- /dev/null +++ "b/tag/\350\275\256\346\222\255/feed.json" @@ -0,0 +1,20 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"轮播\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/pageswitching.html", + "url": "https://pyqt5.com/pageswitching.html", + "title": "PyQt5之图片轮播", + "date_published": "2018-11-24T13:45:06.000Z", + "content_html": "

    之前看到了 QStackedWidget 做切换动画,让界面不那么生硬,于是参考了 http://qt.shoutwiki.com/wiki/Extending_QStackedWidget_for_sliding_page_animations_in_Qt 做了一个 QStackedWidget 的切换动画,然后利用 QStackedWidget 结合多个 QLabel 显示图片来做一个轮播效果。

    \n

    其实在写之前也在网上找了很多例子,参看过后发现大多例子都是利用到了 paintEvent 去绘制,这样其实还是比较麻烦,个人觉得更好的方式是使用 QPropertyAnimation 属性动画修改控件中 QLabel 图片控件的 pos 位置属性就可以达到移动效果了。

    \n\n
      \n
    1. 比较核心的算法就是要计算当前页面和下一个页面的位置偏移量,比如:
    2. \n
    \n
    # 计算偏移量\noffsetX = self.frameRect().width()\noffsetY = self.frameRect().height()\nw_next.setGeometry(0, 0, offsetX, offsetY)\n\nif direction == self.BOTTOM2TOP:\n    offsetX = 0\n    offsetY = -offsetY\nelif direction == self.TOP2BOTTOM:\n    offsetX = 0\nelif direction == self.RIGHT2LEFT:\n    offsetX = -offsetX\n    offsetY = 0\nelif direction == self.LEFT2RIGHT:\n    offsetY = 0\n\n# 重新定位显示区域外部/旁边的下一个窗口小部件\npnext = w_next.pos()\npnow = w_now.pos()\nself._pnow = pnow\n\n# 移动到指定位置并显示\nw_next.move(pnext.x() - offsetX, pnext.y() - offsetY)\nw_next.show()\nw_next.raise_()
    \n
      \n
    1. \n

      其次是对这两个页面增加关联 pos 属性的 QPropertyAnimation 动画,然后加入到并行动画组 QParallelAnimationGroup 中再启动即可。

      \n
    2. \n
    3. \n

      QStackedWidgetsetCurrentIndexsetCurrentWidget 这两个函数进行了覆盖重写达到及时手动调用这两个函数也会产生动画效果的目的。

      \n
    4. \n
    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/PageSwitching.py

    \n

    # 效果图

    \n

    \"PageSwitching\"

    \n", + "tags": [ + "PyQt", + "动画", + "轮播" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\350\275\256\346\222\255/rss.xml" "b/tag/\350\275\256\346\222\255/rss.xml" new file mode 100644 index 00000000..36ee1ea3 --- /dev/null +++ "b/tag/\350\275\256\346\222\255/rss.xml" @@ -0,0 +1,110 @@ + + + + PyQt • Posts by "轮播" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Sat, 24 Nov 2018 13:45:06 +0000 + Sat, 24 Nov 2018 13:45:06 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/pageswitching.html + PyQt5之图片轮播 + https://pyqt5.com/pageswitching.html + PyQt + 动画 + 轮播 + Sat, 24 Nov 2018 13:45:06 +0000 + + + + diff --git "a/tag/\350\276\271\346\241\206/feed.json" "b/tag/\350\276\271\346\241\206/feed.json" new file mode 100644 index 00000000..9097dd72 --- /dev/null +++ "b/tag/\350\276\271\346\241\206/feed.json" @@ -0,0 +1,19 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"边框\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/showframe.html", + "url": "https://pyqt5.com/showframe.html", + "title": "PyQt5调整窗口显示边框", + "date_published": "2019-04-26T14:19:26.000Z", + "content_html": "

    windows 某些场景下调整窗口大小或者移动后就会导致里面的内容重绘(速度慢,卡顿,闪烁),其实在以前 windows 在低配置设备为了减少这种频繁绘制的情况,默认会开启这种效果,不过目前设备越来越好了就关闭了该功能。具体是在控制面板中 -> 调整 Windows 的外观和性能 -> 去掉勾选 拖动时显示窗口内容。

    \n\n

    由于这个开关是全局状态的,而我们只需要在自己的窗口中实现该效果有两种方式。

    \n
      \n
    1. 一种是自己绘制一个边框效果,放开鼠标时才操作真正的窗口。
    2. \n
    3. 二是替换窗口的处理过程函数 wndproc 处理 WM_NCLBUTTONDOWN 消息事件。
    4. \n
    \n

    今天讲第二种方法:

    \n
      \n
    1. 需要了解 SystemParametersInfo  API 函数
    2. \n
    3. SPI_GETDRAGFULLWINDOWS :确定是否允许拖拉到最大窗口
    4. \n
    5. SPI_SETDRAGFULLWINDOWS :设置是否允许拖至最大窗口
    6. \n
    \n

    效果就是这样的:

    \n

    \"ShowFrameWhenDrag\"

    \n

    正如图片所看的那样,窗体在移动的时候,窗体并没有绘制出来,而是绘制出窗体的边框,等到窗体不在移动的时候就直接把窗体图像数据全部绘制出来,这样就避免了窗体在移动的时候出现闪烁的现象。

    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/Demo/ShowFrameWhenDrag.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月23日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: ShowFrameWhenDrag\n@description: 调整窗口显示边框\n\"\"\"\nfrom ctypes import sizeof, windll, c_int, byref, c_long, c_void_p, c_ulong, c_longlong,\\\n    c_ulonglong, WINFUNCTYPE, c_uint\n\nfrom PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019 Irony'\n__Version__ = 1.0\n\nif sizeof(c_long) == sizeof(c_void_p):\n    WPARAM = c_ulong\n    LPARAM = c_long\nelif sizeof(c_longlong) == sizeof(c_void_p):\n    WPARAM = c_ulonglong\n    LPARAM = c_longlong\n\nWM_NCLBUTTONDOWN = 0x00a1\nGWL_WNDPROC = -4\nSPI_GETDRAGFULLWINDOWS = 38\nSPI_SETDRAGFULLWINDOWS = 37\nWNDPROC = WINFUNCTYPE(c_long, c_void_p, c_uint, WPARAM, LPARAM)\n\ntry:\n    CallWindowProc = windll.user32.CallWindowProcW\n    SetWindowLong = windll.user32.SetWindowLongW\n    SystemParametersInfo = windll.user32.SystemParametersInfoW\nexcept:\n    CallWindowProc = windll.user32.CallWindowProcA\n    SetWindowLong = windll.user32.SetWindowLongA\n    SystemParametersInfo = windll.user32.SystemParametersInfoA\n\n\ndef GetDragFullwindows():\n    rv = c_int()\n    SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, byref(rv), 0)\n    return rv.value\n\n\ndef SetDragFullwindows(value):\n    SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, value, 0, 0)\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QVBoxLayout(self)\n        layout.addWidget(QLabel('拖动或者调整窗口试试看'))\n\n        # 重点替换窗口处理过程\n        self._newwndproc = WNDPROC(self._wndproc)\n        self._oldwndproc = SetWindowLong(\n            int(self.winId()), GWL_WNDPROC, self._newwndproc)\n\n    def _wndproc(self, hwnd, msg, wparam, lparam):\n        if msg == WM_NCLBUTTONDOWN:\n            # 获取系统本身是否已经开启\n            isDragFullWindow = GetDragFullwindows()\n            if isDragFullWindow != 0:\n                # 开启虚线框\n                SetDragFullwindows(0)\n                # 系统本身处理\n                ret = CallWindowProc(\n                    self._oldwndproc, hwnd, msg, wparam, lparam)\n                # 关闭虚线框\n                SetDragFullwindows(1)\n                return ret\n        return CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 片尾

    \n

    替换窗口过程可以处理很多系统窗口的处理过程,更多需要读者自行去发现。

    \n", + "tags": [ + "PyQt", + "边框" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\350\276\271\346\241\206/rss.xml" "b/tag/\350\276\271\346\241\206/rss.xml" new file mode 100644 index 00000000..f7086c73 --- /dev/null +++ "b/tag/\350\276\271\346\241\206/rss.xml" @@ -0,0 +1,175 @@ + + + + PyQt • Posts by "边框" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Fri, 26 Apr 2019 14:19:26 +0000 + Fri, 26 Apr 2019 14:19:26 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/showframe.html + PyQt5调整窗口显示边框 + https://pyqt5.com/showframe.html + PyQt + 边框 + Fri, 26 Apr 2019 14:19:26 +0000 + + + + diff --git "a/tag/\350\277\233\347\250\213/feed.json" "b/tag/\350\277\233\347\250\213/feed.json" new file mode 100644 index 00000000..db8d8e3d --- /dev/null +++ "b/tag/\350\277\233\347\250\213/feed.json" @@ -0,0 +1,18 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"进程\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/processinclass_625781186.html", + "url": "https://pyqt5.com/processinclass_625781186.html", + "title": "python 在类里使用进程池", + "date_published": "2018-11-16T13:37:31.000Z", + "content_html": "
      \n
    1. 首先, 进程池的作用就是减少进程的创建和释放 开销的, 所以在类中作为局部变量是不合适的;
    2. \n
    3. 其次, 进程池必须在 if __name__ == "__main__" 里 ,否则会报 frozen_ 什么什么的错误;(这一点可能解释有误);
    4. \n
    \n\n
      \n
    1. 然后, 线程池的 apply_async 中如果传入 self.xxx 方法,会报 multiprocessing.Pool pickling error 什么的错误, 具体解释见 https://blog.csdn.net/dutsoft/article/details/70336462, 里面有解决方法,但是我没有成功(最开始测试没有现在理解的透彻, 不过应该是可以的); 由于第 1 点 不合理, 所以有什么办法在类 函数中获取 进程池对象 po 的地址:
    2. \n
    \n

    \"processinclass1\"

    \n

    我的解决思路和方法是:

    \n
      \n
    1. 通过 globals () 取得全局变量 , 测试证明 :不同文件的 globals() 是不同的: 如 Tab2.py globals() main_extra_func_file.py 中的 globals() 是不同的 , 所以 这样在 Tab2.py 中取不到 po 对象;
    2. \n
    3. 通过 __main__.po 来获取 (为什么会想到这个呢, 因为有时候导包 import .xxx 和 import xxx 会报 __main__ 没有什么属性什么的):
    4. \n
    \n
    def getPoolObject():\n# po 的名字在main函数中定义\n# __main__ 模块在sys.modules 的键是\"__mp_main__\"\n    return sys.modules[\"__mp_main__\"].po
    \n

    ps : (图没截好 , rglob_worker 是外部函数 , 非类内函数 ,po = getPoolBojcet () 这一行是类内函数 ,红色箭头 2. 在的那条白色分割线 是 2 个函数。)

    \n

    \"processinclass2\"

    \n

    len(po._cache) == 1 : po._cache 是当前有任务的进程数, ==1 表示所有任务结束;利用回调 , 可以更轻松地进行进程通信。

    \n", + "tags": [ + "进程" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\350\277\233\347\250\213/rss.xml" "b/tag/\350\277\233\347\250\213/rss.xml" new file mode 100644 index 00000000..be6b1fe3 --- /dev/null +++ "b/tag/\350\277\233\347\250\213/rss.xml" @@ -0,0 +1,86 @@ + + + + PyQt • Posts by "进程" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Fri, 16 Nov 2018 13:37:31 +0000 + Fri, 16 Nov 2018 13:37:31 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/processinclass_625781186.html + python 在类里使用进程池 + https://pyqt5.com/processinclass_625781186.html + 进程 + Fri, 16 Nov 2018 13:37:31 +0000 + + + + diff --git "a/tag/\351\230\264\345\275\261/feed.json" "b/tag/\351\230\264\345\275\261/feed.json" new file mode 100644 index 00000000..08396345 --- /dev/null +++ "b/tag/\351\230\264\345\275\261/feed.json" @@ -0,0 +1,33 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "PyQt • All posts by \"阴影\" tag", + "description": "Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6", + "home_page_url": "https://pyqt5.com", + "items": [ + { + "id": "https://pyqt5.com/shadowradius.html", + "url": "https://pyqt5.com/shadowradius.html", + "title": "PyQt5无边框圆角阴影", + "date_published": "2019-04-25T16:06:26.000Z", + "content_html": "

    在做 PyQt 窗口开发中经常会遇到要做一些无边框不规则的窗口,可能还会带有阴影效果,这里演示做一个简单的无边框圆角的窗口,原理就在于背景窗口的透明和一层有色背景控件的叠加。

    \n\n

    # 原理说明

    \n
      \n
    1. 黑色(方便说明)的 QDialog 或者 QWidget 作为全透明无边框窗口。
    2. \n
    3. 其中白色的 QWidget 才是主要显示圆角和阴影的窗口,用于承载其它控件的显示。
    4. \n
    5. 注意红色和紫色的方框内的层次。
    6. \n
    7. 另:如果要熟悉纯代码编写请看 FramelessDialog.py
    8. \n
    \n

    如图:

    \n

    \"FramelessDialog1\"

    \n

    # 代码

    \n

    https://github.com/PyQt5/PyQt/blob/master/Demo/FramelessDialog.py

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2019年4月25日\n@author: Irony\n@site: https://pyqt5.com https://github.com/892768447\n@email: 892768447@qq.com\n@file: FramelessWidget\n@description: 无边框圆角带阴影窗口 \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect\nfrom frameless import Ui_Dialog\n\n\n__Author__ = 'Irony'\n__Copyright__ = 'Copyright (c) 2019'\n\n\nclass Window(QDialog, Ui_Dialog):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        self.mPos = None\n        self.setupUi(self)\n        self.closeButton.clicked.connect(self.close)\n        # 重点\n        # 无边框\n        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)\n        # 背景透明(就是ui中黑色背景的那个控件)\n        self.setAttribute(Qt.WA_TranslucentBackground, True)\n\n        # 添加阴影\n        effect = QGraphicsDropShadowEffect(self)\n        effect.setBlurRadius(12)\n        effect.setOffset(0, 0)\n        effect.setColor(Qt.gray)\n        self.setGraphicsEffect(effect)\n\n    # 加上简单的移动功能\n\n    def mousePressEvent(self, event):\n        \"\"\"鼠标点击事件\"\"\"\n        if event.button() == Qt.LeftButton:\n            self.mPos = event.pos()\n        event.accept()\n\n    def mouseReleaseEvent(self, event):\n        '''鼠标弹起事件'''\n        self.mPos = None\n        event.accept()\n\n    def mouseMoveEvent(self, event):\n        if event.buttons() == Qt.LeftButton and self.mPos:\n            self.move(self.mapToGlobal(event.pos() - self.mPos))\n        event.accept()\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    \n

    # 效果图

    \n

    \"FramelessDialog\"

    \n

    # 下载

    \n

    无边框圆角阴影.zip

    \n", + "tags": [ + "PyQt", + "阴影", + "无边框", + "圆角" + ] + }, + { + "id": "https://pyqt5.com/animateshadow.html", + "url": "https://pyqt5.com/animateshadow.html", + "title": "PyQt5动画边框阴影", + "date_published": "2018-09-25T15:38:12.000Z", + "content_html": "

    为子控件增加动画阴影效果,结合 QGraphicsDropShadowEffectQPropertyAnimation 动态改变阴影半径达到效果,在旧版本的 Qt 中 QGraphicsDropShadowEffect 可能会有点问题(父控件会影响子控件)

    \n\n

    # 原理

    \n

    原理是利用 QGraphicsDropShadowEffect 添加边框阴影,然后使用动画不停改变阴影的模糊半径来达到效果,如图:

    \n

    \"ShadowEffect\"

    \n

    # 简单说明

    \n
      \n
    1. 继承 QGraphicsDropShadowEffect 增加动态属性 radius
    2. \n
    3. 通过 setGraphicsEffect 方法设置控件的边框阴影
    4. \n
    5. 通过 QPropertyAnimation 属性动画不断改变 radius 的值并调用 setBlurRadius 更新半径值
    6. \n
    \n

    https://github.com/PyQt5/PyQt/blob/master/QGraphicsDropShadowEffect/ShadowEffect.py

    \n

    # 自定义类

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年9月25日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: AnimationShadowEffect\n@description: 边框动画阴影动画\n\"\"\"\nfrom PyQt5.QtCore import QPropertyAnimation, pyqtProperty\nfrom PyQt5.QtWidgets import QGraphicsDropShadowEffect\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\nclass AnimationShadowEffect(QGraphicsDropShadowEffect):\n\n    def __init__(self, color, *args, **kwargs):\n        super(AnimationShadowEffect, self).__init__(*args, **kwargs)\n        self.setColor(color)\n        self.setOffset(0, 0)\n        self.setBlurRadius(0)\n        self._radius = 0\n        self.animation = QPropertyAnimation(self)\n        self.animation.setTargetObject(self)\n        self.animation.setDuration(2000)  # 一次循环时间\n        self.animation.setLoopCount(-1)  # 永久循环\n        self.animation.setPropertyName(b'radius')\n        # 插入线行值\n        self.animation.setKeyValueAt(0, 1)\n        self.animation.setKeyValueAt(0.5, 30)\n        self.animation.setKeyValueAt(1, 1)\n\n    def start(self):\n        self.animation.start()\n\n    @pyqtProperty(int)\n    def radius(self):\n        return self._radius\n\n    @radius.setter\n    def radius(self, r):\n        self._radius = r\n        self.setBlurRadius(r)
    \n

    # 测试代码

    \n
    #!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nCreated on 2018年9月25日\n@author: Irony\n@site: https://pyqt5.com, https://github.com/892768447\n@email: 892768447@qq.com\n@file: Test\n@description: \n\"\"\"\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import QPixmap\nfrom PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QLineEdit\n\nfrom AnimationShadowEffect import AnimationShadowEffect  # @UnresolvedImport\n\n\n__Author__ = \"\"\"By: Irony\nQQ: 892768447\nEmail: 892768447@qq.com\"\"\"\n__Copyright__ = 'Copyright (c) 2018 Irony'\n__Version__ = 1.0\n\n\nclass Window(QWidget):\n\n    def __init__(self, *args, **kwargs):\n        super(Window, self).__init__(*args, **kwargs)\n        layout = QHBoxLayout(self)\n\n        # 绿色边框\n        labelGreen = QLabel(self, pixmap=QPixmap('1.jpg').scaled(100, 100))\n        layout.addWidget(labelGreen)\n        aniGreen = AnimationShadowEffect(Qt.darkGreen, labelGreen)\n        labelGreen.setGraphicsEffect(aniGreen)\n        aniGreen.start()\n\n        # 红色边框,圆形图片\n        labelRed = QLabel(self)\n        labelRed.setMinimumSize(100, 100)\n        labelRed.setMaximumSize(100, 100)\n        labelRed.setStyleSheet('border-image: url(1.jpg);border-radius: 50px;')\n        layout.addWidget(labelRed)\n        aniRed = AnimationShadowEffect(Qt.red, labelGreen)\n        labelRed.setGraphicsEffect(aniRed)\n        aniRed.start()\n\n        # 蓝色边框按钮\n        button = QPushButton('按钮', self)\n        aniButton = AnimationShadowEffect(Qt.blue, button)\n        layout.addWidget(button)\n        button.setGraphicsEffect(aniButton)\n        aniButton.start()\n\n        # 青色边框输入框\n        lineedit = QLineEdit(self)\n        aniEdit = AnimationShadowEffect(Qt.cyan, lineedit)\n        layout.addWidget(lineedit)\n        lineedit.setGraphicsEffect(aniEdit)\n        aniEdit.start()\n\n\nif __name__ == '__main__':\n    import sys\n    from PyQt5.QtWidgets import QApplication\n    app = QApplication(sys.argv)\n    w = Window()\n    w.show()\n    sys.exit(app.exec_())
    ", + "tags": [ + "PyQt", + "动画", + "阴影" + ] + } + ] +} \ No newline at end of file diff --git "a/tag/\351\230\264\345\275\261/rss.xml" "b/tag/\351\230\264\345\275\261/rss.xml" new file mode 100644 index 00000000..afc9b0ec --- /dev/null +++ "b/tag/\351\230\264\345\275\261/rss.xml" @@ -0,0 +1,295 @@ + + + + PyQt • Posts by "阴影" tag + https://pyqt5.com + Python PyQt PyQt6 PyQt5 PyQt4 PySide PySide2 PySide6 + zh-CN + Thu, 25 Apr 2019 16:06:26 +0000 + Thu, 25 Apr 2019 16:06:26 +0000 + PyQt + 动画 + 阴影 + 信号 + Python + 截图 + 异常 + 圆形 + 图片 + 线程 + Model + FFmpeg + HLS + 翻转 + 窗口 + 滑动条 + 特效 + Mac + M1 + 菜单 + 轮播 + 进程 + pyqt hook key + Asyncio + 异步 + subprocess.Popen + 拦截print + pytest-qt + python 状态机 + 气泡 + .9png + QWebView + QWebEngineView + 浏览器 + debug + snoop + 无边框 + 圆角 + 边框 + Github + 编辑器 + PyQt5 PySide2 + Designer + 设计师 + virtualenvwrapper + virtualenv + + https://pyqt5.com/shadowradius.html + PyQt5无边框圆角阴影 + https://pyqt5.com/shadowradius.html + PyQt + 阴影 + 无边框 + 圆角 + Thu, 25 Apr 2019 16:06:26 +0000 + + + + https://pyqt5.com/animateshadow.html + PyQt5动画边框阴影 + https://pyqt5.com/animateshadow.html + PyQt + 动画 + 阴影 + Tue, 25 Sep 2018 15:38:12 +0000 + + + + diff --git a/tags/9png/index.html b/tags/9png/index.html new file mode 100644 index 00000000..4cd063db --- /dev/null +++ b/tags/9png/index.html @@ -0,0 +1,288 @@ +标签: .9png | PyQt + + + + + + + + + + + +
    PyQt5显示.9格式的PNG图片
    \ No newline at end of file diff --git a/tags/Asyncio/index.html b/tags/Asyncio/index.html new file mode 100644 index 00000000..ccbee059 --- /dev/null +++ b/tags/Asyncio/index.html @@ -0,0 +1,307 @@ +标签: Asyncio | PyQt + + + + + + + + + + + +
    PyQt5结合Asyncio异步
    \ No newline at end of file diff --git a/tags/Designer/index.html b/tags/Designer/index.html new file mode 100644 index 00000000..bb887448 --- /dev/null +++ b/tags/Designer/index.html @@ -0,0 +1,290 @@ +标签: Designer | PyQt + + + + + + + + + + + +
    如何和设计师中查看ui转换的py代码
    \ No newline at end of file diff --git a/tags/FFmpeg/index.html b/tags/FFmpeg/index.html new file mode 100644 index 00000000..d23e7fd2 --- /dev/null +++ b/tags/FFmpeg/index.html @@ -0,0 +1,302 @@ +标签: FFmpeg | PyQt + + + + + + + + + + + +
    FFmpeg合成加密HLS记录
    \ No newline at end of file diff --git a/tags/Github/index.html b/tags/Github/index.html new file mode 100644 index 00000000..9610da88 --- /dev/null +++ b/tags/Github/index.html @@ -0,0 +1,296 @@ +标签: Github | PyQt + + + + + + + + + + + +
    解决GitHub下载速度缓慢的问题
    \ No newline at end of file diff --git a/tags/HLS/index.html b/tags/HLS/index.html new file mode 100644 index 00000000..dbbb3b52 --- /dev/null +++ b/tags/HLS/index.html @@ -0,0 +1,302 @@ +标签: HLS | PyQt + + + + + + + + + + + +
    FFmpeg合成加密HLS记录
    \ No newline at end of file diff --git a/tags/M1/index.html b/tags/M1/index.html new file mode 100644 index 00000000..f19767cb --- /dev/null +++ b/tags/M1/index.html @@ -0,0 +1,292 @@ +标签: M1 | PyQt + + + + + + + + + + + +
    如何在Mac M1上快速安装PyQt5
    \ No newline at end of file diff --git a/tags/Mac/index.html b/tags/Mac/index.html new file mode 100644 index 00000000..6d1be542 --- /dev/null +++ b/tags/Mac/index.html @@ -0,0 +1,292 @@ +标签: Mac | PyQt + + + + + + + + + + + +
    如何在Mac M1上快速安装PyQt5
    \ No newline at end of file diff --git a/tags/Model/index.html b/tags/Model/index.html new file mode 100644 index 00000000..ba68684c --- /dev/null +++ b/tags/Model/index.html @@ -0,0 +1,296 @@ +标签: Model | PyQt + + + + + + + + + + + +
    QDataWidgetMapper 数据库绑定 QLineEdit控件
    \ No newline at end of file diff --git a/tags/PyQt/index.html b/tags/PyQt/index.html new file mode 100644 index 00000000..2c83e944 --- /dev/null +++ b/tags/PyQt/index.html @@ -0,0 +1,430 @@ +标签: PyQt | PyQt + + + + + + + + + + + +
    如何在Mac M1上快速安装PyQt5
    PyQt学习心得
    QtWebkit和QWebEngineView与Javascript交互
    PyQt5窗口翻转动画
    PyQt属性动画(QPropertyAnimation)
    如何查阅Qt文档
    三种方式绑定信号槽
    QRunnable线程池发信号
    如何和设计师中查看ui转换的py代码
    PyQt5调整窗口显示边框
    \ No newline at end of file diff --git a/tags/PyQt/page/2/index.html b/tags/PyQt/page/2/index.html new file mode 100644 index 00000000..090c7853 --- /dev/null +++ b/tags/PyQt/page/2/index.html @@ -0,0 +1,414 @@ +标签: PyQt | PyQt + + + + + + + + + + + +
    PyQt5判断信号是否连接
    PyQt5无边框圆角阴影
    在Mac上以正确的姿势使用PyQtClient看Demo
    PyQtClient例子客户端
    PyQt5编译QWebView与QWebEngineView共存
    PyQt5之图片轮播
    PyQt5之QSlider滑动条点击定位
    PyQt5仿网页鼠标移动点阵特效
    QDataWidgetMapper 数据库绑定 QLineEdit控件
    PyQt5显示.9格式的PNG图片
    \ No newline at end of file diff --git a/tags/PyQt/page/3/index.html b/tags/PyQt/page/3/index.html new file mode 100644 index 00000000..04663bfb --- /dev/null +++ b/tags/PyQt/page/3/index.html @@ -0,0 +1,414 @@ +标签: PyQt | PyQt + + + + + + + + + + + +
    PyQt5菜单之多选功能
    PyQt5结合Asyncio异步
    PyQt5仿网页图片鼠标移动特效
    PyQt5窗口跟随其它窗口
    PyQt5动画边框阴影
    PyQt5圆形图片
    \ No newline at end of file diff --git a/tags/PyQt5-PySide2/index.html b/tags/PyQt5-PySide2/index.html new file mode 100644 index 00000000..0b92f1d0 --- /dev/null +++ b/tags/PyQt5-PySide2/index.html @@ -0,0 +1,288 @@ +标签: PyQt5 PySide2 | PyQt + + + + + + + + + + + +
    修改pyuic代替pyside2-uic.
    \ No newline at end of file diff --git a/tags/Python/index.html b/tags/Python/index.html new file mode 100644 index 00000000..eb278b81 --- /dev/null +++ b/tags/Python/index.html @@ -0,0 +1,462 @@ +标签: Python | PyQt + + + + + + + + + + + +
    python 判断屏幕等宽字符串的长度  
    python 状态机模块  
    在pyqt中使用python全局钩子模块
    像读文章一样读源码
    python 获取子进程print信息  
    python 拷贝虚拟环境(一)  
    Python调用Java对Excel截图
    FFmpeg合成加密HLS记录
    多线程之守护线程和阻塞线程
    异常捕获之cgitb模块
    \ No newline at end of file diff --git a/tags/QWebEngineView/index.html b/tags/QWebEngineView/index.html new file mode 100644 index 00000000..e997143c --- /dev/null +++ b/tags/QWebEngineView/index.html @@ -0,0 +1,286 @@ +标签: QWebEngineView | PyQt + + + + + + + + + + + +
    QtWebkit和QWebEngineView与Javascript交互
    \ No newline at end of file diff --git a/tags/QWebView/index.html b/tags/QWebView/index.html new file mode 100644 index 00000000..34a6e88c --- /dev/null +++ b/tags/QWebView/index.html @@ -0,0 +1,299 @@ +标签: QWebView | PyQt + + + + + + + + + + + +
    QtWebkit和QWebEngineView与Javascript交互
    PyQt5编译QWebView与QWebEngineView共存
    \ No newline at end of file diff --git a/tags/debug/index.html b/tags/debug/index.html new file mode 100644 index 00000000..a67f6b06 --- /dev/null +++ b/tags/debug/index.html @@ -0,0 +1,297 @@ +标签: debug | PyQt + + + + + + + + + + + +
    像读文章一样读源码
    \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 00000000..dc23ffe3 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,278 @@ +标签 | PyQt + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/tags/pyqt-hook-key/index.html b/tags/pyqt-hook-key/index.html new file mode 100644 index 00000000..14aaeeed --- /dev/null +++ b/tags/pyqt-hook-key/index.html @@ -0,0 +1,295 @@ +标签: pyqt hook key | PyQt + + + + + + + + + + + +
    在pyqt中使用python全局钩子模块
    \ No newline at end of file diff --git a/tags/pytest-qt/index.html b/tags/pytest-qt/index.html new file mode 100644 index 00000000..b2cb656f --- /dev/null +++ b/tags/pytest-qt/index.html @@ -0,0 +1,309 @@ +标签: pytest-qt | PyQt + + + + + + + + + + + +
    pytest-qt 测试模态窗体.
    \ No newline at end of file diff --git "a/tags/python-\347\212\266\346\200\201\346\234\272/index.html" "b/tags/python-\347\212\266\346\200\201\346\234\272/index.html" new file mode 100644 index 00000000..b8876683 --- /dev/null +++ "b/tags/python-\347\212\266\346\200\201\346\234\272/index.html" @@ -0,0 +1,289 @@ +标签: python 状态机 | PyQt + + + + + + + + + + + +
    python 状态机模块  
    \ No newline at end of file diff --git a/tags/snoop/index.html b/tags/snoop/index.html new file mode 100644 index 00000000..0fffd526 --- /dev/null +++ b/tags/snoop/index.html @@ -0,0 +1,297 @@ +标签: snoop | PyQt + + + + + + + + + + + +
    像读文章一样读源码
    \ No newline at end of file diff --git a/tags/subprocess-Popen/index.html b/tags/subprocess-Popen/index.html new file mode 100644 index 00000000..6b47b931 --- /dev/null +++ b/tags/subprocess-Popen/index.html @@ -0,0 +1,296 @@ +标签: subprocess.Popen | PyQt + + + + + + + + + + + +
    python 获取子进程print信息  
    \ No newline at end of file diff --git a/tags/virtualenv/index.html b/tags/virtualenv/index.html new file mode 100644 index 00000000..965ee402 --- /dev/null +++ b/tags/virtualenv/index.html @@ -0,0 +1,293 @@ +标签: virtualenv | PyQt + + + + + + + + + + + +
    python 拷贝虚拟环境(一)  
    \ No newline at end of file diff --git a/tags/virtualenvwrapper/index.html b/tags/virtualenvwrapper/index.html new file mode 100644 index 00000000..224a7a4f --- /dev/null +++ b/tags/virtualenvwrapper/index.html @@ -0,0 +1,293 @@ +标签: virtualenvwrapper | PyQt + + + + + + + + + + + +
    python 拷贝虚拟环境(一)  
    \ No newline at end of file diff --git "a/tags/\344\277\241\345\217\267/index.html" "b/tags/\344\277\241\345\217\267/index.html" new file mode 100644 index 00000000..6d8e49bd --- /dev/null +++ "b/tags/\344\277\241\345\217\267/index.html" @@ -0,0 +1,312 @@ +标签: 信号 | PyQt + + + + + + + + + + + +
    三种方式绑定信号槽
    QRunnable线程池发信号
    PyQt5判断信号是否连接
    \ No newline at end of file diff --git "a/tags/\345\212\250\347\224\273/index.html" "b/tags/\345\212\250\347\224\273/index.html" new file mode 100644 index 00000000..edfbdfb9 --- /dev/null +++ "b/tags/\345\212\250\347\224\273/index.html" @@ -0,0 +1,366 @@ +标签: 动画 | PyQt + + + + + + + + + + + +
    PyQt5窗口翻转动画
    PyQt属性动画(QPropertyAnimation)
    PyQt5之图片轮播
    PyQt5仿网页鼠标移动点阵特效
    PyQt5动画边框阴影
    \ No newline at end of file diff --git "a/tags/\345\233\276\347\211\207/index.html" "b/tags/\345\233\276\347\211\207/index.html" new file mode 100644 index 00000000..4cf04420 --- /dev/null +++ "b/tags/\345\233\276\347\211\207/index.html" @@ -0,0 +1,308 @@ +标签: 图片 | PyQt + + + + + + + + + + + +
    PyQt5显示.9格式的PNG图片
    PyQt5圆形图片
    \ No newline at end of file diff --git "a/tags/\345\234\206\345\275\242/index.html" "b/tags/\345\234\206\345\275\242/index.html" new file mode 100644 index 00000000..ae1f986b --- /dev/null +++ "b/tags/\345\234\206\345\275\242/index.html" @@ -0,0 +1,296 @@ +标签: 圆形 | PyQt + + + + + + + + + + + +
    PyQt5圆形图片
    \ No newline at end of file diff --git "a/tags/\345\234\206\350\247\222/index.html" "b/tags/\345\234\206\350\247\222/index.html" new file mode 100644 index 00000000..5eac5881 --- /dev/null +++ "b/tags/\345\234\206\350\247\222/index.html" @@ -0,0 +1,298 @@ +标签: 圆角 | PyQt + + + + + + + + + + + +
    PyQt5无边框圆角阴影
    \ No newline at end of file diff --git "a/tags/\345\274\202\345\270\270/index.html" "b/tags/\345\274\202\345\270\270/index.html" new file mode 100644 index 00000000..06c349b6 --- /dev/null +++ "b/tags/\345\274\202\345\270\270/index.html" @@ -0,0 +1,294 @@ +标签: 异常 | PyQt + + + + + + + + + + + +
    异常捕获之cgitb模块
    \ No newline at end of file diff --git "a/tags/\345\274\202\346\255\245/index.html" "b/tags/\345\274\202\346\255\245/index.html" new file mode 100644 index 00000000..50179a13 --- /dev/null +++ "b/tags/\345\274\202\346\255\245/index.html" @@ -0,0 +1,307 @@ +标签: 异步 | PyQt + + + + + + + + + + + +
    PyQt5结合Asyncio异步
    \ No newline at end of file diff --git "a/tags/\346\210\252\345\233\276/index.html" "b/tags/\346\210\252\345\233\276/index.html" new file mode 100644 index 00000000..76709466 --- /dev/null +++ "b/tags/\346\210\252\345\233\276/index.html" @@ -0,0 +1,301 @@ +标签: 截图 | PyQt + + + + + + + + + + + +
    Python调用Java对Excel截图
    \ No newline at end of file diff --git "a/tags/\346\213\246\346\210\252print/index.html" "b/tags/\346\213\246\346\210\252print/index.html" new file mode 100644 index 00000000..3eaec1d5 --- /dev/null +++ "b/tags/\346\213\246\346\210\252print/index.html" @@ -0,0 +1,296 @@ +标签: 拦截print | PyQt + + + + + + + + + + + +
    python 获取子进程print信息  
    \ No newline at end of file diff --git "a/tags/\346\227\240\350\276\271\346\241\206/index.html" "b/tags/\346\227\240\350\276\271\346\241\206/index.html" new file mode 100644 index 00000000..b4834b7f --- /dev/null +++ "b/tags/\346\227\240\350\276\271\346\241\206/index.html" @@ -0,0 +1,298 @@ +标签: 无边框 | PyQt + + + + + + + + + + + +
    PyQt5无边框圆角阴影
    \ No newline at end of file diff --git "a/tags/\346\260\224\346\263\241/index.html" "b/tags/\346\260\224\346\263\241/index.html" new file mode 100644 index 00000000..d2a8d209 --- /dev/null +++ "b/tags/\346\260\224\346\263\241/index.html" @@ -0,0 +1,288 @@ +标签: 气泡 | PyQt + + + + + + + + + + + +
    PyQt5显示.9格式的PNG图片
    \ No newline at end of file diff --git "a/tags/\346\265\217\350\247\210\345\231\250/index.html" "b/tags/\346\265\217\350\247\210\345\231\250/index.html" new file mode 100644 index 00000000..73941c53 --- /dev/null +++ "b/tags/\346\265\217\350\247\210\345\231\250/index.html" @@ -0,0 +1,299 @@ +标签: 浏览器 | PyQt + + + + + + + + + + + +
    QtWebkit和QWebEngineView与Javascript交互
    PyQt5编译QWebView与QWebEngineView共存
    \ No newline at end of file diff --git "a/tags/\346\273\221\345\212\250\346\235\241/index.html" "b/tags/\346\273\221\345\212\250\346\235\241/index.html" new file mode 100644 index 00000000..a6bc7427 --- /dev/null +++ "b/tags/\346\273\221\345\212\250\346\235\241/index.html" @@ -0,0 +1,284 @@ +标签: 滑动条 | PyQt + + + + + + + + + + + +
    PyQt5之QSlider滑动条点击定位
    \ No newline at end of file diff --git "a/tags/\347\211\271\346\225\210/index.html" "b/tags/\347\211\271\346\225\210/index.html" new file mode 100644 index 00000000..0e54bb14 --- /dev/null +++ "b/tags/\347\211\271\346\225\210/index.html" @@ -0,0 +1,319 @@ +标签: 特效 | PyQt + + + + + + + + + + + +
    PyQt5仿网页鼠标移动点阵特效
    PyQt5仿网页图片鼠标移动特效
    \ No newline at end of file diff --git "a/tags/\347\252\227\345\217\243/index.html" "b/tags/\347\252\227\345\217\243/index.html" new file mode 100644 index 00000000..6aefe397 --- /dev/null +++ "b/tags/\347\252\227\345\217\243/index.html" @@ -0,0 +1,301 @@ +标签: 窗口 | PyQt + + + + + + + + + + + +
    PyQt5窗口跟随其它窗口
    \ No newline at end of file diff --git "a/tags/\347\272\277\347\250\213/index.html" "b/tags/\347\272\277\347\250\213/index.html" new file mode 100644 index 00000000..9a1c3cd1 --- /dev/null +++ "b/tags/\347\272\277\347\250\213/index.html" @@ -0,0 +1,300 @@ +标签: 线程 | PyQt + + + + + + + + + + + +
    QRunnable线程池发信号
    多线程之守护线程和阻塞线程
    \ No newline at end of file diff --git "a/tags/\347\274\226\350\276\221\345\231\250/index.html" "b/tags/\347\274\226\350\276\221\345\231\250/index.html" new file mode 100644 index 00000000..1ddb35ad --- /dev/null +++ "b/tags/\347\274\226\350\276\221\345\231\250/index.html" @@ -0,0 +1,289 @@ +标签: 编辑器 | PyQt + + + + + + + + + + + +
    推荐编辑器LiClipse
    \ No newline at end of file diff --git "a/tags/\347\277\273\350\275\254/index.html" "b/tags/\347\277\273\350\275\254/index.html" new file mode 100644 index 00000000..915846e5 --- /dev/null +++ "b/tags/\347\277\273\350\275\254/index.html" @@ -0,0 +1,292 @@ +标签: 翻转 | PyQt + + + + + + + + + + + +
    PyQt5窗口翻转动画
    \ No newline at end of file diff --git "a/tags/\350\217\234\345\215\225/index.html" "b/tags/\350\217\234\345\215\225/index.html" new file mode 100644 index 00000000..16497071 --- /dev/null +++ "b/tags/\350\217\234\345\215\225/index.html" @@ -0,0 +1,297 @@ +标签: 菜单 | PyQt + + + + + + + + + + + +
    PyQt5菜单之多选功能
    \ No newline at end of file diff --git "a/tags/\350\256\276\350\256\241\345\270\210/index.html" "b/tags/\350\256\276\350\256\241\345\270\210/index.html" new file mode 100644 index 00000000..badcfbda --- /dev/null +++ "b/tags/\350\256\276\350\256\241\345\270\210/index.html" @@ -0,0 +1,290 @@ +标签: 设计师 | PyQt + + + + + + + + + + + +
    如何和设计师中查看ui转换的py代码
    \ No newline at end of file diff --git "a/tags/\350\275\256\346\222\255/index.html" "b/tags/\350\275\256\346\222\255/index.html" new file mode 100644 index 00000000..54f27fb4 --- /dev/null +++ "b/tags/\350\275\256\346\222\255/index.html" @@ -0,0 +1,285 @@ +标签: 轮播 | PyQt + + + + + + + + + + + +
    PyQt5之图片轮播
    \ No newline at end of file diff --git "a/tags/\350\276\271\346\241\206/index.html" "b/tags/\350\276\271\346\241\206/index.html" new file mode 100644 index 00000000..c198566e --- /dev/null +++ "b/tags/\350\276\271\346\241\206/index.html" @@ -0,0 +1,291 @@ +标签: 边框 | PyQt + + + + + + + + + + + +
    PyQt5调整窗口显示边框
    \ No newline at end of file diff --git "a/tags/\350\277\233\347\250\213/index.html" "b/tags/\350\277\233\347\250\213/index.html" new file mode 100644 index 00000000..18a9fdb2 --- /dev/null +++ "b/tags/\350\277\233\347\250\213/index.html" @@ -0,0 +1,287 @@ +标签: 进程 | PyQt + + + + + + + + + + + +
    python 在类里使用进程池
    \ No newline at end of file diff --git "a/tags/\351\230\264\345\275\261/index.html" "b/tags/\351\230\264\345\275\261/index.html" new file mode 100644 index 00000000..51ba840a --- /dev/null +++ "b/tags/\351\230\264\345\275\261/index.html" @@ -0,0 +1,314 @@ +标签: 阴影 | PyQt + + + + + + + + + + + +
    PyQt5无边框圆角阴影
    PyQt5动画边框阴影
    \ No newline at end of file diff --git a/source/tencent18139824940595684397.txt b/tencent18139824940595684397.txt similarity index 100% rename from source/tencent18139824940595684397.txt rename to tencent18139824940595684397.txt diff --git a/themes/anzhiyu b/themes/anzhiyu deleted file mode 160000 index 661b90b2..00000000 --- a/themes/anzhiyu +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 661b90b288d4c5389883244a0053777f17dee446 diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 4f260daa..00000000 --- a/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Bug report -description: Create a report to help us improve -title: '[Bug]: ' - -body: - - type: markdown - attributes: - value: | - 重要:請依照該模板來提交 - Please follow the template to create a new issue - - type: input - id: butterfly-ver - attributes: - label: 使用的 Butterfly 版本? | What version of Butterfly are you use? - description: 檢視主題的 package.json | Check the theme's package.json - validations: - required: true - - - type: dropdown - id: modify - attributes: - label: 是否修改过主题文件? || Has the theme files been modified? - options: - - 是 (Yes) - - 不是 (No) - validations: - required: true - - - type: dropdown - id: browser - attributes: - label: 使用的瀏覽器? || What browse are you using? - options: - - Chrome - - Edge - - Safari - - Opera - - Other - validations: - required: true - - - type: dropdown - id: platform - attributes: - label: 使用的系統? || What operating system are you using? - options: - - Windows - - macOS - - Linux - - Android - - iOS - - Other - validations: - required: true - - - type: textarea - id: description - attributes: - label: 問題描述 | Describe the bug - description: 請描述你的問題現象 | A clear and concise description of what the bug is. - placeholder: 請儘量提供截圖來定位問題 | If applicable, add screenshots to help explain your problem - value: - validations: - required: true - - - type: input - id: website - attributes: - label: 出現問題網站 | Website - description: 請提供下可復現網站地址 | Please supply a website url which can reproduce problem. - placeholder: - validations: - required: true diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 9b338d0d..00000000 --- a/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,22 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Questions about Butterfly - url: https://github.com/jerryc127/hexo-theme-butterfly/discussions - about: 一些使用問題請到 Discussion 詢問。 Please ask questions in Discussion. - - - name: Butterfly Q&A - url: https://butterfly.js.org/posts/98d20436/ - about: Butterfly Q&A - - - name: Telegram - url: https://t.me/bu2fly - about: 'Official Telegram Group' - - - name: QQ 1群 - url: https://jq.qq.com/?_wv=1027&k=KU9105XR - about: '群號 1070540070,不要兩個Q群都添加' - - - name: QQ 2群 - url: https://jq.qq.com/?_wv=1027&k=r1nK0DQz - about: '群號 978221020,不要兩個Q群都添加' - diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 3bf7c30b..00000000 --- a/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Feature request -description: Suggest an idea for this project -title: '[Feature]: ' - -body: - - type: textarea - id: feature-request - attributes: - label: 想要的功能 | What feature do you want? - description: 請描述你需要的新功能 | A clear and concise description of what the feature is. - placeholder: - value: - validations: - require: true \ No newline at end of file diff --git a/themes/butterfly/.github/workflows/publish.yml b/themes/butterfly/.github/workflows/publish.yml deleted file mode 100644 index d68365dd..00000000 --- a/themes/butterfly/.github/workflows/publish.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: npm publish - -on: - release: - types: [created] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v1 - with: - node-version: '12.x' - registry-url: 'https://registry.npmjs.org' - - run: npm install - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/themes/butterfly/.github/workflows/stale.yml b/themes/butterfly/.github/workflows/stale.yml deleted file mode 100644 index a50d9de1..00000000 --- a/themes/butterfly/.github/workflows/stale.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: 'Close stale issues and PRs' -on: - schedule: - - cron: '30 1 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v5 - with: - days-before-issue-stale: 30 - days-before-pr-stale: -1 - days-before-close: 7 - stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' - close-pr-message: 'This issue has not seen any activity since it was marked stale. Closing.' - stale-issue-label: 'Stale' - exempt-issue-labels: 'pinned,bug,enhancement,documentation,Plan' - operations-per-run: 1000 \ No newline at end of file diff --git a/themes/butterfly/LICENSE b/themes/butterfly/LICENSE deleted file mode 100644 index 7a4a3ea2..00000000 --- a/themes/butterfly/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/themes/butterfly/README.md b/themes/butterfly/README.md deleted file mode 100644 index 6f74c798..00000000 --- a/themes/butterfly/README.md +++ /dev/null @@ -1,111 +0,0 @@ -
    -中文 -
    - -# hexo-theme-butterfly - -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) -![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) -![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c) -![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) - -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) - -📢 Demo: [Butterfly](https://butterfly.js.org/) || [CrazyWong](https://blog.crazywong.com/) - -📖 Docs: [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) || [Chinese](https://butterfly.js.org/posts/21cfbf15/) - -Based on [hexo-theme-melody](https://github.com/Molunerfinn/hexo-theme-melody) theme. - -## 💻 Installation - -### GIT - -> If you are in Mainland China, you can download in [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git) - -Stable branch [recommend]: - -``` -git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -Dev branch: - -``` -git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -### NPM - -> It supports Hexo 5.0.0 or later - -In Hexo site root directory - -```powershell -npm i hexo-theme-butterfly -``` - -## ⚙ Configuration - - Set theme in the hexo work folder's root config file `_config.yml`: - -> theme: butterfly - - If you don't have pug & stylus renderer, try this: - -> npm install hexo-renderer-pug hexo-renderer-stylus - -## 🎉 Features - -- [x] Card UI Design -- [X] Support sub-menu -- [x] Two-column layout -- [x] Responsive Web Design -- [x] Dark Mode -- [x] Pjax -- [x] Read Mode -- [x] Conversion between Traditional and Simplified Chinese -- [X] TOC catalog is available for both computers and mobile phones -- [X] Built-in Syntax Highlighting Themes (darker/pale night/light/ocean/mac/mac light), also support customization -- [X] Code Blocks (Display code language/close or expand Code Blocks/Copy Button/word wrap) -- [X] Disable copy/Add a Copyright Notice to the Copied Text -- [X] Search (Algolia Search/Local Search) -- [x] Mathjax and Katex -- [x] Built-in 404 page -- [x] WordCount -- [x] Related articles -- [x] Displays outdated notice for a post -- [x] Share (Sharejs/Addtoany) -- [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk) -- [x] Multiple Comment System Support -- [x] Online Chats (Chatra/Tidio/Daovoice/Crisp/messenger) -- [x] Web analytics -- [x] Google AdSense -- [x] Webmaster Verification -- [x] Change website colour scheme -- [x] Typewriter Effect: activate_power_mode -- [x] Background effects (Canvas ribbon/canvas_ribbon_piao/canvas_nest) -- [x] Mouse click effects (Fireworks/Heart/Text) -- [x] Preloader/Loading Animation/pace.js -- [x] Busuanzi visitor counter -- [x] Medium Zoom/Fancybox -- [x] Mermaid -- [x] Justified Gallery -- [x] Lazyload images -- [x] Instantpage/Pangu/Snackbar notification toast/PWA...... - -## ✨ Contributors - - - - - -## 📷 Screenshots - -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN/img/theme-butterfly-readme-homepage-1.png) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN/img/theme-butterfly-readme-homepage-2.png) \ No newline at end of file diff --git a/themes/butterfly/README_CN.md b/themes/butterfly/README_CN.md deleted file mode 100644 index 8b2279ba..00000000 --- a/themes/butterfly/README_CN.md +++ /dev/null @@ -1,111 +0,0 @@ -
    - English -
    - -# hexo-theme-butterfly - -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) -![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) -![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c) -![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) - -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) - -📢 預覽: [Butterfly](https://butterfly.js.org/) || [CrazyWong](https://blog.crazywong.com/) - -📖 文檔: [中文](https://butterfly.js.org/posts/21cfbf15/) || [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) - -一款基於[hexo-theme-melody](https://github.com/Molunerfinn/hexo-theme-melody)修改的主題 - -## 💻 安裝 - -### Git 安裝 - -> 本倉庫同時上傳到 [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git),如果你訪問 Github 緩慢,可從 Gitee 中下載。 - -在博客根目錄裡安裝穩定版【推薦】 - -```powershell -git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -如果想要安裝比較新的dev分支,可以 - -```powershell -git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -### npm 安裝 - -> 此方法只支持Hexo 5.0.0以上版本 - -在博客根目錄裡 - -```powershell -npm i hexo-theme-butterfly -``` - -## ⚙ 應用主題 - -修改hexo配置文件`_config.yml`,把主題改為`Butterfly` - -``` -theme: butterfly -``` - ->如果你沒有pug以及stylus的渲染器,請下載安裝: npm install hexo-renderer-pug hexo-renderer-stylus --save - -## 🎉 特色 - -- [x] 卡片化設計 -- [X] 支持二級目錄 -- [x] 雙欄設計 -- [x] 響應式主題 -- [x] 夜間模式 -- [x] Pjax -- [x] 文章閲讀模式 -- [x] 簡體和繁體轉換 -- [X] 電腦和手機都可查看TOC目錄 -- [X] 內置多種代碼配色(darker/pale night/light/ocean/mac/mac light),可自定義代碼配色 -- [X] 代碼塊顯示代碼語言/關閉或展開代碼塊/代碼複製/代碼自動換行 -- [X] 可關閉文字複製/可開啟內容複製增加版權信息) -- [X] 兩種搜索( Algolia 搜索和本地搜索) -- [x] Mathjax 和 Katex -- [x] 內置404頁面 -- [x] 顯示字數統計 -- [x] 顯示相關文章 -- [x] 過期文章提醒 -- [x] 多種分享系統(Sharejs/Addtoany) -- [X] 多種評論系統(Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk) -- [x] 支持雙評論部署 -- [x] 多種在線聊天(Chatra/Tidio/Daovoice/Crisp/messenger) -- [x] 多種分析系統 -- [x] 谷歌廣告/手動廣告位置 -- [x] 各種站長驗證(Google/Bing/Baidu/360/Yandex) -- [x] 修改網站配色 -- [x] 打字特效 activate_power_mode -- [x] 多種背景特效(靜止彩帶/動態彩帶/Canvas Nest) -- [x] 多種鼠標點擊特效(煙花/文字/愛心) -- [x] 內置一種 Preloader 加載動畫和 pace.js 加載動畫條 -- [x] 不蒜子訪問統計 -- [x] 兩種大圖模式(Medium Zoom/Fancybox) -- [x] Mermaid 圖表顯示 -- [x] 照片牆 -- [x] 圖片懶加載 -- [x] Instantpage/Pangu/Snackbar彈窗/PWA...... - -## ✨ 貢獻者 - - - - - -## 📷 截圖 - -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN/img/theme-butterfly-readme-homepage-1.png) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN/img/theme-butterfly-readme-homepage-2.png) diff --git a/themes/butterfly/_config.yml b/themes/butterfly/_config.yml deleted file mode 100644 index 96cfa5a6..00000000 --- a/themes/butterfly/_config.yml +++ /dev/null @@ -1,993 +0,0 @@ -# Navigation bar settings (導航欄設置) -# see https://butterfly.js.org/posts/4aa8abbe/##導航欄設置-Navigation-bar-settings -# -------------------------------------- - -nav: - logo: # image - display_title: true - fixed: false # fixed navigation bar - -# Menu 目錄 -menu: - # Home: / || fas fa-home - # Archives: /archives/ || fas fa-archive - # Tags: /tags/ || fas fa-tags - # Categories: /categories/ || fas fa-folder-open - # List||fas fa-list: - # Music: /music/ || fas fa-music - # Movie: /movies/ || fas fa-video - # Link: /link/ || fas fa-link - # About: /about/ || fas fa-heart - -# Code Blocks (代碼相關) -# -------------------------------------- - -highlight_theme: light # darker / pale night / light / ocean / mac / mac light / false -highlight_copy: true # copy button -highlight_lang: true # show the code language -highlight_shrink: false # true: shrink the code blocks / false: expand the code blocks | none: expand code blocks and hide the button -highlight_height_limit: false # unit: px -code_word_wrap: false - -# Social Settings (社交圖標設置) -# formal: -# icon: link || the description || color -social: - # fab fa-github: https://github.com/xxxxx || Github || '#24292e' - # fas fa-envelope: mailto:xxxxxx@gmail.com || Email || '#4a7dbe' - -# Image (圖片設置) -# -------------------------------------- - -# Favicon(網站圖標) -favicon: /img/favicon.png - -# Avatar (頭像) -avatar: - img: https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png - effect: false - -# Disable all banner image -disable_top_img: false - -# The banner image of home page -index_img: - -# If the banner of page not setting, it will show the top_img -default_top_img: - -# The banner image of archive page -archive_img: - -# If the banner of tag page not setting, it will show the top_img -# note: tag page, not tags page (子標籤頁面的 top_img) -tag_img: - -# The banner image of tag page -# format: -# - tag name: xxxxx -tag_per_img: - -# If the banner of category page not setting, it will show the top_img -# note: category page, not categories page (子分類頁面的 top_img) -category_img: - -# The banner image of category page -# format: -# - category name: xxxxx -category_per_img: - -cover: - # display the cover or not (是否顯示文章封面) - index_enable: true - aside_enable: true - archives_enable: true - # the position of cover in home page (封面顯示的位置) - # left/right/both - position: both - # When cover is not set, the default cover is displayed (當沒有設置cover時,默認的封面顯示) - default_cover: - # - https://i.loli.net/2020/05/01/gkihqEjXxJ5UZ1C.jpg - -# Replace Broken Images (替換無法顯示的圖片) -error_img: - flink: /img/friend_404.gif - post_page: /img/404.jpg - -# A simple 404 page -error_404: - enable: false - subtitle: 'Page Not Found' - background: https://i.loli.net/2020/05/19/aKOcLiyPl2JQdFD.png - -post_meta: - page: # Home Page - date_type: created # created or updated or both 主頁文章日期是創建日或者更新日或都顯示 - date_format: date # date/relative 顯示日期還是相對日期 - categories: true # true or false 主頁是否顯示分類 - tags: false # true or false 主頁是否顯示標籤 - label: true # true or false 顯示描述性文字 - post: - date_type: both # created or updated or both 文章頁日期是創建日或者更新日或都顯示 - date_format: date # date/relative 顯示日期還是相對日期 - categories: true # true or false 文章頁是否顯示分類 - tags: true # true or false 文章頁是否顯示標籤 - label: true # true or false 顯示描述性文字 - -# Display the article introduction on homepage -# 1: description -# 2: both (if the description exists, it will show description, or show the auto_excerpt) -# 3: auto_excerpt (default) -# false: do not show the article introduction -index_post_content: - method: 3 - length: 500 # if you set method to 2 or 3, the length need to config - -# anchor -anchor: - # when you scroll, the URL will update according to header id. - auto_update: false - # Click the headline to scroll and update the anchor - click_to_scroll: false - -# figcaption (圖片描述文字) -photofigcaption: false - -# copy settings -# copyright: Add the copyright information after copied content (複製的內容後面加上版權信息) -copy: - enable: true - copyright: - enable: false - limit_count: 50 - -# Post -# -------------------------------------- - -# toc (目錄) -toc: - post: true - page: false - number: true - expand: false - style_simple: false # for post - scroll_percent: true - -post_copyright: - enable: true - decode: false - author_href: - license: CC BY-NC-SA 4.0 - license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ - -# Sponsor/reward -reward: - enable: false - text: - QR_code: - # - img: /img/wechat.jpg - # link: - # text: wechat - # - img: /img/alipay.jpg - # link: - # text: alipay - -# Post edit -# Easily browse and edit blog source code online. -post_edit: - enable: false - # url: https://github.com/user-name/repo-name/edit/branch-name/subdirectory-name/ - # For example: https://github.com/jerryc127/butterfly.js.org/edit/main/source/ - url: - -# Related Articles -related_post: - enable: true - limit: 6 # Number of posts displayed - date_type: created # or created or updated 文章日期顯示創建日或者更新日 - -# post_pagination (分頁) -# value: 1 || 2 || false -# 1: The 'next post' will link to old post -# 2: The 'next post' will link to new post -# false: disable pagination -post_pagination: 1 - -# Displays outdated notice for a post (文章過期提醒) -noticeOutdate: - enable: false - style: flat # style: simple/flat - limit_day: 500 # When will it be shown - position: top # position: top/bottom - message_prev: It has been - message_next: days since the last update, the content of the article may be outdated. - -# Footer Settings -# -------------------------------------- -footer: - owner: - enable: true - since: 2020 - custom_text: - copyright: true # Copyright of theme and framework - -# aside (側邊欄) -# -------------------------------------- - -aside: - enable: true - hide: false - button: true - mobile: true # display on mobile - position: right # left or right - display: - archive: true - tag: true - category: true - card_author: - enable: true - description: - button: - enable: true - icon: fab fa-github - text: Follow Me - link: https://github.com/xxxxxx - card_announcement: - enable: true - content: This is my Blog - card_recent_post: - enable: true - limit: 5 # if set 0 will show all - sort: date # date or updated - sort_order: # Don't modify the setting unless you know how it works - card_categories: - enable: true - limit: 8 # if set 0 will show all - expand: none # none/true/false - sort_order: # Don't modify the setting unless you know how it works - card_tags: - enable: true - limit: 40 # if set 0 will show all - color: false - orderby: random # Order of tags, random/name/length - order: 1 # Sort of order. 1, asc for ascending; -1, desc for descending - sort_order: # Don't modify the setting unless you know how it works - card_archives: - enable: true - type: monthly # yearly or monthly - format: MMMM YYYY # eg: YYYY年MM月 - order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending - limit: 8 # if set 0 will show all - sort_order: # Don't modify the setting unless you know how it works - card_webinfo: - enable: true - post_count: true - last_push_date: true - sort_order: # Don't modify the setting unless you know how it works - card_post_series: - enable: true - orderBy: 'date' # Order by title or date - order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending - -# busuanzi count for PV / UV in site -# 訪問人數 -busuanzi: - site_uv: true - site_pv: true - page_pv: true - -# Time difference between publish date and now (網頁運行時間) -# Formal: Month/Day/Year Time or Year/Month/Day Time -runtimeshow: - enable: false - publish_date: - -# Aside widget - Newest Comments -newest_comments: - enable: false - sort_order: # Don't modify the setting unless you know how it works - limit: 6 - storage: 10 # unit: mins, save data to localStorage - avatar: true - -# Bottom right button (右下角按鈕) -# -------------------------------------- - -# Conversion between Traditional and Simplified Chinese (簡繁轉換) -translate: - enable: false - # The text of a button - default: 繁 - # the language of website (1 - Traditional Chinese/ 2 - Simplified Chinese) - defaultEncoding: 2 - # Time delay - translateDelay: 0 - # The text of the button when the language is Simplified Chinese - msgToTraditionalChinese: '繁' - # The text of the button when the language is Traditional Chinese - msgToSimplifiedChinese: '簡' - -# Read Mode (閲讀模式) -readmode: true - -# dark mode -darkmode: - enable: true - # Toggle Button to switch dark/light mode - button: true - # Switch dark/light mode automatically (自動切換 dark mode和 light mode) - # autoChangeMode: 1 Following System Settings, if the system doesn't support dark mode, it will switch dark mode between 6 pm to 6 am - # autoChangeMode: 2 Switch dark mode between 6 pm to 6 am - # autoChangeMode: false - autoChangeMode: false - # Set the light mode time. The value is between 0 and 24. If not set, the default value is 6 and 18 - start: # 8 - end: # 22 - -# show scroll percent in scroll-to-top button -rightside_scroll_percent: false - -# Don't modify the following settings unless you know how they work (非必要請不要修改 ) -# Choose: readmode,translate,darkmode,hideAside,toc,chat,comment -# Don't repeat 不要重複 -rightside_item_order: - enable: false - hide: # readmode,translate,darkmode,hideAside - show: # toc,chat,comment - -# Math (數學) -# -------------------------------------- -# About the per_page -# if you set it to true, it will load mathjax/katex script in each page (true 表示每一頁都加載js) -# if you set it to false, it will load mathjax/katex script according to your setting (add the 'mathjax: true' in page's front-matter) -# (false 需要時加載,須在使用的 Markdown Front-matter 加上 mathjax: true) - -# MathJax -mathjax: - enable: false - per_page: false - -# KaTeX -katex: - enable: false - per_page: false - hide_scrollbar: true - -# search (搜索) -# see https://butterfly.js.org/posts/ceeb73f/#搜索系統 -# -------------------------------------- - -# Algolia search -algolia_search: - enable: false - hits: - per_page: 6 - -# Local search -local_search: - enable: false - # Preload the search data when the page loads. - preload: false - # Show top n results per article, show all results by setting to -1 - top_n_per_article: 1 - # Unescape html strings to the readable one. - unescape: false - CDN: - -# Docsearch -docsearch: - enable: false - appId: - apiKey: - indexName: - option: - -# Share System (分享) -# -------------------------------------- - -# Share.js -# https://github.com/overtrue/share.js -sharejs: - enable: true - sites: facebook,twitter,wechat,weibo,qq - -# AddToAny -# https://www.addtoany.com/ -addtoany: - enable: false - item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link - -# Comments System -# -------------------------------------- - -comments: - # Up to two comments system, the first will be shown as default - # Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/Artalk - use: # Valine,Disqus - text: true # Display the comment name next to the button - # lazyload: The comment system will be load when comment element enters the browser's viewport. - # If you set it to true, the comment count will be invalid - lazyload: false - count: false # Display comment count in post's top_img - card_post_count: false # Display comment count in Home Page - -# disqus -# https://disqus.com/ -disqus: - shortname: - apikey: # For newest comments widget - -# Alternative Disqus - Render comments with Disqus API -# DisqusJS 評論系統,可以實現在網路審查地區載入 Disqus 評論列表,兼容原版 -# https://github.com/SukkaW/DisqusJS -disqusjs: - shortname: - apikey: - option: - -# livere (來必力) -# https://www.livere.com/ -livere: - uid: - -# gitalk -# https://github.com/gitalk/gitalk -gitalk: - client_id: - client_secret: - repo: - owner: - admin: - option: - -# valine -# https://valine.js.org -valine: - appId: # leancloud application app id - appKey: # leancloud application app key - avatar: monsterid # gravatar style https://valine.js.org/#/avatar - serverURLs: # This configuration is suitable for domestic custom domain name users, overseas version will be automatically detected (no need to manually fill in) - bg: # valine background - visitor: false - option: - -# waline - A simple comment system with backend support fork from Valine -# https://waline.js.org/ -waline: - serverURL: # Waline server address url - bg: # waline background - pageview: false - option: - -# utterances -# https://utteranc.es/ -utterances: - repo: - # Issue Mapping: pathname/url/title/og:title - issue_term: pathname - # Theme: github-light/github-dark/github-dark-orange/icy-dark/dark-blue/photon-dark - light_theme: github-light - dark_theme: photon-dark - -# Facebook Comments Plugin -# https://developers.facebook.com/docs/plugins/comments/ -facebook_comments: - app_id: - user_id: # optional - pageSize: 10 # The number of comments to show - order_by: social # social/time/reverse_time - lang: zh_TW # Language en_US/zh_CN/zh_TW and so on - -# Twikoo -# https://github.com/imaegoo/twikoo -twikoo: - envId: - region: - visitor: false - option: - -# Giscus -# https://giscus.app/ -giscus: - repo: - repo_id: - category_id: - theme: - light: light - dark: dark - option: - -# Remark42 -# https://remark42.com/docs/configuration/frontend/ -remark42: - host: # Your Host URL - siteId: # Your Site ID - option: - -# Artalk -# https://artalk.js.org/guide/frontend/config.html -artalk: - server: - site: - visitor: false - option: - -# Chat Services -# -------------------------------------- - -# Chat Button [recommend] -# It will create a button in the bottom right corner of website, and hide the origin button -chat_btn: false - -# The origin chat button is displayed when scrolling up, and the button is hidden when scrolling down -chat_hide_show: false - -# chatra -# https://chatra.io/ -chatra: - enable: false - id: - -# tidio -# https://www.tidio.com/ -tidio: - enable: false - public_key: - -# daovoice -# http://dashboard.daovoice.io/app -daovoice: - enable: false - app_id: - -# crisp -# https://crisp.chat/en/ -crisp: - enable: false - website_id: - -# messenger -# https://developers.facebook.com/docs/messenger-platform/discovery/facebook-chat-plugin/ -messenger: - enable: false - pageID: - lang: zh_TW # Language en_US/zh_CN/zh_TW and so on - -# Analysis -# -------------------------------------- - -# Baidu Analytics -# https://tongji.baidu.com/web/welcome/login -baidu_analytics: - -# Google Analytics -# https://analytics.google.com/analytics/web/ -google_analytics: - -# Cloudflare Analytics -# https://www.cloudflare.com/zh-tw/web-analytics/ -cloudflare_analytics: - -# Microsoft Clarity -# https://clarity.microsoft.com/ -microsoft_clarity: - -# Advertisement -# -------------------------------------- - -# Google Adsense (谷歌廣告) -google_adsense: - enable: false - auto_ads: true - js: https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js - client: - enable_page_level_ads: true - -# Insert ads manually (手動插入廣告) -# ad: -# index: -# aside: -# post: - -# Verification (站長驗證) -# -------------------------------------- - -site_verification: - # - name: google-site-verification - # content: xxxxxx - # - name: baidu-site-verification - # content: xxxxxxx - -# Beautify/Effect (美化/效果) -# -------------------------------------- - -# Theme color for customize -# Notice: color value must in double quotes like "#000" or may cause error! - -# theme_color: -# enable: true -# main: "#49B1F5" -# paginator: "#00c4b6" -# button_hover: "#FF7242" -# text_selection: "#00c4b6" -# link_color: "#99a9bf" -# meta_color: "#858585" -# hr_color: "#A4D8FA" -# code_foreground: "#F47466" -# code_background: "rgba(27, 31, 35, .05)" -# toc_color: "#00c4b6" -# blockquote_padding_color: "#49b1f5" -# blockquote_background_color: "#49b1f5" -# scrollbar_color: "#49b1f5" -# meta_theme_color_light: "ffffff" -# meta_theme_color_dark: "#0d0d0d" - -# The top_img settings of home page -# default: top img - full screen, site info - middle (默認top_img全屏,site_info在中間) -# The position of site info, eg: 300px/300em/300rem/10% (主頁標題距離頂部距離) -index_site_info_top: -# The height of top_img, eg: 300px/300em/300rem (主頁top_img高度) -index_top_img_height: - -# The user interface setting of category and tag page (category和tag頁的UI設置) -# index - same as Homepage UI (index 值代表 UI將與首頁的UI一樣) -# default - same as archives UI 默認跟archives頁面UI一樣 -category_ui: # 留空或 index -tag_ui: # 留空或 index - -# Stretches the lines so that each line has equal width(文字向兩側對齊,對最後一行無效) -text_align_justify: false - -# Website Background (設置網站背景) -# can set it to color or image (可設置圖片 或者 顔色) -# The formal of image: url(http://xxxxxx.com/xxx.jpg) -background: - -# Footer Background -footer_bg: false - -# Add mask to header or footer (为 header 或 footer 添加黑色半透遮罩) -mask: - header: true - footer: true - -# the position of bottom right button/default unit: px (右下角按鈕距離底部的距離/默認單位為px) -rightside_bottom: - -# Enter transitions (開啓網頁進入效果) -enter_transitions: true - -# Typewriter Effect (打字效果) -# https://github.com/disjukr/activate-power-mode -activate_power_mode: - enable: false - colorful: true # open particle animation (冒光特效) - shake: true # open shake (抖動特效) - mobile: false - -# Background effects (背景特效) -# -------------------------------------- - -# canvas_ribbon (靜止彩帶背景) -# See: https://github.com/hustcc/ribbon.js -canvas_ribbon: - enable: false - size: 150 - alpha: 0.6 - zIndex: -1 - click_to_change: false - mobile: false - -# Fluttering Ribbon (動態彩帶) -canvas_fluttering_ribbon: - enable: false - mobile: false - -# canvas_nest -# https://github.com/hustcc/canvas-nest.js -canvas_nest: - enable: false - color: '0,0,255' #color of lines, default: '0,0,0'; RGB values: (R,G,B).(note: use ',' to separate.) - opacity: 0.7 # the opacity of line (0~1), default: 0.5. - zIndex: -1 # z-index property of the background, default: -1. - count: 99 # the number of lines, default: 99. - mobile: false - -# Mouse click effects: fireworks (鼠標點擊效果: 煙火特效) -fireworks: - enable: false - zIndex: 9999 # -1 or 9999 - mobile: false - -# Mouse click effects: Heart symbol (鼠標點擊效果: 愛心) -click_heart: - enable: false - mobile: false - -# Mouse click effects: words (鼠標點擊效果: 文字) -clickShowText: - enable: false - text: - # - I - # - LOVE - # - YOU - fontSize: 15px - random: false - mobile: false - -# Default display mode (網站默認的顯示模式) -# light (default) / dark -display_mode: light - -# Beautify (美化頁面顯示) -beautify: - enable: false - field: post # site/post - title-prefix-icon: # '\f0c1' - title-prefix-icon-color: # '#F47466' - -# Global font settings -# Don't modify the following settings unless you know how they work (非必要不要修改) -font: - global-font-size: - code-font-size: - font-family: - code-font-family: - -# Font settings for the site title and site subtitle -# 左上角網站名字 主頁居中網站名字 -blog_title_font: - font_link: - font-family: - -# The setting of divider icon (水平分隔線圖標設置) -hr_icon: - enable: true - icon: # the unicode value of Font Awesome icon, such as '\3423' - icon-top: - -# the subtitle on homepage (主頁subtitle) -subtitle: - enable: false - # Typewriter Effect (打字效果) - effect: true - # Customize typed.js (配置typed.js) - # https://github.com/mattboldt/typed.js/#customization - typed_option: - # source 調用第三方服務 - # source: false 關閉調用 - # source: 1 調用一言網的一句話(簡體) https://hitokoto.cn/ - # source: 2 調用一句網(簡體) https://yijuzhan.com/ - # source: 3 調用今日詩詞(簡體) https://www.jinrishici.com/ - # subtitle 會先顯示 source , 再顯示 sub 的內容 - source: false - # 如果關閉打字效果,subtitle 只會顯示 sub 的第一行文字 - sub: - -# Loading Animation (加載動畫) -preloader: - enable: false - # source - # 1. fullpage-loading - # 2. pace (progress bar) - source: 1 - # pace theme (see https://codebyzach.github.io/pace/) - pace_css_url: - -# wordcount (字數統計) -# see https://butterfly.js.org/posts/ceeb73f/#字數統計 -wordcount: - enable: false - post_wordcount: true - min2read: true - total_wordcount: true - -# Lightbox (圖片大圖查看模式) -# -------------------------------------- -# You can only choose one, or neither (只能選擇一個 或者 兩個都不選) - -# medium-zoom -# https://github.com/francoischalifour/medium-zoom -medium_zoom: false - -# fancybox -# https://fancyapps.com/fancybox/ -fancybox: true - -# Tag Plugins settings (標籤外掛) -# -------------------------------------- - -# series (系列文章) -series: - enable: true - orderBy: 'title' # Order by title or date - order: 1 # Sort of order. 1, asc for ascending; -1, desc for descending - number: true - -# abcjs (樂譜渲染) -# See https://github.com/paulrosen/abcjs -abcjs: - enable: false - per_page: true - -# mermaid -# see https://github.com/mermaid-js/mermaid -mermaid: - enable: false - # built-in themes: default/forest/dark/neutral - theme: - light: default - dark: dark - -# Note (Bootstrap Callout) -note: - # Note tag style values: - # - simple bs-callout old alert style. Default. - # - modern bs-callout new (v2-v3) alert style. - # - flat flat callout style with background, like on Mozilla or StackOverflow. - # - disabled disable all CSS styles import of note tag. - style: flat - icons: true - border_radius: 3 - # Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6). - # Offset also applied to label tag variables. This option can work with disabled note tag. - light_bg_offset: 0 - -# other -# -------------------------------------- - -# Pjax -# It may contain bugs and unstable, give feedback when you find the bugs. -# https://github.com/MoOx/pjax -pjax: - enable: false - exclude: - # - xxxx - # - xxxx - -# Inject the css and script (aplayer/meting) -aplayerInject: - enable: false - per_page: true - -# Snackbar (Toast Notification 彈窗) -# https://github.com/polonel/SnackBar -# position 彈窗位置 -# 可選 top-left / top-center / top-right / bottom-left / bottom-center / bottom-right -snackbar: - enable: false - position: bottom-left - bg_light: '#49b1f5' # The background color of Toast Notification in light mode - bg_dark: '#1f1f1f' # The background color of Toast Notification in dark mode - -# https://instant.page/ -# prefetch (預加載) -instantpage: false - -# https://github.com/vinta/pangu.js -# Insert a space between Chinese character and English character (中英文之間添加空格) -pangu: - enable: false - field: site # site/post - -# Lazyload (圖片懶加載) -# https://github.com/verlok/vanilla-lazyload -lazyload: - enable: false - field: site # site/post - placeholder: - blur: false - -# PWA -# See https://github.com/JLHwung/hexo-offline -# --------------- -# pwa: -# enable: false -# manifest: /pwa/manifest.json -# apple_touch_icon: /pwa/apple-touch-icon.png -# favicon_32_32: /pwa/32.png -# favicon_16_16: /pwa/16.png -# mask_icon: /pwa/safari-pinned-tab.svg - -# Open graph meta tags -# https://developers.facebook.com/docs/sharing/webmasters/ -Open_Graph_meta: - enable: true - option: - # twitter_card: - # twitter_image: - # twitter_id: - # twitter_site: - # google_plus: - # fb_admins: - # fb_app_id: - -# Add the vendor prefixes to ensure compatibility -css_prefix: true - -# Inject -# Insert the code to head (before '' tag) and the bottom (before '' tag) -# 插入代码到头部 之前 和 底部 之前 -inject: - head: - # - - bottom: - # - - -# CDN -# Don't modify the following settings unless you know how they work -# 非必要請不要修改 -CDN: - # The CDN provider of internal scripts (主題內部 js 的 cdn 配置) - # option: local/jsdelivr/unpkg/cdnjs/custom - # Dev version can only choose. ( dev版的主題只能設置為 local ) - internal_provider: local - - # The CDN provider of third party scripts (第三方 js 的 cdn 配置) - # option: local/jsdelivr/unpkg/cdnjs/custom - # when set it to local, you need to install hexo-butterfly-extjs - third_party_provider: jsdelivr - - # Add version number to url, true or false - version: false - - # Custom format - # For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file} - custom_format: - - option: - # abcjs_basic_js: - # activate_power_mode: - # algolia_js: - # algolia_search: - # aplayer_css: - # aplayer_js: - # artalk_css: - # artalk_js: - # blueimp_md5: - # busuanzi: - # canvas_fluttering_ribbon: - # canvas_nest: - # canvas_ribbon: - # click_heart: - # clickShowText: - # disqusjs: - # disqusjs_css: - # docsearch_css: - # docsearch_js: - # egjs_infinitegrid: - # fancybox: - # fancybox_css: - # fireworks: - # fontawesome: - # gitalk: - # gitalk_css: - # giscus: - # instantpage: - # instantsearch: - # katex: - # katex_copytex: - # lazyload: - # local_search: - # main: - # main_css: - # mathjax: - # medium_zoom: - # mermaid: - # meting_js: - # pangu: - # prismjs_autoloader: - # prismjs_js: - # prismjs_lineNumber_js: - # pjax: - # sharejs: - # sharejs_css: - # snackbar: - # snackbar_css: - # translate: - # twikoo: - # typed: - # utils: - # valine: - # waline_css: - # waline_js: \ No newline at end of file diff --git a/themes/butterfly/languages/default.yml b/themes/butterfly/languages/default.yml deleted file mode 100644 index 59ddd22e..00000000 --- a/themes/butterfly/languages/default.yml +++ /dev/null @@ -1,123 +0,0 @@ -footer: - framework: Framework - theme: Theme - -copy: - success: Copy Successful - error: Copy Error - noSupport: Browser Not Supported - -page: - articles: Articles - tag: Tag - category: Category - archives: Archives - -card_post_count: comments - -no_title: Untitled - -post: - created: Created - updated: Updated - wordcount: Word Count - min2read: Reading Time - min2read_unit: mins - page_pv: Post Views - comments: Comments - copyright: - author: Author - link: Link - copyright_notice: Copyright Notice - copyright_content: 'All articles in this blog are licensed under %s unless stating additionally.' - recommend: Related Articles - edit: Edited on - -search: - title: Search - load_data: Loading the Database - algolia_search: - input_placeholder: Search for Posts - hits_empty: "We didn't find any results for the search: ${query}." - hits_stats: '${hits} results found in ${time} ms' - - local_search: - input_placeholder: Search for Posts - hits_empty: "We didn't find any results for the search: ${query}" - hits_stats: '${hits} results found' - -pagination: - prev: Previous - next: Next - -comment: Comment - -aside: - articles: Articles - tags: Tags - categories: Categories - card_announcement: Announcement - card_categories: Categories - card_tags: Tags - card_archives: Archives - card_recent_post: Recent Post - card_webinfo: - headline: Info - article_name: Article - runtime: - name: Runtime - unit: days - last_push_date: - name: Last Update - site_wordcount: Total Count - site_uv_name: UV - site_pv_name: PV - more_button: View More - card_newest_comments: - headline: Latest Comments - loading_text: loading... - error: Unable to retrieve comments, please check the configuration - zero: No comments - image: image - link: link - code: code - card_toc: Contents - card_post_series: Series - -date_suffix: - just: Just now - min: minutes ago - hour: hours ago - day: days ago - month: months ago - -donate: Sponsor -share: Share - -rightside: - readmode_title: Read Mode - translate_title: Toggle Between Traditional Chinese And Simplified Chinese - night_mode_title: Toggle Between Light And Dark Mode - back_to_top: Back To Top - toc: Table Of Contents - scroll_to_comment: Scroll To Comments - setting: Setting - aside: Toggle between Single-column and Double-column - chat: Chat - -copy_copyright: - author: Author - link: Link - source: Source - info: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source. - -Snackbar: - chs_to_cht: You have switched to Traditional Chinese - cht_to_chs: You have switched to Simplified Chinese - day_to_night: You have switched to Dark Mode - night_to_day: You have switched to Light Mode - -loading: Loading... -load_more: Load More - -error404: Page Not Found diff --git a/themes/butterfly/languages/en.yml b/themes/butterfly/languages/en.yml deleted file mode 100644 index 489b57b6..00000000 --- a/themes/butterfly/languages/en.yml +++ /dev/null @@ -1,123 +0,0 @@ -footer: - framework: Framework - theme: Theme - -copy: - success: Copy Successful - error: Copy Error - noSupport: Browser Not Supported - -page: - articles: Articles - tag: Tag - category: Category - archives: Archives - -card_post_count: comments - -no_title: Untitled - -post: - created: Created - updated: Updated - wordcount: Word Count - min2read: Reading Time - min2read_unit: mins - page_pv: Post Views - comments: Comments - copyright: - author: Author - link: Link - copyright_notice: Copyright Notice - copyright_content: 'All articles in this blog are licensed under %s unless stating additionally.' - recommend: Related Articles - edit: Edited on - -search: - title: Search - load_data: Loading the Database - algolia_search: - input_placeholder: Search for Posts - hits_empty: "We didn't find any results for the search: ${query}." - hits_stats: '${hits} results found in ${time} ms' - - local_search: - input_placeholder: Search for Posts - hits_empty: "We didn't find any results for the search: ${query}" - hits_stats: '${hits} results found' - -pagination: - prev: Previous - next: Next - -comment: Comment - -aside: - articles: Articles - tags: Tags - categories: Categories - card_announcement: Announcement - card_categories: Categories - card_tags: Tags - card_archives: Archives - card_recent_post: Recent Post - card_webinfo: - headline: Info - article_name: Article - runtime: - name: Runtime - unit: days - last_push_date: - name: Last Update - site_wordcount: Total Count - site_uv_name: UV - site_pv_name: PV - more_button: View More - card_newest_comments: - headline: Latest Comments - loading_text: loading... - error: Unable to retrieve comments, please check the configuration - zero: No comments - image: image - link: link - code: code - card_toc: Contents - card_post_series: Series - -date_suffix: - just: Just now - min: minutes ago - hour: hours ago - day: days ago - month: months ago - -donate: Sponsor -share: Share - -rightside: - readmode_title: Read Mode - translate_title: Toggle Between Traditional Chinese And Simplified Chinese - night_mode_title: Toggle Between Light And Dark Mode - back_to_top: Back To Top - toc: Table Of Contents - scroll_to_comment: Scroll To Comments - setting: Setting - aside: Toggle between Single-column and Double-column - chat: Chat - -copy_copyright: - author: Author - link: Link - source: Source - info: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source. - -Snackbar: - chs_to_cht: You have switched to Traditional Chinese - cht_to_chs: You have switched to Simplified Chinese - day_to_night: You have switched to Dark Mode - night_to_day: You have switched to Light Mode - -loading: Loading... -load_more: Load More - -error404: Page Not Found \ No newline at end of file diff --git a/themes/butterfly/languages/zh-CN.yml b/themes/butterfly/languages/zh-CN.yml deleted file mode 100644 index b030079b..00000000 --- a/themes/butterfly/languages/zh-CN.yml +++ /dev/null @@ -1,124 +0,0 @@ -footer: - framework: 框架 - theme: 主题 - -copy: - success: 复制成功 - error: 复制错误 - noSupport: 浏览器不支持 - -page: - articles: 文章总览 - tag: 标签 - category: 分类 - archives: 归档 - -card_post_count: 条评论 - -no_title: 无题 - -post: - created: 发表于 - updated: 更新于 - wordcount: 字数总计 - min2read: 阅读时长 - min2read_unit: 分钟 - page_pv: 阅读量 - comments: 评论数 - copyright: - author: 文章作者 - link: 文章链接 - copyright_notice: 版权声明 - copyright_content: '本博客所有文章除特别声明外,均采用 - %s 许可协议。转载请注明来自 %s!' - recommend: 相关推荐 - edit: 编辑 - -search: - title: 搜索 - load_data: 数据库加载中 - algolia_search: - input_placeholder: 搜索文章 - hits_empty: '找不到您查询的内容:${query}' - hits_stats: '找到 ${hits} 条结果,用时 ${time} 毫秒' - - local_search: - input_placeholder: 搜索文章 - hits_empty: '找不到您查询的内容:${query}' - hits_stats: '共找到 ${hits} 篇文章' - -pagination: - prev: 上一篇 - next: 下一篇 - -comment: 评论 - -aside: - articles: 文章 - tags: 标签 - categories: 分类 - card_announcement: 公告 - card_categories: 分类 - card_tags: 标签 - card_archives: 归档 - card_recent_post: 最新文章 - card_webinfo: - headline: 网站资讯 - article_name: 文章数目 - runtime: - name: 已运行时间 - unit: 天 - last_push_date: - name: 最后更新时间 - site_wordcount: 本站总字数 - site_uv_name: 本站访客数 - site_pv_name: 本站总访问量 - more_button: 查看更多 - card_newest_comments: - headline: 最新评论 - loading_text: 正在加载中... - error: 无法获取评论,请确认相关配置是否正确 - zero: 没有评论 - image: 图片 - link: 链接 - code: 代码 - card_toc: 目录 - card_post_series: 系列文章 - -date_suffix: - just: 刚刚 - min: 分钟前 - hour: 小时前 - day: 天前 - month: 个月前 - -donate: 赞助 -share: 分享 - -rightside: - readmode_title: 阅读模式 - translate_title: 简繁转换 - night_mode_title: 浅色和深色模式转换 - back_to_top: 回到顶部 - toc: 目录 - scroll_to_comment: 直达评论 - setting: 设置 - aside: 单栏和双栏切换 - chat: 聊天 - -copy_copyright: - author: 作者 - link: 链接 - source: 来源 - info: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 - -Snackbar: - chs_to_cht: 你已切换为繁体中文 - cht_to_chs: 你已切换为简体中文 - day_to_night: 你已切换为深色模式 - night_to_day: 你已切换为浅色模式 - -loading: 加载中... -load_more: 加载更多 - -error404: 页面没有找到 diff --git a/themes/butterfly/languages/zh-TW.yml b/themes/butterfly/languages/zh-TW.yml deleted file mode 100644 index 4ac63b84..00000000 --- a/themes/butterfly/languages/zh-TW.yml +++ /dev/null @@ -1,124 +0,0 @@ -footer: - framework: 框架 - theme: 主題 - -copy: - success: 複製成功 - error: 複製錯誤 - noSupport: 瀏覽器不支援 - -page: - articles: 文章總覽 - tag: 標籤 - category: 分類 - archives: 歸檔 - -card_post_count: 條評論 - -no_title: 無標題 - -post: - created: 發表於 - updated: 更新於 - wordcount: 字數總計 - min2read: 閱讀時長 - min2read_unit: 分鐘 - page_pv: 閱讀量 - comments: 評論數 - copyright: - author: 文章作者 - link: 文章連結 - copyright_notice: 版權聲明 - copyright_content: '本部落格所有文章除特別聲明外,均採用 - %s 許可協議。轉載請註明來自 %s!' - recommend: 相關推薦 - edit: 編輯 - -search: - title: 搜尋 - load_data: 資料庫載入中 - algolia_search: - input_placeholder: 搜尋文章 - hits_empty: '找不到您查詢的內容:${query}' - hits_stats: '找到 ${hits} 條結果,用時 ${time} 毫秒' - - local_search: - input_placeholder: 搜尋文章 - hits_empty: '找不到您查詢的內容:${query}' - hits_stats: '共找到 ${hits} 篇文章' - -pagination: - prev: 上一篇 - next: 下一篇 - -comment: 評論 - -aside: - articles: 文章 - tags: 標籤 - categories: 分類 - card_announcement: 公告 - card_categories: 分類 - card_tags: 標籤 - card_archives: 歸檔 - card_recent_post: 最新文章 - card_webinfo: - headline: 網站資訊 - article_name: 文章數目 - runtime: - name: 已執行時間 - unit: 天 - last_push_date: - name: 最後更新時間 - site_wordcount: 本站總字數 - site_uv_name: 本站訪客數 - site_pv_name: 本站總訪問量 - more_button: 檢視更多 - card_newest_comments: - headline: 最新評論 - loading_text: 正在載入中... - error: 無法獲取評論,請確認相關配置是否正確 - zero: 沒有評論 - image: 圖片 - link: 連結 - code: 程式碼 - card_toc: 目錄 - card_post_series: 文章系列 - -date_suffix: - just: 剛剛 - min: 分鐘前 - hour: 小時前 - day: 天前 - month: 個月前 - -donate: 贊助 -share: 分享 - -rightside: - readmode_title: 閱讀模式 - translate_title: 簡繁轉換 - night_mode_title: 淺色和深色模式轉換 - back_to_top: 返回頂部 - toc: 目錄 - scroll_to_comment: 直達評論 - setting: 設定 - aside: 單欄和雙欄切換 - chat: 聊天 - -copy_copyright: - author: 作者 - link: 連結 - source: 來源 - info: 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。 - -Snackbar: - chs_to_cht: 你已切換為繁體中文 - cht_to_chs: 你已切換為簡體中文 - day_to_night: 你已切換為深色模式 - night_to_day: 你已切換為淺色模式 - -loading: 載入中... -load_more: 載入更多 - -error404: 頁面沒有找到 diff --git a/themes/butterfly/layout/archive.pug b/themes/butterfly/layout/archive.pug deleted file mode 100644 index 913dedcd..00000000 --- a/themes/butterfly/layout/archive.pug +++ /dev/null @@ -1,8 +0,0 @@ -extends includes/layout.pug - -block content - include ./includes/mixins/article-sort.pug - #archive - .article-sort-title= `${_p('page.articles')} - ${getArchiveLength()}` - +articleSort(page.posts) - include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/category.pug b/themes/butterfly/layout/category.pug deleted file mode 100644 index 234a0af6..00000000 --- a/themes/butterfly/layout/category.pug +++ /dev/null @@ -1,14 +0,0 @@ -extends includes/layout.pug - -block content - if theme.category_ui == 'index' - include ./includes/mixins/post-ui.pug - #recent-posts.recent-posts.category_ui - +postUI - include includes/pagination.pug - else - include ./includes/mixins/article-sort.pug - #category - .article-sort-title= _p('page.category') + ' - ' + page.category - +articleSort(page.posts) - include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/404.pug b/themes/butterfly/layout/includes/404.pug deleted file mode 100644 index 4a022c85..00000000 --- a/themes/butterfly/layout/includes/404.pug +++ /dev/null @@ -1,12 +0,0 @@ -- var top_img_404 = theme.error_404.background || theme.default_top_img - -#body-wrap.error404 - include ./header/index.pug - - #error-wrap - .error-content - .error-img - img(src=url_for(top_img_404) alt='Page not found') - .error-info - h1.error_title= '404' - .error_subtitle= theme.error_404.subtitle || _p('error404') diff --git a/themes/butterfly/layout/includes/additional-js.pug b/themes/butterfly/layout/includes/additional-js.pug deleted file mode 100644 index f37d8609..00000000 --- a/themes/butterfly/layout/includes/additional-js.pug +++ /dev/null @@ -1,65 +0,0 @@ -div - script(src=url_for(theme.asset.utils)) - script(src=url_for(theme.asset.main)) - - if theme.translate.enable - script(src=url_for(theme.asset.translate)) - - if theme.medium_zoom - script(src=url_for(theme.asset.medium_zoom)) - else if theme.fancybox - script(src=url_for(theme.asset.fancybox)) - - if theme.instantpage - script(src=url_for(theme.asset.instantpage), type='module') - - if theme.lazyload.enable - script(src=url_for(theme.asset.lazyload)) - - if theme.snackbar.enable - script(src=url_for(theme.asset.snackbar)) - - if theme.pangu.enable - != partial("includes/third-party/pangu.pug", {}, { cache: true }) - - .js-pjax - if needLoadCountJs - != partial("includes/third-party/card-post-count/index", {}, { cache: true }) - - if loadSubJs - include ./third-party/subtitle.pug - - include ./third-party/math/index.pug - - include ./third-party/abcjs/index.pug - - if commentsJsLoad - include ./third-party/comments/js.pug - - != partial("includes/third-party/prismjs", {}, { cache: true }) - - if theme.aside.enable && theme.newest_comments.enable - if theme.pjax.enable - != partial("includes/third-party/newest-comments/index", {}, { cache: true }) - else if (!is_post() && page.aside !== false) - != partial("includes/third-party/newest-comments/index", {}, { cache: true }) - - != fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)}) - - != partial("includes/third-party/effect", {}, { cache: true }) - - != partial("includes/third-party/chat/index", {}, { cache: true }) - - if theme.aplayerInject && theme.aplayerInject.enable - if theme.pjax.enable || theme.aplayerInject.per_page - include ./third-party/aplayer.pug - else if page.aplayer - include ./third-party/aplayer.pug - - if theme.pjax.enable - != partial("includes/third-party/pjax", {}, { cache: true }) - - if theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv - script(async data-pjax src= theme.asset.busuanzi || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') - - !=partial('includes/third-party/search/index', {}, {cache: true}) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/footer.pug b/themes/butterfly/layout/includes/footer.pug deleted file mode 100644 index 4d2ae791..00000000 --- a/themes/butterfly/layout/includes/footer.pug +++ /dev/null @@ -1,17 +0,0 @@ -#footer-wrap - if theme.footer.owner.enable - - var now = new Date() - - var nowYear = now.getFullYear() - if theme.footer.owner.since && theme.footer.owner.since != nowYear - .copyright!= `©${theme.footer.owner.since} - ${nowYear} By ${config.author}` - else - .copyright!= `©${nowYear} By ${config.author}` - if theme.footer.copyright - .framework-info - span= _p('footer.framework') + ' ' - a(href='https://hexo.io')= 'Hexo' - span.footer-separator | - span= _p('footer.theme') + ' ' - a(href='https://github.com/jerryc127/hexo-theme-butterfly')= 'Butterfly' - if theme.footer.custom_text - .footer_custom_text!=`${theme.footer.custom_text}` diff --git a/themes/butterfly/layout/includes/head.pug b/themes/butterfly/layout/includes/head.pug deleted file mode 100644 index cd625660..00000000 --- a/themes/butterfly/layout/includes/head.pug +++ /dev/null @@ -1,68 +0,0 @@ -- var pageTitle -- is_archive() ? page.title = findArchivesTitle(page, theme.menu, date) : '' -- if (is_tag()) pageTitle = _p('page.tag') + ': ' + page.tag -- else if (is_category()) pageTitle = _p('page.category') + ': ' + page.category -- else if (is_current('/404.html', [strict])) pageTitle = _p('error404') -- else pageTitle = page.title || config.title || '' - -- var isSubtitle = config.subtitle ? ' - ' + config.subtitle : '' -- var tabTitle = is_home() || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title -- var pageAuthor = config.email ? config.author + ',' + config.email : config.author -- var pageCopyright = config.copyright || config.author -- var themeColorLight = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_light || '#ffffff' -- var themeColorDark = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_dark || '#0d0d0d' -- var themeColor = theme.display_mode === 'dark' ? themeColorDark : themeColorLight - -meta(charset='UTF-8') -meta(http-equiv="X-UA-Compatible" content="IE=edge") -meta(name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover") -title= tabTitle -meta(name="author" content=pageAuthor) -meta(name="copyright" content=pageCopyright) -meta(name ="format-detection" content="telephone=no") -meta(name="theme-color" content=themeColor) - -//- Open_Graph -include ./head/Open_Graph.pug - -!=favicon_tag(theme.favicon || config.favicon) -link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html)) - -//- 預解析 -!=partial('includes/head/preconnect', {}, {cache: true}) - -//- 網站驗證 -!=partial('includes/head/site_verification', {}, {cache: true}) - -//- PWA -if (theme.pwa && theme.pwa.enable) - !=partial('includes/head/pwa', {}, {cache: true}) - -//- main css -link(rel='stylesheet', href=url_for(theme.asset.main_css)) -link(rel='stylesheet', href=url_for(theme.asset.fontawesome) media="print" onload="this.media='all'") - -if (theme.snackbar && theme.snackbar.enable) - link(rel='stylesheet', href=url_for(theme.asset.snackbar_css) media="print" onload="this.media='all'") - -if theme.fancybox - link(rel='stylesheet' href=url_for(theme.asset.fancybox_css) media="print" onload="this.media='all'") - -//- google_adsense -!=partial('includes/head/google_adsense', {}, {cache: true}) - -//- analytics -!=partial('includes/head/analytics', {}, {cache: true}) - -//- font -if theme.blog_title_font && theme.blog_title_font.font_link - link(rel='stylesheet' href=url_for(theme.blog_title_font.font_link) media="print" onload="this.media='all'") - -//- global config -!=partial('includes/head/config', {}, {cache: true}) - -include ./head/config_site.pug - -!=fragment_cache('injectHeadJs', function(){return inject_head_js()}) - -!=fragment_cache('injectHead', function(){return injectHtml(theme.inject.head)}) diff --git a/themes/butterfly/layout/includes/head/Open_Graph.pug b/themes/butterfly/layout/includes/head/Open_Graph.pug deleted file mode 100644 index 8fe04e0c..00000000 --- a/themes/butterfly/layout/includes/head/Open_Graph.pug +++ /dev/null @@ -1,14 +0,0 @@ -if theme.Open_Graph_meta.enable - - - const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img - let ogOption = Object.assign({ - type: is_post() ? 'article' : 'website', - image: coverVal ? full_url_for(coverVal) : '', - fb_admins: theme.facebook_comments.user_id || '', - fb_app_id: theme.facebook_comments.app_id || '', - }, theme.Open_Graph_meta.option) - - - != open_graph(ogOption) -else - meta(name="description" content=page_description()) - diff --git a/themes/butterfly/layout/includes/head/analytics.pug b/themes/butterfly/layout/includes/head/analytics.pug deleted file mode 100644 index ad4a395e..00000000 --- a/themes/butterfly/layout/includes/head/analytics.pug +++ /dev/null @@ -1,28 +0,0 @@ -if theme.baidu_analytics - script. - var _hmt = _hmt || []; - (function() { - var hm = document.createElement("script"); - hm.src = "https://hm.baidu.com/hm.js?!{theme.baidu_analytics}"; - var s = document.getElementsByTagName("script")[0]; - s.parentNode.insertBefore(hm, s); - })(); - -if theme.google_analytics - script(async src=`https://www.googletagmanager.com/gtag/js?id=${theme.google_analytics}`) - script. - window.dataLayer = window.dataLayer || []; - function gtag(){dataLayer.push(arguments);} - gtag('js', new Date()); - gtag('config', '!{theme.google_analytics}'); - -if theme.cloudflare_analytics - script(defer data-pjax src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon=`{"token": "${theme.cloudflare_analytics}"}`) - -if theme.microsoft_clarity - script. - (function(c,l,a,r,i,t,y){ - c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; - t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; - y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); - })(window, document, "clarity", "script", "!{theme.microsoft_clarity}"); \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/config.pug b/themes/butterfly/layout/includes/head/config.pug deleted file mode 100644 index 05b046c8..00000000 --- a/themes/butterfly/layout/includes/head/config.pug +++ /dev/null @@ -1,130 +0,0 @@ -- - let algolia = 'undefined'; - let env = process.env; - if (theme.algolia_search.enable) { - algolia = JSON.stringify({ - appId: env.ALGOLIA_APP_ID || config.algolia.appId || config.algolia.applicationID, - apiKey: env.ALGOLIA_API_KEY || config.algolia.apiKey, - indexName: env.ALGOLIA_INDEX_NAME || config.algolia.indexName, - hits: theme.algolia_search.hits, - // search languages - languages: { - input_placeholder: _p("search.algolia_search.input_placeholder"), - hits_empty: _p("search.algolia_search.hits_empty"), - hits_stats: _p("search.algolia_search.hits_stats"), - } - }) - } - - let localSearch = 'undefined'; - if (theme.local_search && theme.local_search.enable) { - localSearch = JSON.stringify({ - path: theme.local_search.CDN ? theme.local_search.CDN : config.root + config.search.path, - preload: theme.local_search.preload, - top_n_per_article: theme.local_search.top_n_per_article, - unescape: theme.local_search.unescape, - languages: { - // search languages - hits_empty: _p("search.local_search.hits_empty"), - hits_stats: _p("search.local_search.hits_stats"), - } - }) - } - - let translate = 'undefined'; - if (theme.translate && theme.translate.enable){ - translate = JSON.stringify({ - defaultEncoding: theme.translate.defaultEncoding, - translateDelay: theme.translate.translateDelay, - msgToTraditionalChinese: theme.translate.msgToTraditionalChinese, - msgToSimplifiedChinese: theme.translate.msgToSimplifiedChinese - }) - } - - let copyright = 'undefined'; - if (theme.copy.enable && theme.copy.copyright.enable){ - copyright = JSON.stringify({ - limitCount: theme.copy.copyright.limit_count, - languages: { - author: _p("copy_copyright.author") + ': ' + config.author, - link: _p("copy_copyright.link") + ': ', - source: _p("copy_copyright.source") + ': ' + config.title, - info: _p("copy_copyright.info") - } - }) - } - - let Snackbar = 'undefined'; - if (theme.snackbar && theme.snackbar.enable) { - Snackbar = JSON.stringify({ - chs_to_cht: _p("Snackbar.chs_to_cht"), - cht_to_chs: _p("Snackbar.cht_to_chs"), - day_to_night: _p("Snackbar.day_to_night"), - night_to_day: _p("Snackbar.night_to_day"), - bgLight: theme.snackbar.bg_light, - bgDark: theme.snackbar.bg_dark, - position: theme.snackbar.position, - }) - } - - let noticeOutdate = 'undefined'; - if (theme.noticeOutdate && theme.noticeOutdate.enable) { - noticeOutdate = JSON.stringify({ - limitDay: theme.noticeOutdate.limit_day, - position: theme.noticeOutdate.position, - messagePrev: theme.noticeOutdate.message_prev, - messageNext: theme.noticeOutdate.message_next, - }) - } - - let highlight = 'undefined'; - if ((config.highlight && config.highlight.enable) || (config.prismjs && config.prismjs.enable)) { - highlight = JSON.stringify({ - plugin: config.highlight.enable ? 'highlighjs' : 'prismjs', - highlightCopy: theme.highlight_copy, - highlightLang: theme.highlight_lang, - highlightHeightLimit: theme.highlight_height_limit - }) - } - -script. - const GLOBAL_CONFIG = { - root: '!{config.root}', - algolia: !{algolia}, - localSearch: !{localSearch}, - translate: !{translate}, - noticeOutdate: !{noticeOutdate}, - highlight: !{highlight}, - copy: { - success: '!{_p("copy.success")}', - error: '!{_p("copy.error")}', - noSupport: '!{_p("copy.noSupport")}' - }, - relativeDate: { - homepage: !{theme.post_meta.page.date_format === 'relative'}, - post: !{theme.post_meta.post.date_format === 'relative'} - }, - runtime: '!{theme.runtimeshow.enable ? _p("aside.card_webinfo.runtime.unit") : ""}', - dateSuffix: { - just: '!{_p("date_suffix.just")}', - min: '!{_p("date_suffix.min")}', - hour: '!{_p("date_suffix.hour")}', - day: '!{_p("date_suffix.day")}', - month: '!{_p("date_suffix.month")}' - }, - copyright: !{copyright}, - lightbox: '!{ theme.medium_zoom ? "mediumZoom" : (theme.fancybox ? "fancybox" : "null" )}', - Snackbar: !{Snackbar}, - infinitegrid: { - js: '!{url_for(theme.asset.egjs_infinitegrid)}', - buttonText: '!{_p("load_more")}' - }, - isPhotoFigcaption: !{theme.photofigcaption}, - islazyload: !{theme.lazyload.enable}, - isAnchor: !{theme.anchor.auto_update || false}, - percent: { - toc: !{theme.toc.scroll_percent}, - rightside: !{theme.rightside_scroll_percent}, - }, - autoDarkmode: !{theme.darkmode.enable && theme.darkmode.autoChangeMode === 1} - } diff --git a/themes/butterfly/layout/includes/head/config_site.pug b/themes/butterfly/layout/includes/head/config_site.pug deleted file mode 100644 index 94cbb51c..00000000 --- a/themes/butterfly/layout/includes/head/config_site.pug +++ /dev/null @@ -1,30 +0,0 @@ -- - const titleVal = pageTitle.replace(/'/ig,"\\'") - - let isHighlightShrink - if (theme.highlight_shrink == 'none') isHighlightShrink = 'undefined' - else if (page.highlight_shrink === true || page.highlight_shrink === false) isHighlightShrink = page.highlight_shrink - else isHighlightShrink = theme.highlight_shrink - - var showToc = false - if (theme.aside.enable && page.aside !== false) { - let tocEnable = false - if (is_post()) { - if (theme.toc.post) tocEnable = true - } else if (is_page()) { - if (theme.toc.page) tocEnable = true - } - const pageToc = page.toc === true || page.toc === false ? page.toc : tocEnable - showToc = pageToc && (toc(page.content) !== '' || page.encrypt == true ) - } -- - -script#config-diff. - var GLOBAL_CONFIG_SITE = { - title: '!{titleVal}', - isPost: !{is_post()}, - isHome: !{is_home()}, - isHighlightShrink: !{isHighlightShrink}, - isToc: !{showToc}, - postUpdate: '!{full_date(page.updated)}' - } diff --git a/themes/butterfly/layout/includes/head/google_adsense.pug b/themes/butterfly/layout/includes/head/google_adsense.pug deleted file mode 100644 index 3ef1af99..00000000 --- a/themes/butterfly/layout/includes/head/google_adsense.pug +++ /dev/null @@ -1,9 +0,0 @@ -if (theme.google_adsense && theme.google_adsense.enable) - script(async src=theme.google_adsense.js) - - if theme.google_adsense.auto_ads - script. - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: '!{theme.google_adsense.client}', - enable_page_level_ads: '!{theme.google_adsense.enable_page_level_ads}' - }); \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/noscript.pug b/themes/butterfly/layout/includes/head/noscript.pug deleted file mode 100644 index cc3befa6..00000000 --- a/themes/butterfly/layout/includes/head/noscript.pug +++ /dev/null @@ -1,14 +0,0 @@ -noscript. - \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/preconnect.pug b/themes/butterfly/layout/includes/head/preconnect.pug deleted file mode 100644 index f8af3809..00000000 --- a/themes/butterfly/layout/includes/head/preconnect.pug +++ /dev/null @@ -1,35 +0,0 @@ -- - const { internal_provider, third_party_provider, custom_format } = theme.CDN - const providers = { - 'jsdelivr': '//cdn.jsdelivr.net', - 'cdnjs': '//cdnjs.cloudflare.com', - 'unpkg': '//unpkg.com', - 'custom': custom_format && custom_format.match(/^((https?:)?(\/\/[^/]+)|([^/]+))(\/|$)/)[1] - } -- - -if internal_provider === third_party_provider && internal_provider !== 'local' - link(rel="preconnect" href=providers[internal_provider]) -else - if internal_provider !== 'local' - link(rel="preconnect" href=providers[internal_provider]) - if third_party_provider !== 'local' - link(rel="preconnect" href=providers[third_party_provider]) - -if theme.google_analytics - link(rel="preconnect" href="//www.google-analytics.com" crossorigin='') - -if theme.baidu_analytics - link(rel="preconnect" href="//hm.baidu.com") - -if theme.cloudflare_analytics - link(rel="preconnect" href="//static.cloudflareinsights.com") - -if theme.microsoft_clarity - link(rel="preconnect" href="//www.clarity.ms") - -if theme.blog_title_font && theme.blog_title_font.font_link && theme.blog_title_font.font_link.indexOf('//fonts.googleapis.com') != -1 - link(rel="preconnect" href="//fonts.googleapis.com" crossorigin='') - -if !theme.asset.busuanzi && (theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv) - link(rel="preconnect" href="//busuanzi.ibruce.info") \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/pwa.pug b/themes/butterfly/layout/includes/head/pwa.pug deleted file mode 100644 index e9152f2e..00000000 --- a/themes/butterfly/layout/includes/head/pwa.pug +++ /dev/null @@ -1,11 +0,0 @@ -link(rel="manifest" href=url_for(theme.pwa.manifest)) -if(theme.pwa.theme_color) - meta(name="msapplication-TileColor" content=theme.pwa.theme_color) -if(theme.pwa.apple_touch_icon) - link(rel="apple-touch-icon" sizes="180x180" href=url_for(theme.pwa.apple_touch_icon)) -if(theme.pwa.favicon_32_32) - link(rel="icon" type="image/png" sizes="32x32" href=url_for(theme.pwa.favicon_32_32)) -if(theme.pwa.favicon_16_16) - link(rel="icon" type="image/png" sizes="16x16" href=url_for(theme.pwa.favicon_16_16)) -if(theme.pwa.mask_icon) - link(rel="mask-icon" href=url_for(theme.pwa.mask_icon) color="#5bbad5") diff --git a/themes/butterfly/layout/includes/head/site_verification.pug b/themes/butterfly/layout/includes/head/site_verification.pug deleted file mode 100644 index 89476449..00000000 --- a/themes/butterfly/layout/includes/head/site_verification.pug +++ /dev/null @@ -1,3 +0,0 @@ -if theme.site_verification - each item in theme.site_verification - meta(name=item.name content=item.content) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/index.pug b/themes/butterfly/layout/includes/header/index.pug deleted file mode 100644 index 33e01dbc..00000000 --- a/themes/butterfly/layout/includes/header/index.pug +++ /dev/null @@ -1,52 +0,0 @@ -if !theme.disable_top_img && page.top_img !== false - if is_post() - - var top_img = page.top_img || page.cover || theme.default_top_img - else if is_page() - - var top_img = page.top_img || theme.default_top_img - else if is_tag() - - var top_img = theme.tag_per_img && theme.tag_per_img[page.tag] - - top_img = top_img ? top_img : (theme.tag_img !== false ? theme.tag_img || theme.default_top_img : false) - else if is_category() - - var top_img = theme.category_per_img && theme.category_per_img[page.category] - - top_img = top_img ? top_img : (theme.category_img !== false ? theme.category_img || theme.default_top_img : false) - else if is_home() - - var top_img = theme.index_img !== false ? theme.index_img || theme.default_top_img : false - else if is_archive() - - var top_img = theme.archive_img !== false ? theme.archive_img || theme.default_top_img : false - else - - var top_img = page.top_img || theme.default_top_img - - if top_img !== false - - var imgSource = top_img && isImgOrUrl(top_img) ? `background-image: url('${url_for(top_img)}')` : `background: ${top_img}` - - var bg_img = top_img ? imgSource : '' - - var site_title = page.title || page.tag || page.category || config.title - - var isHomeClass = is_home() ? 'full_page' : 'not-home-page' - - is_post() ? isHomeClass = 'post-bg' : isHomeClass - else - - var isHomeClass = 'not-top-img' -else - - var top_img = false - - var isHomeClass = 'not-top-img' - -- const isFixedClass = theme.nav.fixed ? ' fixed' : '' - -header#page-header(class=`${isHomeClass+isFixedClass}` style=bg_img) - !=partial('includes/header/nav', {}, {cache: true}) - if top_img !== false - if is_post() - include ./post-info.pug - else if is_home() - #site-info - h1#site-title=site_title - if theme.subtitle.enable - - var loadSubJs = true - #site-subtitle - span#subtitle - if(theme.social) - #site_social_icons - !=partial('includes/header/social', {}, {cache: true}) - #scroll-down - i.fas.fa-angle-down.scroll-down-effects - else - #page-site-info - h1#site-title=site_title \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/menu_item.pug b/themes/butterfly/layout/includes/header/menu_item.pug deleted file mode 100644 index d172db74..00000000 --- a/themes/butterfly/layout/includes/header/menu_item.pug +++ /dev/null @@ -1,27 +0,0 @@ -if theme.menu - .menus_items - each value, label in theme.menu - if typeof value !== 'object' - .menus_item - - const valueArray = value.split('||') - a.site-page(href=url_for(trim(valueArray[0]))) - if valueArray[1] - i.fa-fw(class=trim(valueArray[1])) - span=' '+label - else - .menus_item - - const labelArray = label.split('||') - - const hideClass = labelArray[2] && trim(labelArray[2]) === 'hide' ? 'hide' : '' - a.site-page.group(class=`${hideClass}` href='javascript:void(0);') - if labelArray[1] - i.fa-fw(class=trim(labelArray[1])) - span=' '+ trim(labelArray[0]) - i.fas.fa-chevron-down - ul.menus_item_child - each val,lab in value - - const valArray = val.split('||') - li - a.site-page.child(href=url_for(trim(valArray[0]))) - if valArray[1] - i.fa-fw(class=trim(valArray[1])) - span=' '+ lab \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/nav.pug b/themes/butterfly/layout/includes/header/nav.pug deleted file mode 100644 index f6376ca9..00000000 --- a/themes/butterfly/layout/includes/header/nav.pug +++ /dev/null @@ -1,21 +0,0 @@ -nav#nav - span#blog-info - a(href=url_for('/') title=config.title) - if theme.nav.logo - img.site-icon(src=url_for(theme.nav.logo)) - if theme.nav.display_title - span.site-name=config.title - - #menus - if (theme.algolia_search.enable || theme.local_search.enable || theme.docsearch.enable) - #search-button - a.site-page.social-icon.search(href="javascript:void(0);") - i.fas.fa-search.fa-fw - span=' '+_p('search.title') - !=partial('includes/header/menu_item', {}, {cache: true}) - - #toggle-menu - a.site-page(href="javascript:void(0);") - i.fas.fa-bars.fa-fw - - diff --git a/themes/butterfly/layout/includes/header/post-info.pug b/themes/butterfly/layout/includes/header/post-info.pug deleted file mode 100644 index def23123..00000000 --- a/themes/butterfly/layout/includes/header/post-info.pug +++ /dev/null @@ -1,144 +0,0 @@ -- let comments = theme.comments -#post-info - h1.post-title= page.title || _p('no_title') - if theme.post_edit.enable - a.post-edit-link(href=theme.post_edit.url + page.source title=_p('post.edit') target="_blank") - i.fas.fa-pencil-alt - - #post-meta - .meta-firstline - if (theme.post_meta.post.date_type) - span.post-meta-date - if (theme.post_meta.post.date_type === 'both') - i.far.fa-calendar-alt.fa-fw.post-meta-icon - span.post-meta-label= _p('post.created') - time.post-meta-date-created(datetime=date_xml(page.date) title=_p('post.created') + ' ' + full_date(page.date))=date(page.date, config.date_format) - span.post-meta-separator | - i.fas.fa-history.fa-fw.post-meta-icon - span.post-meta-label= _p('post.updated') - time.post-meta-date-updated(datetime=date_xml(page.updated) title=_p('post.updated') + ' ' + full_date(page.updated))=date(page.updated, config.date_format) - else - - let data_type_update = theme.post_meta.post.date_type === 'updated' - - let date_type = data_type_update ? 'updated' : 'date' - - let date_icon = data_type_update ? 'fas fa-history' :'far fa-calendar-alt' - - let date_title = data_type_update ? _p('post.updated') : _p('post.created') - i.fa-fw.post-meta-icon(class=date_icon) - span.post-meta-label= date_title - time(datetime=date_xml(page[date_type]) title=date_title + ' ' + full_date(page[date_type]))=date(page[date_type], config.date_format) - if (theme.post_meta.post.categories && page.categories.data.length > 0) - span.post-meta-categories - if (theme.post_meta.post.date_type) - span.post-meta-separator | - - each item, index in page.categories.data - i.fas.fa-inbox.fa-fw.post-meta-icon - a(href=url_for(item.path)).post-meta-categories #[=item.name] - if (index < page.categories.data.length - 1) - i.fas.fa-angle-right.post-meta-separator - - .meta-secondline - - let postWordcount = theme.wordcount.enable && (theme.wordcount.post_wordcount || theme.wordcount.min2read) - if (postWordcount) - span.post-meta-separator | - span.post-meta-wordcount - if theme.wordcount.post_wordcount - i.far.fa-file-word.fa-fw.post-meta-icon - span.post-meta-label= _p('post.wordcount') + ':' - span.word-count= wordcount(page.content) - if theme.wordcount.min2read - span.post-meta-separator | - if theme.wordcount.min2read - i.far.fa-clock.fa-fw.post-meta-icon - span.post-meta-label= _p('post.min2read') + ':' - span= min2read(page.content, {cn: 350, en: 160}) + _p('post.min2read_unit') - - //- for pv and count - mixin pvBlock(parent_id,parent_class,parent_title) - span.post-meta-separator | - span(class=parent_class id=parent_id data-flag-title=page.title) - i.far.fa-eye.fa-fw.post-meta-icon - span.post-meta-label=_p('post.page_pv') + ':' - if block - block - - - const commentUse = comments.use - if page.comments !== false && commentUse && !comments.lazyload - if commentUse[0] === 'Valine' && theme.valine.visitor - +pvBlock(url_for(page.path),'leancloud_visitors',page.title) - span.leancloud-visitors-count - i.fa-solid.fa-spinner.fa-spin - else if commentUse[0] === 'Waline' && theme.waline.pageview - +pvBlock('','','') - span.waline-pageview-count(data-path=url_for(page.path)) - i.fa-solid.fa-spinner.fa-spin - else if commentUse[0] === 'Twikoo' && theme.twikoo.visitor - +pvBlock('','','') - span#twikoo_visitors - i.fa-solid.fa-spinner.fa-spin - else if commentUse[0] === 'Artalk' && theme.artalk.visitor - +pvBlock('','','') - span#ArtalkPV - i.fa-solid.fa-spinner.fa-spin - else if theme.busuanzi.page_pv - +pvBlock('','post-meta-pv-cv','') - span#busuanzi_value_page_pv - i.fa-solid.fa-spinner.fa-spin - else if theme.busuanzi.page_pv - +pvBlock('','post-meta-pv-cv','') - span#busuanzi_value_page_pv - i.fa-solid.fa-spinner.fa-spin - - if comments.count && !comments.lazyload && page.comments !== false && comments.use - - var whichCount = comments.use[0] - - mixin countBlock - span.post-meta-separator | - span.post-meta-commentcount - i.far.fa-comments.fa-fw.post-meta-icon - span.post-meta-label= _p('post.comments') + ':' - if block - block - - case whichCount - when 'Disqus' - +countBlock - a.disqus-comment-count(href=full_url_for(page.path) + '#post-comment') - i.fa-solid.fa-spinner.fa-spin - when 'Disqusjs' - +countBlock - a.disqusjs-comment-count(href=full_url_for(page.path) + '#post-comment') - i.fa-solid.fa-spinner.fa-spin - when 'Valine' - +countBlock - a(href=url_for(page.path) + '#post-comment' itemprop="discussionUrl") - span.valine-comment-count(data-xid=url_for(page.path) itemprop="commentCount") - i.fa-solid.fa-spinner.fa-spin - when 'Waline' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.waline-comment-count(data-path=url_for(page.path)) - i.fa-solid.fa-spinner.fa-spin - when 'Gitalk' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.gitalk-comment-count - i.fa-solid.fa-spinner.fa-spin - when 'Twikoo' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span#twikoo-count - i.fa-solid.fa-spinner.fa-spin - when 'Facebook Comments' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.fb-comments-count(data-href=urlNoIndex()) - when 'Remark42' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.remark42__counter(data-url=urlNoIndex()) - i.fa-solid.fa-spinner.fa-spin - when 'Artalk' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.artalk-count - i.fa-solid.fa-spinner.fa-spin \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/social.pug b/themes/butterfly/layout/includes/header/social.pug deleted file mode 100644 index b7b586e7..00000000 --- a/themes/butterfly/layout/includes/header/social.pug +++ /dev/null @@ -1,4 +0,0 @@ -each url, icon in theme.social - a.social-icon(href=url_for(trim(url.split('||')[0])) target="_blank" - title=url.split('||')[1] === undefined ? '' : trim(url.split('||')[1])) - i(class=icon style=url.split('||')[2] === undefined ? '' : `color: ${trim(url.split('||')[2]).replace(/[\'\"]/g, '')};`) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/layout.pug b/themes/butterfly/layout/includes/layout.pug deleted file mode 100644 index dde47c52..00000000 --- a/themes/butterfly/layout/includes/layout.pug +++ /dev/null @@ -1,47 +0,0 @@ -- var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : '' -- page.aside = is_archive() ? theme.aside.display.archive: is_category() ? theme.aside.display.category : is_tag() ? theme.aside.display.tag : page.aside -- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : '' -- var pageType = is_post() ? 'post' : 'page' - -doctype html -html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside) - head - include ./head.pug - body - if theme.preloader.enable - !=partial('includes/loading/index', {}, {cache: true}) - - if theme.background - #web_bg - - !=partial('includes/sidebar', {}, {cache: true}) - - if page.type !== '404' - #body-wrap(class=pageType) - include ./header/index.pug - - main#content-inner.layout(class=hideAside) - if body - div!= body - else - block content - if theme.aside.enable && page.aside !== false - include widget/index.pug - - - var footerBg = theme.footer_bg - if (footerBg) - if (footerBg === true) - - var footer_bg = bg_img - else - - var footer_bg = isImgOrUrl(theme.footer_bg) ? `background-image: url('${url_for(footerBg)}')` : `background: ${footerBg}` - else - - var footer_bg = '' - - footer#footer(style=footer_bg) - !=partial('includes/footer', {}, {cache: true}) - - else - include ./404.pug - - include ./rightside.pug - include ./additional-js.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/fullpage-loading.pug b/themes/butterfly/layout/includes/loading/fullpage-loading.pug deleted file mode 100644 index 4a2c8bf5..00000000 --- a/themes/butterfly/layout/includes/loading/fullpage-loading.pug +++ /dev/null @@ -1,33 +0,0 @@ -#loading-box - .loading-left-bg - .loading-right-bg - .spinner-box - .configure-border-1 - .configure-core - .configure-border-2 - .configure-core - .loading-word= _p('loading') - -script. - (()=>{ - const $loadingBox = document.getElementById('loading-box') - const $body = document.body - const preloader = { - endLoading: () => { - $body.style.overflow = '' - $loadingBox.classList.add('loaded') - }, - initLoading: () => { - $body.style.overflow = 'hidden' - $loadingBox.classList.remove('loaded') - } - } - - preloader.initLoading() - window.addEventListener('load',() => { preloader.endLoading() }) - - if (!{theme.pjax && theme.pjax.enable}) { - document.addEventListener('pjax:send', () => { preloader.initLoading() }) - document.addEventListener('pjax:complete', () => { preloader.endLoading() }) - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/index.pug b/themes/butterfly/layout/includes/loading/index.pug deleted file mode 100644 index 663fe501..00000000 --- a/themes/butterfly/layout/includes/loading/index.pug +++ /dev/null @@ -1,4 +0,0 @@ -if theme.preloader.source === 1 - include ./fullpage-loading.pug -else - include ./pace.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/pace.pug b/themes/butterfly/layout/includes/loading/pace.pug deleted file mode 100644 index 7abd6178..00000000 --- a/themes/butterfly/layout/includes/loading/pace.pug +++ /dev/null @@ -1,11 +0,0 @@ -script. - window.paceOptions = { - restartOnPushState: false - } - - document.addEventListener('pjax:send', () => { - Pace.restart() - }) - -link(rel="stylesheet", href=url_for(theme.preloader.pace_css_url || theme.asset.pace_default_css)) -script(src=url_for(theme.asset.pace_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/mixins/article-sort.pug b/themes/butterfly/layout/includes/mixins/article-sort.pug deleted file mode 100644 index 3eaf0383..00000000 --- a/themes/butterfly/layout/includes/mixins/article-sort.pug +++ /dev/null @@ -1,23 +0,0 @@ -mixin articleSort(posts) - .article-sort - - var year - - posts.each(function (article) { - - let tempYear = date(article.date, 'YYYY') - - let no_cover = article.cover === false || !theme.cover.archives_enable ? 'no-article-cover' : '' - - let title = article.title || _p('no_title') - if tempYear !== year - - year = tempYear - .article-sort-item.year= year - .article-sort-item(class=no_cover) - if article.cover && theme.cover.archives_enable - a.article-sort-item-img(href=url_for(article.path) title=title) - if article.cover_type === 'img' - img(src=url_for(article.cover) alt=title onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'`) - else - div(style=`background: ${article.cover}`) - .article-sort-item-info - .article-sort-item-time - i.far.fa-calendar-alt - time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))= date(article.date, config.date_format) - a.article-sort-item-title(href=url_for(article.path) title=title)= title - - }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/mixins/post-ui.pug b/themes/butterfly/layout/includes/mixins/post-ui.pug deleted file mode 100644 index 5621b6db..00000000 --- a/themes/butterfly/layout/includes/mixins/post-ui.pug +++ /dev/null @@ -1,129 +0,0 @@ -mixin postUI(posts) - each article , index in page.posts.data - .recent-post-item - - - let link = article.link || article.path - let title = article.title || _p('no_title') - const position = theme.cover.position - let leftOrRight = position === 'both' - ? index%2 == 0 ? 'left' : 'right' - : position === 'left' ? 'left' : 'right' - let post_cover = article.cover - let no_cover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : '' - - - if post_cover && theme.cover.index_enable - .post_cover(class=leftOrRight) - a(href=url_for(link) title=title) - if article.cover_type === 'img' - img.post-bg(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) - else - div.post-bg(style=`background: ${post_cover}`) - .recent-post-info(class=no_cover) - a.article-title(href=url_for(link) title=title) - if (is_home() && (article.top || article.sticky > 0)) - i.fas.fa-thumbtack.sticky - = title - .article-meta-wrap - if (theme.post_meta.page.date_type) - span.post-meta-date - if (theme.post_meta.page.date_type === 'both') - i.far.fa-calendar-alt - span.article-meta-label=_p('post.created') - time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))=date(article.date, config.date_format) - span.article-meta-separator | - i.fas.fa-history - span.article-meta-label=_p('post.updated') - time.post-meta-date-updated(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated))=date(article.updated, config.date_format) - else - - let data_type_updated = theme.post_meta.page.date_type === 'updated' - - let date_type = data_type_updated ? 'updated' : 'date' - - let date_icon = data_type_updated ? 'fas fa-history' :'far fa-calendar-alt' - - let date_title = data_type_updated ? _p('post.updated') : _p('post.created') - i(class=date_icon) - span.article-meta-label=date_title - time(datetime=date_xml(article[date_type]) title=date_title + ' ' + full_date(article[date_type]))=date(article[date_type], config.date_format) - if (theme.post_meta.page.categories && article.categories.data.length > 0) - span.article-meta - span.article-meta-separator | - i.fas.fa-inbox - each item, index in article.categories.data - a(href=url_for(item.path)).article-meta__categories #[=item.name] - if (index < article.categories.data.length - 1) - i.fas.fa-angle-right.article-meta-link - if (theme.post_meta.page.tags && article.tags.data.length > 0) - span.article-meta.tags - span.article-meta-separator | - i.fas.fa-tag - each item, index in article.tags.data - a(href=url_for(item.path)).article-meta__tags #[=item.name] - if (index < article.tags.data.length - 1) - span.article-meta-link #[='•'] - - mixin countBlockInIndex - - needLoadCountJs = true - span.article-meta - span.article-meta-separator | - i.fas.fa-comments - if block - block - span.article-meta-label= ' ' + _p('card_post_count') - - if theme.comments.card_post_count && theme.comments.use - case theme.comments.use[0] - when 'Disqus' - when 'Disqusjs' - +countBlockInIndex - a.disqus-count(href=full_url_for(link) + '#post-comment') - i.fa-solid.fa-spinner.fa-spin - when 'Valine' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.valine-comment-count(data-xid=url_for(link)) - i.fa-solid.fa-spinner.fa-spin - when 'Waline' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.waline-comment-count(data-path=url_for(link)) - i.fa-solid.fa-spinner.fa-spin - when 'Twikoo' - +countBlockInIndex - a.twikoo-count(href=url_for(link) + '#post-comment') - i.fa-solid.fa-spinner.fa-spin - when 'Facebook Comments' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.fb-comments-count(data-href=urlNoIndex(article.permalink)) - when 'Remark42' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.remark42__counter(data-url=urlNoIndex(article.permalink)) - i.fa-solid.fa-spinner.fa-spin - when 'Artalk' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.artalk-count(data-page-key=url_for(link)) - i.fa-solid.fa-spinner.fa-spin - - //- Display the article introduction on homepage - case theme.index_post_content.method - when false - - break - when 1 - .content!= article.description - when 2 - if article.description - .content!= article.description - else - - const content = strip_html(article.content) - - let expert = content.substring(0, theme.index_post_content.length) - - content.length > theme.index_post_content.length ? expert += ' ...' : '' - .content!= expert - default - - const content = strip_html(article.content) - - let expert = content.substring(0, theme.index_post_content.length) - - content.length > theme.index_post_content.length ? expert += ' ...' : '' - .content!= expert - - if theme.ad && theme.ad.index - if (index + 1) % 3 == 0 - .recent-post-item.ads-wrap!=theme.ad.index diff --git a/themes/butterfly/layout/includes/page/categories.pug b/themes/butterfly/layout/includes/page/categories.pug deleted file mode 100644 index 79153c8d..00000000 --- a/themes/butterfly/layout/includes/page/categories.pug +++ /dev/null @@ -1 +0,0 @@ -.category-lists!= list_categories() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/default-page.pug b/themes/butterfly/layout/includes/page/default-page.pug deleted file mode 100644 index e7057f73..00000000 --- a/themes/butterfly/layout/includes/page/default-page.pug +++ /dev/null @@ -1,2 +0,0 @@ -#article-container - != page.content \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/flink.pug b/themes/butterfly/layout/includes/page/flink.pug deleted file mode 100644 index 10d3893a..00000000 --- a/themes/butterfly/layout/includes/page/flink.pug +++ /dev/null @@ -1,82 +0,0 @@ -#article-container - .flink - - let { content, random, flink_url } = page - - let pageContent = content - - if flink_url || random - - const linkData = flink_url ? false : site.data.link || false - script. - (()=>{ - const replaceSymbol = (str) => { - return str.replace(/[\p{P}\p{S}]/gu, "-") - } - - let result = "" - const add = (str) => { - for(let i = 0; i < str.length; i++){ - const replaceClassName = replaceSymbol(str[i].class_name) - const className = str[i].class_name ? `

    ${str[i].class_name}

    ` : "" - const classDesc = str[i].class_desc ? `` : "" - - let listResult = "" - const lists = str[i].link_list - if (!{random === true}) { - lists.sort(() => Math.random() - 0.5) - } - for(let j = 0; j < lists.length; j++){ - listResult += ` - ` - } - - result += `${className}${classDesc} ` - } - - document.querySelector(".flink").insertAdjacentHTML("afterbegin", result) - window.lazyLoadInstance && window.lazyLoadInstance.update() - } - - const linkData = !{JSON.stringify(linkData)} - if (!{Boolean(flink_url)}) { - fetch("!{url_for(flink_url)}") - .then(response => response.json()) - .then(add) - } else if (linkData) { - add(linkData) - } - })() - - else - if site.data.link - - let result = "" - each i in site.data.link - - let className = i.class_name ? markdown(`## ${i.class_name}`) : "" - - let classDesc = i.class_desc ? `` : "" - - - let listResult = "" - - each j in i.link_list - - - listResult += ` - ` - - - - - result += `${className}${classDesc} ` - - - pageContent = result + pageContent - != pageContent diff --git a/themes/butterfly/layout/includes/page/tags.pug b/themes/butterfly/layout/includes/page/tags.pug deleted file mode 100644 index ed7d22c1..00000000 --- a/themes/butterfly/layout/includes/page/tags.pug +++ /dev/null @@ -1,2 +0,0 @@ -.tag-cloud-list.is-center - !=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 2.1, limit: 0, unit: 'em'}) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/pagination.pug b/themes/butterfly/layout/includes/pagination.pug deleted file mode 100644 index 2af0e00b..00000000 --- a/themes/butterfly/layout/includes/pagination.pug +++ /dev/null @@ -1,41 +0,0 @@ -- - var options = { - prev_text: '', - next_text: '', - mid_size: 1, - escape: false - } - -if is_post() - - let prev = theme.post_pagination === 1 ? page.prev : page.next - - let next = theme.post_pagination === 1 ? page.next : page.prev - nav#pagination.pagination-post - if(prev) - - var hasPageNext = next ? 'pull-left' : 'pull-full' - .prev-post(class=hasPageNext) - a(href=url_for(prev.path) title=prev.title) - if prev.cover_type === 'img' - img.cover(src=url_for(prev.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of previous post') - else - .cover(style=`background: ${prev.cover || 'var(--default-bg-color)'}`) - .pagination-info - .label=_p('pagination.prev') - .prev_info=prev.title - - if(next) - - var hasPagePrev = prev ? 'pull-right' : 'pull-full' - .next-post(class=hasPagePrev) - a(href=url_for(next.path) title=next.title) - if next.cover_type === 'img' - img.cover(src=url_for(next.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of next post') - else - .cover(style=`background: ${next.cover || 'var(--default-bg-color)'}`) - .pagination-info - .label=_p('pagination.next') - .next_info=next.title -else - nav#pagination - .pagination - if is_home() - - options.format = 'page/%d/#content-inner' - !=paginator(options) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/post/post-copyright.pug b/themes/butterfly/layout/includes/post/post-copyright.pug deleted file mode 100644 index e0b09df5..00000000 --- a/themes/butterfly/layout/includes/post/post-copyright.pug +++ /dev/null @@ -1,23 +0,0 @@ -if theme.post_copyright.enable && page.copyright !== false - - let author = page.copyright_author || config.author - - let authorHref = page.copyright_author_href || theme.post_copyright.author_href || config.url - - let url = page.copyright_url || page.permalink - - let info = page.copyright_info || _p('post.copyright.copyright_content', theme.post_copyright.license_url, theme.post_copyright.license, config.url, config.title) - .post-copyright - .post-copyright__author - span.post-copyright-meta - i.fas.fa-circle-user.fa-fw - = _p('post.copyright.author') + ": " - span.post-copyright-info - a(href=authorHref)=author - .post-copyright__type - span.post-copyright-meta - i.fas.fa-square-arrow-up-right.fa-fw - = _p('post.copyright.link') + ": " - span.post-copyright-info - a(href=url_for(url))= theme.post_copyright.decode ? decodeURI(url) : url - .post-copyright__notice - span.post-copyright-meta - i.fas.fa-circle-exclamation.fa-fw - = _p('post.copyright.copyright_notice') + ": " - span.post-copyright-info!= info diff --git a/themes/butterfly/layout/includes/post/reward.pug b/themes/butterfly/layout/includes/post/reward.pug deleted file mode 100644 index fb559465..00000000 --- a/themes/butterfly/layout/includes/post/reward.pug +++ /dev/null @@ -1,13 +0,0 @@ -.post-reward - .reward-button - i.fas.fa-qrcode - = theme.reward.text || _p('donate') - .reward-main - ul.reward-all - each item in theme.reward.QR_code - - var clickTo = item.link ? item.link : item.img - li.reward-item - a(href=url_for(clickTo) target='_blank') - img.post-qr-code-img(src=url_for(item.img) alt=item.text) - .post-qr-code-desc=item.text - diff --git a/themes/butterfly/layout/includes/rightside.pug b/themes/butterfly/layout/includes/rightside.pug deleted file mode 100644 index 377813db..00000000 --- a/themes/butterfly/layout/includes/rightside.pug +++ /dev/null @@ -1,61 +0,0 @@ -- const { readmode, translate, darkmode, aside, chat_btn } = theme -mixin rightsideItem(array) - each item in array - case item - when 'readmode' - if is_post() && readmode - button#readmode(type="button" title=_p('rightside.readmode_title')) - i.fas.fa-book-open - when 'translate' - if translate.enable - button#translateLink(type="button" title=_p('rightside.translate_title'))= translate.default - when 'darkmode' - if darkmode.enable && darkmode.button - button#darkmode(type="button" title=_p('rightside.night_mode_title')) - i.fas.fa-adjust - when 'hideAside' - if aside.enable && aside.button && page.aside !== false - button#hide-aside-btn(type="button" title=_p('rightside.aside')) - i.fas.fa-arrows-alt-h - when 'toc' - if showToc - button#mobile-toc-button.close(type="button" title=_p("rightside.toc")) - i.fas.fa-list-ul - when 'chat' - if chat_btn - button#chat-btn(type="button" title=_p("rightside.chat")) - i.fas.fa-sms - when 'comment' - if commentsJsLoad - a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment")) - i.fas.fa-comments - -#rightside - - const { enable, hide, show } = theme.rightside_item_order - - const hideArray = enable ? hide && hide.split(',') : ['readmode','translate','darkmode','hideAside'] - - const showArray = enable ? show && show.split(',') : ['toc','chat','comment'] - - - #rightside-config-hide - if hideArray - +rightsideItem(hideArray) - #rightside-config-show - if enable - if hide - button#rightside-config(type="button" title=_p("rightside.setting")) - i.fas.fa-cog.fa-spin - else - if is_post() - if (readmode || translate.enable || (darkmode.enable && darkmode.button)) - button#rightside-config(type="button" title=_p("rightside.setting")) - i.fas.fa-cog.fa-spin - else if translate.enable || (darkmode.enable && darkmode.button) - button#rightside-config(type="button" title=_p("rightside.setting")) - i.fas.fa-cog.fa-spin - - if showArray - +rightsideItem(showArray) - - button#go-up(type="button" title=_p("rightside.back_to_top")) - span.scroll-percent - i.fas.fa-arrow-up \ No newline at end of file diff --git a/themes/butterfly/layout/includes/sidebar.pug b/themes/butterfly/layout/includes/sidebar.pug deleted file mode 100644 index 66c67a9d..00000000 --- a/themes/butterfly/layout/includes/sidebar.pug +++ /dev/null @@ -1,18 +0,0 @@ -#sidebar - #menu-mask - #sidebar-menus - .avatar-img.is-center - img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar") - .sidebar-site-data.site-data.is-center - a(href=url_for(config.archive_dir) + '/') - .headline= _p('aside.articles') - .length-num= site.posts.length - a(href=url_for(config.tag_dir) + '/' ) - .headline= _p('aside.tags') - .length-num= site.tags.length - a(href=url_for(config.category_dir) + '/') - .headline= _p('aside.categories') - .length-num= site.categories.length - - hr.custom-hr - !=partial('includes/header/menu_item', {}, {cache: true}) diff --git a/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug b/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug deleted file mode 100644 index 79f86c4f..00000000 --- a/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug +++ /dev/null @@ -1,15 +0,0 @@ -script. - (() => { - const abcjsInit = () => { - const abcjsFn = () => { - document.querySelectorAll(".abc-music-sheet").forEach(ele => { - ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'}) - }) - } - - typeof ABCJS === 'object' ? abcjsFn() - : getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn) - } - - window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/abcjs/index.pug b/themes/butterfly/layout/includes/third-party/abcjs/index.pug deleted file mode 100644 index 1112626e..00000000 --- a/themes/butterfly/layout/includes/third-party/abcjs/index.pug +++ /dev/null @@ -1,6 +0,0 @@ -if theme.abcjs && theme.abcjs.enable - if theme.abcjs.per_page - if is_post() || is_page() - include ./abcjs.pug - else if page.abcjs - include ./abcjs.pug diff --git a/themes/butterfly/layout/includes/third-party/aplayer.pug b/themes/butterfly/layout/includes/third-party/aplayer.pug deleted file mode 100644 index c439c4cd..00000000 --- a/themes/butterfly/layout/includes/third-party/aplayer.pug +++ /dev/null @@ -1,3 +0,0 @@ -link(rel='stylesheet' href=url_for(theme.asset.aplayer_css) media="print" onload="this.media='all'") -script(src=url_for(theme.asset.aplayer_js)) -script(src=url_for(theme.asset.meting_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug b/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug deleted file mode 100644 index 9e9aaed4..00000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug +++ /dev/null @@ -1,35 +0,0 @@ -- const { server, site } = theme.artalk - -script. - (() => { - const getArtalkCount = async() => { - try { - const eleGroup = document.querySelectorAll('#recent-posts .artalk-count') - const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-page-key')) - - const headerList = { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Origin': window.location.origin - }, - body: new URLSearchParams({ - 'site_name': '!{site}', - 'type':'page_comment', - 'page_keys': keyArray - }) - } - - const res = await fetch('!{server}/api/stat', headerList) - const result = await res.json() - - keyArray.forEach((key, index) => { - eleGroup[index].textContent = result.data[key] || 0 - }) - } catch (err) { - console.error(err) - } - } - - window.pjax ? getArtalkCount() : window.addEventListener('load', getArtalkCount) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug b/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug deleted file mode 100644 index 92a2d5bc..00000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug +++ /dev/null @@ -1,25 +0,0 @@ -- const { shortname, apikey } = theme.disqus -script. - (() => { - const getCount = async () => { - try { - const eleGroup = document.querySelectorAll('#recent-posts .disqus-count') - const cleanedLinks = Array.from(eleGroup).map(i => `thread:link=${i.href.replace(/#post-comment$/, '')}`); - - const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&${cleanedLinks.join('&')}`,{ - method: 'GET' - }) - const result = await res.json() - - eleGroup.forEach(i => { - const cleanedLink = i.href.replace(/#post-comment$/, '') - const urlData = result.response.find(data => data.link === cleanedLink) || { posts: 0 } - i.textContent = urlData.posts - }) - } catch (err) { - console.error(err) - } - } - - window.pjax ? getCount() : window.addEventListener('load', getCount) - })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug b/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug deleted file mode 100644 index 75d6b4a5..00000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug +++ /dev/null @@ -1,18 +0,0 @@ -- const fbSDKVer = 'v16.0' -- const fbSDK = theme.messenger.enable ? `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk/xfbml.customerchat.js#xfbml=1&version=${fbSDKVer}` : `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` - -script. - (()=>{ - function loadFBComment () { - if (typeof FB === 'object') FB.XFBML.parse(document.getElementById('recent-posts')) - else { - let ele = document.createElement('script') - ele.setAttribute('src','!{fbSDK}') - ele.setAttribute('async', 'true') - ele.setAttribute('defer', 'true') - ele.setAttribute('crossorigin', 'anonymous') - document.body.appendChild(ele) - } - } - window.pjax ? loadFBComment() : window.addEventListener('load', loadFBComment) - })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/index.pug b/themes/butterfly/layout/includes/third-party/card-post-count/index.pug deleted file mode 100644 index 5b2685c0..00000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/index.pug +++ /dev/null @@ -1,16 +0,0 @@ -case theme.comments.use[0] - when 'Twikoo' - include ./twikoo.pug - when 'Disqus' - when 'Disqusjs' - include ./disqus.pug - when 'Valine' - include ./valine.pug - when 'Waline' - include ./waline.pug - when 'Facebook Comments' - include ./fb.pug - when 'Remark42' - include ./remark42.pug - when 'Artalk' - include ./artalk.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug b/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug deleted file mode 100644 index b67164f7..00000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug +++ /dev/null @@ -1,18 +0,0 @@ -- const { host, siteId, option } = theme.remark42 - -script. - (()=>{ - window.remark_config = Object.assign({ - host: '!{host}', - site_id: '!{siteId}', - },!{JSON.stringify(option)}) - - function getCount () { - const s = document.createElement('script') - s.src = remark_config.host + '/web/counter.js' - s.defer = true - document.head.appendChild(s) - } - - window.pjax ? getCount() : window.addEventListener('load', getCount) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug b/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug deleted file mode 100644 index 72a42231..00000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug +++ /dev/null @@ -1,37 +0,0 @@ -script. - (() => { - const getCommentUrl = () => { - const eleGroup = document.querySelectorAll('#recent-posts .article-title') - let urlArray = [] - eleGroup.forEach(i=>{ - urlArray.push(i.getAttribute('href')) - }) - return urlArray - } - - const getCount = () => { - const runTwikoo = () => { - twikoo.getCommentsCount({ - envId: '!{theme.twikoo.envId}', - region: '!{theme.twikoo.region}', - urls: getCommentUrl(), - includeReply: false - }).then(function (res) { - document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => { - item.textContent = res[index].count - }) - }).catch(function (err) { - console.log(err) - }) - } - - if (typeof twikoo === 'object') { - runTwikoo() - } else { - getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo) - } - } - - window.pjax ? getCount() : window.addEventListener('load', getCount) - - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug b/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug deleted file mode 100644 index dfe9b6d5..00000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug +++ /dev/null @@ -1,20 +0,0 @@ -script. - (() => { - function loadValine () { - function initValine () { - let initData = { - el: '#vcomment', - appId: '#{theme.valine.appId}', - appKey: '#{theme.valine.appKey}', - serverURLs: '#{theme.valine.serverURLs}' - } - - const valine = new Valine(initData) - } - - if (typeof Valine === 'function') initValine() - else getScript('!{url_for(theme.asset.valine)}').then(initValine) - } - - window.pjax ? loadValine() : window.addEventListener('load', loadValine) - })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug b/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug deleted file mode 100644 index a8faf963..00000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug +++ /dev/null @@ -1,21 +0,0 @@ -- const serverURL = theme.waline.serverURL.replace(/\/$/, '') -script. - (() => { - async function loadWaline () { - try { - const eleGroup = document.querySelectorAll('#recent-posts .waline-comment-count') - const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-path')) - - const res = await fetch(`!{serverURL}/api/comment?type=count&url=${keyArray}`, { method: 'GET' }) - const result = await res.json() - - result.data.forEach((count, index) => { - eleGroup[index].textContent = count - }) - } catch (err) { - console.error(err) - } - } - - window.pjax ? loadWaline() : window.addEventListener('load', loadWaline) - })() diff --git a/themes/butterfly/layout/includes/third-party/chat/chatra.pug b/themes/butterfly/layout/includes/third-party/chat/chatra.pug deleted file mode 100644 index 5cc7b5a9..00000000 --- a/themes/butterfly/layout/includes/third-party/chat/chatra.pug +++ /dev/null @@ -1,50 +0,0 @@ -//- https://chatra.io/help/api/ -script. - (() => { - const isChatBtn = !{theme.chat_btn} - const isChatHideShow = !{theme.chat_hide_show} - - if (isChatBtn) { - const close = () => { - Chatra('minimizeWidget') - Chatra('hide') - } - - const open = () => { - Chatra('openChat', true) - Chatra('show') - } - - window.ChatraSetup = { - startHidden: true - } - - window.chatBtnFn = () => { - const isShow = document.getElementById('chatra').classList.contains('chatra--expanded') - isShow ? close() : open() - } - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => { - Chatra('hide') - }, - show: () => { - Chatra('show') - } - } - } - - (function(d, w, c) { - w.ChatraID = '#{theme.chatra.id}' - var s = d.createElement('script') - w[c] = w[c] || function() { - (w[c].q = w[c].q || []).push(arguments) - } - s.async = true - s.src = 'https://call.chatra.io/chatra.js' - if (d.head) d.head.appendChild(s) - })(document, window, 'Chatra') - - })() - - diff --git a/themes/butterfly/layout/includes/third-party/chat/crisp.pug b/themes/butterfly/layout/includes/third-party/chat/crisp.pug deleted file mode 100644 index 6c8b6bf4..00000000 --- a/themes/butterfly/layout/includes/third-party/chat/crisp.pug +++ /dev/null @@ -1,45 +0,0 @@ -script. - (() => { - window.$crisp = []; - window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"; - (function () { - d = document; - s = d.createElement("script"); - s.src = "https://client.crisp.chat/l.js"; - s.async = 1; - d.getElementsByTagName("head")[0].appendChild(s); - })(); - $crisp.push(["safe", true]) - - const isChatBtn = !{theme.chat_btn} - const isChatHideShow = !{theme.chat_hide_show} - - if (isChatBtn) { - const open = () => { - $crisp.push(["do", "chat:show"]) - $crisp.push(["do", "chat:open"]) - } - - const close = () => { - $crisp.push(["do", "chat:hide"]) - } - - close() - $crisp.push(["on", "chat:closed", function() { - close() - }]) - - window.chatBtnFn = () => { - $crisp.is("chat:visible") ? close() : open() - } - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => { - $crisp.push(["do", "chat:hide"]) - }, - show: () => { - $crisp.push(["do", "chat:show"]) - } - } - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/chat/daovoice.pug b/themes/butterfly/layout/includes/third-party/chat/daovoice.pug deleted file mode 100644 index 5a7c33dc..00000000 --- a/themes/butterfly/layout/includes/third-party/chat/daovoice.pug +++ /dev/null @@ -1,40 +0,0 @@ -//- https://guide.daocloud.io/daovoice/javascript-api-5869833.html -script. - (() => { - (function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/!{theme.daovoice.app_id}.js","daovoice") - - const isChatBtn = !{theme.chat_btn} - const isChatHideShow = !{theme.chat_hide_show} - - daovoice('init', { - app_id: '!{theme.daovoice.app_id}',},{ - launcher: { - disableLauncherIcon: isChatBtn - }, - }); - daovoice('update'); - - if (isChatBtn) { - window.chatBtnFn = () => { - const isShow = document.getElementById('daodream-messenger').classList.contains('daodream-messenger-active') - isShow ? daovoice('hide') : daovoice('show') - } - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => { - daovoice('update', {},{ - launcher: { - disableLauncherIcon: true - } - }) - }, - show: () => { - daovoice('update', {}, { - launcher: { - disableLauncherIcon: false - } - }) - } - } - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/chat/index.pug b/themes/butterfly/layout/includes/third-party/chat/index.pug deleted file mode 100644 index 4b6bda68..00000000 --- a/themes/butterfly/layout/includes/third-party/chat/index.pug +++ /dev/null @@ -1,10 +0,0 @@ -if theme.chatra && theme.chatra.enable - include ./chatra.pug -else if theme.tidio && theme.tidio.enable - include ./tidio.pug -else if theme.daovoice && theme.daovoice.enable - include ./daovoice.pug -else if theme.crisp && theme.crisp.enable - include ./crisp.pug -else if theme.messenger && theme.messenger.enable - include ./messenger.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/chat/messenger.pug b/themes/butterfly/layout/includes/third-party/chat/messenger.pug deleted file mode 100644 index 3d244fc0..00000000 --- a/themes/butterfly/layout/includes/third-party/chat/messenger.pug +++ /dev/null @@ -1,44 +0,0 @@ -- let { pageID, lang } = theme.messenger -- lang = theme.comments.use && theme.comments.use.includes('Facebook Comments') ? theme.facebook_comments.lang : lang - -#fb-customer-chat.fb-customerchat(page_id=pageID attribution='biz_inbox') - -script. - (() => { - document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '
    ') - - window.fbAsyncInit = function() { - FB.init({ - xfbml: true, - version: 'v16.0' - }); - }; - - (function(d, s, id) { - var js, fjs = d.getElementsByTagName(s)[0]; - if (d.getElementById(id)) return; - js = d.createElement(s); js.id = id; - js.src = 'https://connect.facebook.net/!{lang}/sdk/xfbml.customerchat.js'; - fjs.parentNode.insertBefore(js, fjs); - }(document, 'script', 'facebook-jssdk')); - - const isChatBtn = !{theme.chat_btn} - const isChatHideShow = !{theme.chat_hide_show} - - if (isChatBtn) { - window.chatBtnFn = () => { - const isShow = document.querySelector('.fb_customer_chat_bounce_in_v2') - isShow ? FB.CustomerChat.hide() : FB.CustomerChat.show() - } - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => { - FB.CustomerChat.hide() - }, - show: () => { - FB.CustomerChat.show(false) - } - } - } - })() - diff --git a/themes/butterfly/layout/includes/third-party/chat/tidio.pug b/themes/butterfly/layout/includes/third-party/chat/tidio.pug deleted file mode 100644 index 20104f53..00000000 --- a/themes/butterfly/layout/includes/third-party/chat/tidio.pug +++ /dev/null @@ -1,45 +0,0 @@ -script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async) -script. - (() => { - const isChatBtn = !{theme.chat_btn} - const isChatHideShow = !{theme.chat_hide_show} - - if (isChatBtn) { - let isShow = false - const close = () => { - window.tidioChatApi.hide() - isShow = false - } - - const open = () => { - window.tidioChatApi.open() - window.tidioChatApi.show() - isShow = true - } - - const onTidioChatApiReady = () => { - window.tidioChatApi.hide() - window.tidioChatApi.on("close", close) - } - if (window.tidioChatApi) { - window.tidioChatApi.on("ready", onTidioChatApiReady) - } else { - document.addEventListener("tidioChat-ready", onTidioChatApiReady) - } - - window.chatBtnFn = () => { - if (!window.tidioChatApi) return - isShow ? close() : open() - } - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => { - window.tidioChatApi && window.tidioChatApi.hide() - }, - show: () => { - window.tidioChatApi && window.tidioChatApi.show() - } - } - } - })() - diff --git a/themes/butterfly/layout/includes/third-party/comments/artalk.pug b/themes/butterfly/layout/includes/third-party/comments/artalk.pug deleted file mode 100644 index 700b91ae..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/artalk.pug +++ /dev/null @@ -1,52 +0,0 @@ -- const { server, site, option } = theme.artalk -- const { use, lazyload } = theme.comments - -script. - (() => { - const initArtalk = () => { - window.artalkItem = new Artalk(Object.assign({ - el: '#artalk-wrap', - server: '!{server}', - site: '!{site}', - pageKey: location.pathname, - darkMode: document.documentElement.getAttribute('data-theme') === 'dark', - countEl: '.artalk-count' - },!{JSON.stringify(option)})) - - if (GLOBAL_CONFIG.lightbox === 'null') return - window.artalkItem.use(ctx => { - ctx.on('list-loaded', () => { - ctx.getCommentList().forEach(comment => { - const $content = comment.getRender().$content - btf.loadLightbox($content.querySelectorAll('img:not([atk-emoticon])')) - }) - }) - }) - } - - const loadArtalk = async () => { - if (typeof window.artalkItem === 'object') initArtalk() - else { - await getCSS('!{theme.asset.artalk_css}') - await getScript('!{theme.asset.artalk_js}') - initArtalk() - } - } - - const artalkChangeMode = theme => { - const artalkWrap = document.getElementById('artalk-wrap') - if (!(artalkWrap && artalkWrap.children.length)) return - const isDark = theme === 'dark' - window.artalkItem.setDarkMode(isDark) - } - - - btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk') - - if ('!{use[0]}' === 'Artalk' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk) - else loadArtalk() - } else { - window.loadOtherComment = loadArtalk - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/disqus.pug b/themes/butterfly/layout/includes/third-party/comments/disqus.pug deleted file mode 100644 index 8d8864cf..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/disqus.pug +++ /dev/null @@ -1,59 +0,0 @@ -- const disqusPageTitle = page.title.replace(/'/ig,"\\'") -- const { shortname, apikey } = theme.disqus -- const { use, lazyload, count } = theme.comments - -script. - (() => { - const disqus_config = function () { - this.page.url = '!{ page.permalink }' - this.page.identifier = '!{ url_for(page.path) }' - this.page.title = '!{ disqusPageTitle }' - } - - const disqusReset = () => { - window.DISQUS && window.DISQUS.reset({ - reload: true, - config: disqus_config - }) - } - - btf.addGlobalFn('themeChange', disqusReset, 'disqus') - - const loadDisqus = () =>{ - if (window.DISQUS) disqusReset() - else { - const script = document.createElement('script') - script.src = 'https://!{shortname}.disqus.com/embed.js' - script.setAttribute('data-timestamp', +new Date()) - document.head.appendChild(script) - } - } - - const getCount = async() => { - try { - const eleGroup = document.querySelector('#post-meta .disqus-comment-count') - if (!eleGroup) return - const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '') - - const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&thread:link=${cleanedLinks}`,{ - method: 'GET' - }) - const result = await res.json() - - const count = result.response.length ? result.response[0].posts : 0 - eleGroup.textContent = count - } catch (err) { - console.error(err) - } - } - - if ('!{use[0]}' === 'Disqus' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus) - else { - loadDisqus() - !{ count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } - } - } else { - window.loadOtherComment = loadDisqus - } - })() diff --git a/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug b/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug deleted file mode 100644 index af416969..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug +++ /dev/null @@ -1,64 +0,0 @@ -- let disqusjsPageTitle = page.title.replace(/'/ig,"\\'") -- const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs - -script. - (() => { - const initDisqusjs = () => { - window.disqusjs = null - disqusjs = new DisqusJS(Object.assign({ - shortname: '!{dqShortname}', - identifier: '!{ url_for(page.path) }', - url: '!{ page.permalink }', - title: '!{ disqusjsPageTitle }', - apikey: '!{dqApikey}', - },!{JSON.stringify(dqOption)})) - - disqusjs.render(document.getElementById('disqusjs-wrap')) - } - - const themeChange = () => { - const ele = document.getElementById('disqus_thread') - if(!ele) return - disqusjs.destroy() - initDisqusjs() - } - - btf.addGlobalFn('themeChange', themeChange, 'disqusjs') - - const loadDisqusjs = async() => { - if (window.disqusJsLoad) initDisqusjs() - else { - await getCSS('!{url_for(theme.asset.disqusjs_css)}') - await getScript('!{url_for(theme.asset.disqusjs)}') - initDisqusjs() - window.disqusJsLoad = true - } - } - - const getCount = async() => { - try { - const eleGroup = document.querySelector('#post-meta .disqusjs-comment-count') - if (!eleGroup) return - const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '') - - const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{dqShortname}&api_key=!{dqApikey}&thread:link=${cleanedLinks}`,{ - method: 'GET' - }) - const result = await res.json() - const count = result.response.length ? result.response[0].posts : 0 - eleGroup.textContent = count - } catch (err) { - console.error(err) - } - } - - if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) { - if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs) - else { - loadDisqusjs() - !{ theme.comments.count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } - } - } else { - window.loadOtherComment = loadDisqusjs - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug b/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug deleted file mode 100644 index f0cc9165..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug +++ /dev/null @@ -1,46 +0,0 @@ -- const fbSDKVer = 'v16.0' -- const fbSDK = theme.messenger.enable ? `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk/xfbml.customerchat.js#xfbml=1&version=${fbSDKVer}` : `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` - -script. - (()=>{ - const loadFBComment = () => { - document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '
    ') - - const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' - const $fbComment = document.getElementsByClassName('fb-comments')[0] - $fbComment.setAttribute('data-colorscheme',themeNow) - $fbComment.setAttribute('data-href', '!{urlNoIndex(page.permalink)}') - - if (typeof FB === 'object') { - FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0]) - FB.XFBML.parse(document.getElementById('post-comment')) - } - else { - let ele = document.createElement('script') - ele.setAttribute('src','!{fbSDK}') - ele.setAttribute('async', 'true') - ele.setAttribute('defer', 'true') - ele.setAttribute('crossorigin', 'anonymous') - ele.setAttribute('id', 'facebook-jssdk') - document.getElementById('fb-root').insertAdjacentElement('afterbegin',ele) - } - } - - const fbModeChange = theme => { - const $fbComment = document.getElementsByClassName('fb-comments')[0] - if ($fbComment && typeof FB === 'object') { - $fbComment.setAttribute('data-colorscheme',theme) - FB.XFBML.parse(document.getElementById('post-comment')) - } - } - - btf.addGlobalFn('themeChange', fbModeChange, 'facebook_comments') - - if ('!{theme.comments.use[0]}' === 'Facebook Comments' || !!{theme.comments.lazyload}) { - if (!{theme.comments.lazyload}) btf.loadComment(document.querySelector('#post-comment .fb-comments'), loadFBComment) - else loadFBComment() - } else { - window.loadOtherComment = loadFBComment - } - })() - diff --git a/themes/butterfly/layout/includes/third-party/comments/giscus.pug b/themes/butterfly/layout/includes/third-party/comments/giscus.pug deleted file mode 100644 index 3e1b5188..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/giscus.pug +++ /dev/null @@ -1,54 +0,0 @@ -- const { repo, repo_id, category_id, theme:themes, option } = theme.giscus -- const giscusUrl = theme.asset.giscus || 'https://giscus.app/client.js' -- const giscusOriginUrl = new URL(giscusUrl).origin -- const { use, lazyload } = theme.comments - -script. - (()=>{ - const getGiscusTheme = theme => { - return theme === 'dark' ? '!{themes.dark}' : '!{themes.light}' - } - - const loadGiscus = () => { - const config = Object.assign({ - src: '!{giscusUrl}', - 'data-repo': '!{repo}', - 'data-repo-id': '!{repo_id}', - 'data-category-id': '!{category_id}', - 'data-mapping': 'pathname', - 'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')), - 'data-reactions-enabled': '1', - crossorigin: 'anonymous', - async: true - },!{JSON.stringify(option)}) - - const ele = document.createElement('script') - for (let key in config) { - ele.setAttribute(key, config[key]) - } - document.getElementById('giscus-wrap').appendChild(ele) - } - - const changeGiscusTheme = theme => { - const sendMessage = message => { - const iframe = document.querySelector('iframe.giscus-frame') - if (!iframe) return - iframe.contentWindow.postMessage({ giscus: message }, '!{giscusOriginUrl}') - } - - sendMessage({ - setConfig: { - theme: getGiscusTheme(theme) - } - }); - } - - btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus') - - if ('!{use[0]}' === 'Giscus' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus) - else loadGiscus() - } else { - window.loadOtherComment= loadGiscus - } - })() diff --git a/themes/butterfly/layout/includes/third-party/comments/gitalk.pug b/themes/butterfly/layout/includes/third-party/comments/gitalk.pug deleted file mode 100644 index e0a13897..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/gitalk.pug +++ /dev/null @@ -1,44 +0,0 @@ -- const { client_id, client_secret, repo, owner, admin, option } = theme.gitalk - -script. - (() => { - const initGitalk = () => { - const gitalk = new Gitalk(Object.assign({ - clientID: '!{client_id}', - clientSecret: '!{client_secret}', - repo: '!{repo}', - owner: '!{owner}', - admin: ['!{admin}'], - id: '!{md5(page.path)}', - updateCountCallback: commentCount - },!{JSON.stringify(option)})) - - gitalk.render('gitalk-container') - } - - const loadGitalk = async() => { - if (typeof Gitalk === 'function') initGitalk() - else { - await getCSS('!{url_for(theme.asset.gitalk_css)}') - await getScript('!{url_for(theme.asset.gitalk)}') - initGitalk() - } - } - - const commentCount = n => { - const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count') - if (isCommentCount) { - isCommentCount.textContent= n - } - } - - if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) { - if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk) - else loadGitalk() - } else { - window.loadOtherComment = loadGitalk - } - })() - - - diff --git a/themes/butterfly/layout/includes/third-party/comments/index.pug b/themes/butterfly/layout/includes/third-party/comments/index.pug deleted file mode 100644 index 3b8ceff5..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/index.pug +++ /dev/null @@ -1,46 +0,0 @@ -- let defaultComment = theme.comments.use[0] -hr.custom-hr -#post-comment - .comment-head - .comment-headline - i.fas.fa-comments.fa-fw - span= ' ' + _p('comment') - - if theme.comments.use.length > 1 - .comment-switch - span.first-comment=defaultComment - span#switch-btn - span.second-comment=theme.comments.use[1] - - - .comment-wrap - each name in theme.comments.use - div - case name - when 'Disqus' - #disqus_thread - when 'Valine' - #vcomment.vcomment - when 'Disqusjs' - #disqusjs-wrap - when 'Livere' - #lv-container(data-id="city" data-uid=theme.livere.uid) - when 'Gitalk' - #gitalk-container - when 'Utterances' - #utterances-wrap - when 'Twikoo' - #twikoo-wrap - when 'Waline' - #waline-wrap - when 'Giscus' - #giscus-wrap - when 'Facebook Comments' - .fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light' - data-numposts= theme.facebook_comments.pageSize || 10 - data-order-by= theme.facebook_comments.order_by || 'social' - data-width="100%") - when 'Remark42' - #remark42 - when 'Artalk' - #artalk-wrap diff --git a/themes/butterfly/layout/includes/third-party/comments/js.pug b/themes/butterfly/layout/includes/third-party/comments/js.pug deleted file mode 100644 index bf1d8728..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/js.pug +++ /dev/null @@ -1,26 +0,0 @@ -each name in theme.comments.use - case name - when 'Valine' - !=partial('includes/third-party/comments/valine', {}, {cache: true}) - when 'Disqus' - include ./disqus.pug - when 'Disqusjs' - include ./disqusjs.pug - when 'Livere' - !=partial('includes/third-party/comments/livere', {}, {cache: true}) - when 'Gitalk' - include ./gitalk.pug - when 'Utterances' - !=partial('includes/third-party/comments/utterances', {}, {cache: true}) - when 'Twikoo' - !=partial('includes/third-party/comments/twikoo', {}, {cache: true}) - when 'Waline' - !=partial('includes/third-party/comments/waline', {}, {cache: true}) - when 'Giscus' - !=partial('includes/third-party/comments/giscus', {}, {cache: true}) - when 'Facebook Comments' - include ./facebook_comments.pug - when 'Remark42' - !=partial('includes/third-party/comments/remark42', {}, {cache: true}) - when 'Artalk' - !=partial('includes/third-party/comments/artalk', {}, {cache: true}) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/livere.pug b/themes/butterfly/layout/includes/third-party/comments/livere.pug deleted file mode 100644 index ecacb596..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/livere.pug +++ /dev/null @@ -1,25 +0,0 @@ -- const { use, lazyload } = theme.comments - -script. - (()=>{ - const loadLivere = () => { - if (typeof LivereTower === 'object') window.LivereTower.init() - else { - (function(d, s) { - var j, e = d.getElementsByTagName(s)[0]; - if (typeof LivereTower === 'function') { return; } - j = d.createElement(s); - j.src = 'https://cdn-city.livere.com/js/embed.dist.js'; - j.async = true; - e.parentNode.insertBefore(j, e); - })(document, 'script'); - } - } - - if ('!{use[0]}' === 'Livere' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere) - else loadLivere() - } else { - window.loadOtherComment = loadLivere - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/remark42.pug b/themes/butterfly/layout/includes/third-party/comments/remark42.pug deleted file mode 100644 index 388fb97c..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/remark42.pug +++ /dev/null @@ -1,68 +0,0 @@ -- const { host, siteId, option } = theme.remark42 -script. - var remark_config = Object.assign({ - host: '!{host}', - site_id: '!{siteId}', - components: ['embed'], - theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' - },!{JSON.stringify(option)}) - - function addRemark42(){ - for (let i = 0; i < remark_config.components.length; i++) { - const s = document.createElement('script') - s.src = remark_config.host + '/web/' + remark_config.components[i] + '.js' - s.defer = true - document.head.appendChild(s) - } - } - - function initRemark42() { - if (window.REMARK42) { - if (this.remark42Instance) { - this.remark42Instance.destroy() - } - - this.remark42Instance = window.REMARK42.createInstance({ - ...remark_config - }) - } - } - - function getCount () { - const ele = document.querySelector('.remark42__counter') - if (ele) { - const s = document.createElement('script') - s.src = remark_config.host + '/web/counter.js' - s.defer = true - document.head.appendChild(s) - } - } - - function loadRemark42 () { - if (window.REMARK42) { - this.initRemark42() - getCount() - } else { - addRemark42() - window.addEventListener('REMARK42::ready', () => { - this.initRemark42() - getCount() - }) - } - } - - function remarkChangeMode (theme) { - if (!window.REMARK42) return - window.REMARK42.changeTheme(theme) - } - - btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42') - - if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) { - if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42) - else loadRemark42() - } else { - function loadOtherComment () { - loadRemark42() - } - } diff --git a/themes/butterfly/layout/includes/third-party/comments/twikoo.pug b/themes/butterfly/layout/includes/third-party/comments/twikoo.pug deleted file mode 100644 index 858fad3b..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/twikoo.pug +++ /dev/null @@ -1,46 +0,0 @@ -- const { envId, region, option } = theme.twikoo -- const { use, lazyload, count } = theme.comments - -script. - (() => { - const init = () => { - twikoo.init(Object.assign({ - el: '#twikoo-wrap', - envId: '!{envId}', - region: '!{region}', - onCommentLoaded: () => { - btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)')) - } - }, !{JSON.stringify(option)})) - } - - const loadTwikoo = () => { - if (typeof twikoo === 'object') setTimeout(init,0) - else getScript('!{url_for(theme.asset.twikoo)}').then(init) - } - - const getCount = () => { - const countELement = document.getElementById('twikoo-count') - if(!countELement) return - twikoo.getCommentsCount({ - envId: '!{envId}', - region: '!{region}', - urls: [window.location.pathname], - includeReply: false - }).then(res => { - countELement.textContent = res[0].count - }).catch(err => { - console.error(err) - }) - } - - if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo) - else { - loadTwikoo() - !{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''} - } - } else { - window.loadOtherComment = loadTwikoo - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/utterances.pug b/themes/butterfly/layout/includes/third-party/comments/utterances.pug deleted file mode 100644 index 0ca01e7e..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/utterances.pug +++ /dev/null @@ -1,39 +0,0 @@ -- const { use, lazyload } = theme.comments -- const { repo, issue_term, light_theme, dark_theme } = theme.utterances - -script. - (() => { - const loadUtterances = () => { - let ele = document.createElement('script') - ele.id = 'utterances_comment' - ele.src = 'https://utteranc.es/client.js' - ele.setAttribute('repo', '!{repo}') - ele.setAttribute('issue-term', '!{issue_term}') - const nowTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? '#{dark_theme}' : '#{light_theme}' - ele.setAttribute('theme', nowTheme) - ele.crossOrigin = 'anonymous' - ele.async = true - document.getElementById('utterances-wrap').appendChild(ele) - } - - const utterancesTheme = theme => { - const iframe = document.querySelector('.utterances-frame') - if (iframe) { - const theme = theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' - const message = { - type: 'set-theme', - theme: theme - }; - iframe.contentWindow.postMessage(message, 'https://utteranc.es'); - } - } - - btf.addGlobalFn('themeChange', utterancesTheme, 'utterances') - - if ('!{use[0]}' === 'Utterances' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances) - else loadUtterances() - } else { - window.loadOtherComment = loadUtterances - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/valine.pug b/themes/butterfly/layout/includes/third-party/comments/valine.pug deleted file mode 100644 index 85781d5f..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/valine.pug +++ /dev/null @@ -1,38 +0,0 @@ -- const { use, lazyload } = theme.comments -- const { appId, appKey, avatar, serverURLs, visitor, option } = theme.valine - -- let emojiMaps = '""' -if site.data.valine - - emojiMaps = JSON.stringify(site.data.valine) - -script. - (() => { - const initValine = () => { - const valine = new Valine(Object.assign({ - el: '#vcomment', - appId: '#{appId}', - appKey: '#{appKey}', - avatar: '#{avatar}', - serverURLs: '#{serverURLs}', - emojiMaps: !{emojiMaps}, - path: window.location.pathname, - visitor: #{visitor} - }, !{JSON.stringify(option)})) - } - - const loadValine = async () => { - if (typeof Valine === 'function') initValine() - else { - await getScript('!{url_for(theme.asset.valine)}') - initValine() - } - } - - if ('!{use[0]}' === 'Valine' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine) - else setTimeout(loadValine, 0) - } else { - window.loadOtherComment = loadValine - } - })() - diff --git a/themes/butterfly/layout/includes/third-party/comments/waline.pug b/themes/butterfly/layout/includes/third-party/comments/waline.pug deleted file mode 100644 index a3baccae..00000000 --- a/themes/butterfly/layout/includes/third-party/comments/waline.pug +++ /dev/null @@ -1,33 +0,0 @@ -- const { serverURL, option, pageview } = theme.waline -- const { lazyload, count, use } = theme.comments - -script. - (() => { - const initWaline = () => { - const waline = Waline.init(Object.assign({ - el: '#waline-wrap', - serverURL: '!{serverURL}', - pageview: !{lazyload ? false : pageview}, - dark: 'html[data-theme="dark"]', - path: window.location.pathname, - comment: !{lazyload ? false : count}, - }, !{JSON.stringify(option)})) - } - - const loadWaline = async () => { - if (typeof Waline === 'object') initWaline() - else { - await getCSS('!{url_for(theme.asset.waline_css)}') - await getScript('!{url_for(theme.asset.waline_js)}') - initWaline() - } - } - - if ('!{use[0]}' === 'Waline' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline) - else setTimeout(loadWaline, 0) - } else { - window.loadOtherComment = loadWaline - } - })() - diff --git a/themes/butterfly/layout/includes/third-party/effect.pug b/themes/butterfly/layout/includes/third-party/effect.pug deleted file mode 100644 index a3b29958..00000000 --- a/themes/butterfly/layout/includes/third-party/effect.pug +++ /dev/null @@ -1,35 +0,0 @@ -if theme.fireworks && theme.fireworks.enable - canvas.fireworks(mobile=`${theme.fireworks.mobile}`) - script(src=url_for(theme.asset.fireworks)) - -if (theme.canvas_ribbon && theme.canvas_ribbon.enable) - script(defer id="ribbon" src=url_for(theme.asset.canvas_ribbon) size=theme.canvas_ribbon.size - alpha=theme.canvas_ribbon.alpha zIndex=theme.canvas_ribbon.zIndex mobile=`${theme.canvas_ribbon.mobile}` data-click=`${theme.canvas_ribbon.click_to_change}`) - -if (theme.canvas_fluttering_ribbon && theme.canvas_fluttering_ribbon.enable) - script(defer id="fluttering_ribbon" mobile=`${theme.canvas_fluttering_ribbon.mobile}` src=url_for(theme.asset.canvas_fluttering_ribbon)) - -if (theme.canvas_nest && theme.canvas_nest.enable) - script#canvas_nest(defer color=theme.canvas_nest.color opacity=theme.canvas_nest.opacity zIndex=theme.canvas_nest.zIndex count=theme.canvas_nest.count mobile=`${theme.canvas_nest.mobile}` src=url_for(theme.asset.canvas_nest)) - -if theme.activate_power_mode.enable - script(src=url_for(theme.asset.activate_power_mode)) - script. - POWERMODE.colorful = !{theme.activate_power_mode.colorful}; - POWERMODE.shake = !{theme.activate_power_mode.shake}; - POWERMODE.mobile = !{theme.activate_power_mode.mobile}; - document.body.addEventListener('input', POWERMODE); - -//- 鼠標特效 -if theme.click_heart && theme.click_heart.enable - script#click-heart(src=url_for(theme.asset.click_heart) async mobile=`${theme.click_heart.mobile}`) - -if theme.clickShowText && theme.clickShowText.enable - script#click-show-text( - src= url_for(theme.asset.clickShowText) - data-mobile= `${theme.clickShowText.mobile}` - data-text= theme.clickShowText.text.join(",") - data-fontsize= theme.clickShowText.fontSize - data-random= `${theme.clickShowText.random}` - async - ) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/index.pug b/themes/butterfly/layout/includes/third-party/math/index.pug deleted file mode 100644 index 2b16302e..00000000 --- a/themes/butterfly/layout/includes/third-party/math/index.pug +++ /dev/null @@ -1,18 +0,0 @@ -if theme.mathjax && theme.mathjax.enable - if theme.mathjax.per_page - if is_post() || is_page() - include ./mathjax.pug - else - if page.mathjax - include ./mathjax.pug - -if theme.katex && theme.katex.enable - if theme.katex.per_page - if is_post() || is_page() - include ./katex.pug - else - if page.katex - include ./katex.pug - -if theme.mermaid.enable - include ./mermaid.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/katex.pug b/themes/butterfly/layout/includes/third-party/math/katex.pug deleted file mode 100644 index c1f0bd69..00000000 --- a/themes/butterfly/layout/includes/third-party/math/katex.pug +++ /dev/null @@ -1,9 +0,0 @@ -link(rel="stylesheet" type="text/css" href=url_for(theme.asset.katex)) -script(src=url_for(theme.asset.katex_copytex)) -script. - (() => { - document.querySelectorAll('#article-container span.katex-display').forEach(item => { - btf.wrap(item, 'div', { class: 'katex-wrap'}) - }) - })() - \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/mathjax.pug b/themes/butterfly/layout/includes/third-party/math/mathjax.pug deleted file mode 100644 index 470aa451..00000000 --- a/themes/butterfly/layout/includes/third-party/math/mathjax.pug +++ /dev/null @@ -1,38 +0,0 @@ -//- Mathjax 3 -script. - if (!window.MathJax) { - window.MathJax = { - tex: { - inlineMath: [['$', '$'], ['\\(', '\\)']], - tags: 'ams' - }, - chtml: { - scale: 1.1 - }, - options: { - renderActions: { - findScript: [10, doc => { - for (const node of document.querySelectorAll('script[type^="math/tex"]')) { - const display = !!node.type.match(/; *mode=display/) - const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display) - const text = document.createTextNode('') - node.parentNode.replaceChild(text, node) - math.start = {node: text, delim: '', n: 0} - math.end = {node: text, delim: '', n: 0} - doc.math.push(math) - } - }, ''] - } - } - } - - const script = document.createElement('script') - script.src = '!{url_for(theme.asset.mathjax)}' - script.id = 'MathJax-script' - script.async = true - document.head.appendChild(script) - } else { - MathJax.startup.document.state(0) - MathJax.texReset() - MathJax.typesetPromise() - } \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/mermaid.pug b/themes/butterfly/layout/includes/third-party/math/mermaid.pug deleted file mode 100644 index 2cfc1c7c..00000000 --- a/themes/butterfly/layout/includes/third-party/math/mermaid.pug +++ /dev/null @@ -1,38 +0,0 @@ -script. - (() => { - const $mermaid = document.querySelectorAll('#article-container .mermaid-wrap') - if ($mermaid.length === 0) return - const runMermaid = () => { - window.loadMermaid = true - const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}' - - Array.from($mermaid).forEach((item, index) => { - const mermaidSrc = item.firstElementChild - const mermaidThemeConfig = '%%{init:{ \'theme\':\'' + theme + '\'}}%%\n' - const mermaidID = 'mermaid-' + index - const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent - - const renderFn = mermaid.render(mermaidID, mermaidDefinition) - - const renderV10 = () => { - renderFn.then(({svg}) => { - mermaidSrc.insertAdjacentHTML('afterend', svg) - }) - } - - const renderV9 = svg => { - mermaidSrc.insertAdjacentHTML('afterend', svg) - } - - typeof renderFn === 'string' ? renderV9(renderFn) : renderV10() - }) - } - - const loadMermaid = () => { - window.loadMermaid ? runMermaid() : getScript('!{url_for(theme.asset.mermaid)}').then(runMermaid) - } - - btf.addGlobalFn('themeChange', runMermaid, 'mermaid') - - window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug b/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug deleted file mode 100644 index e2e83bac..00000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug +++ /dev/null @@ -1,110 +0,0 @@ -- const { server, site, option } = theme.artalk -- const avatarCdn = option !== null && option.gravatar && option.gravatar.mirror -- const avatarDefault = option !== null && option.gravatar && (option.gravatar.params || option.gravatar.default) - -script. - window.addEventListener('load', () => { - const changeContent = (content) => { - if (content === '') return content - - content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link - content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url - content = content.replace(/
    .*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
    -      content = content.replace(/<[^>]+>/g,"") // remove html tag
    -
    -      if (content.length > 150) {
    -        content = content.substring(0,150) + '...'
    -      }
    -      return content
    -    }
    -
    -    const generateHtml = array => {
    -      let result = ''
    -
    -      if (array.length) {
    -        for (let i = 0; i < array.length; i++) {
    -          result += '
    ' - - if (!{theme.newest_comments.avatar}) { - const name = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' - result += `${array[i].nick}` - } - - result += `
    - ${array[i].content} -
    ${array[i].nick} /
    -
    ` - } - } else { - result += '!{_p("aside.card_newest_comments.zero")}' - } - - let $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.innerHTML= result - window.lazyLoadInstance && window.lazyLoadInstance.update() - window.pjax && window.pjax.refresh($dom) - } - - const getSetting = async () => { - try { - const res = await fetch('!{server}/api/conf', { method: 'GET' }) - return await res.json() - } catch (e) { - console.log(e) - } - } - - const headerList = { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Origin': window.location.origin - }, - body: new URLSearchParams({ - 'site_name': '!{site}', - 'limit': '!{theme.newest_comments.limit}', - 'type':'latest_comments' - }) - } - - const getComment = async () => { - try { - const res = await fetch('!{server}/api/stat', headerList) - const result = await res.json() - const avatarStr = await getSetting() - const { mirror, params, default:defaults } = avatarStr.data.frontend_conf.gravatar - const avatarCdn = !{avatarCdn} || mirror - let avatarDefault = !{avatarDefault} || params || defaults - avatarDefault = avatarDefault.startsWith('d=') ? avatarDefault : `d=${avatarDefault}` - const artalk = result.data.map(function (e) { - return { - 'avatar': `${avatarCdn}${e.email_encrypted}?${avatarDefault}`, - 'content': changeContent(e.content_marked), - 'nick': e.nick, - 'url': e.page_url, - 'date': e.date, - } - }) - saveToLocal.set('artalk-newest-comments', JSON.stringify(artalk), !{theme.newest_comments.storage}/(60*24)) - generateHtml(artalk) - } catch (e) { - console.log(e) - const $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.innerHTML= "!{_p('aside.card_newest_comments.error')}" - } - } - - const newestCommentInit = () => { - if (document.querySelector('#card-newest-comments .aside-list')) { - const data = saveToLocal.get('artalk-newest-comments') - if (data) { - generateHtml(JSON.parse(data)) - } else { - getComment() - } - } - } - - newestCommentInit() - document.addEventListener('pjax:complete', newestCommentInit) - }) diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug b/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug deleted file mode 100644 index 2acba1c0..00000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug +++ /dev/null @@ -1,82 +0,0 @@ -script. - window.addEventListener('load', () => { - const changeContent = (content) => { - if (content === '') return content - - content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link - content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url - content = content.replace(/.*?<\/code>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code - content = content.replace(/<[^>]+>/g,"") // remove html tag - - if (content.length > 150) { - content = content.substring(0,150) + '...' - } - return content - } - - const getComment = () => { - fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{theme.newest_comments.limit}&api_key=!{apiKey}') - .then(response => response.json()) - .then(data => { - const disqusArray = data.response.map(item => { - return { - 'avatar': item.author.avatar.cache, - 'content': changeContent(item.message), - 'nick': item.author.name, - 'url': item.url, - 'date': item.createdAt - } - }) - - saveToLocal.set('disqus-newest-comments', JSON.stringify(disqusArray), !{theme.newest_comments.storage}/(60*24)) - generateHtml(disqusArray) - }).catch(e => { - const $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.textContent= "!{_p('aside.card_newest_comments.error')}" - }) - } - - const generateHtml = array => { - let result = '' - - if (array.length) { - for (let i = 0; i < array.length; i++) { - result += '
    ' - - if (!{theme.newest_comments.avatar}) { - const name = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' - result += `${array[i].nick}` - } - - result += `
    - ${array[i].content} -
    ${array[i].nick}
    -
    ` - } - } else { - result += '!{_p("aside.card_newest_comments.zero")}' - } - - let $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.innerHTML= result - window.lazyLoadInstance && window.lazyLoadInstance.update() - window.pjax && window.pjax.refresh($dom) - } - - const newestCommentInit = () => { - if (document.querySelector('#card-newest-comments .aside-list')) { - const data = saveToLocal.get('disqus-newest-comments') - if (data) { - generateHtml(JSON.parse(data)) - } else { - getComment() - } - } - } - - newestCommentInit() - document.addEventListener('pjax:complete', newestCommentInit) - }) - - - diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug b/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug deleted file mode 100644 index 79430db6..00000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug +++ /dev/null @@ -1,109 +0,0 @@ -script. - window.addEventListener('load', () => { - const changeContent = (content) => { - if (content === '') return content - - content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link - content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url - content = content.replace(/
    .*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
    -      content = content.replace(/<[^>]+>/g,"") // remove html tag
    -
    -      if (content.length > 150) {
    -        content = content.substring(0,150) + '...'
    -      }
    -      return content
    -    }
    -
    -    const findTrueUrl = (array) => {
    -      Promise.all(array.map(item =>
    -        fetch(item.url).then(resp => resp.json()).then(data => {
    -          const urlArray = data.body.match(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig)
    -          if (data.user.login === 'utterances-bot') {
    -            return urlArray.pop()
    -          } else {
    -            return urlArray.shift()
    -          }
    -        })
    -      )).then(res => {
    -          array = array.map((i,index)=> {
    -            return {
    -              ...i,
    -              url: res[index]
    -            }
    -          })
    -
    -          saveToLocal.set('github-newest-comments', JSON.stringify(array), !{theme.newest_comments.storage}/(60*24))
    -          generateHtml(array)
    -      });
    -    }
    -
    -    const getComment = () => {
    -      fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{theme.newest_comments.limit}&page=1',{
    -        "headers": {
    -          Accept: 'application/vnd.github.v3.html+json'
    -        }
    -      })
    -        .then(response => response.json())
    -        .then(data => {
    -          const githubArray = data.map(item => {
    -            return {
    -              'avatar': item.user.avatar_url,
    -              'content': changeContent(item.body_html),
    -              'nick': item.user.login,
    -              'url': item.issue_url,
    -              'date': item.updated_at,
    -              'githubUrl': item.html_url
    -            }
    -          })
    -          findTrueUrl(githubArray)
    -        }).catch(e => {
    -          const $dom = document.querySelector('#card-newest-comments .aside-list')
    -          $dom.textContent= "!{_p('aside.card_newest_comments.error')}"
    -        })
    -    }
    -
    -    const generateHtml = array => {
    -      let result = ''
    -
    -      if (array.length) {
    -        for (let i = 0; i < array.length; i++) {
    -          result += '
    ' - - if (!{theme.newest_comments.avatar}) { - const name = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' - result += `${array[i].nick}` - } - - result += `
    - ${array[i].content} -
    ${array[i].nick} /
    -
    ` - } - } else { - result += '!{_p("aside.card_newest_comments.zero")}' - } - - let $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.innerHTML= result - window.lazyLoadInstance && window.lazyLoadInstance.update() - window.pjax && window.pjax.refresh($dom) - } - - const newestCommentInit = () => { - if (document.querySelector('#card-newest-comments .aside-list')) { - const data = saveToLocal.get('github-newest-comments') - if (data) { - generateHtml(JSON.parse(data)) - } else { - getComment() - } - } - } - - newestCommentInit() - document.addEventListener('pjax:complete', newestCommentInit) - }) - - - - diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/index.pug b/themes/butterfly/layout/includes/third-party/newest-comments/index.pug deleted file mode 100644 index 8ceaccf8..00000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/index.pug +++ /dev/null @@ -1,30 +0,0 @@ -- let { use } = theme.comments - -if use - - let forum,apiKey,userRepo - case use[0] - when 'Valine' - include ./valine.pug - when 'Waline' - include ./waline.pug - when 'Twikoo' - include ./twikoo-comment.pug - when 'Disqus' - - forum = theme.disqus.shortname - - apiKey = theme.disqus.apikey - include ./disqus-comment.pug - when 'Disqusjs' - - forum = theme.disqusjs.shortname - - apiKey = theme.disqusjs.apikey - include ./disqus-comment.pug - when 'Gitalk' - - let { repo,owner } = theme.gitalk - - userRepo = owner + '/' + repo - include ./github-issues.pug - when 'Utterances' - - userRepo = theme.utterances.repo - include ./github-issues.pug - when 'Remark42' - include ./remark42.pug - when 'Artalk' - include ./artalk.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug b/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug deleted file mode 100644 index 71c26037..00000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug +++ /dev/null @@ -1,80 +0,0 @@ -- const { host, siteId } = theme.remark42 - -script. - window.addEventListener('load', () => { - const changeContent = (content) => { - if (content === '') return content - - content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link - content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url - content = content.replace(/
    .*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
    -      content = content.replace(/<[^>]+>/g,"") // remove html tag
    -
    -      if (content.length > 150) {
    -        content = content.substring(0,150) + '...'
    -      }
    -      return content
    -    }
    -
    -    const generateHtml = array => {
    -      let result = ''
    -
    -      if (array.length) {
    -        for (let i = 0; i < array.length; i++) {
    -          result += '
    ' - - if (!{theme.newest_comments.avatar}) { - const name = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' - result += `${array[i].nick}` - } - - result += `
    - ${array[i].content} -
    ${array[i].nick} /
    -
    ` - } - } else { - result += '!{_p("aside.card_newest_comments.zero")}' - } - - let $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.innerHTML= result - window.lazyLoadInstance && window.lazyLoadInstance.update() - window.pjax && window.pjax.refresh($dom) - } - - const getComment = () => { - fetch('!{host}/api/v1/last/!{theme.newest_comments.limit}?site=!{siteId}') - .then(response => response.json()) - .then(data => { - const remark42 = data.map(function (e) { - return { - 'avatar': e.user.picture, - 'content': changeContent(e.text), - 'nick': e.user.name, - 'url': e.locator.url, - 'date': e.time, - } - }) - saveToLocal.set('remark42-newest-comments', JSON.stringify(remark42), !{theme.newest_comments.storage}/(60*24)) - generateHtml(remark42) - }).catch(e => { - const $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.textContent= "!{_p('aside.card_newest_comments.error')}" - }) - } - - const newestCommentInit = () => { - if (document.querySelector('#card-newest-comments .aside-list')) { - const data = saveToLocal.get('remark42-newest-comments') - if (data) { - generateHtml(JSON.parse(data)) - } else { - getComment() - } - } - } - - newestCommentInit() - document.addEventListener('pjax:complete', newestCommentInit) - }) diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug b/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug deleted file mode 100644 index 8615f160..00000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug +++ /dev/null @@ -1,93 +0,0 @@ -script. - window.addEventListener('load', () => { - const changeContent = (content) => { - if (content === '') return content - - content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link - content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url - content = content.replace(/
    .*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
    -      content = content.replace(/<[^>]+>/g,"") // remove html tag
    -
    -      if (content.length > 150) {
    -        content = content.substring(0,150) + '...'
    -      }
    -      return content
    -    }
    -
    -    const getComment = () => {
    -      const runTwikoo = () => {
    -        twikoo.getRecentComments({
    -          envId: '!{theme.twikoo.envId}',
    -          region: '!{theme.twikoo.region}',
    -          pageSize: !{theme.newest_comments.limit},
    -          includeReply: true
    -        }).then(function (res) {
    -          const twikooArray = res.map(e => {
    -            return {
    -              'content': changeContent(e.comment),
    -              'avatar': e.avatar,
    -              'nick': e.nick,
    -              'url': e.url + '#' + e.id,
    -              'date': new Date(e.created).toISOString()
    -            }
    -          })
    -
    -          saveToLocal.set('twikoo-newest-comments', JSON.stringify(twikooArray), !{theme.newest_comments.storage}/(60*24))
    -          generateHtml(twikooArray)
    -        }).catch(function (err) {
    -          const $dom = document.querySelector('#card-newest-comments .aside-list')
    -          $dom.textContent= "!{_p('aside.card_newest_comments.error')}"
    -        })
    -      }
    -
    -      if (typeof twikoo === 'object') {
    -        runTwikoo()
    -      } else {
    -        getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo)
    -      }
    -    }
    -
    -    const generateHtml = array => {
    -      let result = ''
    -
    -      if (array.length) {
    -        for (let i = 0; i < array.length; i++) {
    -          result += '
    ' - - if (!{theme.newest_comments.avatar}) { - const name = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' - result += `${array[i].nick}` - } - - result += `
    - ${array[i].content} -
    ${array[i].nick} /
    -
    ` - } - } else { - result += '!{_p("aside.card_newest_comments.zero")}' - } - - let $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.innerHTML= result - window.lazyLoadInstance && window.lazyLoadInstance.update() - window.pjax && window.pjax.refresh($dom) - } - - const newestCommentInit = () => { - if (document.querySelector('#card-newest-comments .aside-list')) { - const data = saveToLocal.get('twikoo-newest-comments') - if (data) { - generateHtml(JSON.parse(data)) - } else { - getComment() - } - } - } - - newestCommentInit() - document.addEventListener('pjax:complete', newestCommentInit) - }) - - - diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug b/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug deleted file mode 100644 index 6f78774d..00000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug +++ /dev/null @@ -1,99 +0,0 @@ -- let default_avatar = theme.valine.avatar - -script(src=url_for(theme.asset.blueimp_md5)) -script. - window.addEventListener('load', () => { - const changeContent = (content) => { - if (content === '') return content - - content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link - content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url - content = content.replace(/
    .*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
    -      content = content.replace(/<[^>]+>/g,"") // remove html tag
    -
    -      if (content.length > 150) {
    -        content = content.substring(0,150) + '...'
    -      }
    -      return content
    -    }
    -
    -    const getIcon = (icon, mail) => {
    -      if (icon) return icon
    -      let defaultIcon = '!{ default_avatar ? `?d=${default_avatar}` : ''}'
    -      let iconUrl = `https://gravatar.loli.net/avatar/${md5(mail.toLowerCase()) + defaultIcon}`
    -      return iconUrl
    -    }
    -
    -    const generateHtml = array => {
    -      let result = ''
    -
    -      if (array.length) {
    -        for (let i = 0; i < array.length; i++) {
    -          result += '
    ' - - if (!{theme.newest_comments.avatar}) { - const name = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' - result += `${array[i].nick}` - } - - result += `
    - ${array[i].content} -
    ${array[i].nick} /
    -
    ` - } - } else { - result += '!{_p("aside.card_newest_comments.zero")}' - } - - let $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.innerHTML= result - window.lazyLoadInstance && window.lazyLoadInstance.update() - window.pjax && window.pjax.refresh($dom) - } - - const getComment = () => { - const serverURL = '!{theme.valine.serverURLs || `https://${theme.valine.appId.substring(0,8)}.api.lncldglobal.com` }' - - var settings = { - "method": "GET", - "headers": { - "X-LC-Id": '!{theme.valine.appId}', - "X-LC-Key": '!{theme.valine.appKey}', - "Content-Type": "application/json" - }, - } - - fetch(`${serverURL}/1.1/classes/Comment?limit=!{theme.newest_comments.limit}&order=-createdAt`,settings) - .then(response => response.json()) - .then(data => { - const valineArray = data.results.map(function (e) { - return { - 'avatar': getIcon(e.QQAvatar, e.mail), - 'content': changeContent(e.comment), - 'nick': e.nick, - 'url': e.url + '#' + e.objectId, - 'date': e.updatedAt, - } - }) - saveToLocal.set('valine-newest-comments', JSON.stringify(valineArray), !{theme.newest_comments.storage}/(60*24)) - generateHtml(valineArray) - }).catch(e => { - const $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.textContent= "!{_p('aside.card_newest_comments.error')}" - }) - } - - const newestCommentInit = () => { - if (document.querySelector('#card-newest-comments .aside-list')) { - const data = saveToLocal.get('valine-newest-comments') - if (data) { - generateHtml(JSON.parse(data)) - } else { - getComment() - } - } - } - - newestCommentInit() - document.addEventListener('pjax:complete', newestCommentInit) - }) diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug b/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug deleted file mode 100644 index be25326b..00000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug +++ /dev/null @@ -1,81 +0,0 @@ -- const serverURL = theme.waline.serverURL.replace(/\/$/, '') - -script. - window.addEventListener('load', () => { - const changeContent = content => { - if (content === '') return content - - content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link - content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url - content = content.replace(/
    .*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
    -      content = content.replace(/<[^>]+>/g,"") // remove html tag
    -
    -      if (content.length > 150) {
    -        content = content.substring(0,150) + '...'
    -      }
    -      return content
    -    }
    -
    -    const generateHtml = array => {
    -      let result = ''
    -
    -      if (array.length) {
    -        for (let i = 0; i < array.length; i++) {
    -          result += '
    ' - - if (!{theme.newest_comments.avatar}) { - const name = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' - result += `${array[i].nick}` - } - - result += `
    - ${array[i].content} -
    ${array[i].nick} /
    -
    ` - } - } else { - result += '!{_p("aside.card_newest_comments.zero")}' - } - - let $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.innerHTML= result - window.lazyLoadInstance && window.lazyLoadInstance.update() - window.pjax && window.pjax.refresh($dom) - } - - const getComment = async () => { - try { - const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{theme.newest_comments.limit}', { method: 'GET' }) - const result = await res.json() - const walineArray = result.data.map(e => { - return { - 'content': changeContent(e.comment), - 'avatar': e.avatar, - 'nick': e.nick, - 'url': e.url + '#' + e.objectId, - 'date': e.time || e.insertedAt - } - }) - saveToLocal.set('waline-newest-comments', JSON.stringify(walineArray), !{theme.newest_comments.storage}/(60*24)) - generateHtml(walineArray) - } catch (err) { - console.error(err) - const $dom = document.querySelector('#card-newest-comments .aside-list') - $dom.textContent= "!{_p('aside.card_newest_comments.error')}" - } - } - - const newestCommentInit = () => { - if (document.querySelector('#card-newest-comments .aside-list')) { - const data = saveToLocal.get('waline-newest-comments') - if (data) { - generateHtml(JSON.parse(data)) - } else { - getComment() - } - } - } - - newestCommentInit() - document.addEventListener('pjax:complete', newestCommentInit) - }) diff --git a/themes/butterfly/layout/includes/third-party/pangu.pug b/themes/butterfly/layout/includes/third-party/pangu.pug deleted file mode 100644 index 2027ca7b..00000000 --- a/themes/butterfly/layout/includes/third-party/pangu.pug +++ /dev/null @@ -1,20 +0,0 @@ -script. - function panguFn () { - if (typeof pangu === 'object') pangu.autoSpacingPage() - else { - getScript('!{url_for(theme.asset.pangu)}') - .then(() => { - pangu.autoSpacingPage() - }) - } - } - - function panguInit () { - if (!{theme.pangu.field === 'post'}){ - GLOBAL_CONFIG_SITE.isPost && panguFn() - } else { - panguFn() - } - } - - document.addEventListener('DOMContentLoaded', panguInit) diff --git a/themes/butterfly/layout/includes/third-party/pjax.pug b/themes/butterfly/layout/includes/third-party/pjax.pug deleted file mode 100644 index 2b783c63..00000000 --- a/themes/butterfly/layout/includes/third-party/pjax.pug +++ /dev/null @@ -1,83 +0,0 @@ -- var pjaxExclude = 'a:not([target="_blank"])' -if theme.pjax.exclude - each val in theme.pjax.exclude - - pjaxExclude = pjaxExclude + `:not([href="${val}"])` - -- let pjaxSelectors = ['head > title','#config-diff','#body-wrap','#rightside-config-hide','#rightside-config-show','.js-pjax'] - -- let choose = theme.comments.use -if choose - if theme.Open_Graph_meta.enable && (choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')) - - pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]') - if choose.includes('Utterances') || choose.includes('Giscus') - - pjaxSelectors.unshift('link[rel="canonical"]') - -script(src=url_for(theme.asset.pjax)) -script. - let pjaxSelectors = !{JSON.stringify(pjaxSelectors)} - - var pjax = new Pjax({ - elements: '!{pjaxExclude}', - selectors: pjaxSelectors, - cacheBust: false, - analytics: !{theme.google_analytics ? true : false}, - scrollRestoration: false - }) - - document.addEventListener('pjax:send', function () { - - // removeEventListener - btf.removeGlobalFnEvent('pjax') - btf.removeGlobalFnEvent('themeChange') - - document.getElementById('rightside').classList.remove('rightside-show') - - if (window.aplayers) { - for (let i = 0; i < window.aplayers.length; i++) { - if (!window.aplayers[i].options.fixed) { - window.aplayers[i].destroy() - } - } - } - - typeof typed === 'object' && typed.destroy() - - //reset readmode - const $bodyClassList = document.body.classList - $bodyClassList.contains('read-mode') && $bodyClassList.remove('read-mode') - - typeof disqusjs === 'object' && disqusjs.destroy() - }) - - document.addEventListener('pjax:complete', function () { - window.refreshFn() - - document.querySelectorAll('script[data-pjax]').forEach(item => { - const newScript = document.createElement('script') - const content = item.text || item.textContent || item.innerHTML || "" - Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value)) - newScript.appendChild(document.createTextNode(content)) - item.parentNode.replaceChild(newScript, item) - }) - - GLOBAL_CONFIG.islazyload && window.lazyLoadInstance.update() - - typeof panguInit === 'function' && panguInit() - - // google analytics - typeof gtag === 'function' && gtag('config', '!{theme.google_analytics}', {'page_path': window.location.pathname}); - - // baidu analytics - typeof _hmt === 'object' && _hmt.push(['_trackPageview',window.location.pathname]); - - typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting() - - // prismjs - typeof Prism === 'object' && Prism.highlightAll() - }) - - document.addEventListener('pjax:error', e => { - if (e.request.status === 404) { - pjax.loadUrl('!{url_for("/404.html")}') - } - }) diff --git a/themes/butterfly/layout/includes/third-party/prismjs.pug b/themes/butterfly/layout/includes/third-party/prismjs.pug deleted file mode 100644 index 6af44ddd..00000000 --- a/themes/butterfly/layout/includes/third-party/prismjs.pug +++ /dev/null @@ -1,5 +0,0 @@ -if config.prismjs && config.prismjs.enable && !config.prismjs.preprocess - script(src=url_for(theme.asset.prismjs_js)) - script(src=url_for(theme.asset.prismjs_autoloader)) - if config.prismjs.line_number - script(src=url_for(theme.asset.prismjs_lineNumber_js)) diff --git a/themes/butterfly/layout/includes/third-party/search/algolia.pug b/themes/butterfly/layout/includes/third-party/search/algolia.pug deleted file mode 100644 index b1f3c3d4..00000000 --- a/themes/butterfly/layout/includes/third-party/search/algolia.pug +++ /dev/null @@ -1,22 +0,0 @@ -#algolia-search - .search-dialog - nav.search-nav - span.search-dialog-title= _p('search.title') - button.search-close-button - i.fas.fa-times - - .search-wrap - #algolia-search-input - hr - #algolia-search-results - #algolia-hits - #algolia-pagination - #algolia-info - .algolia-stats - .algolia-poweredBy - - #search-mask - - script(src=url_for(theme.asset.algolia_search)) - script(src=url_for(theme.asset.instantsearch)) - script(src=url_for(theme.asset.algolia_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/search/docsearch.pug b/themes/butterfly/layout/includes/third-party/search/docsearch.pug deleted file mode 100644 index 9adad8b3..00000000 --- a/themes/butterfly/layout/includes/third-party/search/docsearch.pug +++ /dev/null @@ -1,28 +0,0 @@ -- const { appId, apiKey, indexName, option } = theme.docsearch - -.docsearch-wrap - #docsearch(style="display:none") - link(rel="stylesheet" href=url_for(theme.asset.docsearch_css)) - script(src=url_for(theme.asset.docsearch_js)) - script. - (() => { - docsearch(Object.assign({ - appId: '!{appId}', - apiKey: '!{apiKey}', - indexName: '!{indexName}', - container: '#docsearch', - }, !{JSON.stringify(option)})) - - const handleClick = () => { - document.querySelector('.DocSearch-Button').click() - } - - const searchClickFn = () => { - btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', handleClick) - } - - searchClickFn() - window.addEventListener('pjax:complete', searchClickFn) - })() - - diff --git a/themes/butterfly/layout/includes/third-party/search/index.pug b/themes/butterfly/layout/includes/third-party/search/index.pug deleted file mode 100644 index 160df79c..00000000 --- a/themes/butterfly/layout/includes/third-party/search/index.pug +++ /dev/null @@ -1,6 +0,0 @@ -if theme.algolia_search.enable - include ./algolia.pug -else if theme.local_search.enable - include ./local-search.pug -else if theme.docsearch.enable - include ./docsearch.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/search/local-search.pug b/themes/butterfly/layout/includes/third-party/search/local-search.pug deleted file mode 100644 index f22f05bb..00000000 --- a/themes/butterfly/layout/includes/third-party/search/local-search.pug +++ /dev/null @@ -1,22 +0,0 @@ -#local-search - .search-dialog - nav.search-nav - span.search-dialog-title= _p('search.title') - span#loading-status - button.search-close-button - i.fas.fa-times - - #loading-database.is-center - i.fas.fa-spinner.fa-pulse - span= ' ' + _p("search.load_data") - - .search-wrap - #local-search-input - .local-search-box - input(placeholder=_p("search.local_search.input_placeholder") type="text").local-search-box--input - hr - #local-search-results - #local-search-stats-wrap - #search-mask - - script(src=url_for(theme.asset.local_search)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/share/add-this.pug b/themes/butterfly/layout/includes/third-party/share/add-this.pug deleted file mode 100644 index ab44267d..00000000 --- a/themes/butterfly/layout/includes/third-party/share/add-this.pug +++ /dev/null @@ -1,2 +0,0 @@ -.addthis_inline_share_toolbox -script(src=`//s7.addthis.com/js/300/addthis_widget.js#pubid=${theme.addThis.pubid}` async) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/share/addtoany.pug b/themes/butterfly/layout/includes/third-party/share/addtoany.pug deleted file mode 100644 index d42f3be4..00000000 --- a/themes/butterfly/layout/includes/third-party/share/addtoany.pug +++ /dev/null @@ -1,10 +0,0 @@ -.addtoany - .a2a_kit.a2a_kit_size_32.a2a_default_style - - let addtoanyItem = theme.addtoany.item.split(',') - each name in addtoanyItem - a(class="a2a_button_" + name) - - a.a2a_dd(href="https://www.addtoany.com/share") -script(async src='https://static.addtoany.com/menu/page.js') - - diff --git a/themes/butterfly/layout/includes/third-party/share/index.pug b/themes/butterfly/layout/includes/third-party/share/index.pug deleted file mode 100644 index f8122c0e..00000000 --- a/themes/butterfly/layout/includes/third-party/share/index.pug +++ /dev/null @@ -1,5 +0,0 @@ -.post_share - if theme.sharejs.enable - include ./share-js.pug - else if theme.addtoany.enable - !=partial('includes/third-party/share/addtoany', {}, {cache: true}) diff --git a/themes/butterfly/layout/includes/third-party/share/share-js.pug b/themes/butterfly/layout/includes/third-party/share/share-js.pug deleted file mode 100644 index 612332c4..00000000 --- a/themes/butterfly/layout/includes/third-party/share/share-js.pug +++ /dev/null @@ -1,4 +0,0 @@ -- const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img -.social-share(data-image=url_for(coverVal) data-sites= theme.sharejs.sites) -link(rel='stylesheet' href=url_for(theme.asset.sharejs_css) media="print" onload="this.media='all'") -script(src=url_for(theme.asset.sharejs) defer) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/subtitle.pug b/themes/butterfly/layout/includes/third-party/subtitle.pug deleted file mode 100644 index 0b4f6cc9..00000000 --- a/themes/butterfly/layout/includes/third-party/subtitle.pug +++ /dev/null @@ -1,92 +0,0 @@ -- const { effect,source,sub,typed_option } = theme.subtitle -- let subContent = sub || new Array() - -script. - window.typedJSFn = { - init: (str) => { - window.typed = new Typed('#subtitle', Object.assign({ - strings: str, - startDelay: 300, - typeSpeed: 150, - loop: true, - backSpeed: 50, - }, !{JSON.stringify(typed_option)})) - }, - run: (subtitleType) => { - if (!{effect}) { - if (typeof Typed === 'function') { - subtitleType() - } else { - getScript('!{url_for(theme.asset.typed)}').then(subtitleType) - } - } else { - subtitleType() - } - } - } - -case source - when 1 - script. - function subtitleType () { - fetch('https://v1.hitokoto.cn') - .then(response => response.json()) - .then(data => { - if (!{effect}) { - const from = '出自 ' + data.from - const sub = !{JSON.stringify(subContent)} - sub.unshift(data.hitokoto, from) - typedJSFn.init(sub) - } else { - document.getElementById('subtitle').textContent = data.hitokoto - } - }) - } - typedJSFn.run(subtitleType) - - when 2 - script. - function subtitleType () { - getScript('https://yijuzhan.com/api/word.php?m=js').then(() => { - const con = str[0] - if (!{effect}) { - const from = '出自 ' + str[1] - const sub = !{JSON.stringify(subContent)} - sub.unshift(con, from) - typedJSFn.init(sub) - } else { - document.getElementById('subtitle').textContent = con - } - }) - } - typedJSFn.run(subtitleType) - - when 3 - script. - function subtitleType () { - getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => { - jinrishici.load(result =>{ - if (!{effect}) { - const sub = !{JSON.stringify(subContent)} - const content = result.data.content - sub.unshift(content) - typedJSFn.init(sub) - } else { - document.getElementById('subtitle').textContent = result.data.content - } - }) - }) - } - typedJSFn.run(subtitleType) - - default - - subContent = subContent.length ? subContent : new Array(config.subtitle) - script. - function subtitleType () { - if (!{effect}) { - typedJSFn.init(!{JSON.stringify(subContent)}) - } else { - document.getElementById("subtitle").textContent = !{JSON.stringify(subContent[0])} - } - } - typedJSFn.run(subtitleType) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_ad.pug b/themes/butterfly/layout/includes/widget/card_ad.pug deleted file mode 100644 index b8e00fd4..00000000 --- a/themes/butterfly/layout/includes/widget/card_ad.pug +++ /dev/null @@ -1,3 +0,0 @@ -if theme.ad && theme.ad.aside - .card-widget.ads-wrap - != theme.ad.aside diff --git a/themes/butterfly/layout/includes/widget/card_announcement.pug b/themes/butterfly/layout/includes/widget/card_announcement.pug deleted file mode 100644 index 9e636276..00000000 --- a/themes/butterfly/layout/includes/widget/card_announcement.pug +++ /dev/null @@ -1,6 +0,0 @@ -if theme.aside.card_announcement.enable - .card-widget.card-announcement - .item-headline - i.fas.fa-bullhorn.fa-shake - span= _p('aside.card_announcement') - .announcement_content!= theme.aside.card_announcement.content \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_archives.pug b/themes/butterfly/layout/includes/widget/card_archives.pug deleted file mode 100644 index bb0e78eb..00000000 --- a/themes/butterfly/layout/includes/widget/card_archives.pug +++ /dev/null @@ -1,7 +0,0 @@ -if theme.aside.card_archives.enable - .card-widget.card-archives - - let type = theme.aside.card_archives.type || 'monthly' - - let format = theme.aside.card_archives.format || 'MMMM YYYY' - - let order = theme.aside.card_archives.order || -1 - - let limit = theme.aside.card_archives.limit === 0 ? 0 : theme.aside.card_archives.limit || 8 - != aside_archives({ type:type, format: format, order: order, limit: limit }) diff --git a/themes/butterfly/layout/includes/widget/card_author.pug b/themes/butterfly/layout/includes/widget/card_author.pug deleted file mode 100644 index 0a1e71ae..00000000 --- a/themes/butterfly/layout/includes/widget/card_author.pug +++ /dev/null @@ -1,27 +0,0 @@ -if theme.aside.card_author.enable - .card-widget.card-info - .is-center - .avatar-img - img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar") - .author-info__name= config.author - .author-info__description!= theme.aside.card_author.description || config.description - - .card-info-data.site-data.is-center - a(href=url_for(config.archive_dir) + '/') - .headline= _p('aside.articles') - .length-num= site.posts.length - a(href=url_for(config.tag_dir) + '/') - .headline= _p('aside.tags') - .length-num= site.tags.length - a(href=url_for(config.category_dir) + '/') - .headline= _p('aside.categories') - .length-num= site.categories.length - - if theme.aside.card_author.button.enable - a#card-info-btn(href=theme.aside.card_author.button.link) - i(class=theme.aside.card_author.button.icon) - span=theme.aside.card_author.button.text - - if(theme.social) - .card-info-social-icons.is-center - !=partial('includes/header/social', {}, {cache: true}) diff --git a/themes/butterfly/layout/includes/widget/card_bottom_self.pug b/themes/butterfly/layout/includes/widget/card_bottom_self.pug deleted file mode 100644 index e32907dc..00000000 --- a/themes/butterfly/layout/includes/widget/card_bottom_self.pug +++ /dev/null @@ -1,9 +0,0 @@ -if site.data.widget && site.data.widget.bottom - each item in site.data.widget.bottom - .card-widget(class=item.class_name id=item.id_name style=item.order ? `order: ${item.order}` : '') - .item-headline - i(class=item.icon) - span=item.name - .item-content - !=item.html - diff --git a/themes/butterfly/layout/includes/widget/card_categories.pug b/themes/butterfly/layout/includes/widget/card_categories.pug deleted file mode 100644 index 529ea55d..00000000 --- a/themes/butterfly/layout/includes/widget/card_categories.pug +++ /dev/null @@ -1,4 +0,0 @@ -if theme.aside.card_categories.enable - if site.categories.length - .card-widget.card-categories - !=aside_categories({ limit: theme.aside.card_categories.limit === 0 ? 0 : theme.aside.card_categories.limit || 8 , expand: theme.aside.card_categories.expand }) diff --git a/themes/butterfly/layout/includes/widget/card_newest_comment.pug b/themes/butterfly/layout/includes/widget/card_newest_comment.pug deleted file mode 100644 index a7a213c6..00000000 --- a/themes/butterfly/layout/includes/widget/card_newest_comment.pug +++ /dev/null @@ -1,7 +0,0 @@ -if theme.newest_comments.enable && theme.comments.use && !['Livere','Facebook Comments','Giscus'].includes(theme.comments.use[0]) - .card-widget#card-newest-comments - .item-headline - i.fas.fa-comment-dots - span= _p('aside.card_newest_comments.headline') - .aside-list - span= _p('aside.card_newest_comments.loading_text') diff --git a/themes/butterfly/layout/includes/widget/card_post_series.pug b/themes/butterfly/layout/includes/widget/card_post_series.pug deleted file mode 100644 index c216929e..00000000 --- a/themes/butterfly/layout/includes/widget/card_post_series.pug +++ /dev/null @@ -1,21 +0,0 @@ -if theme.aside.card_post_series.enable - - const array = fragment_cache('seriesArr', groupPosts) - .card-widget.card-post-series - .item-headline - i.fa-solid.fa-layer-group - span= _p('aside.card_post_series') - .aside-list - each item in array[page.series] - - const { path, title = _p('no_title'), cover, cover_type, date:dateA } = item - - let link = url_for(path) - - let no_cover = cover === false || !theme.cover.aside_enable ? 'no-cover' : '' - .aside-list-item(class=no_cover) - if cover && theme.cover.aside_enable - a.thumbnail(href=link title=title) - if cover_type === 'img' - img(src=url_for(cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) - else - div(style=`background: ${cover}`) - .content - a.title(href=link title=title)= title - time(datetime=date_xml(dateA) title=_p('post.created') + ' ' + full_date(dateA)) #[=date(dateA, config.date_format)] diff --git a/themes/butterfly/layout/includes/widget/card_post_toc.pug b/themes/butterfly/layout/includes/widget/card_post_toc.pug deleted file mode 100644 index 41542219..00000000 --- a/themes/butterfly/layout/includes/widget/card_post_toc.pug +++ /dev/null @@ -1,15 +0,0 @@ -- let tocNumber = page.toc_number !== undefined ? page.toc_number : theme.toc.number -- let tocExpand = page.toc_expand !== undefined ? page.toc_expand : theme.toc.expand -- let tocExpandClass = tocExpand ? 'is-expand' : '' - -#card-toc.card-widget - .item-headline - i.fas.fa-stream - span= _p('aside.card_toc') - span.toc-percentage - - if (page.encrypt == true) - .toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber}) - else - .toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber}) - \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_recent_post.pug b/themes/butterfly/layout/includes/widget/card_recent_post.pug deleted file mode 100644 index dddf0fcb..00000000 --- a/themes/butterfly/layout/includes/widget/card_recent_post.pug +++ /dev/null @@ -1,27 +0,0 @@ -if theme.aside.card_recent_post.enable - .card-widget.card-recent-post - .item-headline - i.fas.fa-history - span= _p('aside.card_recent_post') - .aside-list - - let postLimit = theme.aside.card_recent_post.limit === 0 ? site.posts.length : theme.aside.card_recent_post.limit || 5 - - let sort = theme.aside.card_recent_post.sort === 'updated' ? 'updated' : 'date' - - site.posts.sort(sort, -1).limit(postLimit).each(function(article){ - - let link = article.link || article.path - - let title = article.title || _p('no_title') - - let no_cover = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : '' - - let post_cover = article.cover - .aside-list-item(class=no_cover) - if post_cover && theme.cover.aside_enable - a.thumbnail(href=url_for(link) title=title) - if article.cover_type === 'img' - img(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) - else - div(style=`background: ${post_cover}`) - .content - a.title(href=url_for(link) title=title)= title - if theme.aside.card_recent_post.sort === 'updated' - time(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated)) #[=date(article.updated, config.date_format)] - else - time(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date)) #[=date(article.date, config.date_format)] - - }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_tags.pug b/themes/butterfly/layout/includes/widget/card_tags.pug deleted file mode 100644 index 49296b7b..00000000 --- a/themes/butterfly/layout/includes/widget/card_tags.pug +++ /dev/null @@ -1,14 +0,0 @@ -if theme.aside.card_tags.enable - if site.tags.length - .card-widget.card-tags - .item-headline - i.fas.fa-tags - span= _p('aside.card_tags') - - - let { limit, orderby, order } = theme.aside.card_tags - - limit = limit === 0 ? 0 : limit || 40 - - if theme.aside.card_tags.color - .card-tag-cloud!= cloudTags({source: site.tags, orderby: orderby, order: order, minfontsize: 1.15, maxfontsize: 1.45, limit: limit, unit: 'em'}) - else - .card-tag-cloud!= tagcloud({orderby: orderby, order: order, min_font: 1.1, max_font: 1.5, amount: limit , color: true, start_color: '#999', end_color: '#99a9bf', unit: 'em'}) diff --git a/themes/butterfly/layout/includes/widget/card_top_self.pug b/themes/butterfly/layout/includes/widget/card_top_self.pug deleted file mode 100644 index 6e81059d..00000000 --- a/themes/butterfly/layout/includes/widget/card_top_self.pug +++ /dev/null @@ -1,8 +0,0 @@ -if site.data.widget && site.data.widget.top - each item in site.data.widget.top - .card-widget(class=item.class_name id=item.id_name) - .item-headline - i(class=item.icon) - span=item.name - .item-content - !=item.html \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_webinfo.pug b/themes/butterfly/layout/includes/widget/card_webinfo.pug deleted file mode 100644 index 9ff3de09..00000000 --- a/themes/butterfly/layout/includes/widget/card_webinfo.pug +++ /dev/null @@ -1,35 +0,0 @@ -if theme.aside.card_webinfo.enable - .card-widget.card-webinfo - .item-headline - i.fas.fa-chart-line - span= _p('aside.card_webinfo.headline') - .webinfo - if theme.aside.card_webinfo.post_count - .webinfo-item - .item-name= _p('aside.card_webinfo.article_name') + " :" - .item-count= site.posts.length - if theme.runtimeshow.enable - .webinfo-item - .item-name= _p('aside.card_webinfo.runtime.name') + " :" - .item-count#runtimeshow(data-publishDate=date_xml(theme.runtimeshow.publish_date)) - i.fa-solid.fa-spinner.fa-spin - if theme.wordcount.enable && theme.wordcount.total_wordcount - .webinfo-item - .item-name=_p('aside.card_webinfo.site_wordcount') + " :" - .item-count=totalcount(site) - if theme.busuanzi.site_uv - .webinfo-item - .item-name= _p('aside.card_webinfo.site_uv_name') + " :" - .item-count#busuanzi_value_site_uv - i.fa-solid.fa-spinner.fa-spin - if theme.busuanzi.site_pv - .webinfo-item - .item-name= _p('aside.card_webinfo.site_pv_name') + " :" - .item-count#busuanzi_value_site_pv - i.fa-solid.fa-spinner.fa-spin - if theme.aside.card_webinfo.last_push_date - .webinfo-item - .item-name= _p('aside.card_webinfo.last_push_date.name') + " :" - .item-count#last-push-date(data-lastPushDate=date_xml(Date.now())) - i.fa-solid.fa-spinner.fa-spin - diff --git a/themes/butterfly/layout/includes/widget/index.pug b/themes/butterfly/layout/includes/widget/index.pug deleted file mode 100644 index 388ea1cc..00000000 --- a/themes/butterfly/layout/includes/widget/index.pug +++ /dev/null @@ -1,36 +0,0 @@ -#aside-content.aside-content - //- post - if is_post() - - const tocStyle = page.toc_style_simple - - const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple - if showToc && tocStyleVal - .sticky_layout - include ./card_post_toc.pug - else - !=partial('includes/widget/card_author', {}, {cache: true}) - !=partial('includes/widget/card_announcement', {}, {cache: true}) - !=partial('includes/widget/card_top_self', {}, {cache: true}) - .sticky_layout - if showToc - include ./card_post_toc.pug - if page.series - include ./card_post_series.pug - !=partial('includes/widget/card_recent_post', {}, {cache: true}) - !=partial('includes/widget/card_ad', {}, {cache: true}) - else - //- page - !=partial('includes/widget/card_author', {}, {cache: true}) - !=partial('includes/widget/card_announcement', {}, {cache: true}) - !=partial('includes/widget/card_top_self', {}, {cache: true}) - - .sticky_layout - if showToc - include ./card_post_toc.pug - !=partial('includes/widget/card_recent_post', {}, {cache: true}) - !=partial('includes/widget/card_ad', {}, {cache: true}) - !=partial('includes/widget/card_newest_comment', {}, {cache: true}) - !=partial('includes/widget/card_categories', {}, {cache: true}) - !=partial('includes/widget/card_tags', {}, {cache: true}) - !=partial('includes/widget/card_archives', {}, {cache: true}) - !=partial('includes/widget/card_webinfo', {}, {cache: true}) - !=partial('includes/widget/card_bottom_self', {}, {cache: true}) \ No newline at end of file diff --git a/themes/butterfly/layout/index.pug b/themes/butterfly/layout/index.pug deleted file mode 100644 index 2655cb4b..00000000 --- a/themes/butterfly/layout/index.pug +++ /dev/null @@ -1,7 +0,0 @@ -extends includes/layout.pug - -block content - include ./includes/mixins/post-ui.pug - #recent-posts.recent-posts - +postUI - include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/page.pug b/themes/butterfly/layout/page.pug deleted file mode 100644 index 6c810ef7..00000000 --- a/themes/butterfly/layout/page.pug +++ /dev/null @@ -1,20 +0,0 @@ -extends includes/layout.pug - -block content - #page - if top_img === false - h1.page-title= page.title - - case page.type - when 'tags' - include includes/page/tags.pug - when 'link' - include includes/page/flink.pug - when 'categories' - include includes/page/categories.pug - default - include includes/page/default-page.pug - - if page.comments !== false && theme.comments && theme.comments.use - - var commentsJsLoad = true - !=partial('includes/third-party/comments/index', {}, {cache: true}) \ No newline at end of file diff --git a/themes/butterfly/layout/post.pug b/themes/butterfly/layout/post.pug deleted file mode 100644 index f54ab7a0..00000000 --- a/themes/butterfly/layout/post.pug +++ /dev/null @@ -1,32 +0,0 @@ -extends includes/layout.pug - -block content - #post - if top_img === false - include includes/header/post-info.pug - - article#article-container.post-content!=page.content - include includes/post/post-copyright.pug - .tag_share - if (theme.post_meta.post.tags) - .post-meta__tag-list - each item, index in page.tags.data - a(href=url_for(item.path)).post-meta__tags #[=item.name] - include includes/third-party/share/index.pug - - if theme.reward.enable && theme.reward.QR_code - !=partial('includes/post/reward', {}, {cache: true}) - - //- ad - if theme.ad && theme.ad.post - .ads-wrap!=theme.ad.post - - if theme.post_pagination - include includes/pagination.pug - if theme.related_post && theme.related_post.enable - != related_posts(page,site.posts) - - if page.comments !== false && theme.comments && theme.comments.use - - var commentsJsLoad = true - !=partial('includes/third-party/comments/index', {}, {cache: true}) - \ No newline at end of file diff --git a/themes/butterfly/layout/tag.pug b/themes/butterfly/layout/tag.pug deleted file mode 100644 index 9f99658d..00000000 --- a/themes/butterfly/layout/tag.pug +++ /dev/null @@ -1,14 +0,0 @@ -extends includes/layout.pug - -block content - if theme.tag_ui == 'index' - include ./includes/mixins/post-ui.pug - #recent-posts.recent-posts - +postUI - include includes/pagination.pug - else - include ./includes/mixins/article-sort.pug - #tag - .article-sort-title= _p('page.tag') + ' - ' + page.tag - +articleSort(page.posts) - include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/package.json b/themes/butterfly/package.json deleted file mode 100644 index 0ed17d43..00000000 --- a/themes/butterfly/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "hexo-theme-butterfly", - "version": "4.10.0", - "description": "A Simple and Card UI Design theme for Hexo", - "main": "package.json", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [ - "hexo", - "theme", - "butterfly", - "Card UI Design", - "Jerry", - "hexo-theme-butterfly" - ], - "repository": { - "type" : "git", - "url" : "https://github.com/jerryc127/hexo-theme-butterfly.git" - }, - "bugs": { - "url": "https://github.com/jerryc127/hexo-theme-butterfly/issues", - "email": "my@crazywong.com" - }, - "dependencies": { - "hexo-renderer-stylus": "^3.0.0", - "hexo-renderer-pug": "^3.0.0" - }, - "homepage": "https://butterfly.js.org/", - "author": "Jerry ", - "license": "Apache-2.0" -} diff --git a/themes/butterfly/plugins.yml b/themes/butterfly/plugins.yml deleted file mode 100644 index b25c304a..00000000 --- a/themes/butterfly/plugins.yml +++ /dev/null @@ -1,211 +0,0 @@ -algolia_search: - name: algoliasearch - file: dist/algoliasearch-lite.umd.js - version: 4.20.0 -instantsearch: - name: instantsearch.js - file: dist/instantsearch.production.min.js - version: 4.57.0 -pjax: - name: pjax - file: pjax.min.js - version: 0.2.8 -gitalk: - name: gitalk - file: dist/gitalk.min.js - version: 1.8.0 -gitalk_css: - name: gitalk - file: dist/gitalk.css - version: 1.8.0 -blueimp_md5: - name: blueimp-md5 - file: js/md5.min.js - version: 2.19.0 -valine: - name: valine - file: dist/Valine.min.js - version: 1.5.1 -disqusjs: - name: disqusjs - file: dist/browser/disqusjs.es2015.umd.min.js - version: 3.0.2 -disqusjs_css: - name: disqusjs - file: dist/browser/styles/disqusjs.css - version: 3.0.2 -twikoo: - name: twikoo - file: dist/twikoo.all.min.js - version: 1.6.22 -waline_js: - name: '@waline/client' - file: dist/waline.js - other_name: waline - version: 2.15.8 -waline_css: - name: '@waline/client' - file: dist/waline.css - other_name: waline - version: 2.15.8 -sharejs: - name: butterfly-extsrc - file: sharejs/dist/js/social-share.min.js - version: 1.1.3 -sharejs_css: - name: butterfly-extsrc - file: sharejs/dist/css/share.min.css - version: 1.1.3 -mathjax: - name: mathjax - file: es5/tex-mml-chtml.js - version: 3.2.2 -katex: - name: katex - file: dist/katex.min.css - other_name: KaTeX - version: 0.16.9 -katex_copytex: - name: katex - file: dist/contrib/copy-tex.min.js - other_name: KaTeX - version: 0.16.9 -mermaid: - name: mermaid - file: dist/mermaid.min.js - version: 10.5.0 -canvas_ribbon: - name: butterfly-extsrc - file: dist/canvas-ribbon.min.js - version: 1.1.3 -canvas_fluttering_ribbon: - name: butterfly-extsrc - file: dist/canvas-fluttering-ribbon.min.js - version: 1.1.3 -canvas_nest: - name: butterfly-extsrc - file: dist/canvas-nest.min.js - version: 1.1.3 -activate_power_mode: - name: butterfly-extsrc - file: dist/activate-power-mode.min.js - version: 1.1.3 -fireworks: - name: butterfly-extsrc - file: dist/fireworks.min.js - version: 1.1.3 -click_heart: - name: butterfly-extsrc - file: dist/click-heart.min.js - version: 1.1.3 -clickShowText: - name: butterfly-extsrc - file: dist/click-show-text.min.js - version: 1.1.3 -lazyload: - name: vanilla-lazyload - file: dist/lazyload.iife.min.js - version: 17.8.4 -instantpage: - name: instant.page - file: instantpage.js - version: 5.2.0 -typed: - name: typed.js - file: dist/typed.umd.js - version: 2.0.16 -pangu: - name: pangu - file: dist/browser/pangu.min.js - version: 4.0.7 -fancybox_css: - name: '@fancyapps/ui' - file: dist/fancybox/fancybox.css - version: 5.0.24 - other_name: fancyapps-ui -fancybox: - name: '@fancyapps/ui' - file: dist/fancybox/fancybox.umd.js - version: 5.0.24 - other_name: fancyapps-ui -medium_zoom: - name: medium-zoom - file: dist/medium-zoom.min.js - version: 1.0.8 -snackbar_css: - name: node-snackbar - file: dist/snackbar.min.css - version: 0.1.16 -snackbar: - name: node-snackbar - file: dist/snackbar.min.js - version: 0.1.16 -fontawesome: - name: '@fortawesome/fontawesome-free' - file: css/all.min.css - other_name: font-awesome - version: 6.4.2 -egjs_infinitegrid: - name: '@egjs/infinitegrid' - other_name: egjs-infinitegrid - file: dist/infinitegrid.min.js - version: 4.10.1 -aplayer_css: - name: aplayer - file: dist/APlayer.min.css - version: 1.10.1 -aplayer_js: - name: aplayer - file: dist/APlayer.min.js - version: 1.10.1 -meting_js: - name: butterfly-extsrc - file: metingjs/dist/Meting.min.js - version: 1.1.3 -prismjs_js: - name: prismjs - file: prism.js - other_name: prism - version: 1.29.0 -prismjs_lineNumber_js: - name: prismjs - file: plugins/line-numbers/prism-line-numbers.min.js - other_name: prism - version: 1.29.0 -prismjs_autoloader: - name: prismjs - file: plugins/autoloader/prism-autoloader.min.js - other_name: prism - version: 1.29.0 -artalk_js: - name: artalk - file: dist/Artalk.js - version: 2.6.3 -artalk_css: - name: artalk - file: dist/Artalk.css - version: 2.6.3 -pace_js: - name: pace-js - other_name: pace - file: pace.min.js - version: 1.2.4 -pace_default_css: - name: pace-js - other_name: pace - file: themes/blue/pace-theme-minimal.css - version: 1.2.4 -docsearch_js: - name: '@docsearch/js' - other_name: docsearch-js - file: dist/umd/index.js - version: 3.5.2 -docsearch_css: - name: '@docsearch/css' - other_name: docsearch-css - file: dist/style.css - version: 3.5.2 -abcjs_basic_js: - name: abcjs - file: dist/abcjs-basic-min.js - version: 6.2.2 diff --git a/themes/butterfly/scripts/events/404.js b/themes/butterfly/scripts/events/404.js deleted file mode 100644 index 02491ce7..00000000 --- a/themes/butterfly/scripts/events/404.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Butterfly - * 404 error page - */ - -'use strict' - -hexo.extend.generator.register('404', function (locals) { - if (!hexo.theme.config.error_404.enable) return - return { - path: '404.html', - layout: ['page'], - data: { - type: '404', - top_img: false - } - } -}) diff --git a/themes/butterfly/scripts/events/cdn.js b/themes/butterfly/scripts/events/cdn.js deleted file mode 100644 index b83404fe..00000000 --- a/themes/butterfly/scripts/events/cdn.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Butterfly - * Merge CDN - */ - -'use strict' - -const { version } = require('../../package.json') -const path = require('path') - -hexo.extend.filter.register('before_generate', () => { - const themeConfig = hexo.theme.config - const { CDN } = themeConfig - - const thirdPartySrc = hexo.render.renderSync({ path: path.join(hexo.theme_dir, '/plugins.yml'), engine: 'yaml' }) - const internalSrc = { - main: { - name: 'hexo-theme-butterfly', - file: 'js/main.js', - version - }, - utils: { - name: 'hexo-theme-butterfly', - file: 'js/utils.js', - version - }, - translate: { - name: 'hexo-theme-butterfly', - file: 'js/tw_cn.js', - version - }, - local_search: { - name: 'hexo-theme-butterfly', - file: 'js/search/local-search.js', - version - }, - algolia_js: { - name: 'hexo-theme-butterfly', - file: 'js/search/algolia.js', - version - } - } - - const minFile = file => { - return file.replace(/(? '.min' + ext) - } - - const createCDNLink = (data, type, cond = '') => { - Object.keys(data).forEach(key => { - let { name, version, file, other_name } = data[key] - const cdnjs_name = other_name || name - const cdnjs_file = file.replace(/^[lib|dist]*\/|browser\//g, '') - const min_cdnjs_file = minFile(cdnjs_file) - if (cond === 'internal') file = `source/${file}` - const min_file = minFile(file) - const verType = CDN.version ? (type === 'local' ? `?v=${version}` : `@${version}`) : '' - - const value = { - version, - name, - file, - cdnjs_file, - min_file, - min_cdnjs_file, - cdnjs_name - } - - const cdnSource = { - local: cond === 'internal' ? `${cdnjs_file + verType}` : `/pluginsSrc/${name}/${file + verType}`, - jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${min_file}`, - unpkg: `https://unpkg.com/${name}${verType}/${file}`, - cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${min_cdnjs_file}`, - custom: (CDN.custom_format || '').replace(/\$\{(.+?)\}/g, (match, $1) => value[$1]) - } - - data[key] = cdnSource[type] - }) - - if (cond === 'internal') data.main_css = 'css/index.css' + (CDN.version ? `?v=${version}` : '') - return data - } - - // delete null value - const deleteNullValue = obj => { - if (!obj) return - for (const i in obj) { - obj[i] === null && delete obj[i] - } - return obj - } - - themeConfig.asset = Object.assign( - createCDNLink(internalSrc, CDN.internal_provider, 'internal'), - createCDNLink(thirdPartySrc, CDN.third_party_provider), - deleteNullValue(CDN.option) - ) -}) diff --git a/themes/butterfly/scripts/events/comment.js b/themes/butterfly/scripts/events/comment.js deleted file mode 100644 index ebbe439a..00000000 --- a/themes/butterfly/scripts/events/comment.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Capitalize the first letter of comment name - */ - -hexo.extend.filter.register('before_generate', () => { - const themeConfig = hexo.theme.config - let { use } = themeConfig.comments - if (!use) return - if (typeof use === 'string') { - use = use.split(',') - } - const newArray = use.map(item => item.toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase())) - themeConfig.comments.use = newArray -}) diff --git a/themes/butterfly/scripts/events/init.js b/themes/butterfly/scripts/events/init.js deleted file mode 100644 index 36304d89..00000000 --- a/themes/butterfly/scripts/events/init.js +++ /dev/null @@ -1,20 +0,0 @@ -hexo.extend.filter.register('before_generate', () => { - // Get first two digits of the Hexo version number - const { version, log, locals } = hexo - const hexoVer = version.replace(/(^.*\..*)\..*/, '$1') - - if (hexoVer < 5.3) { - log.error('Please update Hexo to V5.3.0 or higher!') - log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!') - process.exit(-1) - } - - if (locals.get) { - const data = locals.get('data') - if (data && data.butterfly) { - log.error("'butterfly.yml' is deprecated. Please use '_config.butterfly.yml'") - log.error("'butterfly.yml' 已經棄用,請使用 '_config.butterfly.yml'") - process.exit(-1) - } - } -}) diff --git a/themes/butterfly/scripts/events/stylus.js b/themes/butterfly/scripts/events/stylus.js deleted file mode 100644 index 2e6b802d..00000000 --- a/themes/butterfly/scripts/events/stylus.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Stylus renderer - */ - -'use strict' - -hexo.extend.filter.register('stylus:renderer', style => { - const { enable: highlightEnable, line_number: highlightLineNumber } = hexo.config.highlight - const { enable: prismjsEnable, line_number: prismjsLineNumber } = hexo.config.prismjs - - style.define('$highlight_enable', highlightEnable) - .define('$highlight_line_number', highlightLineNumber) - .define('$prismjs_enable', prismjsEnable) - .define('$prismjs_line_number', prismjsLineNumber) - // .import(`${this.source_dir.replace(/\\/g, '/')}_data/css/*`) -}) diff --git a/themes/butterfly/scripts/events/welcome.js b/themes/butterfly/scripts/events/welcome.js deleted file mode 100644 index f4c018ab..00000000 --- a/themes/butterfly/scripts/events/welcome.js +++ /dev/null @@ -1,13 +0,0 @@ -hexo.on('ready', () => { - const { version } = require('../../package.json') - hexo.log.info(` - =================================================================== - ##### # # ##### ##### ###### ##### ###### # # # - # # # # # # # # # # # # # - ##### # # # # ##### # # ##### # # - # # # # # # # ##### # # # - # # # # # # # # # # # # - ##### #### # # ###### # # # ###### # - ${version} - ===================================================================`) -}) diff --git a/themes/butterfly/scripts/filters/post_lazyload.js b/themes/butterfly/scripts/filters/post_lazyload.js deleted file mode 100644 index cecc6ce2..00000000 --- a/themes/butterfly/scripts/filters/post_lazyload.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Butterfly - * lazyload - * replace src to data-lazy-src - */ - -'use strict' - -const urlFor = require('hexo-util').url_for.bind(hexo) - -const lazyload = htmlContent => { - const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' - return htmlContent.replace(/( { - const { enable, field } = hexo.theme.config.lazyload - if (!enable || field !== 'site') return - return lazyload(data) -}) - -hexo.extend.filter.register('after_post_render', data => { - const { enable, field } = hexo.theme.config.lazyload - if (!enable || field !== 'post') return - data.content = lazyload(data.content) - return data -}) diff --git a/themes/butterfly/scripts/filters/random_cover.js b/themes/butterfly/scripts/filters/random_cover.js deleted file mode 100644 index 7dac3cf8..00000000 --- a/themes/butterfly/scripts/filters/random_cover.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Butterfly - * ramdom cover - */ - -'use strict' - -hexo.extend.filter.register('before_post_render', data => { - const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i - let { cover: coverVal, top_img: topImg } = data - - // Add path to top_img and cover if post_asset_folder is enabled - if (hexo.config.post_asset_folder) { - if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = `${data.path}${topImg}` - if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = `${data.path}${coverVal}` - } - - const randomCoverFn = () => { - const { cover: { default_cover: defaultCover } } = hexo.theme.config - if (!defaultCover) return false - if (!Array.isArray(defaultCover)) return defaultCover - const num = Math.floor(Math.random() * defaultCover.length) - return defaultCover[num] - } - - if (coverVal === false) return data - - // If cover is not set, use random cover - if (!coverVal) { - const randomCover = randomCoverFn() - data.cover = randomCover - coverVal = randomCover // update coverVal - } - - if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { - data.cover_type = 'img' - } - - return data -}) diff --git a/themes/butterfly/scripts/helpers/aside_archives.js b/themes/butterfly/scripts/helpers/aside_archives.js deleted file mode 100644 index 779c1d45..00000000 --- a/themes/butterfly/scripts/helpers/aside_archives.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Butterfly - * for aside archives - */ - -'use strict' - -hexo.extend.helper.register('aside_archives', function (options = {}) { - const { config } = this - const archiveDir = config.archive_dir - const { timezone } = config - const lang = toMomentLocale(this.page.lang || this.page.language || config.language) - let { format } = options - const type = options.type || 'monthly' - const { transform } = options - const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true - const order = options.order || -1 - const compareFunc = type === 'monthly' - ? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB - : (yearA, monthA, yearB, monthB) => yearA === yearB - const limit = options.limit - let result = '' - - if (!format) { - format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY' - } - - const posts = this.site.posts.sort('date', order) - if (!posts.length) return result - - const data = [] - let length = 0 - - posts.forEach(post => { - // Clone the date object to avoid pollution - let date = post.date.clone() - - if (timezone) date = date.tz(timezone) - - const year = date.year() - const month = date.month() + 1 - const lastData = data[length - 1] - - if (!lastData || !compareFunc(lastData.year, lastData.month, year, month)) { - if (lang) date = date.locale(lang) - const name = date.format(format) - length = data.push({ - name, - year, - month, - count: 1 - }) - } else { - lastData.count++ - } - }) - - const link = item => { - let url = `${archiveDir}/${item.year}/` - - if (type === 'monthly') { - if (item.month < 10) url += '0' - url += `${item.month}/` - } - - return this.url_for(url) - } - - const len = data.length - const Judge = limit === 0 ? len : Math.min(len, limit) - - result += `
    ${this._p('aside.card_archives')}` - - if (len > Judge) { - result += ` - ` - } - - result += '
    ' - return result -}) - -const toMomentLocale = function (lang) { - if (lang === undefined) { - return undefined - } - - // moment.locale('') equals moment.locale('en') - // moment.locale(null) equals moment.locale('en') - if (!lang || lang === 'en' || lang === 'default') { - return 'en' - } - return lang.toLowerCase().replace('_', '-') -} diff --git a/themes/butterfly/scripts/helpers/aside_categories.js b/themes/butterfly/scripts/helpers/aside_categories.js deleted file mode 100644 index 244b163e..00000000 --- a/themes/butterfly/scripts/helpers/aside_categories.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Butterfly - * for aside categories - */ - -'use strict' - -hexo.extend.helper.register('aside_categories', function (categories, options) { - if (!options && (!categories || !Object.prototype.hasOwnProperty.call(categories, 'length')) - ) { - options = categories - categories = this.site.categories - } - - if (!categories || !categories.length) return '' - options = options || {} - const { config } = this - const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') - ? options.show_count - : true - const depth = options.depth ? parseInt(options.depth, 10) : 0 - const orderby = options.orderby || 'name' - const order = options.order || 1 - const categoryDir = this.url_for(config.category_dir) - const limit = options.limit === 0 ? categories.length : options.limit - const isExpand = options.expand !== 'none' - const expandClass = isExpand && options.expand === true ? 'expand' : '' - const buttonLabel = this._p('aside.more_button') - const prepareQuery = (parent) => { - const query = {} - if (parent) { query.parent = parent } else { query.parent = { $exists: false } } - return categories.find(query).sort(orderby, order).filter((cat) => cat.length) - } - let expandBtn = '' - - const hierarchicalList = (t, level, parent, topparent = true) => { - let result = '' - const isTopParent = topparent - if (t > 0) { - prepareQuery(parent).forEach((cat, i) => { - if (t > 0) { - t = t - 1 - let child - if (!depth || level + 1 < depth) { - const childList = hierarchicalList(t, level + 1, cat._id, false) - child = childList[0] - t = childList[1] - } - - const parentClass = isExpand && isTopParent && child ? 'parent' : '' - - result += `
  • ` - - result += `` - - result += `${cat.name}` - - if (showCount) { - result += `${cat.length}` - } - - if (isExpand && isTopParent && child) { - expandBtn = ' expandBtn' - result += `` - } - - result += '' - - if (child) { - result += `
      ${child}
    ` - } - - result += '
  • ' - } - }) - } - - return [result, t] - } - - const list = hierarchicalList(limit, 0) - - const moreButton = function () { - if (categories.length <= limit) return '' - const moreHtml = ` - ` - - return moreHtml - } - - return `
    - - ${this._p('aside.card_categories')} - ${moreButton()} -
    -
      - ${list[0]} -
    ` -}) diff --git a/themes/butterfly/scripts/helpers/findArchiveLength.js b/themes/butterfly/scripts/helpers/findArchiveLength.js deleted file mode 100644 index 152b15ec..00000000 --- a/themes/butterfly/scripts/helpers/findArchiveLength.js +++ /dev/null @@ -1,58 +0,0 @@ -hexo.extend.helper.register('getArchiveLength', function () { - const { archive_generator: archiveGenerator } = hexo.config - if (archiveGenerator && archiveGenerator.enable === false) return this.site.posts.length - const { yearly, monthly, daily } = archiveGenerator - const { year, month, day } = this.page - if (yearly === false || !year) return this.site.posts.length - - const posts = this.site.posts.sort('date') - - const compareFunc = (type, y1, m1, d1, y2, m2, d2) => { - switch (type) { - case 'year': - return y1 === y2 - case 'month': - return y1 === y2 && m1 === m2 - case 'day': - return y1 === y2 && m1 === m2 && d1 === d2 - default: - return false - } - } - - const generateDateObj = (type) => { - return posts.reduce((dateObj, post) => { - const date = post.date.clone() - const year = date.year() - const month = date.month() + 1 - const day = date.date() - const lastData = dateObj[dateObj.length - 1] - - if (!lastData || !compareFunc(type, lastData.year, lastData.month, lastData.day, year, month, day)) { - const name = type === 'year' ? year : type === 'month' ? `${year}-${month}` : `${year}-${month}-${day}` - dateObj.push({ - name, - year, - month, - day, - count: 1 - }) - } else { - lastData.count++ - } - - return dateObj - }, []) - } - - const data = this.fragment_cache('createArchiveObj', () => { - const dateObjs = [] - if (yearly) dateObjs.push(...generateDateObj('year')) - if (monthly) dateObjs.push(...generateDateObj('month')) - if (daily) dateObjs.push(...generateDateObj('day')) - return dateObjs - }) - - const name = month ? (day ? `${year}-${month}-${day}` : `${year}-${month}`) : year - return data.find(item => item.name === name).count -}) diff --git a/themes/butterfly/scripts/helpers/inject_head_js.js b/themes/butterfly/scripts/helpers/inject_head_js.js deleted file mode 100644 index 5871fd74..00000000 --- a/themes/butterfly/scripts/helpers/inject_head_js.js +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Butterfly - * inject js to head - */ - -'use strict' - -hexo.extend.helper.register('inject_head_js', function () { - const { darkmode, aside } = this.theme - const start = darkmode.start || 6 - const end = darkmode.end || 18 - const { theme_color } = hexo.theme.config - const themeColorLight = (theme_color && theme_color.enable && theme_color.meta_theme_color_light) || '#ffffff' - const themeColorDark = (theme_color && theme_color.enable && theme_color.meta_theme_color_dark) || '#0d0d0d' - - const createLocalStore = () => { - return ` - win.saveToLocal = { - set: (key, value, ttl) => { - if (ttl === 0) return - const now = Date.now() - const expiry = now + ttl * 86400000 - const item = { - value, - expiry - } - localStorage.setItem(key, JSON.stringify(item)) - }, - - get: key => { - const itemStr = localStorage.getItem(key) - - if (!itemStr) { - return undefined - } - const item = JSON.parse(itemStr) - const now = Date.now() - - if (now > item.expiry) { - localStorage.removeItem(key) - return undefined - } - return item.value - } - } - ` - } - - // https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript - const createGetScript = () => { - return ` - win.getScript = (url, attr = {}) => new Promise((resolve, reject) => { - const script = document.createElement('script') - script.src = url - script.async = true - script.onerror = reject - script.onload = script.onreadystatechange = function() { - const loadState = this.readyState - if (loadState && loadState !== 'loaded' && loadState !== 'complete') return - script.onload = script.onreadystatechange = null - resolve() - } - - Object.keys(attr).forEach(key => { - script.setAttribute(key, attr[key]) - }) - - document.head.appendChild(script) - }) - ` - } - - const createGetCSS = () => { - return ` - win.getCSS = (url, id = false) => new Promise((resolve, reject) => { - const link = document.createElement('link') - link.rel = 'stylesheet' - link.href = url - if (id) link.id = id - link.onerror = reject - link.onload = link.onreadystatechange = function() { - const loadState = this.readyState - if (loadState && loadState !== 'loaded' && loadState !== 'complete') return - link.onload = link.onreadystatechange = null - resolve() - } - document.head.appendChild(link) - }) - ` - } - - const createDarkmodeJs = () => { - if (!darkmode.enable) return '' - - let darkmodeJs = ` - win.activateDarkMode = () => { - document.documentElement.setAttribute('data-theme', 'dark') - if (document.querySelector('meta[name="theme-color"]') !== null) { - document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorDark}') - } - } - win.activateLightMode = () => { - document.documentElement.setAttribute('data-theme', 'light') - if (document.querySelector('meta[name="theme-color"]') !== null) { - document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorLight}') - } - } - const t = saveToLocal.get('theme') - ` - - const autoChangeMode = darkmode.autoChangeMode - - if (autoChangeMode === 1) { - darkmodeJs += ` - const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches - const isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches - const isNotSpecified = window.matchMedia('(prefers-color-scheme: no-preference)').matches - const hasNoSupport = !isDarkMode && !isLightMode && !isNotSpecified - - if (t === undefined) { - if (isLightMode) activateLightMode() - else if (isDarkMode) activateDarkMode() - else if (isNotSpecified || hasNoSupport) { - const now = new Date() - const hour = now.getHours() - const isNight = hour <= ${start} || hour >= ${end} - isNight ? activateDarkMode() : activateLightMode() - } - window.matchMedia('(prefers-color-scheme: dark)').addListener(e => { - if (saveToLocal.get('theme') === undefined) { - e.matches ? activateDarkMode() : activateLightMode() - } - }) - } else if (t === 'light') activateLightMode() - else activateDarkMode() - ` - } else if (autoChangeMode === 2) { - darkmodeJs += ` - const now = new Date() - const hour = now.getHours() - const isNight = hour <= ${start} || hour >= ${end} - if (t === undefined) isNight ? activateDarkMode() : activateLightMode() - else if (t === 'light') activateLightMode() - else activateDarkMode() - ` - } else { - darkmodeJs += ` - if (t === 'dark') activateDarkMode() - else if (t === 'light') activateLightMode() - ` - } - - return darkmodeJs - } - - const createAsideStatus = () => { - if (!aside.enable || !aside.button) return '' - - return ` - const asideStatus = saveToLocal.get('aside-status') - if (asideStatus !== undefined) { - if (asideStatus === 'hide') { - document.documentElement.classList.add('hide-aside') - } else { - document.documentElement.classList.remove('hide-aside') - } - } - ` - } - - const createDetectApple = () => { - return ` - const detectApple = () => { - if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ - document.documentElement.classList.add('apple') - } - } - detectApple() - ` - } - - return `` -}) diff --git a/themes/butterfly/scripts/helpers/page.js b/themes/butterfly/scripts/helpers/page.js deleted file mode 100644 index 6c773e34..00000000 --- a/themes/butterfly/scripts/helpers/page.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict' - -const { stripHTML, escapeHTML, prettyUrls } = require('hexo-util') -const crypto = require('crypto') - -hexo.extend.helper.register('page_description', function () { - const { config, page } = this - let description = page.description || page.content || page.title || config.description - - if (description) { - description = escapeHTML(stripHTML(description).substring(0, 150) - .trim() - ).replace(/\n/g, ' ') - return description - } -}) - -hexo.extend.helper.register('cloudTags', function (options = {}) { - const env = this - let { source, minfontsize, maxfontsize, limit, unit, orderby, order } = options - unit = unit || 'px' - - let result = '' - if (limit > 0) { - source = source.limit(limit) - } - - const sizes = [] - source.sort('length').forEach(tag => { - const { length } = tag - if (sizes.includes(length)) return - sizes.push(length) - }) - - const length = sizes.length - 1 - source.sort(orderby, order).forEach(tag => { - const ratio = length ? sizes.indexOf(tag.length) / length : 0 - const size = minfontsize + ((maxfontsize - minfontsize) * ratio) - let style = `font-size: ${parseFloat(size.toFixed(2))}${unit};` - const color = 'rgb(' + Math.floor(Math.random() * 201) + ', ' + Math.floor(Math.random() * 201) + ', ' + Math.floor(Math.random() * 201) + ')' // 0,0,0 -> 200,200,200 - style += ` color: ${color}` - result += `${tag.name}` - }) - return result -}) - -hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex = false, trailingHtml = false) { - return prettyUrls(url || this.url, { trailing_index: trailingIndex, trailing_html: trailingHtml }) -}) - -hexo.extend.helper.register('md5', function (path) { - return crypto.createHash('md5').update(decodeURI(this.url_for(path))).digest('hex') -}) - -hexo.extend.helper.register('injectHtml', function (data) { - if (!data) return '' - return data.join('') -}) - -hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) { - if (page.year) { - const dateStr = page.month ? `${page.year}-${page.month}` : `${page.year}` - const dateFormat = page.month ? hexo.theme.config.aside.card_archives.format : 'YYYY' - return date(dateStr, dateFormat) - } - - const defaultTitle = this._p('page.archives') - if (!menu) return defaultTitle - - const loop = (m) => { - for (const key in m) { - if (typeof m[key] === 'object') { - loop(m[key]) - } - - if (/\/archives\//.test(m[key])) { - return key - } - } - } - - return loop(menu) || defaultTitle -}) - -hexo.extend.helper.register('isImgOrUrl', function (path) { - const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i - return path.indexOf('//') !== -1 || imgTestReg.test(path) -}) diff --git a/themes/butterfly/scripts/helpers/related_post.js b/themes/butterfly/scripts/helpers/related_post.js deleted file mode 100644 index 9a46f684..00000000 --- a/themes/butterfly/scripts/helpers/related_post.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Butterfly - * Related Posts - * According the tag - */ - -'use strict' - -hexo.extend.helper.register('related_posts', function (currentPost, allPosts) { - let relatedPosts = [] - currentPost.tags.forEach(function (tag) { - allPosts.forEach(function (post) { - if (isTagRelated(tag.name, post.tags)) { - const relatedPost = { - title: post.title, - path: post.path, - cover: post.cover, - cover_type: post.cover_type, - weight: 1, - updated: post.updated, - created: post.date - } - const index = findItem(relatedPosts, 'path', post.path) - if (index !== -1) { - relatedPosts[index].weight += 1 - } else { - if (currentPost.path !== post.path) { - relatedPosts.push(relatedPost) - } - } - } - }) - }) - if (relatedPosts.length === 0) { - return '' - } - let result = '' - const hexoConfig = hexo.config - const config = hexo.theme.config - - const limitNum = config.related_post.limit || 6 - const dateType = config.related_post.date_type || 'created' - const headlineLang = this._p('post.recommend') - - relatedPosts = relatedPosts.sort(compare('weight')) - - if (relatedPosts.length > 0) { - result += '