如何使用HTML格式的可单击单元格制作快速的QTableView? [英] How to make a fast QTableView with HTML-formatted and clickable cells?

查看:89
本文介绍了如何使用HTML格式的可单击单元格制作快速的QTableView?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在制作一个字典程序,当用户键入它们时,它在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 like QLabel 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 have paint() method called);
  • In subclassed QStyledItemDelegate create a function that figures out which link was clicked by QAbstractTextDocumentLayout::anchorAt(). You cannot create QAbstractTextDocumentLayout yourself, but you can get it from QTextDocument::documentLayout() and, according to Qt source code, it's guaranteed to be non-null.
  • In subclassed QTableView modify QCursor 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 QTextDocuments 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:

这篇关于如何使用HTML格式的可单击单元格制作快速的QTableView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