Russian Qt Forum

Qt => Вопросы новичков => Тема начата: Иваныч от Август 20, 2019, 17:38



Название: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 20, 2019, 17:38
Не в первый раз звучит вопрос :)
Значит, так и не решённый прежде.
Уберегите от флуда, но про иерархические заголовки решения не нашёл.
Опирался на сообщения аж 2008 года.
http://www.prog.org.ru/topic_6637_15.html
Написал всё в рамках изложенной стратегии Zmeishe.
Всё работает, но есть нюансы.
У меня условия - QT 4.8.0.
Не претендую на авторские права.
Готов выложить свой код, поскольку появились вопросы.
Если есть кто живой в этой теме, то помогите неопытному.


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 13:48
И так, всё по порядку.
Это .pro - файл

Код:
QT       += core

TARGET = testTable
TEMPLATE = app

DESTDIR = ../

SOURCES += ./source/main.cpp \
           ./source/testTable.cpp

 
HEADERS += ./include/testTable.h
          
 
FORMS += ./ui/testTable.ui

INCLUDEPATH += ./include/

INCLUDEPATH += ./tmp/release/moc \
    ./tmp/release/uic \
    ./tmp/release/rcc

MOC_DIR += ./tmp/release/moc
OBJECTS_DIR += ./tmp/release/objecs
UI_DIR += ./tmp/release/uic
RCC_DIR += ./tmp/release/rcc

это testTable.ui


Код:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>testTable</class>
 <widget class="QMainWindow" name="testTable">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>650</width>
    <height>428</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Тестирование таблицы</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QTableWidget" name="tableWidget"/>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>650</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 13:50
Содержимое main.cpp

Код:
#include "testTable.h"
#include <QTextCodec>
#include <QTranslator>

int main(int argc, char* argv[])
{

    // UNICODE
    QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
    QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
    
    QApplication app(argc, argv);
    QTranslator qtTranslator;
    qtTranslator.load("qt_" + QLocale::system().name(),"./translations");

    app.installTranslator(&qtTranslator);

    CTestTable ctrlWindow;
    ctrlWindow.show();

    return app.exec();
}


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 13:51
Содержимое testTable.h

Код:
#ifndef TEST_TABLE_H_21_08_2019_11_49
#define TEST_TABLE_H_21_08_2019_11_49

#include "ui_testTable.h"

#include <QStandardItemModel>

class CDataHeaderView: public QHeaderView
{
    Q_OBJECT

public:
    CDataHeaderView(QWidget* pParent);
    ~CDataHeaderView();

protected:
    virtual QSize sizeHint() const;
    virtual void paintSection(QPainter* pPainter,const QRect& rect,int logicalIndex) const;

private:
    QStandardItemModel m_headerModel;
    int m_rowCount;
};

class CTestTable:public QMainWindow, public Ui::testTable
{
    Q_OBJECT

public:
    CTestTable();
    ~CTestTable();
};

#endif // TEST_TABLE_H_21_08_2019_11_49


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 13:52
Содержимое testTable.cpp

Код:
#include "testTable.h"
#include <QPainter>

CDataHeaderView::CDataHeaderView(QWidget* pParent):
QHeaderView(Qt::Horizontal,pParent),
m_headerModel(this)
{
    m_rowCount=2;//пока без вычислений просто задаю две строки

    //Формирую сложный столбец
    QStandardItem* pItem=new QStandardItem(tr("Период"));
    QList<QStandardItem*> col;
    col.append(new QStandardItem(tr("Начало")));
    col.append(new QStandardItem(tr("Конец")));
    pItem->appendRow(col);//Добавил два подстолбца

    QList<QStandardItem*> columns;
    columns.append(pItem);//Сложный столбец в общую строку

    //Далее несколько формальных столбцов
    columns.append(new QStandardItem("Второй"));

    columns.append(new QStandardItem("Третий"));

    columns.append(new QStandardItem("Четвертый"));

    m_headerModel.appendRow(columns);//Добавляю строку в модель

    setModel(&m_headerModel);//Устанавливаю модель для CDataHeaderView
}

CDataHeaderView::~CDataHeaderView()
{

}

QSize CDataHeaderView::sizeHint() const
{
    QSize size=QHeaderView::sizeHint();
    size.rheight()*=m_rowCount;
    return size;
}


