Привет, Хабр.


В этой статье я решил оставить решение проблемы непонятного поведения QTreeWidget – GUI компонента кроссплатформенного фреймворка Qt. Проблема, мне кажется, актуальная, потому что вопрос задаётся на многих форумах, но верного решения не приводится. Впрочем, если я ошибаюсь, НЛО мне об этом сообщит.


Проблема


В Qt есть компонент QTreeWidget, предназначенный для древовидного отображения информации. Элементом или узлом дерева может быть текст с картинкой, или же другой виджет.


В ходе эксплуатации этого компонента у некоторых пользователей возникает проблема, когда текст элемента или узла не помещается в ширину виджета, и компонент при этом не позволяет пользователю скролить содержимое влево/вправо чтобы тот мог прочитать текст целиком. Вместо этого появляется троеточие в конце текста. Проблема встречается в случаях использования виджета с одной колонкой, или с несколькими (тогда проблема возникает в последней колонке).


Никакое свойство класса QTreeWidget или его предков не даёт возможности активировать автоматическое изменение размера области просмотра колонки. Так что, если вы тоже столкнулись с этим недоразумением, прошу под кат.


Простое решение


Решение снизошло на мою головушку когда я читал документацию к QTreeWidget. Решение состоит в использовании следующего метода (слота) :


void QTreeView::resizeColumnToContents(int column);

Как можно догадаться, метод "подгоняет" размер колонки, номер которой передаётся в виде параметра, под размер содержимого. То, что как раз компонент должен делать сам, но не делает.


Применение решения


Если проблема возникает сразу после создания виджета (то есть пользователь ещё не успел ничего сделать, а текст уже не помещается), то в конструкторе виджета можно запустить цикл с применением этого метода ко всем колонкам (или только к последней) :


//применить ко всем колонкам
for(int i = 0; i < treeWidget->columnCount(); ++i)
        treeWidget->resizeColumnToContents(i);
//или применить только к последней
treeWidget->resizeColumnToContents(treeWidget->columnCount() - 1);

Если же нужно вместо этого (или вместе с этим) изменять размер колонки после раскрытия или сворачивания узла, то нужно воспользоваться следующими событиями (сигналами) :


void QTreeView::expanded(const QModelIndex &index); //раскрытие узла
void QTreeView::collapsed(const QModelIndex &index); //сворачивание узла

Нужно либо самостоятельно создать слоты для реагирования на эти сигналы и соединить их, либо сгенерировать всё это в один клик на форме (ПКМ по виджету на форме, "перейти к слоту", выбрать слот).


Так или иначе, слоты реагирования на события будут содержать только вызов уже знакомого нам метода :


void MainWindow::on_tree_widget_expanded(const QModelIndex &index)
{
    treeWidget->resizeColumnToContents(index.column());
}

void MainWindow::on_tree_widget_collapsed(const QModelIndex &index)
{
    treeWidget->resizeColumnToContents(index.column());
}

Здесь index.column() сообщает методу номер колонки, в которой произошло событие.


Итог


Вот таким костыльным незамысловатым способом можно научить компонент QTreeWidget делать то, что он должен и так уметь. Надеюсь что решение кому-то пригодится. Буду рад ответить на вопросы в комментариях. Спасибо за внимание.

Комментарии (5)


  1. Tantrido
    26.02.2018 19:48

    Уже много лет таким пользуемся, статьи конечно не писали, но на форумах полно было: Qt Forum, Qt Centre, http://prog.org.ru/ и др. В основном конечно применялось к табличным виджетам, но к древовидным тоже интересно через expanded, collapsed.


  1. CodeRush
    26.02.2018 21:43

    Автоматически это делать нельзя и вот почему: ширина колонок в виджете может настраиваться пользователем, и как только пользователь эту ширину настроил — менять ее уже не нужно, потому что это дико бесит. Особенно дико это будет бесить когда у вас не две колонки, а штук десять, и все элементы двигаются туда-сюда просто потому, что какую-то ветку свернули.


    1. CodeRush
      26.02.2018 22:08

      Забыл добавить: массовый resizeColumnToContents — весьма небыстрая операция, и если у вас много элементов в дереве (тысяч тридцать, например), она может занимать пару секунд даже на хорошем железе.
      Вешать такие операции на collapse/expand — опять же отличный вариант выбесить пользователя тормозами.


      1. Princess_York Автор
        26.02.2018 23:42

        Вы конечно же правы насчёт неэффективности использования метода массового ресайза. Но его я привел скорее для полноты решения. На самом деле решается лишь проблема появления горизонтального скролла при выходе размера дерева за границы виджета в целом. Кейс довольно узкоспециализированный, но всё таки ходовой. Предполагается что виджет дерева не нужно изменять в размерах чтобы увидеть весь текст, как в «Проводнике» Windows. Там ведь тоже используется дерево с одной колонкой для отображения файловой структуры. И там колонка как раз ресайзится если содержимое становится больше его размеров, рисуется скролл и все счастливы. Может быть там это тоже как-то костыльно сделано, я не знаю. Но мне нужно было добиться такого эффекта, компонента с такой функцией я не нашел, зато нашлось вот такое решение. Может быть пора задуматься над созданием компонента дерева с одной колонкой, лол.


      1. Princess_York Автор
        26.02.2018 23:54

        Скорректировал название статьи. Речь тут скорее про дерево с одной колонкой, хотя на форумах встречались люди, которым нужно было ресайзить последнюю колонку из нескольких.