When I select several QGraphicsItem (with Ctrl key) I can move them together, but the mouseMoveEvent is triggered only for the item that actually receives the event. Is there a way to make every selected items receive the event ? I can't find it in Qt's doc.
Could I group selected items together and handle it within QGraphicsView's mouseMoveEvent ?
Thanks a lot for any help :)
No there is no default way to do what you want as far as I know. Something you could do is the following:
Subclass QGraphicsScene and implement the mouseMoveEvent
In the mouse move event check if there is an item at the event position using the itemAt function
If there is an item and it is selected (isSelected), get all selected items of the scene.
For all selected items call the same function you would call.
Sample code follows:
void mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
QPointF mousePosition = mouseEvent->scenePos();
QGraphicsItem* pItem = itemAt(mousePosition.x(), mousePosition.y());
if (pItem == NULL)
{
QGraphicsScene::mouseMoveEvent(mouseEvent);
return;
}
if (pItem->isSelected() == false)
{
QGraphicsScene::mouseMoveEvent(mouseEvent);
return;
}
// Get all selected items
QList<QGraphicsItem *> items = selectedItems();
for (unsinged i=0; i<items.count(); i++)
// Do what you want to do when a mouse move over a selected item.
items[i]->doSomething();
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
I'm reading between the lines of your question a little, but it sounds like you might be better served by implementing QGraphicsItem::itemChange on your QGraphicsItem class(es). This will get called whenever the position changes--whether by mouse, keyboard, programmatic, etc. You can even cancel the change if you want to.
http://doc.qt.io/qt-5/qgraphicsitem.html#itemChange
Related
How to be given setFocus() properties into QGraphicsItemGroup item?
I have 3 GraphicsRectItem in QGraphicsItemGroup with bondingrect()
and I want to give setfocus() preporty this QGraphicsItemGroup to can be controled with keyPressEvent
Although your question does not provide enough details, but it seems that you have some graphics items inside a group, and you want to control the items through the keyboard, right?
If so, let's assume that you have some QGraphicsItem items in a QGraphicItemGroup and you want to change the active item by using the Tab key and then do some actions on the active item by using other keys (e.g., rotate it or so on).
My solution is as follows:
define a QGraphicsItem *activeItem pointer which will point to the active item in the group.
overriding the group's keyPressedEvent function as follows:
void MainWindow::keyPressEvent(QKeyEvent *event)
{
//Which key is pressed? ---> <QKeyEvent> must be included
if (event->key() == Qt::Key_Tab) {
/* set activeItem as you desire.
* For example, find the index of the
* item that currently activeItem is pointing
* to and then go to the next item.
*/
//then return
}
if (event->key() == Qt::Key::YourDesiredKey) {
//do your actions and then return
}
}
I looked around and it seems that the problem is present not only for tree widget but also for other widgets. But in my case, I found a solution, although an incomplete one. I am adding actions to my tree widget, so that when you right click on it, a popup with these actions appears. However, when I add items to my tree widget and I right click on them, the same popup appears.
What I would like to do is that when you right click on the tree widget, a tree widget popup menu appears and when you right click on items, another corresponding popup menu appears. Does anybody knows how to do this?
First,config QTreeWidget to response(emit signal) right mouse click:
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
Second,connect the signal with your slot "MainWindow::prepareMenu":
connect(treeWidget,&QTreeWidget::customContextMenuRequested,this,&MainWindow::prepareMenu);
Third,create context menu in the slot:
void MainWindow::prepareMenu( const QPoint & pos )
{
QTreeWidget *tree = treeWid;
QTreeWidgetItem *nd = tree->itemAt( pos );
qDebug()<<pos<<nd->text(0);
QAction *newAct = new QAction(QIcon(":/Resource/warning32.ico"), tr("&New"), this);
newAct->setStatusTip(tr("new sth"));
connect(newAct, SIGNAL(triggered()), this, SLOT(newDev()));
QMenu menu(this);
menu.addAction(newAct);
QPoint pt(pos);
menu.exec( tree->mapToGlobal(pos) );
}
First you should set the context menu policy to CustomContextMenu:
treeView->setContextMenuPolicy(Qt::CustomContextMenu);
Then you can connect to the QWidget::customContextMenuRequested(const QPoint&) signal and show your context menu.
For those who prefer to use designer more, here is another way to do it:
1) Set context menu policy to custom context menu
Either by code:
ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
or using graphical designer, click on the tree widget and set it using Property Editor:
2) Create Handler function
In designer, right click on the treeWidget and select "Go to Slot..." option. A window similar to this will appear:
Click on the "CustomContextMenuRequested(QPoint)" option. Handler function will be defined, declared, and it will be connected automatically.
void MainWindow::on_treeWidget_customContextMenuRequested(const QPoint &pos)
{
// this function will be called on right click
}
This step can also be done by defining and connecting the slot function yourself.
3) Create the options on the context menu.
Go to action editor tab (Usually docked at the bottom of designer). Add actions you want to have on context menu by clicking new button on top left. You will encounter such an interface :
You can (optionally) have a tooltip or icon for the action, or make it checkable. You can crate a shortcut like Ctrl+C for a copy action.
4) Create the menu and fire it
void MainWindow::on_treeWidget_customContextMenuRequested(const QPoint &pos)
{
QMenu menu(this); // add menu items
menu.addAction(ui->actionDelete);
menu.addEdit(ui->actionDelete);
...
ui->actionDelete->setData(QVariant(pos)); // if you will need the position data save it to the action
menu.exec( ui->treeWidget->mapToGlobal(pos) );
}
5) Create handler functions for each action
Like in step 2, either create slot function and connect it manually, or right-click on on an action, click the "Go to slots..." option and select triggered() slot.
6) Lastly, apply your logic in the slot function
void MainWindow::on_actionEdit_triggered()
{
QTreeWidgetItem *clickedItem = ui->treeWidget->itemAt(ui->actionDelete->data().toPoint());
// your logic
}
Take a look at overloading QAbstractItemModel and providing your own OnContextMenuRequested. Via this function you can have different items create different context menus.
Here's some shortened pseudo-ish code from one of my projects that may be helpful:
void MyModel::OnContextMenuRequested(const QModelIndex& index, const QPoint& globalPos)
{
// find 'node' corresponding to 'index'
vector<pair<string,BaseNode*> > actions = node->GetActions(true);
if(actions.size()==0) return;
// the ptr list helps us delete the actions
boost::ptr_list<QObject> actionPtrList;
QList<QAction*> qtActions;
for(unsigned int i=0;i<actions.size();i++)
{
QAction* act = new QAction(actions[i].first.c_str(),NULL);
act->setData(qVariantFromValue(actions[i].second));
actionPtrList.push_back(act);
qtActions.append(act);
}
// create and show the context menu
QMenu *menu = new QMenu("Item actions",NULL);
actionPtrList.push_back(menu);
QAction* act = menu->exec(qtActions,globalPos);
if(act==NULL) return;
// act on the resulting action 'act'
}
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.
Does a simple method exist to select part of a QGraphicsItem (like for a selection, with a dashed border for example), despite its position in the QGraphicsScene
I've found QGraphicsItem::ItemIsSelectable but it doesn't help me much.
Thx
You can't select a part of QGraphicsItem. You can select whole item. Usualy it will draw a dashed rectangle around itself when selected.
You can select item by:
QGraphicsItem::setSelected
or
QGraphicsScene::setSelectionArea
Do you want to select it when you're clicking on it? If yes, you can override the mousePressEvent(QGraphicsSceneMouseEvent event) listener for this item, and use setSelected(). In Java (sorry about it), it would be something like:
#Override
public void mousePressEvent (QGraphicsSceneMouseEvent event) {
if (event.button() == Qt.MouseButton.LeftButton) {
this.setSelected(true);
}
}
If you want to select it with a left button of course. :)
You can call QGraphicsScene::setSelectionArea(QPainter path, QTransform), and later retrieve the area by calling selectionArea. However if you want to retrieve the items currently under selection, you will get only whole QGraphicsItems.
Here is an example of how to use ItemIsSelectable:
QGraphicsRectItem* item = new QGraphicsRectItem(rect);
item->setFlag(QGraphicsItem::ItemIsSelectable);
graphicsScene->addItem(item);
You can then connect the selectionChanged signal to a slot:
connect(graphicsScene, &QGraphicsScene::selectionChanged, this, &MyWidget::itemClicked);
Note that selectionChanged is triggered on double click only, not on a single click.
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.