void CDataHeaderView::paintSection(QPainter* pPainter,const QRect& rect,int logicalIndex) const
{
    //Пока, для появления ощущений, рисую прямоугольник и вывожу тест
    QStandardItem* pItem=m_headerModel.item(0,logicalIndex);
    pPainter->drawRect(rect);
    pPainter->drawText(rect,Qt::AlignHCenter|Qt::AlignVCenter,QString("%1").arg(pItem->text()));
}


CTestTable::CTestTable():
QMainWindow(NULL)
{
    setupUi(this);
    //Устанавливаю горизонтальный заголовок для таблицы
    tableWidget->setHorizontalHeader(new CDataHeaderView(tableWidget));
}

CTestTable::~CTestTable()
{
    
}


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 13:54
Реализую стратегию выше упомянутого Zmeishe, за что ему очень благодарен.


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 14:02
Наблюдаем результат
(http://)


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 14:05
Получается, что QTableWidget не видит вложенные столбцы,
а значит мне их нужно добавлять вручную?


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 14:07
У меня есть код с ручным добавлением и я его выложу,
 но может это уже "костыль"? Что я на данном этапе не так делаю?


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 16:05
Изменяю код
Код:
CDataHeaderView::CDataHeaderView(QWidget* pParent):
QHeaderView(Qt::Horizontal,pParent),
m_headerModel(this)
{
    m_rowCount=2;//пока без вычислений просто задаю две строки

    //Формирую сложный столбец
    QStandardItem* pItem=new QStandardItem(tr("Период"));
    QList<QStandardItem*> col;
    col.append(new QStandardItem(tr("Начало")));
    col.append(new QStandardItem(tr("Конец")));
    pItem->appendRow(col);//Добавил два подстолбца

    QList<QStandardItem*> columns;
    columns.append(pItem);//Сложный столбец в общую строку

    [color=red]//Искусственно добавляю еще один столбец
    pItem=new QStandardItem(tr("без имени"));//дополнительный столбец
    QVariant data(-1);//и пока "костыльно" его помечаю
    pItem->setData(data);
    columns.append(pItem);[/color]

    //Далее несколько формальных столбцов
    columns.append(new QStandardItem("Второй"));



    columns.append(new QStandardItem("Третий"));

    columns.append(new QStandardItem("Четвертый"));

    m_headerModel.appendRow(columns);//Добавляю строку в модель

    setModel(&m_headerModel);//Устанавливаю модель для CDataHeaderView
}


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 16:07
Модифицирую код отрисовки
Код:
void CDataHeaderView::paintSection(QPainter* pPainter,const QRect& rect,int logicalIndex) const
{
QStandardItem* pItem=m_headerModel.item(0,logicalIndex);
    //Теперь начинаю разбирать новости
int colCount=pItem->columnCount();//ловлю элементы с вложенными колонками

QRect rc=rect;
if(colCount)//знаю про первую сложную колонку
    {
        int w=sectionSize(1);//индекс пока в тупую 1
        rc.setWidth(rc.width()+w);
        rc.setHeight(QHeaderView::sizeHint().height());
    }
QVariant data=pItem->data();
    if(!data.isNull())//пока вот так ловлю "костыль"
    {
        return;//и ничего тут не рисую
    }
    pPainter->drawRect(rc);
    pPainter->drawText(rc,Qt::AlignHCenter|Qt::AlignVCenter,QString("%1").arg(pItem->text()));
}


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 16:09
Наблюдаем результат
(http://)


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 16:12
И всё бы хорошо, но стоит провести курсор над заголовком - начинаются неприятности.
Понятно, что я не рисую "костыльный" заголовок, но попытка его отрисовать -
тоже не спасает. Помогите форумчане ;)


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 16:13
Подожду ответа :)


Название: Re: QTableWidget иерархические заголовки
Отправлено: ViTech от Август 21, 2019, 16:50
Лучше упакуйте все необходимые файлы проекта в архив и "вложите" в сообщение, тогда может кто-нибудь и посмотрит его.


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 21, 2019, 17:00
Спасибо за совет :) Я же новичок:)
В архиве последняя версия.


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 22, 2019, 11:23
Чёт я сам с собою :)
Иногда полезно просто высказаться.
paintSection константная функция и вызвать из нее слот для обновления соседней секции ну никак не удавалось.
Написал в своем классе константный сигнал и законнектил на неконстантный слот. Всё заработало.
Новый код paintSection

