如何使用HTML格式的可单击单元格制作快速的QTableView? [英] How to make a fast QTableView with HTML-formatted and clickable cells?
问题描述
我正在制作一个字典程序,当用户键入它们时,它在3列 QTableView
子类中显示单词定义,并从 QAbstractTableModel
子类中获取数据.像这样的东西:
我想向文本添加各种格式,我使用 QAbstractItemView :: setIndexWidget
在数据进入时向每个单元格添加 QLabel
:
WordView.h
#include< QTableView>QLabel类;WordView类:公共QTableView {Q_OBJECT上市:显式WordView(QWidget * parent = 0);void rowsInserted(const QModelIndex& parent,int start,int end);私人的:void insertLabels(int row);void removeLabels(int row);};
WordView.cpp
#include< QLabel>#include"WordView.h"WordView :: WordView(QWidget * parent):QTableView(父级){}无效的WordView :: rowsInserted(const QModelIndex& parent,int start,int end){QTableView :: rowsInserted(父,开始,结束);for(int row = start; row< = end; ++ row){insertLabels(row);}}无效的WordView :: insertLabels(int row){对于(int i = 0; i< 3; ++ i){自动标签=新的QLabel(this);label-> setTextFormat(Qt :: RichText);label-> setAutoFillBackground(true);QModelIndex ix = model()-> index(row,i);label-> setText(model()-> data(ix,Qt :: DisplayRole).toString());//这有HTML标签-> setWordWrap(true);setIndexWidget(ix,标签);//这会调用QAbstractItemView :: dataChanged}}
但是,这非常慢-大约需要1秒才能刷新100行(删除所有行,然后添加100个新行).使用原始的QTableView可以快速运行,但是我没有格式设置和添加链接的能力(字典中的交叉引用).如何使其更快?还是我可以使用什么其他小部件来显示该数据?
我的要求是:
- 在约0.2秒的时间内添加/删除约1000行,一次即可看到约30行 每个单元格中的
- 可点击的多个内部链接(
< a>
?)(例如,QLabel
有,QItemDelegate
可能很快,但我不知道如何获取我点击过的链接的信息) - 允许不同字体大小和颜色,自动换行,不同单元格高度的格式
- 在
QTableView
上我并没有真正陷入僵局,任何看起来像可滚动表格并且看起来与Qt图形一致的东西都可以
注意:
- 我尝试使用HTML
< table>
制作单个标签,但是速度并不快.似乎QLabel
并非可行之路. - 样本中的数据由JMdict项目提供.
我通过汇总几个答案并查看Qt的内部结构来解决了这个问题.
对于静态html内容,其在 QTableView
中具有链接的解决方案非常快,如下所示:
- 子类
QTableView
并在那里处理鼠标事件; - 子类
QStyledItemDelegate
并在其中绘制html(与RazrFalcon的答案相反,它非常快,因为一次只能看到少量单元格,并且只有那些具有paint()的单元格
方法); - 在子类化的
QStyledItemDelegate
中,创建一个函数,该函数可以找出QAbstractTextDocumentLayout :: anchorAt()
单击了哪个链接.您不能自行创建QAbstractTextDocumentLayout
,但可以从QTextDocument :: documentLayout()
获取它,并且根据Qt源代码,它保证是非空的. - 在子类化的
QTableView
中,根据其是否悬停在链接上,修改QCursor
指针形状
下面是 QTableView
和 QStyledItemDelegate
子类的完整,有效的实现,这些子类绘制HTML并在链接悬停/激活时发送信号.委托和模型仍然必须在外部设置,如下所示:
wordTable-> setModel(& myModel);自动wordItemDelegate =新的WordItemDelegate(this);wordTable-> setItemDelegate(wordItemDelegate);//或仅选择特定的列/行
WordView.h
class WordView:public QTableView {Q_OBJECT上市:显式WordView(QWidget * parent = 0);信号:void linkActivated(QString链接);void linkHovered(QString link);void linkUnhovered();受保护的:void mousePressEvent(QMouseEvent * event);void mouseMoveEvent(QMouseEvent * event);void mouseReleaseEvent(QMouseEvent * event);私人的:QString anchorAt(const QPoint& pos)const;私人的:QString _mousePressAnchor;QString _lastHoveredAnchor;};
WordView.cpp
#include< QApplication>#include< QCursor>#include< QMouseEvent>#include"WordItemDelegate.h"#include"WordView.h"WordView :: WordView(QWidget * parent):QTableView(父级){//悬停功能所需setMouseTracking(true);}无效的WordView :: mousePressEvent(QMouseEvent * event){QTableView :: mousePressEvent(event);自动锚点= anchorAt(event-> pos());_mousePressAnchor =锚点;}无效的WordView :: mouseMoveEvent(QMouseEvent * event){自动锚点= anchorAt(event-> pos());如果(_mousePressAnchor!=锚点){_mousePressAnchor.clear();}如果(_lastHoveredAnchor!=锚点){_lastHoveredAnchor =锚点;如果(!_lastHoveredAnchor.isEmpty()){QApplication :: setOverrideCursor(QCursor(Qt :: PointingHandCursor));发出linkHovered(_lastHoveredAnchor);} 别的 {QApplication :: restoreOverrideCursor();发出linkUnhovered();}}}无效的WordView :: mouseReleaseEvent(QMouseEvent * event){如果(!_mousePressAnchor.isEmpty()){自动锚点= anchorAt(event-> pos());如果(锚== _mousePressAnchor){发出linkActivated(_mousePressAnchor);}_mousePressAnchor.clear();}QTableView :: mouseReleaseEvent(event);}QString WordView :: anchorAt(const QPoint& pos)const {自动索引= indexAt(pos);如果(index.isValid()){自动委托= itemDelegate(index);自动wordDelegate = qobject_cast< WordItemDelegate *>(delegate);如果(wordDelegate!= 0){自动itemRect = visualRect(index);自动relativeClickPosition = pos-itemRect.topLeft();自动html = model()-> data(index,Qt :: DisplayRole).toString();返回wordDelegate-> anchorAt(html,relativeClickPosition);}}返回QString();}
WordItemDelegate.h
#include< QStyledItemDelegate>类WordItemDelegate:公共QStyledItemDelegate {Q_OBJECT上市:显式WordItemDelegate(QObject * parent = 0);QString anchorAt(QString html,const QPoint& point)const;受保护的: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(父级){}QString WordItemDelegate :: anchorAt(QString html,const QPoint& point)const {QTextDocument doc;doc.setHtml(html);自动textLayout = doc.documentLayout();Q_ASSERT(textLayout!= 0);返回textLayout-> anchorAt(point);}void WordItemDelegate :: paint(QPainter * painter,const QStyleOptionViewItem& option,const QModelIndex& index)const {自动选项=选项;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 =选项;initStyleOption(& options,index);QTextDocument doc;doc.setHtml(options.text);doc.setTextWidth(options.rect.width());返回QSize(doc.idealWidth(),doc.size().height());}
请注意,此解决方案之所以快速,是因为仅一次渲染一小部分行,因此一次渲染的 QTextDocument
并不多.一次自动调整所有行高或列宽仍然很慢.如果需要该功能,则可以使委托通知视图它已经绘制了一些东西,然后使该视图调整高度/宽度(如果以前没有).将其与 QAbstractItemView :: rowsAboutToBeRemoved
结合使用以删除缓存的信息,您将获得一个可行的解决方案.如果您对滚动条的大小和位置比较挑剔,则可以基于 QAbstractItemView :: rowsInserted
中的一些样本元素来计算平均高度,并相应地调整其余部分的大小,而无需 sizeHint
./p>
参考文献:
- RazrFalcon将我指向正确方向的答案
- 回答代码示例以在QTableView中呈现HTML:没有QLabel的QTreeView
-
QLabel
和内部Qt的QWidgetTextControl
的源代码,介绍如何处理链接的鼠标单击/移动/释放
I'm making a dictionary program that displays word definitions in a 3-column QTableView
subclass, as user types them, taking data from a QAbstractTableModel
subclass. Something like that:
I want to add various formatting to the text, I'm using QAbstractItemView::setIndexWidget
to add a QLabel
to each cell as data comes in:
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
}
}
However, this is very slow - it takes around 1 second to refresh 100 rows (remove all, then add 100 new ones) like that. With original QTableView it worked fast, but I did not have formatting and ability to add links (cross-references in dictionary). How to make this much faster? Or what other widget can I use to display that data?
My requirements are:
- Adding/removing around 1000 rows in ~0.2s, where around 30 will be visible at once
- Clickable, multiple internal links (
<a>
?) in each cell (e.g.QLabel
has that,QItemDelegate
might have been fast, but I don't know how to get info which link I clicked there) - Formatting that allows different font sizes and colors, word wrap, different cell heights
- I'm not really dead-set on
QTableView
, anything that looks like a scrollable table and looks consistent with Qt graphics is okay
Notes:
- I tried making a single label with HTML
<table>
instead, but it wasn't much faster. Seems likeQLabel
isn't the way to go. - Data in the sample courtesy of the JMdict project.
I solved the problem by putting together few answers and looking at Qt's internals.
A solution which works very fast for static html content with links in QTableView
is as folows:
- Subclass
QTableView
and handle mouse events there; - Subclass
QStyledItemDelegate
and paint the html there (contrary to RazrFalcon's answer, it is very fast, as only a small amount of cells is visible at a time and only those havepaint()
method called); - In subclassed
QStyledItemDelegate
create a function that figures out which link was clicked byQAbstractTextDocumentLayout::anchorAt()
. You cannot createQAbstractTextDocumentLayout
yourself, but you can get it fromQTextDocument::documentLayout()
and, according to Qt source code, it's guaranteed to be non-null. - In subclassed
QTableView
modifyQCursor
pointer shape accordingly to whether it's hovering over a link
Below is a complete, working implementation of QTableView
and QStyledItemDelegate
subclasses that paint the HTML and send signals on link hover/activation. The delegate and model still have to be set outside, as follows:
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());
}
Note that this solution is fast only because a small subset of rows is rendered at once, and therefore not many QTextDocument
s are rendered at once. Automatic adjusting all row heights or column widths at once will still be slow. If you need that functionality, you can make the delegate inform the view that it painted something and then making the view adjust the height/width if it hasn't before. Combine that with QAbstractItemView::rowsAboutToBeRemoved
to remove cached information and you have a working solution. If you're picky about scrollbar size and position, you can compute average height based on a few sample elements in QAbstractItemView::rowsInserted
and resize the rest accordingly without sizeHint
.
References:
- RazrFalcon's answer for pointing me to the right direction
- Answer with code sample to render HTML in QTableView: How to make item view render rich (html) text in Qt
- Answer with code sample on detecting links in
QTreeView
: Hyperlinks in QTreeView without QLabel QLabel
's and internal Qt'sQWidgetTextControl
's source code on how to handle mouse click/move/release for links
这篇关于如何使用HTML格式的可单击单元格制作快速的QTableView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!