Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt Модель/Представление _ Delegate 2 - HtmlDelegate

Автор: jim1406 10.4.2009, 11:05

Доброго времени суток!..
Задача - отображать в таблице и/или списке форматированный текст (переносы строки, различные шрифты и пр. смотри, как вариант, firefox). Делать виджета-наследника не очень желательно. Хочется использовать делегаты. Подскажите, с какого боку лучше подойти? Отрисовывать итемы в виде различных контролов я научился (в прошлой теме Delegate :) ). Предполагал это и использовать, но что-то застопорился, не найдя подходящего контрола QStyle::CE_ , умеющего отображать, скажем, html.

Автор: Litkevich Yuriy 10.4.2009, 11:09

Цитата(jim1406 @ 10.4.2009, 15:05) *
умеющего отображать, скажем, html.
А у тебя какая версия Qt?

тот же QLable может форматированный текст отображать

Автор: jim1406 13.4.2009, 5:46

Цитата(Litkevich Yuriy @ 10.4.2009, 15:09) *
А у тебя какая версия Qt?

тот же QLable может форматированный текст отображать


Qt 4.5
Про QLabel я тоже думал... Только среди стилей контролов QStyle::CE_ не нашел его :( ....

Автор: igor_bogomolov 14.4.2009, 0:07

jim1406, с тебя пиво :drinks:
Шучу. :D
Интересную ты задачку задал, если честно не сразу справился. Но было очень интерестно повозиться, т.ч. спасибо.
В архиве готовый делегат, с тестовым примером. Обрати внимание на параметр Qt::Alignment align, который передается в конструкторе, он позволяет ориентировать текст в ячейке.

Раскрывающийся текст
#include <QtGui>
#include "htmltextdelegate.h"

void HtmlDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                         const QModelIndex &index) const
{
    QString str = index.data(Qt::DisplayRole).toString();
    QTextDocument td;
    td.setHtml(str);

    QStyleOptionViewItem opt = option;
    QRectF lr = layoutRect(td, opt.rect);

    painter->save();
    painter->translate(lr.topLeft());
    painter->setClipRect(lr.translated(-lr.x(), -lr.y()));
    td.drawContents(painter, QRectF());
    painter->restore();
}

QWidget *HtmlDelegate::createEditor(QWidget *parent,
                                    const QStyleOptionViewItem &option,
                                    const QModelIndex &index) const
{
    QTextEdit *textEdit = new QTextEdit(parent);
    return textEdit;
}

void HtmlDelegate::setEditorData(QWidget *editor,
                                 const QModelIndex &index) const
{
    QString str = index.data(Qt::DisplayRole).toString();
    QTextEdit *textEdit = qobject_cast<QTextEdit*>(editor);
    textEdit->setHtml(str);
}

void HtmlDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                const QModelIndex &index) const
{
    QTextEdit *textEdit = qobject_cast<QTextEdit*>(editor);
    QString str = textEdit->toHtml();
    model->setData(index, str, Qt::DisplayRole);
}


QRectF HtmlDelegate::layoutRect(const QTextDocument & td, QRect rect) const
{
    QRectF rectf;
    qreal xo;
    qreal yo;

    if(align & Qt::AlignLeft) {
        xo = 0;
    } else if(align & Qt::AlignRight) {
        qreal rw = td.documentLayout()->documentSize().width();
        xo = 0;
        xo = qMax((rect.width()-rw), qreal(0));
    } else if(align & Qt::AlignHCenter) {
        qreal rw = td.documentLayout()->documentSize().width();
        xo = 0;
        xo = qMax((rect.width()-rw)/2, qreal(0));
    }

    if(align & Qt::AlignTop) {
        yo = 0;
    } else if(align & Qt::AlignBottom) {
        qreal rh = td.documentLayout()->documentSize().height();
        yo = 0;
        yo = qMax((rect.height()-rh), qreal(0));
    } else if(align & Qt::AlignVCenter) {
        qreal rh = td.documentLayout()->documentSize().height();
        yo = 0;
        yo = qMax((rect.height()-rh)/2, qreal(0));
    }
    return QRectF(xo + rect.x(), yo + rect.y(), rect.width(), rect.height());
}


 

 htmltextdelegate.zip ( 2.36 килобайт ) : 407
 

