Change what column of QTreeView displays expand/collapse icon - c++

Qt version 4.8.4
I have a QTreeView that reflects a QAbstractItemModel-derived model. The model provides data in multiple columns. The expand/collapse icon for a row of the view is always displayed in the cell that is in the column that has a logical index of zero, even if that column has been moved so that it has a visual index other than zero.
Is QTreeView compelled to always draw the expand/collapse icon in logical column zero? Can that be customized, either via the QTreeView or the model?

The expand/collapse icon, along with all the other tree branching lines, is indeed compelled to always draw in logical column zero. In Qt/4.8.4/src/gui/itemviews/qtreeview.cpp, in the QTreeView::drawRow() method, it is hardcoded to only call QTreeView::drawBranches() if the headerSection (which contains the logical index of the column) is zero.
void QTreeView::drawRow( QPainter * painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
...
for ( int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection )
{
int headerSection = logicalIndices.at(currentLogicalSection);
...
// If the zeroeth logical column, ...
if ( headerSection == 0 )
{
...
// ... draw tree branches and stuff.
drawBranches(painter, branches, index);
}
else
{
...
}
...
}
...
}

Related

Resizing QTableView section with custom editor

I have an application with a QTableView and a model derived from QAbstractItemModel: the first column of the table contains a text (a label for each row), while the second column shows a value that can be selected using a QComboBox created from a custom item delegate. The content of the table may change dynamically (number of rows, language...).
I'd like to resize columns so the second one fits the content and the first one stretches occupying the remaining space.
My first attempt was:
tblData->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
tblData->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
Result:
The problem is that when the QComboBox is selected it doesn't fit the section and it is clipped:
I've managed to solve this issue by manually increasing the width:
tblData->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
tblData->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
tblData->resizeColumnToContents(1);
tblData->horizontalHeader()->resizeSection(1, tblData->horizontalHeader()->sectionSize(1) + 40);
Now the issue here is that by using such constant (40 in this case) the width of the section will vary depending on values displayed rather than in all the possible values (if the largest ones are already displayed vs if only the shortest). Also, that constant will be dependant to the style used, since it is also related to the space consumed by the QComboBox.
I've thought about using the Qt::SizeHintRole to manually compute the section width, but it is completely ignored. Even if it was, I cannot compute the actual width of the text (using QFontMetrics::width) because I don't have any font information in the model.
Another approach I've tried is to set the adjust size policy of the QComboBox in the QItemDelegate::createEditor method:
QWidget* myItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const {
auto comboBox = new QComboBox(parent);
comboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
// ...
}
But now the combo boxes are either clipped or shortened.
How can I solve set the section size based on the complete range of content instead of just visible data?
I'm self-answering the question with the best approach I've found so far, and the one I'm using in the project right now, but I'm not convinced with it (I detail reasons on the answer) so I'd like to know the correct way to do it. Thanks!
The sizeHint in the delegate is the right way to go but, instead of creating an editor, fill a QStyleOptionComboBox struct and use qApp->style()->sizeFromContents(QStyle::CT_ComboBox, &opt, sh, nullptr);, where sh is the size of the internal string. You can use QFontMetrics to calculate that or just call the base class QStyledItemDelegate::sizeHint(...).
The best option I've found so far is to re-implement the QItemDelegate::sizeHint: I have the font information from the QStyleOptionViewItem and the list of elements to be included in the QComboBox.
QSize myItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto hint = QItemDelegate::sizeHint(option, index);
QFontMetrics fm(option.font);
std::unique_ptr<QWidget> editor(createEditor(nullptr, option, index));
auto comboBox = qobject_cast<QComboBox*>(editor.get());
if (comboBox != nullptr) {
int width = 0;
for (int ii = 0; ii < comboBox->count(); ++ii) {
width = std::max(width, fm.width(comboBox->itemText(ii)) + 20);
}
hint.setWidth(std::max(hint.width(), width));
}
return hint;
}
Results:
Drawbacks of this solution are:
I don't have information regarding the additional space required by the QComboBox so it is not style-independant yet (as with the second approach on the question)
If new editors are added to the item delegate then I'd have to include them manually in the size hint computation too, which is not a terrible pain but feels like a bad design.
PS: using the QComboBox::sizeHint here doesn't work since size hint is computed using the QComboBox::sizeAdjustPolicy which, as highlighted in the question, doesn't adjust combo boxes correctly into the cell.
UPDATE
I've updated the solution following the indications from comments and accepted answer. Here is the complete code for future reference:
QStringList myItemDelegate::getPossibleValuesForIndex(const QModelIndex& index) const
{
// returns list of all possible values for given index (the content of the combo box)
}
QSize myItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto hint = QItemDelegate::sizeHint(option, index);
QFontMetrics fm(option.font);
QStyleOptionComboBox comboOption;
comboOption.rect = option.rect;
comboOption.state = option.state | QStyle::State_Enabled;
Q_FOREACH (const auto& value, getPossibleValuesForIndex(index)) {
hint = hint.expandedTo(qApp->style()->sizeFromContents(QStyle::CT_ComboBox,
&comboOption, QSize(fm.width(value), hint.height())));
}
return hint;
}

Autofill to correct size in Qt Tables

I am trying to learn Qt by doing some project, and would like quick pointer on one part of my requirement.
I have database with multi-line passages, that I want to show in Qt using some view.
What I additionally want is that user does not have to re-size the window in order to read, so, if big passage comes in, then size shrinks, and will small passage, the font increase such that it takes the total space to display.
Kindly suggest :
what logic or functionality will suit to shrink and expand size, or is there a widget/view that already do so(by modifying the property) or suggestion on how to achieve it.
Same question again, to show shrink/expanded things, but using only using tree view. Then can I do this in tree view? And how?
Here is a way you could do it with a custom QItemDelegate.
Note that this solution isn't complete, you still need to do some work here.
First, some code to setup a QStandardItemModel and QTreeView.
The View uses a custom Delegate, which is described below.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model(4, 4);
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
model.setItem(row, column, item);
}
}
Delegate delegate;
QTreeView view;
view.setItemDelegate(&delegate);
view.setModel(&model);
view.setWindowTitle("QTreeView with custom delegate");
view.show();
return a.exec();
}
Here comes the code for the Delegate.
It looks how much space is available for the text, and then tries to find a font size that fits.
I'm currently only checking the width and ignoring height.
class Delegate : public QItemDelegate
{
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QRect availableSpace = option.rect;
// padding taking place, don't know how to find out how many pixels are padded.
// for me, subtracting 6 looked ok, but that's not a good solution
availableSpace.setWidth(availableSpace.width() - 6);
QString text = index.data().toString();
QStyleOptionViewItem newOption(option);
// initial font size guess
float size = 20;
int width;
// try to make font smaller until the text fits
do
{
newOption.font.setPointSizeF(size);
size -= .1;
width = QFontMetrics(newOption.font).width(text);
}
while (width > availableSpace.width());
newOption.textElideMode = Qt::ElideNone;
// call the parent paint method with the new font size
QItemDelegate::paint(painter, newOption, index);
}
This is what the result looks like:

How can I set the line style of a specific cell in a QTableView?

