How to get list of visible QModelIndex in QAbstractItemView - qt

Is there any way to get a list of currently visible items in QAbstractItemView? And, if it possible, to receive any notifications about changing of this list.
Upd:
I'm asking exactly about QAbstractItemView or QTreeView with non-plain structure, not QTableView.
Upd2:
I'm implementing tree view model with checkboxes. I want next behavior (same for checking/uncheking):
If one of checkbox is checked - then all childs must be checked
If all child checkboxes are checked - then parent check box should be checked too. And same for parent of parent, and so on...
Check state are monitored/modified by external data source, so I need a mechanism to update all changed children/parents. dataChanged signal is not enough for me because it is very expansive to build a list of all changed QModelIndex for updating. And it is not necessary at all, because all fresh data will be picked from QAbstractItemModel::data.
I found next dirty hack to update all items: emit dataChanged( QModelIndex(), QModelIndex() ); but it's undocumented for invalid indexes.
So, I need a way to force all visible items redraw they content with fresh data.

You can get the topleft and bottom right cell by calling:
tableview->indexAt(tableview->rect().topLeft())
tableview->indexAt(tableview->rect().bottomRight())
To get notification of change, reimplement the virtual function of qabstractscrollarea
scrollContentsBy
This function is called when the view port is scrolled.
call QTableView::scrollContentsBy and then do whatever you need to.

For QTreeView, the list of visible items can be traversed like this:
QTreeView& tv (yourTreeView);
// Get model index for first visible item
QModelIndex modelIndex = tv.indexAt(tv.rect().topLeft());
while (modelIndex.isValid())
{
// do something with the item indexed by modelIndex
...
// This navigates to the next visible item
modelIndex = tv.indexBelow(modelIndex);
}

I think that there are no cases where list of visible items is requred. In case of correct model implementation all items are updated automatically.
Hard part of implementation - force children and parents to update. I wrote following code:
bool TreeModel::setData( const QModelIndex &index, const QVariant &value, int role )
case Qt::CheckStateRole:
{
TreeItemList updateRangeList; // Filled with items, in which all childred must be updated
TreeItemList updateSingleList; // Filled with items, which must be updated
item->setCheckState( value.toBool(), updateRangeList, updateSingleList ); // All magic there
foreach ( TreeAbstractItem *i, updateRangeList )
{
const int nRows = i->rowCount();
QModelIndex topLeft = indexForItem( i->m_childs[0] );
QModelIndex bottomRight = indexForItem( i->m_childs[nRows - 1] );
emit dataChanged( topLeft, bottomRight );
}
foreach ( TreeAbstractItem *i, updateSingleList )
{
QModelIndex updateIndex = indexForItem( i );
emit dataChanged( updateIndex, updateIndex );
}
}

i always update whole QAbstractTableModel with:
emit dataChanged(index(0, 0), index(rowCount(), columnCount()-1)); // update whole view

Method 1
i, j = table.indexAt(table.rect().topLeft()).row(), table.indexAt(table.rect().bottomLeft()).row() - 1
Method 2
i, j = table.rowAt(0), table.rowAt(table.height()) - 1

Related

QlistView setCurrentIndex() not working

