我正在使用PySide6在Python 3.11中开发一个自定义记事本风格的日志查看器。
ui是用QtDesigner创建的,必须用pyuic命令编译成python,并用import“main_ui”导入到以下代码中。
应用程序将名为sample.log的文件加载到QPlainTextEdit字段中。
然后,用户可以单击按钮查找并突出显示包含“Error”子字符串的所有行。
每个突出显示的行的相对位置在自定义滚动条中用红线表示。
我的问题是如何正确计算滚动条中红线的位置?
我目前的逻辑是可行的,但立场并不准确。
这就是界面的样子:
本文提供了所有必要的代码,但也可以通过此zip文件下载
logtool.zip
其中包括这些文件:
-
logtool.py
-
main.ui
-
main_ui.py(使用Python6/Scripts/pyside6-uic.exe从main.ui构建)
-
sample.log
这是主程序logtool.py的样子:
import sys
import os
import os.path
from PySide6 import QtWidgets
from PySide6.QtCore import Qt, Slot
from PySide6.QtWidgets import QMainWindow, QScrollBar
from PySide6.QtGui import QTextCursor, QTextCharFormat, QColor, QPainter, QPen
import main_ui
substring_list = ["error", "exception", "fail"]
class MyScroll(QScrollBar):
def __init__(self, parent=None):
super(MyScroll, self).__init__(parent)
def paintEvent(self, event):
super().paintEvent(event)
painter = QPainter(self)
pen = QPen()
pen.setWidth(3)
pen.setColor(Qt.red)
pen.setStyle(Qt.SolidLine)
painter.setPen(pen)
try:
self.values
except:
return
for value in self.values:
painter.drawLine(0, value, 15, value)
def draw(self, values):
self.values = values
self.update()
class MainWindow(QMainWindow, main_ui.Ui_main):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.txtEdit.setCenterOnScroll(True)
fullpath = "sample.log"
self.readfile(fullpath)
self.lines = []
self.scroll = MyScroll()
self.txtEdit.setVerticalScrollBar(self.scroll)
self.show()
def calc_drawline_pixel(self, lineno):
widget = self.txtEdit.verticalScrollBar()
total_lines = widget.maximum()
scroll_pixel_height = widget.height()
factor = lineno / total_lines
uparrow_height = 15
draw_at_pixel = int(factor * scroll_pixel_height) + uparrow_height
return draw_at_pixel
@Slot()
def on_btnMarkErrors_clicked(self):
self.lines = []
self.pos = 0
string = self.txtEdit.toPlainText()
reclist = string.split("\n")
for lineno, line in enumerate(reclist):
flag = any(substring.lower() in line.lower() for substring in substring_list)
if flag:
self.markline(lineno)
self.lines.append(lineno)
self.lines = sorted(self.lines)
values = []
for lineno in self.lines:
pixel = self.calc_drawline_pixel(lineno)
values.append(pixel)
self.scroll.draw(values)
def markline(self, line_number):
"""marks line with red highlighter"""
widget = self.txtEdit
cursor = QTextCursor(widget.document().findBlockByLineNumber(line_number))
cursor.select(QTextCursor.LineUnderCursor)
fmt = QTextCharFormat()
RED = QColor(228,191,190)
fmt.setBackground(RED)
cursor.mergeCharFormat(fmt)
def readfile(self, fullpath):
fullpath = fullpath.replace("\\", "/")
path,file = os.path.split(fullpath)
fin = open(fullpath, encoding="utf8", errors='ignore')
content = fin.read()
fin.close()
self.txtEdit.setPlainText(content)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setStyle("Fusion")
myapp = MainWindow()
rc = app.exec()
sys.exit(rc)
这就是main_ui.py的样子:
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QMainWindow,
QPlainTextEdit, QPushButton, QSizePolicy, QSpacerItem,
QStatusBar, QVBoxLayout, QWidget)
class Ui_main(object):
def setupUi(self, main):
if not main.objectName():
main.setObjectName(u"main")
main.resize(602, 463)
self.centralwidget = QWidget(main)
self.centralwidget.setObjectName(u"centralwidget")
self.verticalLayout = QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.frame = QFrame(self.centralwidget)
self.frame.setObjectName(u"frame")
self.frame.setFrameShape(QFrame.NoFrame)
self.frame.setFrameShadow(QFrame.Raised)
self.horizontalLayout = QHBoxLayout(self.frame)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.btnMarkErrors = QPushButton(self.frame)
self.btnMarkErrors.setObjectName(u"btnMarkErrors")
self.horizontalLayout.addWidget(self.btnMarkErrors)
self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer_2)
self.verticalLayout.addWidget(self.frame)
self.txtEdit = QPlainTextEdit(self.centralwidget)
self.txtEdit.setObjectName(u"txtEdit")
font = QFont()
font.setFamilies([u"Consolas"])
self.txtEdit.setFont(font)
self.txtEdit.setLineWrapMode(QPlainTextEdit.NoWrap)
self.verticalLayout.addWidget(self.txtEdit)
main.setCentralWidget(self.centralwidget)
self.statusbar = QStatusBar(main)
self.statusbar.setObjectName(u"statusbar")
main.setStatusBar(self.statusbar)
self.retranslateUi(main)
QMetaObject.connectSlotsByName(main)
def retranslateUi(self, main):
main.setWindowTitle(QCoreApplication.translate("main", u"Log Viewer", None))
self.btnMarkErrors.setText(QCoreApplication.translate("main", u"Mark Errors", None))