Автор: jim1406 14.4.2009, 4:20

Цитата(igor_bogomolov @ 14.4.2009, 4:07) *
jim1406, с тебя пиво :drinks:
Шучу. :D
Интересную ты задачку задал, если честно не сразу справился. Но было очень интерестно повозиться, т.ч. спасибо.
В архиве готовый делегат, с тестовым примером. Обрати внимание на параметр Qt::Alignment align, который передается в конструкторе, он позволяет ориентировать текст в ячейке.


Спасибо!!! Сейчас буду разбираться в деталях... Насчет использования QTextDocument я уже допетрил, но, насколько я понял, ты решил то, что не получалось у меня - нормальное отображение итема.... Еще раз :clapping:

Автор: igor_bogomolov 14.4.2009, 8:24

Цитата(jim1406 @ 14.4.2009, 5:20) *
Насчет использования QTextDocument я уже допетрил
А я вот долго допетрить не мог. Пока исходники QLabel не перерыл и не узнал, что у QTextDocument drawContents есть. Идея нормального отображения тоже частично у QLabel подсмотрена. А перед этим, не поверите, пытался сам все отрисовать через тот же QTextDocument, QTextCursor и QFontMetrics. Видилибы вы этого уродца :D

Автор: SABROG 14.4.2009, 11:12

    if(align & Qt::AlignLeft) {
        xo = 0;
    }


Это ты таким образом пытаешься скорость отрисовки увеличить?

Автор: igor_bogomolov 14.4.2009, 11:36

Цитата(SABROG @ 14.4.2009, 12:12) *
Это ты таким образом пытаешься скорость отрисовки увеличить?
Логика была сначала несколько подругому построена. А это остаточные рудименты. Я как результата достиг, на этом и успокоился. Надо было конечно ради приличая подчистить код, а то вот теперь и обо мне плохо думают :rolleyes:

Для скорости уж не сильно критично. Только глаз немного мазолит. А человеку помог, да и интерес смотрю есть, восемь закачек уже :rolleyes:

Автор: jim1406 14.4.2009, 11:42

Не могу сообразить, как в QComboBox нормально такие итемы отрисовать :( . Там такого ресайза, как в таблице нету...
Изначально идея была в выпадающем списке отрисовывать сложно-форматированный текст...

Автор: SABROG 14.4.2009, 20:47

Хочу напомнить о существовании одной статьи в 24 выпуске Qt Quarterly: http://doc.trolltech.com/qq/qq24-textlayouts.html
(она есть и на этом сайте, но тут какой-то косяк с форматированием кода - http://www.crossplatform.ru/?q=node/544

Имхо QTextLayout полегковеснее будет, чем весь QTextDocument. Это конечно, если людям понадобится только отображение форматированного текста.
Или вообще использовать QTextLine, если надо отрисовать всего-лишь одну строку:

void QTextLine::draw ( QPainter * painter, const QPointF & position, const QTextLayout::FormatRange * selection = 0 ) const

Автор: Litkevich Yuriy 14.4.2009, 21:30

Цитата(SABROG @ 15.4.2009, 0:47) *
она есть и на этом сайте, но тут какой-то косяк с форматированием кода
лучше в вики смотреть, там и перевести можно: http://www.wiki.crossplatform.ru/index.php/Low-Level_Text_Layouts

Автор: dezconnect 17.5.2010, 9:07

Цитата(Litkevich Yuriy @ 15.4.2009, 2:30) *
Цитата(SABROG @ 15.4.2009, 0:47) *
она есть и на этом сайте, но тут какой-то косяк с форматированием кода
лучше в вики смотреть, там и перевести можно: http://www.wiki.crossplatform.ru/index.php/Low-Level_Text_Layouts


Ну все клева работает =)
Вопрос только один.... как бы ширину QTextDocument подогнать под ширину колонки TableView и сделать перенос строк если строка длинее =\

Автор: dezconnect 19.5.2010, 12:59

Долго ли коротко ли получилось следующее:


Раскрывающийся текст
void HtmlDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                         const QModelIndex &index) const
{
    QTextDocument td;
    td.setHtml(index.data(Qt::DisplayRole).toString());
    td.adjustSize();

    QAbstractTextDocumentLayout::PaintContext context;
    context.palette = option.palette;

    painter->save();

    QAbstractTextDocumentLayout *layout = td.documentLayout();

    painter->translate(0, option.rect.y());

    layout->draw(painter, context);
    painter->restore();


}

