gpt4 book ai didi

qt - 如何在 View (PyQt/PySide/Qt)中实现富文本编辑器?

转载 作者:行者123 更新时间:2023-12-04 02:18:14 24 4
gpt4 key购买 nike

精简版

我有一个 QTreeView 并希望用户能够精细控制文本的外观,为他们提供富文本格式选项。我已经有了它,所以可以选择整个项目进行格式化(例如,粗体),但我需要更大的灵 active 。例如,用户必须能够突出显示项目文本的部分并将其加粗。

请注意,我正在使用 QStandardItemModel(请参阅下面的 SSCCE)。

详细版

给整个项目加粗很简单:

itemFont = item.font()
itemFont.setBold(True)
item.setFont(itemFont)

不幸的是,我的用户需要更细粒度的控制,所以不是

Hi how are you?

他们应该能够使用鼠标只选择第一个单词并使该项目的文本显示为:

Hi how are you?

我正在考虑的两个选项是:

  1. setIndexWidget

    在我需要此功能的每个单元格中,使用 setIndexWidget 将其显示为 QTextEdit 小部件,如下所示: To set widgets on children items on QTreeView .然后我可以使用标准工具在每个单元格中进行富文本编辑。

  2. 自定义委托(delegate)

    使用自定义委托(delegate)在我需要此功能的地方绘制每个项目,就像这里应用的一样: How to make item view render rich (html) text in Qt

请注意,与那个问题不同的是,我不只是问如何呈现富文本,而是如何让用户选择文本并将其呈现为细粒度的富文本。

中南合作商会

from PySide import QtGui, QtCore
import sys