I am working with a QT GUI. I am implementing a simple hex edit control using a QTableView. My initial idea is to use a table with seventeen columns. Each row of the table will have 16 hex bytes and then an ASCII representation of that data in the seventeenth column. Ideally, I would like to edit/set the style of the seventeenth column to have no lines on the top and bottom of each cell to give the text a free flowing appearance. What is the best way to approach this using the QTableView?
I could think about a couple of ways of doing what you need; both would include drawing custom grid as it looks like there is no straight forward way of hooking into the grid painting routine of QTableView class:
1.Switch off the standard grid for your treeview grid by calling setShowGrid(false) and draw grid lines for cells which need them using item delegate. Below is an example:
// custom item delegate to draw grid lines around cells
class CustomDelegate : public QStyledItemDelegate
{
public:
CustomDelegate(QTableView* tableView);
protected:
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
private:
QPen _gridPen;
};
CustomDelegate::CustomDelegate(QTableView* tableView)
{
// create grid pen
int gridHint = tableView->style()->styleHint(QStyle::SH_Table_GridLineColor, new QStyleOptionViewItemV4());
QColor gridColor = static_cast<QRgb>(gridHint);
_gridPen = QPen(gridColor, 0, tableView->gridStyle());
}
void CustomDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyledItemDelegate::paint(painter, option, index);
QPen oldPen = painter->pen();
painter->setPen(_gridPen);
// paint vertical lines
painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
// paint horizontal lines
if (index.column()!=1) //<-- check if column need horizontal grid lines
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
painter->setPen(oldPen);
}
// set up for your tree view:
ui->tableView->setShowGrid(false);
ui->tableView->setItemDelegate(new CustomDelegate(ui->tableView));
2.Create a QTableView descendant and override the paintEvent method. There you could either draw your own grid or let base class to draw it and then paint horizontal lines on top of the grid with using tableview's background color.
hope this helps, regards

Displaying multiple icons in a single cell of a QTableView

I am writing a small gui app with QT4.5 in QtCreator.
The main screen on the app contains a QTreeView with two columns, the first is text the second is a group of icons. These icons represent the last few states of the item displayed in the row.
I am not sure what the best way to do this is. I have currently implemented this by generating a QPixmap the model's data() method.
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole || role == Qt::EditRole) {
switch(index.column()) {
case 0:
return item_.at(index.row()).title();
}
}
if (role == Qt::DecorationRole) {
switch(index.column()) {
case 1:
return makeImage(item_.add(index.row()).lastStates());
}
}
return QVariant();
}
QVariant MyModel::makeImage(const QList<MyState> &states) const
{
const int IconSize = 22;
QPixmap image(IconSize * states.size(), IconSize);
QPainter painter(&image);
painter.fillRect(0, 0, IconSize * count, IconSize, Qt::transparent);
for (int i = 0; i < states.size(); ++i) {
QIcon * icon = stateIcon(state.at(i));
icon->paint(&painter, IconSize * i, 0, IconSize, IconSize);
}
return image;
}
This works but for some small problems, the background which should be transparent is full of random noise, even filling this with a transparent colour does not fix it.
Second this does not seem very efficient, I am generating a new Image every time this is called, should I not just draw the icons onto the widget for the cell?
What is the best way to display multiple icons in a single cell?
I would create a custom delegate, based on a hbox, into which you can place all the pictures. Have a look at delegates in the Qt Documentation about model view programming.

Selecting an index in a QListView

This might be a stupid question, but I can't for the life of me figure out how to select the row of a given index in a QListView.
QAbstractItemView , QListView's parent has a setCurrentIndex(const QModelIndex &index). The problem is, I can't construct a QModelIndex with the row number I want since the row and column field of the QModelIndex has no mutators.
QTableView, which also inherits from QAbstractItemView has a selectRow(int row) function, why in the seven hells doesn't the QListView have this?
Good ol' windows forms has the SelectedIndex property on it's listviews.
This should help you get started
QModelIndex index = model->createIndex( row, column );
if ( index.isValid() )
model->selectionModel()->select( index, QItemSelectionModel::Select );
You construct the QModelIndex by using the createIndex(int row, int column) function of the model you gave to the view. QModelIndexes should only be used once, and must be created by the factory in the model.
My working sample at Qt4.8.0 (MSVC2010 Compiller) based on Michael Bishop
QStandardItemModel *Model = (QStandardItemModel *)this->ui->listView_OptionsCategories->model();
QModelIndex index = Model->index(this->ui->stackedWidget->currentIndex(), 0);
if ( index.isValid() )
this->ui->listView_OptionsCategories->selectionModel()->select( index, QItemSelectionModel::Select );
For Qt 6.3.x:
void selectRowInQListView(int row, QListView *listView) {
QModelIndex index = listView->model()->index(row, 0);
if (index.isValid()) {
//listView->selectionModel()->select(index, QItemSelectionModel::Select);
//listView->selectionModel()->select(index, QItemSelectionModel::Current);
listView->setCurrentIndex(index);
}
}