QSize HtmlDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index) const
{

        QTextDocument td;
        td.setHtml(index.data(Qt::DisplayRole).toString());
        td.adjustSize();
        return td.documentLayout()->documentSize().toSize();

}



теперь ячейка подгоняется под размер adjustSize (); но попрежнему не то что хотелось бы.... если придумать как реализовать word wrap... то можно будет подогнать и под него думаю.

Автор: Litkevich Yuriy 19.5.2010, 13:17

Цитата(dezconnect @ 19.5.2010, 16:59) *
если придумать как реализовать word wrap
посмотри реализацию QLabel, там есть такая возможность

Автор: dezconnect 19.5.2010, 13:43

Цитата(Litkevich Yuriy @ 19.5.2010, 18:17) *
Цитата(dezconnect @ 19.5.2010, 16:59) *
если придумать как реализовать word wrap
посмотри реализацию QLabel, там есть такая возможность


я смотрел там через внутренний макрос Q_D задается TextWordWrap...

Автор: Litkevich Yuriy 19.5.2010, 14:12

Цитата(dezconnect @ 19.5.2010, 17:43) *
макрос Q_D
этот макрос всего лишь объявляет личные (внутренние) данные. Т.е. запись:
Q_D(SomeClass)
d->someFunc();

означает:
SomeClassPrivate *const d = d_func(); // получает указатель на внутренние данные
d->someFunc();
следовательно смотри соответствующий метод этого внутреннего класса (обычно этот класс описан раньше основного)

Автор: dezconnect 20.5.2010, 10:44

Цитата(Litkevich Yuriy @ 19.5.2010, 19:12) *
Цитата(dezconnect @ 19.5.2010, 17:43) *
макрос Q_D
этот макрос всего лишь объявляет личные (внутренние) данные. Т.е. запись:
Q_D(SomeClass)
d->someFunc();

означает:
SomeClassPrivate *const d = d_func(); // получает указатель на внутренние данные
d->someFunc();
следовательно смотри соответствующий метод этого внутреннего класса (обычно этот класс описан раньше основного)



ммм, вообще в QTextDocument есть такая фича как setTextWidth() =) помогло =)

продолжаю копания в сторону sizeHint() ... если в paint() передается painter и ессесно по нему можно узнать ширину TableView через painter->window.width(), то в sizeHint такого счастья не наблюдаю ... Может кто сталкивался...

и что ловить если у меня моделью является QSqlQueryModel и для первых 256 записей, sizeHint отрабатывает корректно, после "дозагрузки" данных, они все ломятся высотой в 22 пиксела =( это куда копять опять же...

Автор: dezconnect 24.10.2010, 14:30

Цитата(dezconnect @ 20.5.2010, 15:44) *
Цитата(Litkevich Yuriy @ 19.5.2010, 19:12) *
Цитата(dezconnect @ 19.5.2010, 17:43) *
макрос Q_D
этот макрос всего лишь объявляет личные (внутренние) данные. Т.е. запись:
Q_D(SomeClass)
d->someFunc();

означает:
SomeClassPrivate *const d = d_func(); // получает указатель на внутренние данные
d->someFunc();
следовательно смотри соответствующий метод этого внутреннего класса (обычно этот класс описан раньше основного)



ммм, вообще в QTextDocument есть такая фича как setTextWidth() =) помогло =)

