如何使用 HTML 格式和可点击的单元格制作快速的 QTableView?
How to make a fast QTableView with HTML-formatted and clickable cells?
我正在制作一个字典程序,当用户键入它们时,它在 3 列 QTableView
子类中显示单词定义,从 QAbstractTableModel
子类中获取数据。类似的东西:
我想为文本添加各种格式,我正在使用 QAbstractItemView::setIndexWidget
在数据输入时向每个单元格添加 QLabel
:
WordView.h
#include <QTableView>
class QLabel;
class WordView : public QTableView {
Q_OBJECT
public:
explicit WordView(QWidget *parent = 0);
void rowsInserted(const QModelIndex &parent, int start, int end);
private:
void insertLabels(int row);
void removeLabels(int row);
};
WordView.cpp
#include <QLabel>
#include "WordView.h"
WordView::WordView(QWidget *parent) :
QTableView(parent)
{}
void WordView::rowsInserted(const QModelIndex &parent, int start, int end) {
QTableView::rowsInserted(parent, start, end);
for (int row = start; row <= end; ++row) {
insertLabels(row);
}
}
void WordView::insertLabels(int row) {
for (int i = 0; i < 3; ++i) {
auto label = new QLabel(this);
label->setTextFormat(Qt::RichText);
label->setAutoFillBackground(true);
QModelIndex ix = model()->index(row, i);
label->setText(model()->data(ix, Qt::DisplayRole).toString()); // this has HTML
label->setWordWrap(true);
setIndexWidget(ix, label); // this calls QAbstractItemView::dataChanged
}
}
但是,这非常慢 - 像这样刷新 100 行(删除所有行,然后添加 100 行)大约需要 1 秒。使用原始 QTableView,它工作得很快,但我没有格式化和添加 links(字典中的交叉引用)的能力。 如何让它更快?或者我可以使用什么其他小部件来显示该数据?
我的要求是:
- Adding/removing 大约 1000 行在 ~0.2 秒内,其中大约 30 行将立即可见
- 每个单元格中的
- 多个内部 links (
<a>
?)(例如 QLabel
有,QItemDelegate
可能很快,但我不知道不知道如何获取 link 我点击那里的信息)
- 允许不同字体大小和颜色、自动换行、不同单元格高度的格式
- 我对
QTableView
并没有死心塌地,任何看起来像可滚动 table 并且看起来与 Qt 图形一致的东西都可以
备注:
- 我尝试用 HTML
<table>
制作一个标签,但速度并没有快多少。似乎 QLabel
不是正确的选择。
- 样本中的数据由 JMdict 项目提供。
在你的情况下,QLabel(重新)绘制很慢,而不是 QTableView。
另一方面,QTableView 根本不支持格式化文本。
可能,你唯一的方法是创建你自己的委托,QStyledItemDelegate,并在其中制作你自己的绘画和点击处理。
PS: 是的,您可以使用 QTextDocument 在委托中呈现 html,但它也会很慢。
我通过整理几个答案并查看 Qt 的内部结构解决了这个问题。
对于 QTableView
中具有 links 的静态 html 内容非常快速的解决方案如下:
- 子类
QTableView
并在那里处理鼠标事件;
- 子类
QStyledItemDelegate
并在那里绘制 html (与 RazrFalcon 的回答相反,它非常快,因为一次只能看到少量的单元格并且只有那些 paint()
调用的方法);
- 在子类
QStyledItemDelegate
中创建一个函数,计算出 QAbstractTextDocumentLayout::anchorAt()
单击了哪个 link。您不能自己创建 QAbstractTextDocumentLayout
,但您可以从 QTextDocument::documentLayout()
获取它,并且根据 Qt 源代码,它保证是非空的。
- 在子类
QTableView
中根据指针是否悬停在 link 上相应地修改 QCursor
指针形状
下面是 QTableView
和 QStyledItemDelegate
子类的完整有效实现,它们绘制 HTML 并在 link hover/activation 上发送信号。 delegate和model还是要在外面设置,如下:
wordTable->setModel(&myModel);
auto wordItemDelegate = new WordItemDelegate(this);
wordTable->setItemDelegate(wordItemDelegate); // or just choose specific columns/rows
WordView.h
class WordView : public QTableView {
Q_OBJECT
public:
explicit WordView(QWidget *parent = 0);
signals:
void linkActivated(QString link);
void linkHovered(QString link);
void linkUnhovered();
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
private:
QString anchorAt(const QPoint &pos) const;
private:
QString _mousePressAnchor;
QString _lastHoveredAnchor;
};
WordView.cpp
#include <QApplication>
#include <QCursor>
#include <QMouseEvent>
#include "WordItemDelegate.h"
#include "WordView.h"
WordView::WordView(QWidget *parent) :
QTableView(parent)
{
// needed for the hover functionality
setMouseTracking(true);
}
void WordView::mousePressEvent(QMouseEvent *event) {
QTableView::mousePressEvent(event);
auto anchor = anchorAt(event->pos());
_mousePressAnchor = anchor;
}
void WordView::mouseMoveEvent(QMouseEvent *event) {
auto anchor = anchorAt(event->pos());
if (_mousePressAnchor != anchor) {
_mousePressAnchor.clear();
}
if (_lastHoveredAnchor != anchor) {
_lastHoveredAnchor = anchor;
if (!_lastHoveredAnchor.isEmpty()) {
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
emit linkHovered(_lastHoveredAnchor);
} else {
QApplication::restoreOverrideCursor();
emit linkUnhovered();
}
}
}
void WordView::mouseReleaseEvent(QMouseEvent *event) {
if (!_mousePressAnchor.isEmpty()) {
auto anchor = anchorAt(event->pos());
if (anchor == _mousePressAnchor) {
emit linkActivated(_mousePressAnchor);
}
_mousePressAnchor.clear();
}
QTableView::mouseReleaseEvent(event);
}
QString WordView::anchorAt(const QPoint &pos) const {
auto index = indexAt(pos);
if (index.isValid()) {
auto delegate = itemDelegate(index);
auto wordDelegate = qobject_cast<WordItemDelegate *>(delegate);
if (wordDelegate != 0) {
auto itemRect = visualRect(index);
auto relativeClickPosition = pos - itemRect.topLeft();
auto html = model()->data(index, Qt::DisplayRole).toString();
return wordDelegate->anchorAt(html, relativeClickPosition);
}
}
return QString();
}
WordItemDelegate.h
#include <QStyledItemDelegate>
class WordItemDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit WordItemDelegate(QObject *parent = 0);
QString anchorAt(QString html, const QPoint &point) const;
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
WordItemDelegate.cpp
#include <QPainter>
#include <QTextDocument>
#include <QAbstractTextDocumentLayout>
#include "WordItemDelegate.h"
WordItemDelegate::WordItemDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{}
QString WordItemDelegate::anchorAt(QString html, const QPoint &point) const {
QTextDocument doc;
doc.setHtml(html);
auto textLayout = doc.documentLayout();
Q_ASSERT(textLayout != 0);
return textLayout->anchorAt(point);
}
void WordItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter);
painter->translate(options.rect.left(), options.rect.top());
QRect clip(0, 0, options.rect.width(), options.rect.height());
doc.drawContents(painter, clip);
painter->restore();
}
QSize WordItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
QTextDocument doc;
doc.setHtml(options.text);
doc.setTextWidth(options.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
请注意,此解决方案之所以快,只是因为一次呈现了一小部分行,因此一次呈现的 QTextDocument
并不多。一次自动调整所有行高或列宽仍然很慢。如果你需要那个功能,你可以让委托通知视图它画了一些东西,然后让视图调整 height/width 如果它之前没有。将其与 QAbstractItemView::rowsAboutToBeRemoved
结合使用以删除缓存的信息,您就有了一个可行的解决方案。如果您对滚动条的大小和位置很挑剔,您可以根据 QAbstractItemView::rowsInserted
中的一些示例元素计算平均高度,并相应地调整其余部分的大小,而无需 sizeHint
.
参考文献:
- RazrFalcon 的回答为我指明了正确的方向
- 用代码示例回答以在 QTableView 中呈现 HTML:How to make item view render rich (html) text in Qt
- 关于在
QTreeView
中检测 links 的代码示例的回答:Hyperlinks in QTreeView without QLabel
QLabel
和内部 Qt QWidgetTextControl
关于如何处理鼠标 click/move/release for links 的源代码
我使用了基于 Xilexio 代码的稍微改进的解决方案。有 3 个根本区别:
- 垂直对齐,因此如果您将文本放在高于文本的单元格中,它将居中对齐而不是顶部对齐。
- 如果单元格包含图标,文本将右移,因此图标不会显示在文本上方。
- 将遵循突出显示单元格的小部件样式,因此您 select 这个单元格的颜色将与没有委托的其他单元格类似。
这是我的 paint() 函数代码(其余代码保持不变)。
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
QSize iconSize = options.icon.actualSize(options.rect.size);
// right shit the icon
painter->translate(options.rect.left() + iconSize.width(), options.rect.top());
QRect clip(0, 0, options.rect.width() + iconSize.width(), options.rect.height());
painter->setClipRect(clip);
QAbstractTextDocumentLayout::PaintContext ctx;
// Adjust color palette if the cell is selected
if (option.state & QStyle::State_Selected)
ctx.palette.setColor(QPalette::Text, option.palette.color(QPalette::Active, QPalette::HighlightedText));
ctx.clip = clip;
// Vertical Center alignment instead of the default top alignment
painter->translate(0, 0.5*(options.rect.height() - doc.size().height()));
doc.documentLayout()->draw(painter, ctx);
painter->restore();
非常感谢这些代码示例,它帮助我在我的应用程序中实现了类似的功能。我正在使用 Python 3 和 QT5,我想分享我的 Python 代码,如果在 Python.
中实现它可能会有所帮助
请注意,如果您使用 QT Designer 进行 UI 设计,您可以使用“升级”将常规“QTableView”小部件更改为在转换 XML 时自动使用您的自定义小部件Python 使用“pyuic5”编码。
代码如下:
from PyQt5 import QtCore, QtWidgets, QtGui
class CustomTableView(QtWidgets.QTableView):
link_activated = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
self.parent = parent
super().__init__(parent)
self.setMouseTracking(True)
self._mousePressAnchor = ''
self._lastHoveredAnchor = ''
def mousePressEvent(self, event):
anchor = self.anchorAt(event.pos())
self._mousePressAnchor = anchor
def mouseMoveEvent(self, event):
anchor = self.anchorAt(event.pos())
if self._mousePressAnchor != anchor:
self._mousePressAnchor = ''
if self._lastHoveredAnchor != anchor:
self._lastHoveredAnchor = anchor
if self._lastHoveredAnchor:
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
else:
QtWidgets.QApplication.restoreOverrideCursor()
def mouseReleaseEvent(self, event):
if self._mousePressAnchor:
anchor = self.anchorAt(event.pos())
if anchor == self._mousePressAnchor:
self.link_activated.emit(anchor)
self._mousePressAnchor = ''
def anchorAt(self, pos):
index = self.indexAt(pos)
if index.isValid():
delegate = self.itemDelegate(index)
if delegate:
itemRect = self.visualRect(index)
relativeClickPosition = pos - itemRect.topLeft()
html = self.model().data(index, QtCore.Qt.DisplayRole)
return delegate.anchorAt(html, relativeClickPosition)
return ''
class CustomDelegate(QtWidgets.QStyledItemDelegate):
def anchorAt(self, html, point):
doc = QtGui.QTextDocument()
doc.setHtml(html)
textLayout = doc.documentLayout()
return textLayout.anchorAt(point)
def paint(self, painter, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
if options.widget:
style = options.widget.style()
else:
style = QtWidgets.QApplication.style()
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
options.text = ''
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options)
painter.save()
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
painter.translate(0, 0.5*(options.rect.height() - doc.size().height()))
doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
doc.setTextWidth(options.rect.width())
return QtCore.QSize(doc.idealWidth(), doc.size().height())
我正在制作一个字典程序,当用户键入它们时,它在 3 列 QTableView
子类中显示单词定义,从 QAbstractTableModel
子类中获取数据。类似的东西:
我想为文本添加各种格式,我正在使用 QAbstractItemView::setIndexWidget
在数据输入时向每个单元格添加 QLabel
:
WordView.h
#include <QTableView>
class QLabel;
class WordView : public QTableView {
Q_OBJECT
public:
explicit WordView(QWidget *parent = 0);
void rowsInserted(const QModelIndex &parent, int start, int end);
private:
void insertLabels(int row);
void removeLabels(int row);
};
WordView.cpp
#include <QLabel>
#include "WordView.h"
WordView::WordView(QWidget *parent) :
QTableView(parent)
{}
void WordView::rowsInserted(const QModelIndex &parent, int start, int end) {
QTableView::rowsInserted(parent, start, end);
for (int row = start; row <= end; ++row) {
insertLabels(row);
}
}
void WordView::insertLabels(int row) {
for (int i = 0; i < 3; ++i) {
auto label = new QLabel(this);
label->setTextFormat(Qt::RichText);
label->setAutoFillBackground(true);
QModelIndex ix = model()->index(row, i);
label->setText(model()->data(ix, Qt::DisplayRole).toString()); // this has HTML
label->setWordWrap(true);
setIndexWidget(ix, label); // this calls QAbstractItemView::dataChanged
}
}
但是,这非常慢 - 像这样刷新 100 行(删除所有行,然后添加 100 行)大约需要 1 秒。使用原始 QTableView,它工作得很快,但我没有格式化和添加 links(字典中的交叉引用)的能力。 如何让它更快?或者我可以使用什么其他小部件来显示该数据?
我的要求是:
- Adding/removing 大约 1000 行在 ~0.2 秒内,其中大约 30 行将立即可见
- 每个单元格中的
- 多个内部 links (
<a>
?)(例如QLabel
有,QItemDelegate
可能很快,但我不知道不知道如何获取 link 我点击那里的信息) - 允许不同字体大小和颜色、自动换行、不同单元格高度的格式
- 我对
QTableView
并没有死心塌地,任何看起来像可滚动 table 并且看起来与 Qt 图形一致的东西都可以
备注:
- 我尝试用 HTML
<table>
制作一个标签,但速度并没有快多少。似乎QLabel
不是正确的选择。 - 样本中的数据由 JMdict 项目提供。
在你的情况下,QLabel(重新)绘制很慢,而不是 QTableView。 另一方面,QTableView 根本不支持格式化文本。
可能,你唯一的方法是创建你自己的委托,QStyledItemDelegate,并在其中制作你自己的绘画和点击处理。
PS: 是的,您可以使用 QTextDocument 在委托中呈现 html,但它也会很慢。
我通过整理几个答案并查看 Qt 的内部结构解决了这个问题。
对于 QTableView
中具有 links 的静态 html 内容非常快速的解决方案如下:
- 子类
QTableView
并在那里处理鼠标事件; - 子类
QStyledItemDelegate
并在那里绘制 html (与 RazrFalcon 的回答相反,它非常快,因为一次只能看到少量的单元格并且只有那些paint()
调用的方法); - 在子类
QStyledItemDelegate
中创建一个函数,计算出QAbstractTextDocumentLayout::anchorAt()
单击了哪个 link。您不能自己创建QAbstractTextDocumentLayout
,但您可以从QTextDocument::documentLayout()
获取它,并且根据 Qt 源代码,它保证是非空的。 - 在子类
QTableView
中根据指针是否悬停在 link 上相应地修改
QCursor
指针形状
下面是 QTableView
和 QStyledItemDelegate
子类的完整有效实现,它们绘制 HTML 并在 link hover/activation 上发送信号。 delegate和model还是要在外面设置,如下:
wordTable->setModel(&myModel);
auto wordItemDelegate = new WordItemDelegate(this);
wordTable->setItemDelegate(wordItemDelegate); // or just choose specific columns/rows
WordView.h
class WordView : public QTableView {
Q_OBJECT
public:
explicit WordView(QWidget *parent = 0);
signals:
void linkActivated(QString link);
void linkHovered(QString link);
void linkUnhovered();
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
private:
QString anchorAt(const QPoint &pos) const;
private:
QString _mousePressAnchor;
QString _lastHoveredAnchor;
};
WordView.cpp
#include <QApplication>
#include <QCursor>
#include <QMouseEvent>
#include "WordItemDelegate.h"
#include "WordView.h"
WordView::WordView(QWidget *parent) :
QTableView(parent)
{
// needed for the hover functionality
setMouseTracking(true);
}
void WordView::mousePressEvent(QMouseEvent *event) {
QTableView::mousePressEvent(event);
auto anchor = anchorAt(event->pos());
_mousePressAnchor = anchor;
}
void WordView::mouseMoveEvent(QMouseEvent *event) {
auto anchor = anchorAt(event->pos());
if (_mousePressAnchor != anchor) {
_mousePressAnchor.clear();
}
if (_lastHoveredAnchor != anchor) {
_lastHoveredAnchor = anchor;
if (!_lastHoveredAnchor.isEmpty()) {
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
emit linkHovered(_lastHoveredAnchor);
} else {
QApplication::restoreOverrideCursor();
emit linkUnhovered();
}
}
}
void WordView::mouseReleaseEvent(QMouseEvent *event) {
if (!_mousePressAnchor.isEmpty()) {
auto anchor = anchorAt(event->pos());
if (anchor == _mousePressAnchor) {
emit linkActivated(_mousePressAnchor);
}
_mousePressAnchor.clear();
}
QTableView::mouseReleaseEvent(event);
}
QString WordView::anchorAt(const QPoint &pos) const {
auto index = indexAt(pos);
if (index.isValid()) {
auto delegate = itemDelegate(index);
auto wordDelegate = qobject_cast<WordItemDelegate *>(delegate);
if (wordDelegate != 0) {
auto itemRect = visualRect(index);
auto relativeClickPosition = pos - itemRect.topLeft();
auto html = model()->data(index, Qt::DisplayRole).toString();
return wordDelegate->anchorAt(html, relativeClickPosition);
}
}
return QString();
}
WordItemDelegate.h
#include <QStyledItemDelegate>
class WordItemDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit WordItemDelegate(QObject *parent = 0);
QString anchorAt(QString html, const QPoint &point) const;
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
WordItemDelegate.cpp
#include <QPainter>
#include <QTextDocument>
#include <QAbstractTextDocumentLayout>
#include "WordItemDelegate.h"
WordItemDelegate::WordItemDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{}
QString WordItemDelegate::anchorAt(QString html, const QPoint &point) const {
QTextDocument doc;
doc.setHtml(html);
auto textLayout = doc.documentLayout();
Q_ASSERT(textLayout != 0);
return textLayout->anchorAt(point);
}
void WordItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter);
painter->translate(options.rect.left(), options.rect.top());
QRect clip(0, 0, options.rect.width(), options.rect.height());
doc.drawContents(painter, clip);
painter->restore();
}
QSize WordItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
QTextDocument doc;
doc.setHtml(options.text);
doc.setTextWidth(options.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
请注意,此解决方案之所以快,只是因为一次呈现了一小部分行,因此一次呈现的 QTextDocument
并不多。一次自动调整所有行高或列宽仍然很慢。如果你需要那个功能,你可以让委托通知视图它画了一些东西,然后让视图调整 height/width 如果它之前没有。将其与 QAbstractItemView::rowsAboutToBeRemoved
结合使用以删除缓存的信息,您就有了一个可行的解决方案。如果您对滚动条的大小和位置很挑剔,您可以根据 QAbstractItemView::rowsInserted
中的一些示例元素计算平均高度,并相应地调整其余部分的大小,而无需 sizeHint
.
参考文献:
- RazrFalcon 的回答为我指明了正确的方向
- 用代码示例回答以在 QTableView 中呈现 HTML:How to make item view render rich (html) text in Qt
- 关于在
QTreeView
中检测 links 的代码示例的回答:Hyperlinks in QTreeView without QLabel QLabel
和内部 QtQWidgetTextControl
关于如何处理鼠标 click/move/release for links 的源代码
我使用了基于 Xilexio 代码的稍微改进的解决方案。有 3 个根本区别:
- 垂直对齐,因此如果您将文本放在高于文本的单元格中,它将居中对齐而不是顶部对齐。
- 如果单元格包含图标,文本将右移,因此图标不会显示在文本上方。
- 将遵循突出显示单元格的小部件样式,因此您 select 这个单元格的颜色将与没有委托的其他单元格类似。
这是我的 paint() 函数代码(其余代码保持不变)。
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
QSize iconSize = options.icon.actualSize(options.rect.size);
// right shit the icon
painter->translate(options.rect.left() + iconSize.width(), options.rect.top());
QRect clip(0, 0, options.rect.width() + iconSize.width(), options.rect.height());
painter->setClipRect(clip);
QAbstractTextDocumentLayout::PaintContext ctx;
// Adjust color palette if the cell is selected
if (option.state & QStyle::State_Selected)
ctx.palette.setColor(QPalette::Text, option.palette.color(QPalette::Active, QPalette::HighlightedText));
ctx.clip = clip;
// Vertical Center alignment instead of the default top alignment
painter->translate(0, 0.5*(options.rect.height() - doc.size().height()));
doc.documentLayout()->draw(painter, ctx);
painter->restore();
非常感谢这些代码示例,它帮助我在我的应用程序中实现了类似的功能。我正在使用 Python 3 和 QT5,我想分享我的 Python 代码,如果在 Python.
中实现它可能会有所帮助请注意,如果您使用 QT Designer 进行 UI 设计,您可以使用“升级”将常规“QTableView”小部件更改为在转换 XML 时自动使用您的自定义小部件Python 使用“pyuic5”编码。
代码如下:
from PyQt5 import QtCore, QtWidgets, QtGui
class CustomTableView(QtWidgets.QTableView):
link_activated = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
self.parent = parent
super().__init__(parent)
self.setMouseTracking(True)
self._mousePressAnchor = ''
self._lastHoveredAnchor = ''
def mousePressEvent(self, event):
anchor = self.anchorAt(event.pos())
self._mousePressAnchor = anchor
def mouseMoveEvent(self, event):
anchor = self.anchorAt(event.pos())
if self._mousePressAnchor != anchor:
self._mousePressAnchor = ''
if self._lastHoveredAnchor != anchor:
self._lastHoveredAnchor = anchor
if self._lastHoveredAnchor:
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
else:
QtWidgets.QApplication.restoreOverrideCursor()
def mouseReleaseEvent(self, event):
if self._mousePressAnchor:
anchor = self.anchorAt(event.pos())
if anchor == self._mousePressAnchor:
self.link_activated.emit(anchor)
self._mousePressAnchor = ''
def anchorAt(self, pos):
index = self.indexAt(pos)
if index.isValid():
delegate = self.itemDelegate(index)
if delegate:
itemRect = self.visualRect(index)
relativeClickPosition = pos - itemRect.topLeft()
html = self.model().data(index, QtCore.Qt.DisplayRole)
return delegate.anchorAt(html, relativeClickPosition)
return ''
class CustomDelegate(QtWidgets.QStyledItemDelegate):
def anchorAt(self, html, point):
doc = QtGui.QTextDocument()
doc.setHtml(html)
textLayout = doc.documentLayout()
return textLayout.anchorAt(point)
def paint(self, painter, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
if options.widget:
style = options.widget.style()
else:
style = QtWidgets.QApplication.style()
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
options.text = ''
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options)
painter.save()
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
painter.translate(0, 0.5*(options.rect.height() - doc.size().height()))
doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
doc.setTextWidth(options.rect.width())
return QtCore.QSize(doc.idealWidth(), doc.size().height())