resizing row's height in QTreeWidget/QTreeView - qt

I have some problems with sizing row's height in QTreeWidget. I use QStyledItemDelegate with QPlainTextEdit. During editing text in QPlainTextEdit i check for changes with a help of:
rect = self.blockBoundingRect(self.firstVisibleBlock())
and if text's height changes i resize editor size and need row in QTreeWidget also resizing. But i don't know how to inform TreeWidget or a Delegate about changes. I tried to initialize editor with index, that i could use in a future, but Delegate creates new editor every time and i failed to use signals. Also I used following function to catch resize event, but it' doesn't:
bool QAbstractItemDelegate::editorEvent ( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index )
How can i bound editor's size changes with TreeWidget?
And one more thing, by default all items (cells) in TreeWidget have -1 or some big value as default width. I need whole text in cell to be visible, so how can i limit cells width only by visible range and make it expand in height? I want for it the same behavior as in instance a table in MSWord.
Thank you in advance,
Serge

I believe you would have to notify model about the data change without closing editor, this should force treeview to recalculate its row height according to the new content of the field it's showing. What you could do is override eventFilter method in your delegate and try to emit commitData signal on key press; smth like this:
bool YourStyledItemDelegate::eventFilter(QObject* object, QEvent* event)
{
bool result = QStyledItemDelegate::eventFilter(object, event);
QWidget* editor = qobject_cast<QWidget*>(object);
if (editor)
{
if (event->type() == QEvent::KeyPress)
{
emit commitData(editor); //<- this should force row to recalculate its size
}
}
return result;
}
hope this would give you an idea on how to proceed, regars

Related

How to share a model's item between a QML ListView and a Grid

I would like to know the best way to achieve the following thing in QML:
I have a ListView with droppable elements and a Grid initialy filled with DropArea. The ListView uses a model derived from QAbstractItemModel. I would like to drop an element on the grid and interact with it (rename it for instance). For now, any modifications in the ListView update the model, but how modifications of the element in the grid could update the model ?
There can be multiple items dropped in the grid corresponding to a subset of ListView's model. I do not know how can I achieve this. The Package can not be used because the Grid is not a GridView and Items must be moved/set at specific positions. So I tried to:
create a ListView displayed on the dropped item, using the same model as the source ListView used to drag items,
set the same rootIndex, then the same index
I am close to a solution but I think it is not the best way to do this.
Any clue ?
Thanks
I would like to have different visual representation of the same model
item in a ListView and in a component in a Grid. So, a modification of
the item in the ListView should update the item in the Grid and
vice-versa.
If your model's data is not QObject derived with NOTIFY and properties, notifications of changes can only be made through the model.
And since you won't be using the model for the grid, that means your model has to use underlying QObject instances. A generic model object might come in handy.
When you have that you only need to reference the underlying QObject model item and use bindings to set up your list view delegate. Then when you drag and drop, you only need to pass a reference to the QObject to that other visual representation which you will be creating inside the grid.
Lastly, take care not to be left with dangling references when you remove items from the model, as that will most likely hard-crash your application. Use the onDestruction attached signal to clear elements from the grid as the model item objects are destroyed.
I finally found a solution by create a C++ type which inherits from QObject an which can be embedded in a QML object. This type has read/write properties and is initialized with the same model as the ListView. The interesting methods are:
/* writing to a property **from QML** goes here */
void ModelItem::setName(const QString& name)
{
setModelData(GroupMemberModel::Nom, name);
}
/* then here */
bool ModelItem::setModelData(GroupMemberModel::Role role, const QVariant& value)
{
return m_model->setData(m_modelIndex, value, role);
}
/* any changes in the model fall here (signals/slots mecanism)*/
void ModelItem::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
{
if(m_modelIndex.row() < topLeft.row() || m_modelIndex.row() > bottomRight.row())
return;
if(m_modelIndex.column() < topLeft.column() || m_modelIndex.column() > bottomRight.column())
return;
//Index is modified, emit signal
foreach(int role, roles) {
emitDataChanged(role);
}
}
/* **notify QML** by emit signal on property */
void ModelItem::emitDataChanged(int role) const
{
if(role < (Qt::UserRole+1))
role+=Qt::UserRole+1;
switch(role)
{
case GroupMemberModel::Nom:
emit nameChanged();
break;
default:
qDebug() << "ModelItem::dataChanged, unknown role";
break;
}
}
This works as needed and is simplier than what I thought.

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.

Some cells in QtableView are not repainted automatically after a clicking on a table cell