продолжаю копания в сторону sizeHint() ... если в paint() передается painter и ессесно по нему можно узнать ширину TableView через painter->window.width(), то в sizeHint такого счастья не наблюдаю ... Может кто сталкивался...

и что ловить если у меня моделью является QSqlQueryModel и для первых 256 записей, sizeHint отрабатывает корректно, после "дозагрузки" данных, они все ломятся высотой в 22 пиксела =( это куда копять опять же...


Трабла с дозагрузкой данных в TableView из модели актуален по прежнему

Автор: Obey-Kun 1.12.2010, 6:49

Цитата(igor_bogomolov @ 14.4.2009, 0:07) *
jim1406, с тебя пиво :drinks:
Шучу. :D
Интересную ты задачку задал, если честно не сразу справился. Но было очень интерестно повозиться, т.ч. спасибо.
В архиве готовый делегат, с тестовым примером. Обрати внимание на параметр Qt::Alignment align, который передается в конструкторе, он позволяет ориентировать текст в ячейке.

Раскрывающийся текст
#include <QtGui>
#include "htmltextdelegate.h"

void HtmlDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                         const QModelIndex &index) const
{
    QString str = index.data(Qt::DisplayRole).toString();
    QTextDocument td;
    td.setHtml(str);

    QStyleOptionViewItem opt = option;
    QRectF lr = layoutRect(td, opt.rect);

    painter->save();
    painter->translate(lr.topLeft());
    painter->setClipRect(lr.translated(-lr.x(), -lr.y()));
    td.drawContents(painter, QRectF());
    painter->restore();
}

QWidget *HtmlDelegate::createEditor(QWidget *parent,
                                    const QStyleOptionViewItem &option,
                                    const QModelIndex &index) const
{
    QTextEdit *textEdit = new QTextEdit(parent);
    return textEdit;
}

void HtmlDelegate::setEditorData(QWidget *editor,
                                 const QModelIndex &index) const
{
    QString str = index.data(Qt::DisplayRole).toString();
    QTextEdit *textEdit = qobject_cast<QTextEdit*>(editor);
    textEdit->setHtml(str);
}

void HtmlDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                const QModelIndex &index) const
{
    QTextEdit *textEdit = qobject_cast<QTextEdit*>(editor);
    QString str = textEdit->toHtml();
    model->setData(index, str, Qt::DisplayRole);
}


QRectF HtmlDelegate::layoutRect(const QTextDocument & td, QRect rect) const
{
    QRectF rectf;
    qreal xo;
    qreal yo;

    if(align & Qt::AlignLeft) {
        xo = 0;
    } else if(align & Qt::AlignRight) {
        qreal rw = td.documentLayout()->documentSize().width();
        xo = 0;
        xo = qMax((rect.width()-rw), qreal(0));
    } else if(align & Qt::AlignHCenter) {
        qreal rw = td.documentLayout()->documentSize().width();
        xo = 0;
        xo = qMax((rect.width()-rw)/2, qreal(0));
    }

    if(align & Qt::AlignTop) {
        yo = 0;
    } else if(align & Qt::AlignBottom) {
        qreal rh = td.documentLayout()->documentSize().height();
        yo = 0;
        yo = qMax((rect.height()-rh), qreal(0));
    } else if(align & Qt::AlignVCenter) {
        qreal rh = td.documentLayout()->documentSize().height();
        yo = 0;
        yo = qMax((rect.height()-rh)/2, qreal(0));
    }
    return QRectF(xo + rect.x(), yo + rect.y(), rect.width(), rect.height());
}


Красавчик. А аналогично QHeaderView модешь переделать? Только там оно уже не делегаты использует, а само рисует.

Автор: igor_bogomolov 1.12.2010, 10:34

