我通过整合一些答案并查看Qt的内部解决了这个问题。
对于具有链接的静态html内容在
QTableView
中非常快速地工作的解决方案如下:
- 子类化
QTableView
并在那里处理鼠标事件;
- 子类化
QStyledItemDelegate
,并在那里绘制html(与 RazrFalcon 的答案相反,非常快,因为仅有少量单元格是可见的,且仅对其调用了 paint()
方法);
- 在子类化的
QStyledItemDelegate
中创建一个函数,通过QAbstractTextDocumentLayout::anchorAt()
找出哪个链接被点击。您无法自己创建 QAbstractTextDocumentLayout
,但可以从 QTextDocument::documentLayout()
获取它,并且据 Qt 源代码保证不为 null。
- 在子类化的
QTableView
中根据鼠标是否悬停在链接上修改 QCursor
指针形状
以下是完整、可工作的
QTableView
和
QStyledItemDelegate
子类实现,用于绘制HTML并在链接悬停/激活时发送信号。委托和模型仍需在外部设置,如下所示:
wordTable->setModel(&myModel);
auto wordItemDelegate = new WordItemDelegate(this);
wordTable->setItemDelegate(wordItemDelegate); // or just choose specific columns/rows
WordView.h
->
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)
{
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
=>
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());
}
请注意,此解决方案之所以快速,是因为一次仅呈现了一个小子集的行,因此没有同时呈现许多。一次自动调整所有行高或列宽仍然很慢。如果您需要该功能,可以使委托通知视图它绘制了某些内容,然后使视图调整高度/宽度(如果之前没有)。将其与结合使用以删除缓存信息,您就有了一个可行的解决方案。如果您对滚动条大小和位置挑剔,您可以基于中的几个示例元素计算平均高度,并相应地调整剩余部分而不使用。
参考资料:
- RazrFalcon的答案指导了我正确的方向。
- 在QTableView中呈现HTML代码示例的答案:
How to make item view render rich (html) text in Qt
- 在QTreeView中检测链接的代码示例的答案:
Hyperlinks in QTreeView without QLabel
-
QLabel
和内部Qt的
QWidgetTextControl
的源代码,用于处理链接的鼠标单击/移动/释放