个人博客:苏三有春的博客
系类往期文章:
PyQt5实战——多脚本集合包,前言与环境配置(一)
PyQt5实战——多脚本集合包,UI以及工程布局(二)
PyQt5实战——多脚本集合包,程序入口QMainWindow(三)
PyQt主窗口框架设计(QWidget)
PrimeWindow.py的主要作用是绘制主窗口,在主窗口中:
- 最左边是各种功能组件的入口,比如:翻译器,UTF-8转换,图像处理等。
- 按下左边的按键,则会切换功能区,切换功能并非重新开一个窗口,而是切换中间部分,这里主要是采用stack的原理
- 最右边是日志打印区,通过重定向的方法,把打印在操作台的内容打印到这个窗口上,这样方便开发人员debug
4.1 import部分
import Classes.TranslatorClass as TranslatorClass
import Classes.ConvertorClass as ConvertorClass
import Classes.AudioProcessClass as AudioProcessClass
import Classes.AudiocodecClass as AudiocodecClass
import Classes.AudioPlayerClass as AudioPlayerClass
import Classes.ImgProcessClass as ImgProcessClass
from component.btnStyle import *
from component.editStyle import *
这些引用均来自作者自己的代码,分别保存在Classes目录下和component目录下,从这些目录下import不同的类或方法供PrimeWindow调用。
Classes中的各种类,如TranslatorClass,保存着翻译功能的功能区UI结构:翻译按钮,翻译内容输入框以及翻译结果展示框等,以及各组件的摆放位置。
component中的各种方法,如btnStyle,该文件中有按钮按下的样式,按钮抬起的样式等,只需在点击按钮时调用该方法即可改变按钮样式。
4.2 重定向操作台输出
重定向操作台输出需要调用一些系统方法
current_directory = os.getcwd() # 获取当前工作目录,并将其存储在current_directory中。
self.original_stdout = sys.stdout # 将当前的标准输出(操作台)保存到self.original_stdout中,这样可以在将来恢复标准输出
self.output_stream = io.StringIO() # 创建一个StringIO对象,这个对象像一个文件,可以在内存中操作字符串,之后的标准输入输出将写入这个对象
sys.stdout = self.output_stream # 将标准输出重定向到刚才创建的StringIO对象self.output_stream。这样,所有通过print语句输出的信息都将储存在output_stream中,而不是打印到操作台
self.timer = QTimer() # 创建一个定时器对象,用于定期触发某些事件
self.timer.timeout.connect(self.updateOutput) # 将定时器的信号连接到self.updateOutput方法上,当定时器达到超时时间时则会调用该方法
self.timer.start(1000) # 设置定时器的超时时间为1000ms即1s
以上实现的是:重定向操作台的输出,开启定时器,每1s后,将原本pirnt方法打印到操作台的字符串储存起来,并调用updateOutput方法
以下是代码逐行分析:
- 获取当前工作目录,并将其存储在current_directory中
- 将当前的标准输出(操作台)保存到self.original_stdout中,这样可以在将来恢复标准输出
- 创建一个StringIO对象,这个对象像一个文件,可以在内存中操作字符串,之后的标准输入输出将写入这个对象
- 将标准输出重定向到刚才创建的StringIO对象self.output_stream。这样,所有通过print语句输出的信息都将储存在output_stream中,而不是打印到操作台
- 创建一个定时器对象,用于定期触发某些事件
- 将定时器的信号连接到self.updateOutput方法上,当定时器达到超时时间时则会调用该方法
- 设置定时器的超时时间为1000ms即1s
updateOutput方法:
def updateOutput(self):
output = self.output_stream.getvalue() # 从output_stream中获取字符串
if output: # 如果其中有字符串的话
cursor = self.consoleedit.textCursor() # 获取日志打印文本编辑控件的当前光标,方便在特定的位置插入文本
cursor.movePosition(QTextCursor.End) # 将光标移动至结尾,以便现有文本插入新的输出内容
cursor.insertText(output) # 将内容添加到日志打印文本编辑控件中
self.consoleedit.setTextCursor(cursor) # 更新光标位置为刚刚移动的光标,这确保了后续的输入或操作将从正确的位置开始
self.consoleedit.ensureCursorVisible() # 确保光标在文本编辑器中可见,特别是光标在底部时,防止用户无法看到最新的插入内容,尤其是文本框不够大时
self.output_stream.truncate(0) # 清空output_stream中的内容,将其内容截断为0,使得下一轮输出时不会将之前的内容重复添加
通过这个方法,即可将打印的内容输出到日志打印区中。并每1秒就检查一次是否有新的内容需要输出
- 因为是利用定时器每1秒检查一次是否有新的输出内容,所以输出并不是实时进行的,如你需要更精确的日志打印,则调小定时器的超时时间,但请注意,定时器的超时时间越短,定时器就会越频繁地调用updateOutput方法,即使什么新内容也没有。如果你需要做一些复杂且精密的操作,则可能需要考虑时间与空间开销的问题。
以下是代码逐行分析:
- 从output_stream中获取字符串
- 如果其中有字符串的话
- 获取日志打印文本编辑控件的当前光标,方便在特定的位置插入文本
- 将光标移动至结尾,以便现有文本插入新的输出内容
- 将内容添加到日志打印文本编辑控件中
- 更新光标位置为刚刚移动的光标,这确保了后续的输入或操作将从正确的位置开始
- 确保光标在文本编辑器中可见,特别是光标在底部时,防止用户无法看到最新的插入内容,尤其是文本框不够大时
- 清空output_stream中的内容,将其内容截断为0,使得下一轮输出时不会将之前的内容重复添加
4.3 stackLayout 切换功能
在PyQt中,
QStackLayout
是用于在同一位置上堆叠多个小组件的布局管理器,允许根据需要在它们之间进行切换,这对实现标签页或动态内容展示非常有用
def create_stack(self):
# create a stack layout
self.stacklayout = QStackedLayout()
convertor = ConvertorClass.Wconvertor()
self.stacklayout.addWidget(convertor)
- 创建stacklayout
- 把功能UI实现的对象添加在stacklayout中
# set the convertor buttons
self.UTF8ConvertorBtn = QPushButton("UTF-8 转换") # 创建按钮对象
btnReleaseStyleA(self.UTF8ConvertorBtn) # 修改按钮样式,该方法为作者创建,并非第三方库调用
self.UTF8ConvertorBtn.clicked.connect(self.Cbtn_press_clicked) # 为按钮连接触发事件,该事件会切换stack
# add the buttons to the layout
self.btnlayout.addWidget(self.UTF8ConvertorBtn) # 将按钮添加到功能按钮区布局
updateButtonStyle(self,self.UTF8ConvertorBtn) # 更新按钮样式,该方法为作者创建,并非第三方库调用
UTF8ConvertorBtn
按钮连接上了Cbtn_press_clicked方法,表示当该按钮被按下时,则调用Cbtn_press_clicked
方法。同时我对按钮的样式做了修改,使用方法btnReleaseStyleA
,因为要对按钮做批量相同的修改,因此集成到一个方法中去调用是常见的编程思想。
def Cbtn_press_clicked(self):
updateButtonStyle(self,self.UTF8ConvertorBtn) # 更新按钮样式,该方法为作者创建,非第三方库调用
self.stacklayout.setCurrentIndex(0) # 将当前stack布局设置为第一个抽屉
setCurrentIndex
方法的意思是,stacklayout所占据的这一片区域,可以随意切换成被它add了的widget,它就像一个抽屉一样,抽屉的大小固定,但是这个固定的大小所展示的内容是由你抽出第几层决定。就比如参数为0
,则抽出的是第1个抽屉,这个抽屉是一个widget,这个widget可以套一个layout,其中放各种各样的组件。同理,参数为1
,则表示抽出第2个抽屉,则stacklayout所占据的这一片区域就会切换成第2个抽屉的样子。
4.4 UI初始化
def initUI(self):
#---------------- create Layout we needed ----------------------------
self.Mainlayout = QHBoxLayout() # 主窗口
self.Primarylayout = QVBoxLayout() # 功能区布局
self.btnlayout = QVBoxLayout() # 功能按钮区布局
#---------------- create Layout we needed ----------------------------
#---------------- there has more btn but unshowing -------------------
# set the convertor buttons
self.UTF8ConvertorBtn = QPushButton("UTF-8 转换") # 创建按钮对象
btnReleaseStyleA(self.UTF8ConvertorBtn) # 修改按钮样式,该方法为作者创建,并非第三方库调用
self.UTF8ConvertorBtn.clicked.connect(self.Cbtn_press_clicked) # 为按钮连接触发事件,该事件会切换stack
# add the buttons to the layout
self.btnlayout.addWidget(self.UTF8ConvertorBtn) # 将按钮添加到功能按钮区布局
updateButtonStyle(self,self.UTF8ConvertorBtn) # 更新按钮样式,该方法为作者创建,并非第三方库调用
#---------------- there has more btn but unshowing -------------------
self.btnlayout.setAlignment(Qt.AlignTop) # 调整按钮区的各组件对齐方式为向上对齐,而非均匀分布
#----------------- put the stack in function area --------------------
widget = QWidget() # 创建一个widget,用于存放stack布局
widget.setLayout(self.stacklayout) # 将stack布局放到widget中
self.Primarylayout.addWidget(widget) # 将widget放到功能区布局中,至此stack在功能区中进行切换
#----------------- put the stack in function area --------------------
#---------------------- create log area ------------------------------
self.Consolelayout = QVBoxLayout() # 创建日志区布局
self.consoleedit = QTextEdit() # 创建日志编辑文本框
self.consoleedit.setReadOnly(True) # 文本框设置为只读
TextEditStyle(self.consoleedit) # 修改文本框样式, 该方法为作者创建,并非第三方库调用
self.consoleedit.verticalScrollBar().setPageStep(100) # 修改滚动条步长
self.Consolelayout.addWidget(self.consoleedit) # 添加文本框到日志区布局中
#---------------------- create log area ------------------------------
#------------------ put the layouts in main layout -------------------
self.Mainlayout.addLayout(self.btnlayout,stretch=1) # 将按钮布局添加到主布局中
self.Vline = QFrame(self) # 添加细线 将按钮区与功能区分隔开
self.Vline.setFrameShape(QFrame.VLine) # 优化细线
self.Vline.setFrameShadow(QFrame.Raised) # 使细线具有凸起的立体感
self.Vline.setLineWidth(3) # 设置细线外部粗细
self.Vline.setMidLineWidth(1) # 设置细线内部粗细
self.Mainlayout.addWidget(self.Vline) # 将细线添加至主布局
self.Mainlayout.addLayout(self.Primarylayout,stretch=4) # 将功能区添加至主布局中
self.Mainlayout.addLayout(self.Consolelayout,stretch=2) # 将日志区添加至主布局中
# add the layout to the window
self.setLayout(self.Mainlayout) # 将主窗口的布局设置为主布局
#------------------ put the layouts in main layout -------------------
4.5 结语
到目前为止,我们脚本工具集合包已经具备了初步的大致框架,三大区块已经被划分出来了,接下来的工作将重点放在要开发哪些功能,如何实现这些功能,以及这些功能的UI页面布局。这个系列将会持续更新,动手能力强的小伙伴可以根据路线自己实操一遍,后续我也会将完整代码开源带GitHub上(等系类差不多结束的时候),同时系列文章也会同步更新到我的个人博客中,如果本系列真的帮助到你,请关注本频道,并给我的CSDN 点赞收藏 QAQ,感激不尽!
如有任何疑问,欢迎CSDN私信我或发邮件到707973090@qq.com,在我看到时会第一时间回复!