class MainTree(QtGui.QMainWindow):
def __init__(self, tree, parent = None):
QtGui.QMainWindow.__init__(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setCentralWidget(tree)
self.createStatusBar()
self.createBoldAction()
self.createToolbar()

def createStatusBar(self):
self.status = self.statusBar()
self.status.setSizeGripEnabled(False)
self.status.showMessage("Ready")

def createToolbar(self):
self.textToolbar = self.addToolBar("Text actions")
self.textToolbar.addAction(self.boldTextAction)

def createBoldAction(self):
self.boldTextAction = QtGui.QAction("Bold", self)
self.boldTextAction.setIcon(QtGui.QIcon("boldText.png"))
self.boldTextAction.triggered.connect(self.emboldenText)
self.boldTextAction.setStatusTip("Make selected text bold")

def emboldenText(self):
print "Make selected text bold...How do I do this?"


class SimpleTree(QtGui.QTreeView):
def __init__(self, parent = None):
QtGui.QTreeView.__init__(self)
model = QtGui.QStandardItemModel()
model.setHorizontalHeaderLabels(['Title', 'Summary'])
rootItem = model.invisibleRootItem()
item0 = [QtGui.QStandardItem('Title0'), QtGui.QStandardItem('Summary0')]
item00 = [QtGui.QStandardItem('Title00'), QtGui.QStandardItem('Summary00')]
rootItem.appendRow(item0)
item0[0].appendRow(item00)
self.setModel(model)
self.expandAll()


def main():
app = QtGui.QApplication(sys.argv)
myTree = SimpleTree()
#myTree.show()
myMainTree = MainTree(myTree)
myMainTree.show()
sys.exit(app.exec_())

if __name__ == "__main__":
main()

最佳答案

唯一合理的方法是使用选项 2:创建自定义委托(delegate)。您的情况几乎就是委托(delegate)的确切类型:使用 createEditor 创建自定义编辑器(例如旋转框或富文本编辑器等),并实现 paint 方法,可让您准确控制数据输入后的外观。虽然可能有其他方法可以做到这一点,但几乎可以肯定它们比使用委托(delegate)更糟糕。

因此,要使其正常工作,您需要为 QStyledItemDelegate 重新实现 paintcreateEditor

不幸的是,为了实现createEditor,Qt 没有提供原生的富文本行编辑器(也就是说,没有像QLineEdit 那样的富文本编辑器)。幸运的是,Mark Summerfield 实际上在他关于 PyQt 的书的第 13 章中编写了这样一个函数,因此我将其应用到下面的一个完整的示例中,其中包括主窗口中的 TreeView ,能够使用以下命令切换文本属性编辑器打开时的工具栏或上下文(右键单击)菜单或键盘快捷键。

enter image description here


相关帖子

我在以下线程中直接获得了实现其中许多功能的帮助:


图标

以下是工具栏中使用的图像:

boldText.png italicText.png strikeoutText.png underlineText.png


代码

这是代码。对于大小,我深表歉意,但它包含了太多可能对那些学习代表有用的东西(正如 OP 显然是的那样),因此我决定不对其进行编辑:

import sys
from xml.sax.saxutils import escape as escape
from PySide import QtGui, QtCore


class MainTree(QtGui.QMainWindow):
def __init__(self, tree, parent = None):
QtGui.QMainWindow.__init__(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setCentralWidget(tree)
self.createStatusBar()
self.createActions()
self.createToolbar()
self.tree = tree
self.setGeometry(500,150,400,300)

def createStatusBar(self):
self.status = self.statusBar()
self.status.setSizeGripEnabled(False)
self.status.showMessage("Ready")

def createActions(self):
'''Create all actions to be used in toolbars/menus: calls createAction()'''
self.boldTextAction = self.createAction("&Bold",
shortcut = QtGui.QKeySequence.Bold, iconName = "boldText", tip = "Embolden",
status = "Toggle bold", disabled = True)
self.italicTextAction = self.createAction("&Italic",
shortcut = QtGui.QKeySequence.Italic, iconName = "italicText", tip = "Italicize",
status = "Toggle italics", disabled = True)
self.underlineTextAction = self.createAction("&Underline",
shortcut = QtGui.QKeySequence.Underline, iconName = "underlineText", tip = "Underline",
status = "Toggle underline", disabled = True)
self.strikeoutTextAction = self.createAction("Stri&keout",
shortcut = QtGui.QKeySequence("Ctrl+K"), iconName = "strikeoutText", tip = "Strikeout",
status = "Toggle strikeout", disabled = True)

def createAction(self, text, slot = None, shortcut = None, iconName = None,
tip = None, status = None, disabled = False):
'''Creates each individual action'''
action = QtGui.QAction(text, self)
if iconName is not None:
action.setIcon(QtGui.QIcon("{0}.png".format(iconName)))
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
if status is not None:
action.setStatusTip(status)
if slot is not None:
action.triggered.connect(slot)
if disabled:
action.setDisabled(True)
return action

def createToolbar(self):
self.textToolbar = self.addToolBar("Text actions")
self.textToolbar.addAction(self.boldTextAction)
self.textToolbar.addAction(self.underlineTextAction)
self.textToolbar.addAction(self.italicTextAction)
self.textToolbar.addAction(self.strikeoutTextAction)

class HtmlTree(QtGui.QTreeView):
def __init__(self, parent = None):
QtGui.QTreeView.__init__(self)
model = QtGui.QStandardItemModel()
model.setHorizontalHeaderLabels(['Task', 'Description'])
self.rootItem = model.invisibleRootItem()
item0 = [QtGui.QStandardItem('Sneeze'), QtGui.QStandardItem('You have been blocked up')]
item00 = [QtGui.QStandardItem('Tickle nose'), QtGui.QStandardItem('Key first step')]
item1 = [QtGui.QStandardItem('Get a job'), QtGui.QStandardItem('Do not blow it')]
item01 = [QtGui.QStandardItem('Call temp agency'), QtGui.QStandardItem('Maybe they will be kind')]
self.rootItem.appendRow(item0)
item0[0].appendRow(item00)
self.rootItem.appendRow(item1)
item1[0].appendRow(item01)
self.setModel(model)
self.expandAll()
self.setItemDelegate(HtmlPainter(self))
self.resizeColumnToContents(0)
self.resizeColumnToContents(1)
#print "unoiform row heights? ", self.uniformRowHeights()

class HtmlPainter(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
print "delegate parent: ", parent, parent.metaObject().className()
QtGui.QStyledItemDelegate.__init__(self, parent)

def paint(self, painter, option, index):
if index.column() == 1 or index.column() == 0:
text = index.model().data(index)
palette = QtGui.QApplication.palette()
document = QtGui.QTextDocument()
document.setDefaultFont(option.font)
#Set text (color depends on whether selected)
if option.state & QtGui.QStyle.State_Selected:
displayString = "<font color={0}>{1}</font>".format(palette.highlightedText().color().name(), text)
document.setHtml(displayString)
else:
document.setHtml(text)
#Set background color
bgColor = palette.highlight().color() if (option.state & QtGui.QStyle.State_Selected)\
else palette.base().color()
painter.save()
painter.fillRect(option.rect, bgColor)
document.setTextWidth(option.rect.width())
offset_y = (option.rect.height() - document.size().height())/2
painter.translate(option.rect.x(), option.rect.y() + offset_y)
document.drawContents(painter)
painter.restore()
else:
QtGui.QStyledItemDelegate.paint(self, painter, option, index)

def sizeHint(self, option, index):
rowHeight = 18
text = index.model().data(index)
document = QtGui.QTextDocument()
document.setDefaultFont(option.font)
document.setHtml(text)
return QtCore.QSize(document.idealWidth() + 5, rowHeight) #fm.height())

def createEditor(self, parent, option, index):
if index.column() == 1:
editor = RichTextLineEdit(option, parent)
editor.returnPressed.connect(self.commitAndCloseEditor)
editor.mainWindow = parent.window()
self.setConnections(editor.mainWindow, editor)
self.enableActions(editor.mainWindow)
return editor
else:
return QtGui.QStyledItemDelegate.createEditor(self, parent, option,
index)

def setConnections(self, mainWindow, editor):
'''Create connections for font toggle actions when editor is created'''
mainWindow.boldTextAction.triggered.connect(editor.toggleBold)
mainWindow.underlineTextAction.triggered.connect(editor.toggleUnderline)
mainWindow.italicTextAction.triggered.connect(editor.toggleItalic)
mainWindow.strikeoutTextAction.triggered.connect(editor.toggleStrikeout)

def enableActions(self, mainWindow):
mainWindow.boldTextAction.setEnabled(True)
mainWindow.underlineTextAction.setEnabled(True)
mainWindow.italicTextAction.setEnabled(True)
mainWindow.strikeoutTextAction.setEnabled(True)

def disableActions(self, mainWindow):
mainWindow.boldTextAction.setDisabled(True)
mainWindow.underlineTextAction.setDisabled(True)
mainWindow.italicTextAction.setDisabled(True)
mainWindow.strikeoutTextAction.setDisabled(True)

def commitAndCloseEditor(self):
editor = self.sender()
if isinstance(editor, (QtGui.QTextEdit, QtGui.QLineEdit)):
self.commitData.emit(editor)
self.closeEditor.emit(editor, QtGui.QAbstractItemDelegate.NoHint)

def setModelData(self, editor, model, index):
if index.column() == 1:
self.disableActions(editor.mainWindow)
model.setData(index, editor.toSimpleHtml())
else:
QtGui.QStyledItemDelegate.setModelData(self, editor, model, index)



class RichTextLineEdit(QtGui.QTextEdit):
'''Single line editor invoked by delegate'''
(Bold, Italic, Underline, StrikeOut) = range(4)
returnPressed = QtCore.Signal()

def __init__(self, option, parent=None):
QtGui.QTextEdit.__init__(self, parent)
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
self.setTabChangesFocus(True)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
#Following lines set it so text is centered in editor
fontMetrics = QtGui.QFontMetrics(self.font())
margin = 2
self.document().setDocumentMargin(margin)
height = fontMetrics.height() + (margin + self.frameWidth()) * 2
self.setFixedHeight(height)
self.setToolTip("Right click for text effect menu.")

def toggleBold(self):
self.setFontWeight(QtGui.QFont.Normal
if self.fontWeight() > QtGui.QFont.Normal else QtGui.QFont.Bold)

def toggleItalic(self):
self.setFontItalic(not self.fontItalic())

def toggleUnderline(self):
self.setFontUnderline(not self.fontUnderline())


def toggleStrikeout(self):
#Adapted from: https://www.binpress.com/tutorial/developing-a-pyqt-text-editor-part-2/145
#https://srinikom.github.io/pyside-docs/PySide/QtGui/QTextCharFormat.html
# Grab the text's format
textFormat = self.currentCharFormat()
# Change the fontStrikeOut property to its opposite
textFormat.setFontStrikeOut(not textFormat.fontStrikeOut())
# Apply the new format
self.setCurrentCharFormat(textFormat)

def contextMenuEvent(self, event):
'''
Context menu for controlling text
'''
textFormat = self.currentCharFormat()
menu = QtGui.QMenu("Text Effects")
for text, shortcut, data, checked in (
("&Bold", "Ctrl+B", RichTextLineEdit.Bold,
self.fontWeight() > QtGui.QFont.Normal),
("&Italic", "Ctrl+I", RichTextLineEdit.Italic,
self.fontItalic()),
("Stri&keout", "Ctrl+K", RichTextLineEdit.StrikeOut,
textFormat.fontStrikeOut()),
("&Underline", "Ctrl+U", RichTextLineEdit.Underline,
self.fontUnderline())):
action = menu.addAction(text, self.setTextEffect)
if shortcut is not None:
action.setShortcut(QtGui.QKeySequence(shortcut))
action.setData(data)
action.setCheckable(True)
action.setChecked(checked)
self.ensureCursorVisible()
menu.exec_(self.viewport().mapToGlobal(
self.cursorRect().center()))

def setTextEffect(self):
'''Called by context menu'''
action = self.sender()
if action is not None and isinstance(action, QtGui.QAction):
what = int(action.data())
if what == RichTextLineEdit.Bold:
self.toggleBold()
return
if what == RichTextLineEdit.Italic:
self.toggleItalic()
return
if what == RichTextLineEdit.Underline:
self.toggleUnderline()
return
format = self.currentCharFormat()
if what == RichTextLineEdit.StrikeOut:
format.setFontStrikeOut(not format.fontStrikeOut())
self.mergeCurrentCharFormat(format)

def keyPressEvent(self, event):
'''
Handles all keyboard shortcuts, and stops retun from returning newline
'''
if event.modifiers() & QtCore.Qt.ControlModifier:
handled = False
if event.key() == QtCore.Qt.Key_B:
self.toggleBold()
handled = True
elif event.key() == QtCore.Qt.Key_I:
self.toggleItalic()
handled = True
elif event.key() == QtCore.Qt.Key_U:
self.toggleUnderline()
handled = True
if handled:
event.accept()
return
if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
self.returnPressed.emit()
event.accept()
else:
QtGui.QTextEdit.keyPressEvent(self, event)

def toSimpleHtml(self):
html = ""
block = self.document().begin()
while block.isValid():
iterator = block.begin()
while iterator != block.end():
fragment = iterator.fragment()
if fragment.isValid():
format = fragment.charFormat()
text = escape(fragment.text())
if format.fontUnderline():
text = "<u>{}</u>".format(text)
if format.fontItalic():
text = "<i>{}</i>".format(text)
if format.fontWeight() > QtGui.QFont.Normal:
text = "<b>{}</b>".format(text)
if format.fontStrikeOut():
text = "<s>{}</s>".format(text)
html += text
iterator += 1
block = block.next()
return html


def main():
app = QtGui.QApplication(sys.argv)
myTree = HtmlTree() #myTree.show()
myMainTree = MainTree(myTree)
myMainTree.show()
sys.exit(app.exec_())


if __name__ == "__main__":
main()

关于qt - 如何在 View (PyQt/PySide/Qt)中实现富文本编辑器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32773679/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com