Цитата(Obey-Kun @ 1.12.2010, 6:49) *
А аналогично QHeaderView модешь переделать?
Если упрощенно, то так.
Раскрывающийся текст
void Header::paintEvent(QPaintEvent *e)
{
    if (!count()) return;

    QPainter painter(viewport());
    QRect currentSectionRect;
    const int height = viewport()->height();
    for (int i = 0; i != model()->columnCount(); ++i) {
        painter.save();
        currentSectionRect.setRect(sectionViewportPosition(i), 0, sectionSize(i), height);

        QStyleOptionHeader opt;
        initStyleOption(&opt);

        opt.rect = currentSectionRect;
        opt.section = i;
        style()->drawControl(QStyle::CE_HeaderSection, &opt, &painter, this);


        QString str = model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();

        QTextDocument td;
        td.setHtml(str);
        QRectF lr = layoutRect(td, currentSectionRect);
        painter.translate(lr.topLeft());
        painter.setClipRect(lr.translated(-lr.x(), -lr.y()));
        td.drawContents(&painter, QRectF());

        painter.restore();
    }
}

Если нужен больший функционал от заголовков, смотри в исходники QHeaderView

Автор: Obey-Kun 2.12.2010, 23:00

Спасибо! Только сделаю это с помощью eventFilter, дабы лишний раз не наследовать.
Кстати, с твоим подходом было бы логичнее использовать QHeaderView::paintSection.

Автор: Obey-Kun 2.12.2010, 23:35

Что я делаю не так? В конструкторе делаю

m_table_view->horizontalHeader()->installEventFilter(this);


И делаю метод
bool SoilsWidget::eventFilter(QObject *watched, QEvent *event)
{
    if ( event->type() == QEvent::Paint ) {
        qDebug() << watched << event;
        return true;
    } else {
        return QWidget::eventFilter(watched, event);
    }
}


И ничего. Ничего в консоль не выводится. Хедер рисуется как ни в чём не бывало.

p.s.: блин, наверное надо было новую тему создать, но уже поздно, это сообщение я не могу удалить.

Автор: igor_bogomolov 3.12.2010, 0:10

фильтр нужно устанавливать для viewport заголовка

m_table_view->horizontalHeader()->viewport()->installEventFilter(this);

Автор: Obey-Kun 3.12.2010, 0:31

спасибо! правда, решение с фильтром не прокатило, ибо используются защищённые методы, пришлось унаследовать.
ты там метод layoutRect используешь... что это? компилятор ругается на его отсутствие, в документации не нашёл...

Автор: Obey-Kun 3.12.2010, 1:15

Да уж, при насследовании, оказывается, не удастся восстановить все фичи QHeaderView. Например, учитывать позицию мыши при отрисовке (в некоторых стилях она учитывается). Не получится, так как в оригинальном методе прорисовки используются некоторые приватные методы.
Пока решил повременить с рендеринг rich text у себя в программе. Заодно проголосовал за баг Qt: http://bugreports.qt.nokia.com/browse/QTBUG-2380.

Кстати, насчёт изначального вопроса темы. Вот более короткое (и менее полное) решение: http://developer.qt.nokia.com/faq/answer/how_can_i_have_richtext_in_my_qtableview

Автор: igor_bogomolov 3.12.2010, 8:42

Цитата(Obey-Kun @ 3.12.2010, 0:31) *
ты там метод layoutRect используешь... что это?
Смотри http://www.forum.crossplatform.ru/index.php?s=&showtopic=2565&view=findpost&p=18499. Это функция которую я писал для выравнивания текста в ячейке. Написана она конечно коряво, но это не главное. Кому надо адаптирует
Цитата(Obey-Kun @ 3.12.2010, 1:15) *
Кстати, насчёт изначального вопроса темы. Вот более короткое (и менее полное) решение: http://developer.qt.nokia.com/faq/answer/h...n_my_qtableview
Решение идентичное нашему. Да и сложно придумать что то иное. Сам когда то это решение подсмотрел в исходниках QLabel. К стати, в исходники рекомендую по чаще заглядывать, там можно на многие вопросы ответ найти.

Цитата(Obey-Kun @ 3.12.2010, 1:15) *
Не получится, так как в оригинальном методе прорисовки используются некоторые приватные методы.
Ну так можно код этих приватных методов позаимствовать.
А какой функционал не удалось поддержать?

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)