I am facing a problem with a QListView component.
I created a simple form with a listview and a tableview.
Then I put this code, both widgets populate with the data model as I want:
QSqlQueryModel * modela = new QSqlQueryModel();
QSqlQueryModel * modelb = new QSqlQueryModel();
[...]
ui->listView->setModel(modela);
ui->tableView->setModel(modelb);
[...]
void MyWindow::on_listView_clicked(const QModelIndex &index)
{
ui->tableView->setCurrentIndex(ui->listView->currentIndex());
}
void MyWindow::on_tableView_clicked(const QModelIndex &index)
{
ui->listView->setCurrentIndex(ui->tableView->currentIndex());
// FAILS, does not react...
}
The first slot (when I click any item in the listview widget) works as expected, it automatically selects the corresponding item in the tableview widget, but the second case does not work, it just does not select any item in the listview...
What I want is that whatever item the user clicks in the tableview gets selected in the listview.
Is it possible?
I tried hard, looking for examples and the official qt documentation, but I don't find the right way to do (also tried to connect with signal/slots, but I don't know how to exactly connect both widgets).
Thanks in advance.
QModelIndex is an integral part of a certain QAbstractItemModel. It means that you can't use an index from model A to select an item in a view of model B.
QModelIndex is not just a couple of x,y. It also keeps a pointer to a model which created it.
So if you need to select the same row as selected in the first view, you need to extract a row from the first index, then get a right index in the second model and use it to select an item in the second view:
void selectTheSameRow(const QModelIndex& indexFromModelA)
{
int row = indexFromModelA.row();
QModelIndex indexFromModelB = modelB->index(row, 0);
viewB->setCurrentIndex(indexFromModelB);
}

Is it possible to use busy progressbar with normal functionality in QListView?

I have created by myself model (MyListModel) that store list of my objects MyObj. MyObj stores some text information. Then I created class ItemDelegate, in order to manage view of every item and also to add QProgressBar to each item. The problem is that I need busy progressbar, but when I execute app, QProgressBar doesn't do any action.
I'm guessing, it's because QListView only show static data. Is there any way to make it work properly?
My realisation of paint method for Delegate:
void StatusItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
StatusItem my_item = index.data().value<StatusItem>();
wdg->name->setText(my_item.m_name);
switch (my_item.m_state)
{
case 0: wdg->progress->setMaximum(0); wdg->progress->setMinimum(0); break;
case 1: wdg->progress->setRange(0, 0); wdg->progress->setValue(100); break;
case 2: wdg->progress->setHidden(true); break;
}
QPalette pal;
QLinearGradient gradient(0, 0, 0, 100);
if ((option.state & QStyle::State_Selected) == QStyle::State_Selected)
{
pal.setBrush(QPalette::Window, QBrush(QColor(0, 255, 200)));
}
else
{
gradient.setColorAt(0.0, QColor(255,250,0));
gradient.setColorAt(1.0, QColor(255,255,255));
pal.setBrush(QPalette::Window, QBrush(QColor(Qt::transparent)));
pal.setBrush(QPalette::Window, QBrush(gradient));
}
wdg->setPalette(pal);
wdg->resize(option.rect.size());
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->translate(option.rect.topLeft());
wdg->render(painter);
painter->restore();
}
I think the problem exists because the view repaints items only when something is changed. While model data remains unchanged, according cells are not repainted.
In this situation you should cause additional repaints from the delegate. Unfortunately there is no convenient way to do it, but there are longer ways. You need to do the following action in the paint method:
view->update(index);
There are two issues:
You don't have view pointer in the paint method. You can either try to use painter->device() and assume that this is the view's viewport (but it's not guaranteed) or store view in the delegate's class member variable.
Calling update inside paint can cause infinite recursion (I'm not sure about this one). If that's the case, you can delay update using QTimer::singleShot(0, ...). But you will need to create a slot for that somewhere an pass view and index somehow so that they will be accessible in the slot.

QT: changing the tab traversal order on runtime?

you might be able to tell that I'm pretty new to QT...
My program contains a window with several Widgets in a QGridLayout. Some of these Widgets have a layout and child widgets themselves. Pressing the Tab key moves the focus like I expect it to, in the order I created the widgets.
Problems occur when a widget changes it's content (I do that by deleting all child widgets and then constructing new ones.) If I do that, new widgets are moved to the end of the tab chain (that indicates to me, that tab order is kind of global for a window). I have tried to use QWidget::setTabOrder() to reorder all widgets (I tried both, setting tab order for only the contends of the main window and setting it for the children too) but the actual order doesn't change. I did this by emitting a signal when the contend of a widget changes and connecting that to a slot on my main Window.
I think I understand the way the setTabOrder() function should work. I do something similar to this:
QWidget* a = firstWidget;
QWidget* b = secondWidget;
QWidget::setTabOrder(a,b);
for (int i = 0; i < widgets.size(); ++i) {
a = b;
b = widgets[i];
QWidget::setTabOrder(a,b);
}
Is there anything special one has to do when changing the tab order?
I also tried to reimplement focusNextPrevChild(bool next) and focusInEvent(QFocusEvent* e) similar to what can be found at this site. 1
I managed to mess up tab order a lot more like this... is this approach a step in the right direction?
I'm sorry if this is something simple that I managed to miss, but I'm struggling for a while now and I can't find a solution.
Any help is very appreciated.
I had the same problem, and resolved it using the info Tim Meyer provided in the comments.
Tab order is not hierarchical - calling setTabOrder on a parent widget that doesn't accept focus will not cause the focus to be passed to the child. You will need to fetch the appropriate children from the widget and set the order on them
In my case, a dynamically constructed QTreeWidget contained editable widgets, and I needed to reset setTabOrder to account for widgets being created out-of-order.
The following code is (most) of the implementation
// Perform a depth-first gather of the child widgets of this item
void gatherTabWidgets( QObject* item, QWidgetList& tabWidgets )
{
if (item->isWidgetType())
{
QWidget* itemWidget = static_cast<QWidget*>(item);
if (itemWidget->focusPolicy() & Qt::TabFocus)
tabWidgets.push_back( itemWidget );
}
const QObjectList& children = item->children();
for (QObjectList::const_iterator itr = children.begin(); itr != children.end(); itr++)
{
gatherTabWidgets( const_cast<QObject*>(*itr), tabWidgets );
}
}
// Perform a depth-first gather of the child items widgets;
void gatherTabWidgets( QTreeWidgetItem* item, QWidgetList& tabWidgets )
{
QWidget* itemWidget = fetchWidgetForItem( item );
gatherTabWidgets( itemWidget, tabWidgets );
for (int i = 0; i < item->childCount(); i++)
{
gatherTabWidgets( item->child( i ), tabWidgets );
}
}
void VETreeWidget::sortTree()
{
// Ensure ordering is maintained.
sortItems( 0, Qt::AscendingOrder );
// Once the items have been re-ordered, re-create the tab ordering
QTreeWidgetItem* baseItem = topLevelItem( 0 );
QWidgetList tabWidgets;
gatherTabWidgets( baseItem, tabWidgets );
if (!tabWidgets.empty())
{
QWidget* lastWidget = tabWidgets.first();
// Connect this treeview to the first widget. This ensures
// if the treeview is tabbed-to, it will tab to the correct sub-widget
setTabOrder( this, lastWidget );
for (QWidgetList::iterator itr = tabWidgets.begin() + 1; itr != tabWidgets.end(); itr++)
{
setTabOrder( lastWidget, *itr );
lastWidget = *itr;
}
}
}

Simple way to get all visible items in the QListView

I am trying to develop an image gallery application using Qt Framework. The application loads all the images from the selected folder and those images are displayed using QListView control.
But now i want to reduce the memory consumption by loading only the images that are visible to user. Since there is no direct function to get all the visible items in the view, i am not able to achieve this.
You can get the visible items of a list view using the indexAt function. For more details and an example you can check the following article:
http://qt-project.org/faq/answer/how_can_i_get_hold_of_all_of_the_visible_items_in_my_qlistview
I found it! You have to connect the vertical scrollbar of the listwidget to a signal:
connect(ui->listWidget->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(launch_timer()));
Every time the user scrolls, the valuechanged(int) signal is being omitted! The thing is that you shouldn't run the code provided by webclectic in this question every time the value of the vertical scrollbar of the listwidget changes, because the program will be unresponsive with so much code to run in so little time.
So, you have to have a singleshot timer and point it to the function that webclectic posted above. When launch_timer() is called, you do something like this:
if(timer->isActive()){
timer->stop();
timer->start(300);
}
else
timer->start(300);
and the timeout() signal of timer will be connected to the slot webclectic talked about. This way, if the user scrolls quickly all the way down only the last items will be updated. Generally, it will be updated anything visible for more than 300 milliseconds!
I think what you need is to implement your own model (take a look to the QAbstractListModel documentation) so that way you could decide when you have to load more images to show and maybe free some of the images that became non-visible.
although this is not so simple in Qt 4 but
it is always simple to copy below:
#include <private/qlistview_p.h>
class QListViewHelper : public QListView
{
typedef QListView super;
inline QListViewHelper() {} //not intended to be constructed
public:
inline static QVector<QModelIndex> indexFromRect(const QListView *view,
const QRect &rect)
{
const QListViewPrivate *d = static_cast<const QListViewPrivate *>(QObjectPrivate::get(view)); //to access "QListViewPrivate::intersectingSet(...)"
const QListViewHelper *helper = static_cast<const QListViewHelper *>(view); //to access "QListView::horizontalOffset()"
return d->intersectingSet(rect.translated(helper->horizontalOffset(), helper->verticalOffset()), false);
}
inline static QVector<QModelIndex> visibleItems(const QListView *view)
{ return indexFromRect(view, view->rect()); }
inline static QModelIndex firstVisible(const QListView *view)
{ return visibleItems(view).value(0); }
inline static QModelIndex lastVisible(const QListView *view) {
const QVector<QModelIndex> &items = visibleItems(view);
return items.value(items.count() - 1);
}
};
void ourTest(const QListView *view) {
QModelIndex &index = QListViewHelper::firstVisible(view);
qDebug("QListViewHelper: first visible row is %d", index.row());
index = QListViewHelper::lastVisible(view);
qDebug("QListViewHelper: last visible row is %d", index.row());
}
usage:
QModelIndex &index =
QListViewHelper::firstVisible(listViewPointerHere)
note: since it does use Qt 4.8 private-headers it may no longer work in latter versions and will need some changes.
You can keep track of all the elements that are drawn per paint event. I used a delegate and overloaded the paint event.
I also overloaded the paint event in the view. During this call, all the visible delegates will get a paint event.
If you just need to know if an item is visible, you can increment a frame count in view->paintEvent and set that number in the delegate item. The item is visible of the item matches the current frame number.
If you need a list of all visible items, clear the visible item list in view->paintEvent and add each item in the int the delegate->paintEvent to the visible items list.

QGraphicsItem validating a position change

I have a custom QGraphicsItem implementation. I need to be able to limit where the item can be moved - i,e. restrict it to a certain area. When I checked the Qt documentation this is what it suggested:
QVariant Component::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionChange && scene()) {
// value is the new position.
QPointF newPos = value.toPointF();
QRectF rect = scene()->sceneRect();
if (!rect.contains(newPos)) {
// Keep the item inside the scene rect.
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
return newPos;
}
}
return QGraphicsItem::itemChange(change, value);
}
So basically, check the position passed to itemChange, and if you don't like it, change it and return the new value.
Seems simple enough, except it doesn't actually work. When I checked the call stack I see that itemChange is being called from QGraphicsItem::setPos, but it doesn't even look at the return value. So there is no purpose to me returning a changed position, no one is looking at it. See code from QGraphicsItem.cpp
// Notify the item that the position is changing.
const QVariant newPosVariant(itemChange(ItemPositionChange, qVariantFromValue<QPointF>(pos)));
QPointF newPos = newPosVariant.toPointF();
if (newPos == d_ptr->pos)
return;
// Update and repositition.
d_ptr->setPosHelper(newPos);
// Send post-notification.
itemChange(QGraphicsItem::ItemPositionHasChanged, newPosVariant);
d_ptr->sendScenePosChange();
Any suggestions? I was hoping to avoid re-implementing the whole click and drag behavior myself using mouse-down mouse-move etc..., but I suppose I will have to if I can't find a better idea.
I didn't actually try it but it looks to me it is checking the return position. The returned restricted position is used in the constructor of newPosVariant to converted to newPos. It's then used to set the position of the item if it's different from the current one.

Resources