Код:
void CDataHeaderView::paintSection(QPainter* pPainter,const QRect& rect,int logicalIndex) const
{
QStandardItem* pItem=m_headerModel.item(0,logicalIndex);
QVariant data=pItem->data();
QRect rc=rect;
    if(!data.isNull())//пока вот так ловлю "костыль"
    {
        pItem=m_headerModel.item(0,0);
rc.setLeft(sectionPosition(0));
rc.setWidth(sectionSize(0));
    }

    //Теперь начинаю разбирать новости
int colCount=pItem->columnCount();//ловлю элементы с вложенными колонками


if(colCount)//знаю про первую сложную колонку
    {
        int w=sectionSize(1);//индекс пока в тупую 1
        rc.setWidth(rc.width()+w);
        rc.setHeight(QHeaderView::sizeHint().height());

QRect rcs=rc;
QList<QStandardItem*>cols=pItem->takeRow(0);
for(int n=0;n<cols.size();n++)
{
QStandardItem* pColItem=cols[n];
rcs.setLeft(sectionPosition(n));
rcs.setWidth(sectionSize(n));
rcs.setTop(QHeaderView::sizeHint().height());
rcs.setHeight(QHeaderView::sizeHint().height());
pPainter->drawRect(rcs);
QString name=pColItem->text();
pPainter->drawText(rcs,Qt::AlignHCenter|Qt::AlignVCenter,name);
}
    }

    pPainter->drawRect(rc);
    pPainter->drawText(rc,Qt::AlignHCenter|Qt::AlignVCenter,pItem->text());

//if(colCount)//знаю про первую сложную колонку
    //{

    //}
if(!data.isNull())//пока вот так ловлю "костыль"
emit drawSection(0);
}


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 22, 2019, 11:27
Сознательно не усложняю код для обработки иерархии.
Хочу на простом примере разобраться.
Сейчас ну никак не рисуются два нижних подзаголовка, хотя отладчик туда попадает.
И геометрия правильная.
(http://)


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 22, 2019, 12:20
Ну что, благодаря форуму вопрос решил :)
Последняя ошибка связана была с тем, что QStandardItem::takeRow(...)
не только возвращает столбцы строки, но и удаляет их.
Перешел на функцию child(...) и всё заработало.


Название: Re: QTableWidget иерархические заголовки
Отправлено: ViTech от Август 22, 2019, 12:43
Ну что, благодаря форуму вопрос решил :)

Это хорошо. На форум надейся, а сам не плошай :).


Название: Re: QTableWidget иерархические заголовки
Отправлено: Dimas от Август 22, 2019, 13:14
Для истории прикрепите пожалуйста итоговую версию


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 22, 2019, 14:34
Ещё раз выражаю огромную благодарность
Zmeishe  
http://www.prog.org.ru/topic_6637_15.html
Ответ #25 : Апрель 05, 2008, 08:40
Не нашёл способа на форуме сделать это лично, да и времени прошло... Надеюсь он вырос в нашей среде.
И дай Бог ему здоровья.

Подведу небольшие итоги.
Итак, QHeaderItemView не подхватывает от QStandardItemModel вложенные заголовки,
поэтому лишние столбцы нужно добавлять вручную.

Переопределяемая QHeaderItemView::paintSection(...) const не позволяет внутри себя вызывать слоты базового класса
для перерисовки объединенных ячеек. Для реализации необходимого был введен константный сигнал
signals:
   void drawSection(int Idx) const;

который я связал с нужным слотом

connect(this,SIGNAL(drawSection(int)),this,SLOT(updateSection(int)));

Работает. Хотя, намеренно последовательно излагал свои шаги, чтобы уберечься от "костылей" :)

Последние штрихи связаны с текущим уровнем новичка, который на Ваших глазах подрос :)
Новичок мотивирован на получение знаний и летает с удовольствием на c++ и Qt :)

Прилагаю финальный архив.
Сейчас занимаюсь абстрагированием изученного кода, чтобы реализовать многоуровневую вложенность заголовков.
Как писал стратег Zmeishe(с)
"
Далее создаём функцию
QVariant headerData(int Section, Qt::Orientation orientation, int nRole) const;
Вообще это функция Модели её там надо было перекрывать, но я сделал здесь, т.к. уже написал, что не принял оконч. решения где им быть.
Для работы headerData понадобятся две рекурсивные функции, которые по int Section будут возвращать номер начальной секции группы (ветки) в которую входит Section и номер конечной секции группы.
"

Некоторое время, пока код не начал обрастать корпоративными особенностями, готов им делиться :)


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 22, 2019, 14:49
Закралась мысль ;)
Будь я поближе к разработчикам Qt, то научил бы QHeaderView правильно разгребать модель,
дабы не устраивать пляски вокруг известной темы иерархических заголовков :)


Название: Re: QTableWidget иерархические заголовки
Отправлено: ViTech от Август 22, 2019, 14:58
Будь я поближе к разработчикам Qt, то научил бы QHeaderView правильно разгребать модель,
дабы не устраивать пляски вокруг известной темы иерархических заголовков :)

Тогда становитесь ближе, вносите свой вклад: Contribute to Qt (https://www.qt.io/contribute-to-qt) :).


Название: Re: QTableWidget иерархические заголовки
Отправлено: ViTech от Август 22, 2019, 15:28
Вот подходящий тикет: Use span data from QAbstractItemModel in QTableView. (https://bugreports.qt.io/browse/QTBUG-6508) Ему всего 10 лет :).

Хотя нет, это не про заголовки.


Название: Re: QTableWidget иерархические заголовки
Отправлено: Авварон от Август 22, 2019, 15:53
Хотя нет, это не про заголовки.

Я начал писать долгий текст с тем же посылом, но потом бросил, ибо...
Если мы хотим иерархические заголовки с несколькими рядами/колонками и спанами то мы придем к тому, что такому заголовку надо устанавливать отдельную _модель_ которая будет отдавать данные для хедера через rowCount/columnCount/index/data а не через headerData. Иначе придется тупо продубрировать весь API еще и для хедера (headerRowCount/headerColumnCount/headerIndex (мы же хотим деревья)/headerData/headerSpan). Кажется, такой подход слишком сложный (особенно если учесть что в горизонтальном хедере должно быть headerRowCount рядов но columnCount (без header) колонок, чтобы колонки/ряды совпадали с теми, что в основной таблице. а в вертикальном - всё наоборот). А еще надо теперь не запутаться где headerIndex а где обычный и не перепутать их случайно.
Так что да, это таки применимо, но как указано выше - для кастомной view, которая игнорит headerData и берет данные из отдельной полноценной модели.


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 22, 2019, 16:56
Абстрагировался немного. Теперь дополнительные столбцы мечу индексами в подзаголовках.
Шалю с третьим столбцом.
Архив и результат на экране выкладываю.
В результате смущает игра с толщиной текста в сложных заголовках.
Понятно, что от лишней перерисовки ??? не зря же слот так просто не вызывался.
Руки чешутся - теперь абстрактно пойду на третий этаж заголовка :)


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 22, 2019, 17:34
Мне бы в данный момент (для лазания по этажам) пригодился QStandatrdItem::parent(),
но он в нуле, казалось бы у дочерних элементов (подстолбцов). Попробовал для их
добавления использовать setChild(...) вместо appendRow(...) эффект тот же.
Ни в конструкторах ничего про parent, ни в методах...

Может подскажет кто как проинициализировать parent в дочерних элементах QStandatrdItem ???

Понятно, что от безысходности можно вопрос решить усложнением QStandardItem::data,
но не хотелось бы :)


Название: Re: QTableWidget иерархические заголовки
Отправлено: ViTech от Август 22, 2019, 18:25
Если хотите нестандартного поведения от таблицы, лучше пользуйтесь QAbstractItemModel и QTableView/QAbstractItemView. Их можно гибче настроить под свои хотелки, чем готовые модели/виды переделывать.


Название: Re: QTableWidget иерархические заголовки
Отправлено: Иваныч от Август 30, 2019, 17:42
Спасибо за участие :). Воспользовался Вашим советом, но в итоге (по моему разумению) всё равно всё сводится к
эффективному переопределению QHeaderView::paintSection(...) const.
Повозился, нашёл, что некорректно работает QHeaderView::sectionPosition(...) при сдвиге ScrollBar-а. Для всех
секций, слева за окном возвращает 0. Справился через перебор QHeaderView::sectionWidth(...).
Разделил операции наполнения "дерева" сложных колонок и формирования дополнительных колонок.
Рекурсия рулит на разборе строки с колонками и их отрисовке.
Для отладки внедрил над таблицей счетчик отрисовок секций.
Жалко, что никто особо не поддержал тему :(.
Честь имею.
Результаты прилагаю.