I'm using a QTableView in the implementation of an interactive board game. Images are to be displayed in the cells of the table. I'm using a QStyledItemDelegate with a paint function to draw the images inside the table cells.
As the images should be displayed only in certain cells of the table and updated when a user clicks on a table cell, an double int array is used which is of the same dimensions as the table. Depending on the values the the array, the painter should draw images in specific cells of the table. Initially there are only 4 images inside 4 cells of the table and as the user clicks on a cell in the table, the array is updated which should consequently mean that whats drawn and displayed inside the cells of rthe table should be changed.
Normally the user clicks on an empty (white) cell which is updated successfully and the specific image is shown in the cell. However, if there are other cells which contain an image and should be updated, the update is not shown, although the double int array is updated. I also saw a weird thing, that is when I click on the cells in which their display should have been updated, the update happens. This of-course occurs regardless of how I update when someone clicks on a cell.
I tried to first erase whats inside the cell before redrawing, but its still not working. Does the delegate runs continuously in a thread and the painter function is called with the index of each cell in the table? I do not get how an update on a cell containing an Image does not update automatically although the painter should have redrawn the cell's area and it occurs only after the a click on the cell has been made. Or its cus a new painter is called to the painter's function each time?!
Well, here is my implementation of the painter's function of the delegate:
void Sphere::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if(tb1[index.row()][index.column()] == 1)
{
QImage Q1("Red Sphere.jpg");
QRectF source(0.0, 0.0, 72.0, 70.0);
painter->eraseRect(option.rect);
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
painter->drawImage(option.rect, Q1, source);
}
else if(tb1[index.row()][index.column()] == 2)
{
QImage Q1("Blue Sphere.jpg");
QRectF source(0.0, 0.0, 72.0, 70.0);
painter->eraseRect(option.rect);
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
painter->drawImage(option.rect, Q1, source);
}
else
{
painter->eraseRect(option.rect);
QStyledItemDelegate::paint(painter, option, index);
}
}
I can give you any more info if u needed to solve my problem. Thanks in advance.
I also saw a weird thing, that is when I click on the cells in which their display should have been updated, the update happens.
I think you have to determine witch cells are affected by the change made by the user in the current cell and force those cells to update(). When you think of it, your delegate can check if the content of the tb1 change to automatically decide to repaint the cell it belong to.
EDIT
A simple way to do that, could be to connect a slot to the clicked ( const QModelIndex & index ) then in here determine what change and call the update ( const QModelIndex & index ) method ...
According to QT Documentation
After painting, you should ensure that
the painter is returned to its the
state it was supplied in when this
function was called. For example, it
may be useful to call QPainter::save()
before painting and
QPainter::restore() afterwards.
I think you are missing save() and restore() methods of QPainter in your function.

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.

How to insert QPushButton into TableView?

I am implementing QAbstractTableModel and I would like to insert a QPushButton in the last column of each row. When users click on this button, a new window is shown with more information about this row.
Do you have any idea how to insert the button? I know about delegating system but all examples are only about "how to edit color with the combo box"...
You can use
QPushButton* viewButton = new QPushButton("View");
tableView->setIndexWidget(model->index(counter,2), viewButton);
The model-view architecture isn't made to insert widgets into different cells, but you can draw the push button within the cell.
The differences are:
It will only be a drawing of a pushbutton
Without extra work (perhaps quite a bit of extra work) the button won't be highlighted on mouseover
In consequence of #1 above, you can't use signals and slots
That said, here's how to do it:
Subclass QAbstractItemDelegate (or QStyledItemDelegate) and implement the paint() method. To draw the pushbutton control (or any other control for that matter) you'll need to use a style or the QStylePainter::drawControl() method:
class PushButtonDelegate : public QAbstractItemDelegate
{
// TODO: handle public, private, etc.
QAbstractItemView *view;
public PushButtonDelegate(QAbstractItemView* view)
{
this->view = view;
}
void PushButtonDelegate::paint(
QPainter* painter,
const QStyleOptionViewItem & option,
const QModelIndex & index
) const
{
// assuming this delegate is only registered for the correct column/row
QStylePainter stylePainter(view);
// OR: stylePainter(painter->device)
stylePainter->drawControl(QStyle::CE_PushButton, option);
// OR: view->style()->drawControl(QStyle::CE_PushButton, option, painter, view);
// OR: QApplication::style()->drawControl(/* params as above */);
}
}
Since the delegate keeps you within the model-view realm, use the views signals about selection and edits to popup your information window.
You can use setCellWidget(row,column,QWidget*) to set a widget in a specific cell.

Resources