I'm adding images as items to a QTableView, I'm also adding a specific text to each images, problem is the text is shown beside the image or the icon, but I want QTableView to show it below the image or the icon. My code snippet is as below:
QStandardItemModel * model = new QStandardItemModel(NumOfRow, 3, this);
Then comes this part which is in the loop
//
QStandardItem * itm = new QStandardItem;
itm->setIcon(image);
itm->setText(text);
model->setItem(row, column, itm);
//
Then this part outside the loop
ui->listOfImages->setModel(model);
ui->listOfImages->setStyleSheet(QString("icon-size: 150px 150px"));
ui->listOfImages->verticalHeader()->setVisible(false);
ui->listOfImages->horizontalHeader()->setVisible(false);
for(int i=0; i<=rowPointer; i++)
{
ui->listOfImages->setRowHeight(i,150);
}
for(int j=0; j<3; j++)
{
ui->listOfImages->setColumnWidth(j,150);
}
Could you say me if there is any way to put the name below the icon rather than in the right side of the icon?
Thanks
Well, I would try to handle the text alignment with the custom QStandardItemModel sub class. Here is the sample model, that implements it:
class ItemModel : public QStandardItemModel
{
public:
ItemModel(int rows, int columns, QObject *parent = 0)
:
QStandardItemModel(rows, columns, parent)
{}
QVariant data(const QModelIndex &index, int role) const
{
if (role == Qt::TextAlignmentRole) {
return Qt::AlignBottom; // <- Make alignment look different, i.e.
// <- text at the bottom.
} else {
return QStandardItemModel::data(index, role);
}
}
};
So, instead of using the Qt class, you will need to create an instance of this custom class:
QStandardItemModel * model = new ItemModel(NumOfRow, 3, this);
The rest will remain the same.
Related
I want to change the color of a single tab, please see screenshot below. The FOO_SUPtab should be red (right now only the button is), all others not.
For the text color there is bar->setTabTextColor(index, QColor(Qt::red)), but not the whole tab. Setting the tab stylesheet for the tab widget changes the color of all tabs.
I have found a stylesheet to change the tab color here: https://stackoverflow.com/a/21687821/356726 but not for a single tab, also I need to be able to decide at runtime if the tab is red or not.
Just to make clear, the widget below shall remain black, the tab only red.
One option would be to implement your own tab bar (as explained here).
Anyway, I find more useful and cleaner the use of a proxy style, since it allows you to partially override the painting without need to use inheritance for the tab bar. It will allow you also to easily apply the new style to existing controls.
It can be something like:
class TabBackgroundProxyStyle : public QProxyStyle {
public:
TabBackgroundProxyStyle(const QString& base_style_name, const QMap<QString, QBrush>& backgrounds)
: QProxyStyle(base_style_name),
m_backgrounds(backgrounds) {
}
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override {
if (element == CE_TabBarTab) {
if (auto tab = qstyleoption_cast<const QStyleOptionTab*>(option)) {
if (m_backgrounds.contains(tab->text)) {
QStyleOptionTab opt(*tab);
opt.palette.setBrush(QPalette::Background, m_backgrounds[tab->text]);
return QProxyStyle::drawControl(element, &opt, painter, widget);
}
}
}
QProxyStyle::drawControl(element, option, painter, widget);
}
private:
const QMap<QString, QBrush> m_backgrounds;
};
To use it, just create the style with the appropriate tabs-color mapping (examples using C++11):
auto theTabWidget = new QTabWidget();
for (int ii = 0; ii < 10; ++ii) theTabWidget->addTab(new QWidget(), QString("Tab %1").arg(ii + 1));
const QMap<QString, QBrush> backgrounds = {
{"Tab 2", QBrush(Qt::red)},
{"Tab 3", QBrush("#c0b050")},
};
theTabWidget->tabBar()->setStyle(new TabBackgroundProxyStyle("", backgrounds));
If your user interface allows the tab's text to change on runtime (e.g., on-the-fly translations, or the text is a filename...) then you must modify the map accordingly.
The use of the tab's label for indexing is because the style option doesn't store any other direct information about the tab (not even the associated widget, because QTabBar is in charge of rendering only the tabs, it is not the container).
Another option would be to check the tab's rectangle, not much time-consuming for tab bars with just a few dozens of tabs, and more versatile if you don't want to deal with labels:
class TabBackgroundProxyStyle : public QProxyStyle {
public:
TabBackgroundProxyStyle(const QString& base_style_name, const QMap<int, QBrush>& backgrounds)
: QProxyStyle(base_style_name),
m_backgrounds(backgrounds) {
}
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override {
if (element == CE_TabBarTab) {
if (auto tab = qstyleoption_cast<const QStyleOptionTab*>(option)) {
auto tabBar = qobject_cast<const QTabBar*>(widget);
for (auto index : m_backgrounds.keys()) {
if (tab->rect == tabBar->tabRect(index)) {
QStyleOptionTab opt(*tab);
opt.palette.setBrush(QPalette::Background, m_backgrounds[index]);
return QProxyStyle::drawControl(element, &opt, painter, widget);
}
}
}
}
QProxyStyle::drawControl(element, option, painter, widget);
}
private:
const QMap<int, QBrush> m_backgrounds;
};
Use:
auto theTabWidget = new QTabWidget();
for (int ii = 0; ii < 10; ++ii) theTabWidget->addTab(new QWidget(), QString("Tab %1").arg(ii + 1));
const QMap<int, QBrush> backgrounds = {
{1, QBrush(Qt::red)},
{4, QBrush("#c0b050")},
};
theTabWidget->tabBar()->setStyle(new TabBackgroundProxyStyle("", backgrounds));
Full source code can be downloaded from https://github.com/cbuchart/stackoverflow/tree/master/54070408-change-color-of-single-qtabwidget-tab
IMPORTANT: The main drawback of this solution is that it doesn't mix well with existing stylesheet for tabs: you have to disable/comment the stylesheets for QTabBar::tab in order to be able to apply the style.
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:
RazrFalcon's answer for pointing me to the right direction
Answer with code sample to render HTML in QTableView: How to make item view render rich (html) text in Qt
Answer with code sample on detecting links in QTreeView: Hyperlinks in QTreeView without QLabel
QLabel's and internal Qt's QWidgetTextControl's source code on how to handle mouse click/move/release for links
Many thanks for these code examples, it helped me implement similar functionalaity in my application. I'm working with Python 3 and QT5 and I would like to share my Python code, is case it may be helpful implementing this in Python.
Note that if you are using QT Designer for the UI design, you can use "promote" to change a regular "QTableView" widget to use your custom widget automatically when converting the XML to Python code with "pyuic5".
Code as follows:
from PyQt5 import QtCore, QtWidgets, QtGui
class CustomTableView(QtWidgets.QTableView):
link_activated = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
self.parent = parent
super().__init__(parent)
self.setMouseTracking(True)
self._mousePressAnchor = ''
self._lastHoveredAnchor = ''
def mousePressEvent(self, event):
anchor = self.anchorAt(event.pos())
self._mousePressAnchor = anchor
def mouseMoveEvent(self, event):
anchor = self.anchorAt(event.pos())
if self._mousePressAnchor != anchor:
self._mousePressAnchor = ''
if self._lastHoveredAnchor != anchor:
self._lastHoveredAnchor = anchor
if self._lastHoveredAnchor:
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
else:
QtWidgets.QApplication.restoreOverrideCursor()
def mouseReleaseEvent(self, event):
if self._mousePressAnchor:
anchor = self.anchorAt(event.pos())
if anchor == self._mousePressAnchor:
self.link_activated.emit(anchor)
self._mousePressAnchor = ''
def anchorAt(self, pos):
index = self.indexAt(pos)
if index.isValid():
delegate = self.itemDelegate(index)
if delegate:
itemRect = self.visualRect(index)
relativeClickPosition = pos - itemRect.topLeft()
html = self.model().data(index, QtCore.Qt.DisplayRole)
return delegate.anchorAt(html, relativeClickPosition)
return ''
class CustomDelegate(QtWidgets.QStyledItemDelegate):
def anchorAt(self, html, point):
doc = QtGui.QTextDocument()
doc.setHtml(html)
textLayout = doc.documentLayout()
return textLayout.anchorAt(point)
def paint(self, painter, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
if options.widget:
style = options.widget.style()
else:
style = QtWidgets.QApplication.style()
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
options.text = ''
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options)
painter.save()
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
painter.translate(0, 0.5*(options.rect.height() - doc.size().height()))
doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
doc.setTextWidth(options.rect.width())
return QtCore.QSize(doc.idealWidth(), doc.size().height())
In your case QLabel (re)painting is slow, not QTableView.
On other hand, QTableView does not support formated text at all.
Probably, your only way, is to create your own delegate, QStyledItemDelegate, and make your own painting and click processing in it.
PS: yes, you can use QTextDocument for rendering html inside delegate, but it will be slow too.
I use a slighted improved solution based on Xilexio code. There is 3 fundamental differences:
Vertical alignment so if you put the text in a cell higher than the text it will be center aligned and not top aligned.
The text will be right shifted if the cell contains an icon so the icon will not be displayed above the text.
The widget style to highlighted cells will be followed, so you select this cell, the colors will behave similar to other cells without the delegate.
Here is my code of the paint() function (the rest of the code remains the same).
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
QSize iconSize = options.icon.actualSize(options.rect.size);
// right shit the icon
painter->translate(options.rect.left() + iconSize.width(), options.rect.top());
QRect clip(0, 0, options.rect.width() + iconSize.width(), options.rect.height());
painter->setClipRect(clip);
QAbstractTextDocumentLayout::PaintContext ctx;
// Adjust color palette if the cell is selected
if (option.state & QStyle::State_Selected)
ctx.palette.setColor(QPalette::Text, option.palette.color(QPalette::Active, QPalette::HighlightedText));
ctx.clip = clip;
// Vertical Center alignment instead of the default top alignment
painter->translate(0, 0.5*(options.rect.height() - doc.size().height()));
doc.documentLayout()->draw(painter, ctx);
painter->restore();
I am trying to create items in one panel and add to a second panel. In the second panel I want them to be movable (and have context menu). The AddItem button should add the item from the RenderArea, to the existing list of items in the CollectionView (which may already have been moved)
In the code below, the ShapeView::addItem() is supposed to create a copy of the item from the RenderArea (where it can change shape, color etc but is not movable, starts at (0,0)), place it in the CollectionView, where it is movable. The RenderArea holds one item - once added to CollectionView, the RenderArea item should reset.
What is happening... I can't seem to be able to separate the item from the two classes.
When I add item, even if the items in
CollectionView have moved, their position resets to 0,0 (or whatever the initial position was in the RenderArea; but the adding
works properly, the individual item properties are correct, like shape and color; also - CollectionView items move, RenderArea item
doesn't).
I am posting all the relevant code:
class Item : public QGraphicsItem
{
Item() { setFlag(ItemIsMovable, false); }
Item::Item(Item ©Item) { // copy constructor
setFlag(ItemIsMovable);
setPos(copyItem.pos()); // doesn't seem to work
QRectF boundingRect() const { return QRectF(-35, -30, 35, 20); }
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) {
painter->drawRect(boundingRect()); }
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsItem::mouseMoveEvent(event);
}
};
class CollectionView : public QGraphicsView
{
Q_OBJECT
public:
CollectionView(QWidget *parent = 0);
void update();
QList<Item*> *m_items;
};
CollectionView::CollectionView(QWidget *parent)
: QGraphicsView(parent)
{
QGraphicsScene *s = new QGraphicsScene(this);
s->setSceneRect(-width()/2, -height()/2, width(), height());
setScene(s);
setViewportUpdateMode(BoundingRectViewportUpdate);
m_items = new QList<Item*>();
scene()->clear();
}
void CollectionView::update()
{
scene()->clear();
for(int i = 0; i< m_items->size(); i++)
{
Item* item = new Item(*m_items->at(i));
item->setPos(m_items->at(i)->p); // doesn't seem to work
scene()->addItem(item);
}
viewport()->update();
}
class RenderArea : public QGraphicsView
{
Q_OBJECT
public:
RenderArea(QWidget *parent = 0);
public:
Item* item;
};
RenderArea::RenderArea(QWidget *parent)
: QGraphicsView(parent)
{
QGraphicsScene *s = new QGraphicsScene(this);
s->setSceneRect(-width()/2, -height()/2, width(), height());
setScene(s);
setViewportUpdateMode(BoundingRectViewportUpdate);
item = new Item();
s->addItem(item);
}
// this is the boss/coordinator class
class ShapeView : public QGraphicsView
{
Q_OBJECT
public:
ShapeView(QWidget *parent = 0);
CollectionView *collectionView;
private slots: // more slots corresponding to more controls
void addItem();
private:
QPushButton *addButton;
RenderArea *renderArea;
};
ShapeView::ShapeView(QWidget *parent)
: QGraphicsView(parent)
{
collectionView = new CollectionView(parent);
renderArea = new RenderArea(this);
addButton = new QPushButton(tr("Add Item"));
connect(addButton, SIGNAL(clicked()), this, SLOT(addItem()));
}
void ShapeView::addItem()
{
Item* item = new Item(*renderArea->item);
collectionView->m_items->append(item);
collectionView->update();
// place a new item on renderarea
renderArea->item = new Item();
renderArea->scene()->clear();
renderArea->scene()->addItem(renderArea->item);
renderArea->viewport()->update();
}
Something is wrong in either the way I copy the item, I just don't know what. I see no reason why, when adding items, the CollectionView items all snap to the (0,0) position. Perhaps the issue is in the copy constructor but I can't figure what is wrong.
I hope someone can help give me a hint
Update: It seems that the problem is in the CollectionView::update() function... adding a 'delete' at the end of the loop removes the item from the collection... Or in the ShapeView::addItem() function... Am I creating copy of the item or just reusing it ?
But how do I create a copy of the item ?
My assumption was that there was a connection between the items I paint on QGraphicsScene and their representation (as pointers to the objects). Wrong... In fact I misunderstood what the pointer was pointing at...
I updated the position of the items in my list after mouse move and everything is fine.
void CollectionView::mouseMoveEvent(QMouseEvent *event)
{
Item *currentItem = (Item*)itemAt(event->pos().x(), event->pos().y());
if(!currentItem) return;
QGraphicsView::mouseMoveEvent(event);
m_items->at(currentItem->z)->setPos(currentItem->pos());
}
The above fixes the code shown. But it is not the best fix.
The better solution to the problem - remove my list of items completely, since QGraphicsScene already holds a copy. (there was one benefit of handling my own list of items - bringForward and sendBackward for items was easier to implement since my z values were equal to item index)
While displaying data, on a QTableView , due to an internal reason, i cannot display the first row and hence have to hide it, using
(qtableobj)->hideRow(0);
The problem is that, now the row labels start from 2.
How is it possible to start the index from 1, while keeping the first row hidden ?
Thanks.
You can try to involve the QSortFilterProxyModel that will filter out the first row in your model.
The code could look like:
class FilterModel : QSortFilterProxyModel
{
[..]
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
if (index.row() == 0) // The first row to filter.
return false;
else
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
}
Finally you need to set this model to your table view:
QTableView *table = new QTableView;
MyItemModel *sourceModel = new MyItemModel;
QSortFilterProxyModel *proxyModel = new FilterModel;
proxyModel->setSourceModel(sourceModel);
table->setModel(proxyModel);
UPDATE
Since the issue is in how the header view displays row numbers, here is the alternative solution which based on special handling of header data in the model:
class Model : public QAbstractTableModel
{
public:
[..]
virtual QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const
{
if (role == Qt::DisplayRole) {
if (orientation == Qt::Vertical) {
// Decrease the row number value for vertical header view.
return section - 1;
}
}
return QAbstractTableModel::headerData(section, orientation, role);
}
[..]
};
Set up the table view with the hidden first row.
QTableView *table = new QTableView;
Model *sourceModel = new Model;
table->setModel(sourceModel);
table->hideRow(0);
table->show();
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: