pyxllib 0.3.197__py3-none-any.whl → 0.3.200__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyxllib/__init__.py +21 -21
- pyxllib/algo/__init__.py +8 -8
- pyxllib/algo/disjoint.py +54 -54
- pyxllib/algo/geo.py +541 -541
- pyxllib/algo/intervals.py +964 -964
- pyxllib/algo/matcher.py +389 -389
- pyxllib/algo/newbie.py +166 -166
- pyxllib/algo/pupil.py +629 -629
- pyxllib/algo/shapelylib.py +67 -67
- pyxllib/algo/specialist.py +241 -241
- pyxllib/algo/stat.py +494 -494
- pyxllib/algo/treelib.py +149 -149
- pyxllib/algo/unitlib.py +66 -66
- pyxllib/autogui/__init__.py +5 -5
- pyxllib/autogui/activewin.py +246 -246
- pyxllib/autogui/all.py +9 -9
- pyxllib/autogui/autogui.py +852 -852
- pyxllib/autogui/uiautolib.py +362 -362
- pyxllib/autogui/virtualkey.py +102 -102
- pyxllib/autogui/wechat.py +827 -827
- pyxllib/autogui/wechat_msg.py +421 -421
- pyxllib/autogui/wxautolib.py +84 -84
- pyxllib/cv/__init__.py +5 -5
- pyxllib/cv/expert.py +267 -267
- pyxllib/cv/imfile.py +159 -159
- pyxllib/cv/imhash.py +39 -39
- pyxllib/cv/pupil.py +9 -9
- pyxllib/cv/rgbfmt.py +1525 -1525
- pyxllib/cv/slidercaptcha.py +137 -137
- pyxllib/cv/trackbartools.py +251 -251
- pyxllib/cv/xlcvlib.py +1040 -1040
- pyxllib/cv/xlpillib.py +423 -423
- pyxllib/data/echarts.py +240 -240
- pyxllib/data/jsonlib.py +89 -89
- pyxllib/data/oss.py +72 -72
- pyxllib/data/pglib.py +1127 -1127
- pyxllib/data/sqlite.py +568 -568
- pyxllib/data/sqllib.py +297 -297
- pyxllib/ext/JLineViewer.py +505 -505
- pyxllib/ext/__init__.py +6 -6
- pyxllib/ext/demolib.py +246 -246
- pyxllib/ext/drissionlib.py +277 -277
- pyxllib/ext/kq5034lib.py +12 -12
- pyxllib/ext/old.py +663 -663
- pyxllib/ext/qt.py +449 -449
- pyxllib/ext/robustprocfile.py +497 -497
- pyxllib/ext/seleniumlib.py +76 -76
- pyxllib/ext/tk.py +173 -173
- pyxllib/ext/unixlib.py +827 -827
- pyxllib/ext/utools.py +351 -351
- pyxllib/ext/webhook.py +124 -119
- pyxllib/ext/win32lib.py +40 -40
- pyxllib/ext/wjxlib.py +88 -88
- pyxllib/ext/wpsapi.py +124 -124
- pyxllib/ext/xlwork.py +9 -9
- pyxllib/ext/yuquelib.py +1105 -1105
- pyxllib/file/__init__.py +17 -17
- pyxllib/file/docxlib.py +761 -761
- pyxllib/file/gitlib.py +309 -309
- pyxllib/file/libreoffice.py +165 -165
- pyxllib/file/movielib.py +148 -148
- pyxllib/file/newbie.py +10 -10
- pyxllib/file/onenotelib.py +1469 -1469
- pyxllib/file/packlib/__init__.py +330 -330
- pyxllib/file/packlib/zipfile.py +2441 -2441
- pyxllib/file/pdflib.py +426 -426
- pyxllib/file/pupil.py +185 -185
- pyxllib/file/specialist/__init__.py +685 -685
- pyxllib/file/specialist/dirlib.py +799 -799
- pyxllib/file/specialist/download.py +193 -193
- pyxllib/file/specialist/filelib.py +2829 -2829
- pyxllib/file/xlsxlib.py +3131 -3131
- pyxllib/file/xlsyncfile.py +341 -341
- pyxllib/prog/__init__.py +5 -5
- pyxllib/prog/cachetools.py +64 -64
- pyxllib/prog/deprecatedlib.py +233 -233
- pyxllib/prog/filelock.py +42 -42
- pyxllib/prog/ipyexec.py +253 -253
- pyxllib/prog/multiprogs.py +940 -940
- pyxllib/prog/newbie.py +451 -451
- pyxllib/prog/pupil.py +1197 -1197
- pyxllib/prog/sitepackages.py +33 -33
- pyxllib/prog/specialist/__init__.py +391 -391
- pyxllib/prog/specialist/bc.py +203 -203
- pyxllib/prog/specialist/browser.py +497 -497
- pyxllib/prog/specialist/common.py +347 -347
- pyxllib/prog/specialist/datetime.py +198 -198
- pyxllib/prog/specialist/tictoc.py +240 -240
- pyxllib/prog/specialist/xllog.py +180 -180
- pyxllib/prog/xlosenv.py +108 -108
- pyxllib/stdlib/__init__.py +17 -17
- pyxllib/stdlib/tablepyxl/__init__.py +10 -10
- pyxllib/stdlib/tablepyxl/style.py +303 -303
- pyxllib/stdlib/tablepyxl/tablepyxl.py +130 -130
- pyxllib/text/__init__.py +8 -8
- pyxllib/text/ahocorasick.py +39 -39
- pyxllib/text/airscript.js +744 -744
- pyxllib/text/charclasslib.py +121 -121
- pyxllib/text/jiebalib.py +267 -267
- pyxllib/text/jinjalib.py +32 -32
- pyxllib/text/jsa_ai_prompt.md +271 -271
- pyxllib/text/jscode.py +922 -922
- pyxllib/text/latex/__init__.py +158 -158
- pyxllib/text/levenshtein.py +303 -303
- pyxllib/text/nestenv.py +1215 -1215
- pyxllib/text/newbie.py +300 -300
- pyxllib/text/pupil/__init__.py +8 -8
- pyxllib/text/pupil/common.py +1121 -1121
- pyxllib/text/pupil/xlalign.py +326 -326
- pyxllib/text/pycode.py +47 -47
- pyxllib/text/specialist/__init__.py +8 -8
- pyxllib/text/specialist/common.py +112 -112
- pyxllib/text/specialist/ptag.py +186 -186
- pyxllib/text/spellchecker.py +172 -172
- pyxllib/text/templates/echart_base.html +10 -10
- pyxllib/text/templates/highlight_code.html +16 -16
- pyxllib/text/templates/latex_editor.html +102 -102
- pyxllib/text/vbacode.py +17 -17
- pyxllib/text/xmllib.py +747 -747
- pyxllib/xl.py +42 -39
- pyxllib/xlcv.py +17 -17
- {pyxllib-0.3.197.dist-info → pyxllib-0.3.200.dist-info}/METADATA +1 -1
- pyxllib-0.3.200.dist-info/RECORD +126 -0
- {pyxllib-0.3.197.dist-info → pyxllib-0.3.200.dist-info}/licenses/LICENSE +190 -190
- pyxllib-0.3.197.dist-info/RECORD +0 -126
- {pyxllib-0.3.197.dist-info → pyxllib-0.3.200.dist-info}/WHEEL +0 -0
pyxllib/ext/qt.py
CHANGED
@@ -1,449 +1,449 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
# @Author : 陈坤泽
|
4
|
-
# @Email : 877362867@qq.com
|
5
|
-
# @Date : 2021/05/26 17:24
|
6
|
-
|
7
|
-
import os.path as osp
|
8
|
-
import sys
|
9
|
-
import time
|
10
|
-
from datetime import datetime, timedelta
|
11
|
-
|
12
|
-
from PyQt5 import QtWidgets
|
13
|
-
from PyQt5.QtCore import QTimer, Qt, QThread, pyqtSignal, QEventLoop
|
14
|
-
from PyQt5.QtWidgets import QApplication, QMainWindow, QFrame, QInputDialog, QMessageBox, QVBoxLayout, \
|
15
|
-
QTextEdit, QSizePolicy, QLabel, QProgressBar, QDialog
|
16
|
-
from PyQt5.QtGui import QTextOption
|
17
|
-
|
18
|
-
from pyxllib.prog.newbie import CvtType
|
19
|
-
|
20
|
-
here = osp.dirname(osp.abspath(__file__))
|
21
|
-
|
22
|
-
|
23
|
-
class QHLine(QFrame):
|
24
|
-
""" https://stackoverflow.com/questions/5671354/how-to-programmatically-make-a-horizontal-line-in-qt """
|
25
|
-
|
26
|
-
def __init__(self):
|
27
|
-
super(QHLine, self).__init__()
|
28
|
-
self.setFrameShape(QFrame.HLine)
|
29
|
-
self.setFrameShadow(QFrame.Sunken)
|
30
|
-
|
31
|
-
|
32
|
-
class QVLine(QFrame):
|
33
|
-
def __init__(self):
|
34
|
-
super(QVLine, self).__init__()
|
35
|
-
self.setFrameShape(QFrame.VLine)
|
36
|
-
self.setFrameShadow(QFrame.Sunken)
|
37
|
-
|
38
|
-
|
39
|
-
class XlLineEdit(QtWidgets.QLineEdit):
|
40
|
-
correctChanged = pyqtSignal(object) # 符合数值类型的修改
|
41
|
-
wrongChanged = pyqtSignal(str) # 不符合数值类型的修改
|
42
|
-
|
43
|
-
def __init__(self, text=None, parent=None, *, valcvt=None):
|
44
|
-
"""
|
45
|
-
:param valcvt: 数值类型转换器
|
46
|
-
"""
|
47
|
-
super().__init__(str(text), parent)
|
48
|
-
|
49
|
-
def check():
|
50
|
-
# TODO 目前是强制重置样式,可以考虑怎么保留原样式基础上修改属性值
|
51
|
-
s = self.text()
|
52
|
-
try:
|
53
|
-
if valcvt:
|
54
|
-
s = valcvt(s)
|
55
|
-
self.setStyleSheet('')
|
56
|
-
self.setToolTip('')
|
57
|
-
self.correctChanged.emit(s)
|
58
|
-
except ValueError:
|
59
|
-
self.setStyleSheet('background-color: lightpink;')
|
60
|
-
self.setToolTip(f'输入数据不是{valcvt}类型')
|
61
|
-
self.wrongChanged.emit(s)
|
62
|
-
|
63
|
-
self.textChanged.connect(check)
|
64
|
-
if text:
|
65
|
-
check()
|
66
|
-
|
67
|
-
# self.setStyleSheet(self.styleSheet() + 'qproperty-cursorPosition: 0;')
|
68
|
-
self.setStyleSheet(self.styleSheet())
|
69
|
-
|
70
|
-
|
71
|
-
class XlComboBox(QtWidgets.QComboBox):
|
72
|
-
# 这个控件一般没有类型检查,但在支持填入自定义值时,是存在类型错误问题的
|
73
|
-
# 但在工程上还是依然写了 correctChanged,方便下游任务统一接口
|
74
|
-
correctChanged = pyqtSignal(object) # 符合数值类型的修改
|
75
|
-
wrongChanged = pyqtSignal(str) # 不符合数值类型的修改
|
76
|
-
|
77
|
-
def __init__(self, parent=None, *, text=None, items=None, valcvt=None, editable=False):
|
78
|
-
"""
|
79
|
-
"""
|
80
|
-
# 1 基础设置
|
81
|
-
super().__init__(parent)
|
82
|
-
self.reset_items(items)
|
83
|
-
self.editable = editable
|
84
|
-
if self.editable:
|
85
|
-
self.setEditable(True)
|
86
|
-
|
87
|
-
# 2 检查功能
|
88
|
-
def check(s):
|
89
|
-
try:
|
90
|
-
if valcvt:
|
91
|
-
s = valcvt(s)
|
92
|
-
|
93
|
-
if self.editable: # 支持自定义值
|
94
|
-
self.setStyleSheet('')
|
95
|
-
self.setToolTip('')
|
96
|
-
self.correctChanged.emit(s)
|
97
|
-
elif s not in self.items_set: # 不支持自定义值,但出现自定义值
|
98
|
-
self.setStyleSheet('background-color: yellow;')
|
99
|
-
self.setToolTip(f'不在清单里的非法值')
|
100
|
-
self.wrongChanged.emit(s)
|
101
|
-
else: # 不支持自定义值,且目前值在清单中
|
102
|
-
self.setEditable(False)
|
103
|
-
self.setStyleSheet('')
|
104
|
-
self.setToolTip('')
|
105
|
-
self.correctChanged.emit(s)
|
106
|
-
except ValueError:
|
107
|
-
self.setStyleSheet('background-color: lightpink;')
|
108
|
-
self.setToolTip(f'输入数据不是{valcvt}类型')
|
109
|
-
self.wrongChanged.emit(s)
|
110
|
-
|
111
|
-
self.currentTextChanged.connect(check)
|
112
|
-
# self.wrongChanged.connect(lambda s: print('非法值:', s)) # 可以监控非法值
|
113
|
-
|
114
|
-
# 3 是否有预设值
|
115
|
-
if text:
|
116
|
-
self.setText(text)
|
117
|
-
|
118
|
-
# 4 补充格式
|
119
|
-
# self.setStyleSheet(self.styleSheet() + 'qproperty-cursorPosition: 0;')
|
120
|
-
self.setStyleSheet(self.styleSheet())
|
121
|
-
|
122
|
-
def setText(self, text):
|
123
|
-
text = str(text)
|
124
|
-
if text not in self.items and not self.editable:
|
125
|
-
# 虽然不支持editable,但是出现了意外值,需要强制升级为可编辑
|
126
|
-
self.setEditable(True)
|
127
|
-
self.setCurrentText(text)
|
128
|
-
|
129
|
-
def reset_items(self, items):
|
130
|
-
# 1 存储配置清单
|
131
|
-
self.clear()
|
132
|
-
self.raw_items = items # noqa
|
133
|
-
self.items = [str(x) for x in items if x is not None] # noqa
|
134
|
-
self.items_set = set(self.items) # noqa 便于判断是否存在的集合类型
|
135
|
-
self.addItems(self.items)
|
136
|
-
|
137
|
-
# 2 画出 元素值、分隔符
|
138
|
-
cnt = 0
|
139
|
-
for i in range(len(items)):
|
140
|
-
if items[i] is None:
|
141
|
-
self.insertSeparator(i - cnt) # 不过这个分割符没那么显眼
|
142
|
-
cnt += 1
|
143
|
-
|
144
|
-
|
145
|
-
def get_input_widget(items=None, cur_value=None, *, parent=None, valcvt=None,
|
146
|
-
n_widget=1, enabled=True,
|
147
|
-
correct_changed=None):
|
148
|
-
""" 根据items参数情况,智能判断生成对应的widget
|
149
|
-
|
150
|
-
:param items:
|
151
|
-
None, 普通的文本编辑框
|
152
|
-
普通数组,下拉框 (可以用None元素表示分隔符)
|
153
|
-
list,除了列表中枚举值,也支持自定义输入其他值
|
154
|
-
tuple,只能用列表中的枚举值
|
155
|
-
多级嵌套数组,多级下拉框 (未实装) (一般都是不可改的,并且同类型的数据)
|
156
|
-
[('福建', [('龙岩', ['连城', '长汀', ...], ...)]), ('北京', ...)]
|
157
|
-
这种情况会返回多个widget
|
158
|
-
:param cur_value: 当前显示的文本值
|
159
|
-
:param valcvt: 数值类型转换函数,非法时抛出ValueError
|
160
|
-
很多输入框是传入文本,有时需要转为int、float、list等类型
|
161
|
-
支持输入常见类型转换的字符串名称,比如int、float
|
162
|
-
:param correct_changed: 文本改变时的回调函数,一般用于数值有效性检查
|
163
|
-
:param n_widget: 配合items为嵌套数组使用,需要指定嵌套层数
|
164
|
-
此时cur_value、cvt、enabled、text_changed等系列值可以传入n_widget长度的list
|
165
|
-
:param enabled: 是否可编辑
|
166
|
-
"""
|
167
|
-
if n_widget > 1:
|
168
|
-
raise NotImplementedError
|
169
|
-
|
170
|
-
# 1 封装类型检查功能
|
171
|
-
if isinstance(valcvt, str):
|
172
|
-
cvtfunc = CvtType.factory(valcvt)
|
173
|
-
else:
|
174
|
-
cvtfunc = valcvt
|
175
|
-
|
176
|
-
# 2 正式生成控件
|
177
|
-
if isinstance(items, (list, tuple)):
|
178
|
-
# 带有 items 的字段支持候选下拉菜单
|
179
|
-
w = XlComboBox(parent, text=cur_value, items=items, valcvt=cvtfunc, editable=isinstance(items, list))
|
180
|
-
elif items is None:
|
181
|
-
# 普通填充框
|
182
|
-
w = XlLineEdit(cur_value, parent=parent, valcvt=cvtfunc)
|
183
|
-
else:
|
184
|
-
raise ValueError(f'{type(items)}')
|
185
|
-
|
186
|
-
# 3 通用配置
|
187
|
-
if callable(correct_changed):
|
188
|
-
w.correctChanged.connect(correct_changed)
|
189
|
-
if not enabled:
|
190
|
-
w.setEnabled(enabled)
|
191
|
-
|
192
|
-
return w
|
193
|
-
|
194
|
-
|
195
|
-
def __other():
|
196
|
-
pass
|
197
|
-
|
198
|
-
|
199
|
-
def main_qapp(window):
|
200
|
-
""" 执行Qt应用 """
|
201
|
-
app = QApplication(sys.argv)
|
202
|
-
window.show() # 展示窗口
|
203
|
-
sys.exit(app.exec_())
|
204
|
-
|
205
|
-
|
206
|
-
def qt_clipboard_monitor(func=None, verbose=1, *, cooldown=0.5):
|
207
|
-
""" qt实现的剪切板监控器
|
208
|
-
|
209
|
-
:param cooldown: cd,冷切时间,防止短时间内因为重复操作响应剪切板,重复执行功能
|
210
|
-
|
211
|
-
感觉这个组件还有很多可以扩展的,比如设置可以退出的快捷键
|
212
|
-
"""
|
213
|
-
import pyperclip
|
214
|
-
|
215
|
-
last_response = time.time()
|
216
|
-
|
217
|
-
if func is None:
|
218
|
-
func = lambda s: s
|
219
|
-
|
220
|
-
def on_clipboard_change():
|
221
|
-
# 1 数据内容一样则跳过不处理,表示很可能是该函数调用pyperclip.copy(s)产生的重复响应
|
222
|
-
nonlocal last_response
|
223
|
-
s0 = pyperclip.paste()
|
224
|
-
s0 = s0.replace('\r\n', '\n')
|
225
|
-
|
226
|
-
cur_time = time.time()
|
227
|
-
|
228
|
-
if cur_time - last_response < cooldown:
|
229
|
-
return
|
230
|
-
last_response = cur_time
|
231
|
-
|
232
|
-
# 2 处理函数
|
233
|
-
s1 = func(s0)
|
234
|
-
if s1 != s0:
|
235
|
-
if verbose:
|
236
|
-
print('【处理前】', time.strftime('%H:%M:%S'))
|
237
|
-
print(s0)
|
238
|
-
print('【处理后】')
|
239
|
-
print(s1)
|
240
|
-
print()
|
241
|
-
pyperclip.copy(s1)
|
242
|
-
|
243
|
-
app = QApplication([])
|
244
|
-
clipboard = app.clipboard()
|
245
|
-
clipboard.dataChanged.connect(on_clipboard_change)
|
246
|
-
app.exec_()
|
247
|
-
|
248
|
-
|
249
|
-
class XlThreadWorker(QThread):
|
250
|
-
result = pyqtSignal(object) # 运行结果信号
|
251
|
-
error = pyqtSignal(Exception) # 错误信号
|
252
|
-
progress = pyqtSignal(int) # 进度信号
|
253
|
-
|
254
|
-
def __init__(self, func, *args, use_progress=False, **kwargs):
|
255
|
-
super().__init__()
|
256
|
-
self.func = func
|
257
|
-
self.args = args
|
258
|
-
self.kwargs = kwargs
|
259
|
-
if use_progress:
|
260
|
-
self.kwargs['progress_callback'] = lambda v: self.progress.emit(v)
|
261
|
-
|
262
|
-
def run(self):
|
263
|
-
try:
|
264
|
-
result = self.func(*self.args, **self.kwargs)
|
265
|
-
self.result.emit(result) # Emit the result when done
|
266
|
-
except Exception as e:
|
267
|
-
self.error.emit(e)
|
268
|
-
|
269
|
-
|
270
|
-
class WaitDialog(QDialog):
|
271
|
-
|
272
|
-
def __init__(self, parent=None, text='', title='正在执行任务...', delay_seconds=5):
|
273
|
-
super().__init__(parent)
|
274
|
-
self.base_text = text
|
275
|
-
self.setWindowTitle(title)
|
276
|
-
|
277
|
-
self.timer = QTimer()
|
278
|
-
self.timer.timeout.connect(self.update_text)
|
279
|
-
self.start_time = None
|
280
|
-
self.result = None
|
281
|
-
self.worker = None
|
282
|
-
self.error = None
|
283
|
-
|
284
|
-
self.delay_milliseconds = delay_seconds * 1000 # 延迟弹窗
|
285
|
-
self.is_running = False
|
286
|
-
|
287
|
-
self.layout = QVBoxLayout() # 布局
|
288
|
-
self.label = QLabel(self.base_text) # 标签
|
289
|
-
self.layout.addWidget(self.label)
|
290
|
-
self.pbar = QProgressBar() # 进度条
|
291
|
-
self.layout.addWidget(self.pbar)
|
292
|
-
self.setLayout(self.layout)
|
293
|
-
|
294
|
-
def handle_result(self, result):
|
295
|
-
self.result = result
|
296
|
-
self.is_running = False
|
297
|
-
|
298
|
-
def handle_error(self, error):
|
299
|
-
self.error = error
|
300
|
-
self.label.setText(f"{self.base_text}\n运行出现错误: {error}")
|
301
|
-
self.is_running = False
|
302
|
-
|
303
|
-
def handle_progress(self, progress):
|
304
|
-
self.pbar.setValue(progress)
|
305
|
-
|
306
|
-
def update_text(self):
|
307
|
-
elapsed_time = int((datetime.now() - self.start_time).total_seconds()) + self.delay_milliseconds // 1000
|
308
|
-
self.label.setText(f"{self.base_text}\n已运行 {elapsed_time} 秒")
|
309
|
-
|
310
|
-
def run(self, func, *args, **kwargs):
|
311
|
-
"""
|
312
|
-
def func():
|
313
|
-
... # 程序功能
|
314
|
-
|
315
|
-
msg = WaitDialog().run(func) # 开一个等待窗口等程序运行
|
316
|
-
"""
|
317
|
-
self.worker = XlThreadWorker(func, *args, **kwargs)
|
318
|
-
self.worker.result.connect(self.handle_result)
|
319
|
-
self.worker.error.connect(self.handle_error)
|
320
|
-
self.is_running = True
|
321
|
-
self.worker.start()
|
322
|
-
|
323
|
-
QTimer.singleShot(self.delay_milliseconds, self.check_and_show)
|
324
|
-
|
325
|
-
# 阻塞主线程,直到子线程完成
|
326
|
-
while self.is_running:
|
327
|
-
QApplication.processEvents() # 刷新UI,保持其响应性
|
328
|
-
time.sleep(0.1) # 等待一段时间,以减少CPU使用率
|
329
|
-
|
330
|
-
self.timer.stop()
|
331
|
-
self.accept()
|
332
|
-
|
333
|
-
return self.result
|
334
|
-
|
335
|
-
def run_with_progress(self, func, *args, **kwargs):
|
336
|
-
"""
|
337
|
-
def func(progress_callback):
|
338
|
-
progress_callback(50) # 可以在运行中设置进度,进度值为0~100
|
339
|
-
... # 其他功能
|
340
|
-
|
341
|
-
msg = WaitDialog().run_with_progress(func) # 运行完获得返回值
|
342
|
-
"""
|
343
|
-
self.worker = XlThreadWorker(func, *args, use_progress=True, **kwargs)
|
344
|
-
self.worker.result.connect(self.handle_result)
|
345
|
-
self.worker.error.connect(self.handle_error)
|
346
|
-
self.worker.progress.connect(self.handle_progress)
|
347
|
-
self.is_running = True
|
348
|
-
self.worker.start()
|
349
|
-
|
350
|
-
QTimer.singleShot(self.delay_milliseconds, self.check_and_show)
|
351
|
-
|
352
|
-
# 阻塞主线程,直到子线程完成
|
353
|
-
while self.is_running:
|
354
|
-
QApplication.processEvents() # 刷新UI,保持其响应性
|
355
|
-
time.sleep(0.1) # 等待一段时间,以减少CPU使用率
|
356
|
-
|
357
|
-
self.timer.stop()
|
358
|
-
self.accept()
|
359
|
-
|
360
|
-
return self.result
|
361
|
-
|
362
|
-
def start_timer(self):
|
363
|
-
self.start_time = datetime.now()
|
364
|
-
self.timer.start(1000)
|
365
|
-
|
366
|
-
def check_and_show(self):
|
367
|
-
if self.is_running:
|
368
|
-
self.show()
|
369
|
-
self.start_timer()
|
370
|
-
|
371
|
-
def __enter__(self):
|
372
|
-
""" with写法比较简洁,但不太推荐这种使用方法,这样并不工程化
|
373
|
-
这样会把要运行的功能变成主线程,这个提示窗口会被挂起
|
374
|
-
|
375
|
-
这里功能设计上也比较简单些,不考虑写的很完善强大了。
|
376
|
-
"""
|
377
|
-
self.show()
|
378
|
-
QApplication.processEvents()
|
379
|
-
return self
|
380
|
-
|
381
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
382
|
-
self.accept()
|
383
|
-
|
384
|
-
|
385
|
-
class CustomMessageBox(QMessageBox):
|
386
|
-
def __init__(self, icon, title, text, copyable):
|
387
|
-
super().__init__(icon, title, "")
|
388
|
-
self.init_ui(title, text, copyable)
|
389
|
-
|
390
|
-
def init_ui(self, title, text, copyable=False):
|
391
|
-
layout = QVBoxLayout()
|
392
|
-
|
393
|
-
if copyable:
|
394
|
-
widget = QTextEdit()
|
395
|
-
widget.setText(text)
|
396
|
-
widget.setReadOnly(True)
|
397
|
-
widget.setWordWrapMode(QTextOption.WrapAnywhere)
|
398
|
-
widget.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
399
|
-
widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
400
|
-
widget.document().documentLayout().documentSizeChanged.connect(
|
401
|
-
lambda: widget.setMinimumHeight(min(widget.document().size().height(), 700))
|
402
|
-
)
|
403
|
-
widget.setMinimumHeight(100)
|
404
|
-
widget.setMaximumHeight(700)
|
405
|
-
else:
|
406
|
-
widget = QLabel()
|
407
|
-
widget.setText(text)
|
408
|
-
widget.setWordWrap(True)
|
409
|
-
|
410
|
-
min_width = max(len(title) * 15, 600)
|
411
|
-
widget.setMinimumWidth(min_width)
|
412
|
-
|
413
|
-
layout.addWidget(widget)
|
414
|
-
self.layout().addLayout(layout, 1, 1)
|
415
|
-
|
416
|
-
|
417
|
-
def show_message_box(text, title=None, icon=None, detail=None,
|
418
|
-
buttons=QMessageBox.Ok | QMessageBox.Cancel,
|
419
|
-
default_button=QMessageBox.Ok, copyable=False):
|
420
|
-
""" 显示一个提示框
|
421
|
-
|
422
|
-
:param text: 提示框的文本内容
|
423
|
-
:param title: 提示框的标题,默认值为 "提示"
|
424
|
-
:param icon: 提示框的图标,默认值为 QMessageBox.NoIcon
|
425
|
-
注意Information、Warning、Critical等都会附带一个提示音
|
426
|
-
而Question是不带提示音的,默认的NoIcon也是不带提示音的
|
427
|
-
:param detail: 提示框的详细信息,默认值为 None
|
428
|
-
:param buttons: 提示框的按钮,默认值为 QMessageBox.Ok
|
429
|
-
:param copyable: 消息窗中的文本是否可复制
|
430
|
-
|
431
|
-
:return: 选择的按钮
|
432
|
-
|
433
|
-
实现上,本来应该依据setMinimumWidth可以搞定的事,但不知道为什么就是会有bug问题,总之最后问gpt靠实现一个类来解决了
|
434
|
-
|
435
|
-
"""
|
436
|
-
if title is None:
|
437
|
-
title = "提示"
|
438
|
-
|
439
|
-
if icon is None:
|
440
|
-
icon = QMessageBox.NoIcon
|
441
|
-
|
442
|
-
msg_box = CustomMessageBox(icon, title, text, copyable)
|
443
|
-
|
444
|
-
if detail is not None:
|
445
|
-
msg_box.setDetailedText(detail)
|
446
|
-
msg_box.setStandardButtons(buttons)
|
447
|
-
msg_box.setDefaultButton(default_button)
|
448
|
-
|
449
|
-
return msg_box.exec_()
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# @Author : 陈坤泽
|
4
|
+
# @Email : 877362867@qq.com
|
5
|
+
# @Date : 2021/05/26 17:24
|
6
|
+
|
7
|
+
import os.path as osp
|
8
|
+
import sys
|
9
|
+
import time
|
10
|
+
from datetime import datetime, timedelta
|
11
|
+
|
12
|
+
from PyQt5 import QtWidgets
|
13
|
+
from PyQt5.QtCore import QTimer, Qt, QThread, pyqtSignal, QEventLoop
|
14
|
+
from PyQt5.QtWidgets import QApplication, QMainWindow, QFrame, QInputDialog, QMessageBox, QVBoxLayout, \
|
15
|
+
QTextEdit, QSizePolicy, QLabel, QProgressBar, QDialog
|
16
|
+
from PyQt5.QtGui import QTextOption
|
17
|
+
|
18
|
+
from pyxllib.prog.newbie import CvtType
|
19
|
+
|
20
|
+
here = osp.dirname(osp.abspath(__file__))
|
21
|
+
|
22
|
+
|
23
|
+
class QHLine(QFrame):
|
24
|
+
""" https://stackoverflow.com/questions/5671354/how-to-programmatically-make-a-horizontal-line-in-qt """
|
25
|
+
|
26
|
+
def __init__(self):
|
27
|
+
super(QHLine, self).__init__()
|
28
|
+
self.setFrameShape(QFrame.HLine)
|
29
|
+
self.setFrameShadow(QFrame.Sunken)
|
30
|
+
|
31
|
+
|
32
|
+
class QVLine(QFrame):
|
33
|
+
def __init__(self):
|
34
|
+
super(QVLine, self).__init__()
|
35
|
+
self.setFrameShape(QFrame.VLine)
|
36
|
+
self.setFrameShadow(QFrame.Sunken)
|
37
|
+
|
38
|
+
|
39
|
+
class XlLineEdit(QtWidgets.QLineEdit):
|
40
|
+
correctChanged = pyqtSignal(object) # 符合数值类型的修改
|
41
|
+
wrongChanged = pyqtSignal(str) # 不符合数值类型的修改
|
42
|
+
|
43
|
+
def __init__(self, text=None, parent=None, *, valcvt=None):
|
44
|
+
"""
|
45
|
+
:param valcvt: 数值类型转换器
|
46
|
+
"""
|
47
|
+
super().__init__(str(text), parent)
|
48
|
+
|
49
|
+
def check():
|
50
|
+
# TODO 目前是强制重置样式,可以考虑怎么保留原样式基础上修改属性值
|
51
|
+
s = self.text()
|
52
|
+
try:
|
53
|
+
if valcvt:
|
54
|
+
s = valcvt(s)
|
55
|
+
self.setStyleSheet('')
|
56
|
+
self.setToolTip('')
|
57
|
+
self.correctChanged.emit(s)
|
58
|
+
except ValueError:
|
59
|
+
self.setStyleSheet('background-color: lightpink;')
|
60
|
+
self.setToolTip(f'输入数据不是{valcvt}类型')
|
61
|
+
self.wrongChanged.emit(s)
|
62
|
+
|
63
|
+
self.textChanged.connect(check)
|
64
|
+
if text:
|
65
|
+
check()
|
66
|
+
|
67
|
+
# self.setStyleSheet(self.styleSheet() + 'qproperty-cursorPosition: 0;')
|
68
|
+
self.setStyleSheet(self.styleSheet())
|
69
|
+
|
70
|
+
|
71
|
+
class XlComboBox(QtWidgets.QComboBox):
|
72
|
+
# 这个控件一般没有类型检查,但在支持填入自定义值时,是存在类型错误问题的
|
73
|
+
# 但在工程上还是依然写了 correctChanged,方便下游任务统一接口
|
74
|
+
correctChanged = pyqtSignal(object) # 符合数值类型的修改
|
75
|
+
wrongChanged = pyqtSignal(str) # 不符合数值类型的修改
|
76
|
+
|
77
|
+
def __init__(self, parent=None, *, text=None, items=None, valcvt=None, editable=False):
|
78
|
+
"""
|
79
|
+
"""
|
80
|
+
# 1 基础设置
|
81
|
+
super().__init__(parent)
|
82
|
+
self.reset_items(items)
|
83
|
+
self.editable = editable
|
84
|
+
if self.editable:
|
85
|
+
self.setEditable(True)
|
86
|
+
|
87
|
+
# 2 检查功能
|
88
|
+
def check(s):
|
89
|
+
try:
|
90
|
+
if valcvt:
|
91
|
+
s = valcvt(s)
|
92
|
+
|
93
|
+
if self.editable: # 支持自定义值
|
94
|
+
self.setStyleSheet('')
|
95
|
+
self.setToolTip('')
|
96
|
+
self.correctChanged.emit(s)
|
97
|
+
elif s not in self.items_set: # 不支持自定义值,但出现自定义值
|
98
|
+
self.setStyleSheet('background-color: yellow;')
|
99
|
+
self.setToolTip(f'不在清单里的非法值')
|
100
|
+
self.wrongChanged.emit(s)
|
101
|
+
else: # 不支持自定义值,且目前值在清单中
|
102
|
+
self.setEditable(False)
|
103
|
+
self.setStyleSheet('')
|
104
|
+
self.setToolTip('')
|
105
|
+
self.correctChanged.emit(s)
|
106
|
+
except ValueError:
|
107
|
+
self.setStyleSheet('background-color: lightpink;')
|
108
|
+
self.setToolTip(f'输入数据不是{valcvt}类型')
|
109
|
+
self.wrongChanged.emit(s)
|
110
|
+
|
111
|
+
self.currentTextChanged.connect(check)
|
112
|
+
# self.wrongChanged.connect(lambda s: print('非法值:', s)) # 可以监控非法值
|
113
|
+
|
114
|
+
# 3 是否有预设值
|
115
|
+
if text:
|
116
|
+
self.setText(text)
|
117
|
+
|
118
|
+
# 4 补充格式
|
119
|
+
# self.setStyleSheet(self.styleSheet() + 'qproperty-cursorPosition: 0;')
|
120
|
+
self.setStyleSheet(self.styleSheet())
|
121
|
+
|
122
|
+
def setText(self, text):
|
123
|
+
text = str(text)
|
124
|
+
if text not in self.items and not self.editable:
|
125
|
+
# 虽然不支持editable,但是出现了意外值,需要强制升级为可编辑
|
126
|
+
self.setEditable(True)
|
127
|
+
self.setCurrentText(text)
|
128
|
+
|
129
|
+
def reset_items(self, items):
|
130
|
+
# 1 存储配置清单
|
131
|
+
self.clear()
|
132
|
+
self.raw_items = items # noqa
|
133
|
+
self.items = [str(x) for x in items if x is not None] # noqa
|
134
|
+
self.items_set = set(self.items) # noqa 便于判断是否存在的集合类型
|
135
|
+
self.addItems(self.items)
|
136
|
+
|
137
|
+
# 2 画出 元素值、分隔符
|
138
|
+
cnt = 0
|
139
|
+
for i in range(len(items)):
|
140
|
+
if items[i] is None:
|
141
|
+
self.insertSeparator(i - cnt) # 不过这个分割符没那么显眼
|
142
|
+
cnt += 1
|
143
|
+
|
144
|
+
|
145
|
+
def get_input_widget(items=None, cur_value=None, *, parent=None, valcvt=None,
|
146
|
+
n_widget=1, enabled=True,
|
147
|
+
correct_changed=None):
|
148
|
+
""" 根据items参数情况,智能判断生成对应的widget
|
149
|
+
|
150
|
+
:param items:
|
151
|
+
None, 普通的文本编辑框
|
152
|
+
普通数组,下拉框 (可以用None元素表示分隔符)
|
153
|
+
list,除了列表中枚举值,也支持自定义输入其他值
|
154
|
+
tuple,只能用列表中的枚举值
|
155
|
+
多级嵌套数组,多级下拉框 (未实装) (一般都是不可改的,并且同类型的数据)
|
156
|
+
[('福建', [('龙岩', ['连城', '长汀', ...], ...)]), ('北京', ...)]
|
157
|
+
这种情况会返回多个widget
|
158
|
+
:param cur_value: 当前显示的文本值
|
159
|
+
:param valcvt: 数值类型转换函数,非法时抛出ValueError
|
160
|
+
很多输入框是传入文本,有时需要转为int、float、list等类型
|
161
|
+
支持输入常见类型转换的字符串名称,比如int、float
|
162
|
+
:param correct_changed: 文本改变时的回调函数,一般用于数值有效性检查
|
163
|
+
:param n_widget: 配合items为嵌套数组使用,需要指定嵌套层数
|
164
|
+
此时cur_value、cvt、enabled、text_changed等系列值可以传入n_widget长度的list
|
165
|
+
:param enabled: 是否可编辑
|
166
|
+
"""
|
167
|
+
if n_widget > 1:
|
168
|
+
raise NotImplementedError
|
169
|
+
|
170
|
+
# 1 封装类型检查功能
|
171
|
+
if isinstance(valcvt, str):
|
172
|
+
cvtfunc = CvtType.factory(valcvt)
|
173
|
+
else:
|
174
|
+
cvtfunc = valcvt
|
175
|
+
|
176
|
+
# 2 正式生成控件
|
177
|
+
if isinstance(items, (list, tuple)):
|
178
|
+
# 带有 items 的字段支持候选下拉菜单
|
179
|
+
w = XlComboBox(parent, text=cur_value, items=items, valcvt=cvtfunc, editable=isinstance(items, list))
|
180
|
+
elif items is None:
|
181
|
+
# 普通填充框
|
182
|
+
w = XlLineEdit(cur_value, parent=parent, valcvt=cvtfunc)
|
183
|
+
else:
|
184
|
+
raise ValueError(f'{type(items)}')
|
185
|
+
|
186
|
+
# 3 通用配置
|
187
|
+
if callable(correct_changed):
|
188
|
+
w.correctChanged.connect(correct_changed)
|
189
|
+
if not enabled:
|
190
|
+
w.setEnabled(enabled)
|
191
|
+
|
192
|
+
return w
|
193
|
+
|
194
|
+
|
195
|
+
def __other():
|
196
|
+
pass
|
197
|
+
|
198
|
+
|
199
|
+
def main_qapp(window):
|
200
|
+
""" 执行Qt应用 """
|
201
|
+
app = QApplication(sys.argv)
|
202
|
+
window.show() # 展示窗口
|
203
|
+
sys.exit(app.exec_())
|
204
|
+
|
205
|
+
|
206
|
+
def qt_clipboard_monitor(func=None, verbose=1, *, cooldown=0.5):
|
207
|
+
""" qt实现的剪切板监控器
|
208
|
+
|
209
|
+
:param cooldown: cd,冷切时间,防止短时间内因为重复操作响应剪切板,重复执行功能
|
210
|
+
|
211
|
+
感觉这个组件还有很多可以扩展的,比如设置可以退出的快捷键
|
212
|
+
"""
|
213
|
+
import pyperclip
|
214
|
+
|
215
|
+
last_response = time.time()
|
216
|
+
|
217
|
+
if func is None:
|
218
|
+
func = lambda s: s
|
219
|
+
|
220
|
+
def on_clipboard_change():
|
221
|
+
# 1 数据内容一样则跳过不处理,表示很可能是该函数调用pyperclip.copy(s)产生的重复响应
|
222
|
+
nonlocal last_response
|
223
|
+
s0 = pyperclip.paste()
|
224
|
+
s0 = s0.replace('\r\n', '\n')
|
225
|
+
|
226
|
+
cur_time = time.time()
|
227
|
+
|
228
|
+
if cur_time - last_response < cooldown:
|
229
|
+
return
|
230
|
+
last_response = cur_time
|
231
|
+
|
232
|
+
# 2 处理函数
|
233
|
+
s1 = func(s0)
|
234
|
+
if s1 != s0:
|
235
|
+
if verbose:
|
236
|
+
print('【处理前】', time.strftime('%H:%M:%S'))
|
237
|
+
print(s0)
|
238
|
+
print('【处理后】')
|
239
|
+
print(s1)
|
240
|
+
print()
|
241
|
+
pyperclip.copy(s1)
|
242
|
+
|
243
|
+
app = QApplication([])
|
244
|
+
clipboard = app.clipboard()
|
245
|
+
clipboard.dataChanged.connect(on_clipboard_change)
|
246
|
+
app.exec_()
|
247
|
+
|
248
|
+
|
249
|
+
class XlThreadWorker(QThread):
|
250
|
+
result = pyqtSignal(object) # 运行结果信号
|
251
|
+
error = pyqtSignal(Exception) # 错误信号
|
252
|
+
progress = pyqtSignal(int) # 进度信号
|
253
|
+
|
254
|
+
def __init__(self, func, *args, use_progress=False, **kwargs):
|
255
|
+
super().__init__()
|
256
|
+
self.func = func
|
257
|
+
self.args = args
|
258
|
+
self.kwargs = kwargs
|
259
|
+
if use_progress:
|
260
|
+
self.kwargs['progress_callback'] = lambda v: self.progress.emit(v)
|
261
|
+
|
262
|
+
def run(self):
|
263
|
+
try:
|
264
|
+
result = self.func(*self.args, **self.kwargs)
|
265
|
+
self.result.emit(result) # Emit the result when done
|
266
|
+
except Exception as e:
|
267
|
+
self.error.emit(e)
|
268
|
+
|
269
|
+
|
270
|
+
class WaitDialog(QDialog):
|
271
|
+
|
272
|
+
def __init__(self, parent=None, text='', title='正在执行任务...', delay_seconds=5):
|
273
|
+
super().__init__(parent)
|
274
|
+
self.base_text = text
|
275
|
+
self.setWindowTitle(title)
|
276
|
+
|
277
|
+
self.timer = QTimer()
|
278
|
+
self.timer.timeout.connect(self.update_text)
|
279
|
+
self.start_time = None
|
280
|
+
self.result = None
|
281
|
+
self.worker = None
|
282
|
+
self.error = None
|
283
|
+
|
284
|
+
self.delay_milliseconds = delay_seconds * 1000 # 延迟弹窗
|
285
|
+
self.is_running = False
|
286
|
+
|
287
|
+
self.layout = QVBoxLayout() # 布局
|
288
|
+
self.label = QLabel(self.base_text) # 标签
|
289
|
+
self.layout.addWidget(self.label)
|
290
|
+
self.pbar = QProgressBar() # 进度条
|
291
|
+
self.layout.addWidget(self.pbar)
|
292
|
+
self.setLayout(self.layout)
|
293
|
+
|
294
|
+
def handle_result(self, result):
|
295
|
+
self.result = result
|
296
|
+
self.is_running = False
|
297
|
+
|
298
|
+
def handle_error(self, error):
|
299
|
+
self.error = error
|
300
|
+
self.label.setText(f"{self.base_text}\n运行出现错误: {error}")
|
301
|
+
self.is_running = False
|
302
|
+
|
303
|
+
def handle_progress(self, progress):
|
304
|
+
self.pbar.setValue(progress)
|
305
|
+
|
306
|
+
def update_text(self):
|
307
|
+
elapsed_time = int((datetime.now() - self.start_time).total_seconds()) + self.delay_milliseconds // 1000
|
308
|
+
self.label.setText(f"{self.base_text}\n已运行 {elapsed_time} 秒")
|
309
|
+
|
310
|
+
def run(self, func, *args, **kwargs):
|
311
|
+
"""
|
312
|
+
def func():
|
313
|
+
... # 程序功能
|
314
|
+
|
315
|
+
msg = WaitDialog().run(func) # 开一个等待窗口等程序运行
|
316
|
+
"""
|
317
|
+
self.worker = XlThreadWorker(func, *args, **kwargs)
|
318
|
+
self.worker.result.connect(self.handle_result)
|
319
|
+
self.worker.error.connect(self.handle_error)
|
320
|
+
self.is_running = True
|
321
|
+
self.worker.start()
|
322
|
+
|
323
|
+
QTimer.singleShot(self.delay_milliseconds, self.check_and_show)
|
324
|
+
|
325
|
+
# 阻塞主线程,直到子线程完成
|
326
|
+
while self.is_running:
|
327
|
+
QApplication.processEvents() # 刷新UI,保持其响应性
|
328
|
+
time.sleep(0.1) # 等待一段时间,以减少CPU使用率
|
329
|
+
|
330
|
+
self.timer.stop()
|
331
|
+
self.accept()
|
332
|
+
|
333
|
+
return self.result
|
334
|
+
|
335
|
+
def run_with_progress(self, func, *args, **kwargs):
|
336
|
+
"""
|
337
|
+
def func(progress_callback):
|
338
|
+
progress_callback(50) # 可以在运行中设置进度,进度值为0~100
|
339
|
+
... # 其他功能
|
340
|
+
|
341
|
+
msg = WaitDialog().run_with_progress(func) # 运行完获得返回值
|
342
|
+
"""
|
343
|
+
self.worker = XlThreadWorker(func, *args, use_progress=True, **kwargs)
|
344
|
+
self.worker.result.connect(self.handle_result)
|
345
|
+
self.worker.error.connect(self.handle_error)
|
346
|
+
self.worker.progress.connect(self.handle_progress)
|
347
|
+
self.is_running = True
|
348
|
+
self.worker.start()
|
349
|
+
|
350
|
+
QTimer.singleShot(self.delay_milliseconds, self.check_and_show)
|
351
|
+
|
352
|
+
# 阻塞主线程,直到子线程完成
|
353
|
+
while self.is_running:
|
354
|
+
QApplication.processEvents() # 刷新UI,保持其响应性
|
355
|
+
time.sleep(0.1) # 等待一段时间,以减少CPU使用率
|
356
|
+
|
357
|
+
self.timer.stop()
|
358
|
+
self.accept()
|
359
|
+
|
360
|
+
return self.result
|
361
|
+
|
362
|
+
def start_timer(self):
|
363
|
+
self.start_time = datetime.now()
|
364
|
+
self.timer.start(1000)
|
365
|
+
|
366
|
+
def check_and_show(self):
|
367
|
+
if self.is_running:
|
368
|
+
self.show()
|
369
|
+
self.start_timer()
|
370
|
+
|
371
|
+
def __enter__(self):
|
372
|
+
""" with写法比较简洁,但不太推荐这种使用方法,这样并不工程化
|
373
|
+
这样会把要运行的功能变成主线程,这个提示窗口会被挂起
|
374
|
+
|
375
|
+
这里功能设计上也比较简单些,不考虑写的很完善强大了。
|
376
|
+
"""
|
377
|
+
self.show()
|
378
|
+
QApplication.processEvents()
|
379
|
+
return self
|
380
|
+
|
381
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
382
|
+
self.accept()
|
383
|
+
|
384
|
+
|
385
|
+
class CustomMessageBox(QMessageBox):
|
386
|
+
def __init__(self, icon, title, text, copyable):
|
387
|
+
super().__init__(icon, title, "")
|
388
|
+
self.init_ui(title, text, copyable)
|
389
|
+
|
390
|
+
def init_ui(self, title, text, copyable=False):
|
391
|
+
layout = QVBoxLayout()
|
392
|
+
|
393
|
+
if copyable:
|
394
|
+
widget = QTextEdit()
|
395
|
+
widget.setText(text)
|
396
|
+
widget.setReadOnly(True)
|
397
|
+
widget.setWordWrapMode(QTextOption.WrapAnywhere)
|
398
|
+
widget.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
399
|
+
widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
400
|
+
widget.document().documentLayout().documentSizeChanged.connect(
|
401
|
+
lambda: widget.setMinimumHeight(min(widget.document().size().height(), 700))
|
402
|
+
)
|
403
|
+
widget.setMinimumHeight(100)
|
404
|
+
widget.setMaximumHeight(700)
|
405
|
+
else:
|
406
|
+
widget = QLabel()
|
407
|
+
widget.setText(text)
|
408
|
+
widget.setWordWrap(True)
|
409
|
+
|
410
|
+
min_width = max(len(title) * 15, 600)
|
411
|
+
widget.setMinimumWidth(min_width)
|
412
|
+
|
413
|
+
layout.addWidget(widget)
|
414
|
+
self.layout().addLayout(layout, 1, 1)
|
415
|
+
|
416
|
+
|
417
|
+
def show_message_box(text, title=None, icon=None, detail=None,
|
418
|
+
buttons=QMessageBox.Ok | QMessageBox.Cancel,
|
419
|
+
default_button=QMessageBox.Ok, copyable=False):
|
420
|
+
""" 显示一个提示框
|
421
|
+
|
422
|
+
:param text: 提示框的文本内容
|
423
|
+
:param title: 提示框的标题,默认值为 "提示"
|
424
|
+
:param icon: 提示框的图标,默认值为 QMessageBox.NoIcon
|
425
|
+
注意Information、Warning、Critical等都会附带一个提示音
|
426
|
+
而Question是不带提示音的,默认的NoIcon也是不带提示音的
|
427
|
+
:param detail: 提示框的详细信息,默认值为 None
|
428
|
+
:param buttons: 提示框的按钮,默认值为 QMessageBox.Ok
|
429
|
+
:param copyable: 消息窗中的文本是否可复制
|
430
|
+
|
431
|
+
:return: 选择的按钮
|
432
|
+
|
433
|
+
实现上,本来应该依据setMinimumWidth可以搞定的事,但不知道为什么就是会有bug问题,总之最后问gpt靠实现一个类来解决了
|
434
|
+
|
435
|
+
"""
|
436
|
+
if title is None:
|
437
|
+
title = "提示"
|
438
|
+
|
439
|
+
if icon is None:
|
440
|
+
icon = QMessageBox.NoIcon
|
441
|
+
|
442
|
+
msg_box = CustomMessageBox(icon, title, text, copyable)
|
443
|
+
|
444
|
+
if detail is not None:
|
445
|
+
msg_box.setDetailedText(detail)
|
446
|
+
msg_box.setStandardButtons(buttons)
|
447
|
+
msg_box.setDefaultButton(default_button)
|
448
|
+
|
449
|
+
return msg_box.exec_()
|