In Qt's QGraphicsScene, if I wanna one item, just click it, and click another selectable item will make the selected item to be unselected. If I want to select multiple items, I'd use Ctrl-key. But this maybe not convenient for some cases, then how to select multiple items without pressing Ctrl-key within QGraphicsScene?
You want to change the default behavior of QGraphicsScene, so you have to create your own scene class, inheriting QGraphicsScene.
In your class, you'll have to reimplement at least mousePressEvent and handle the item selection yourself.
Here is how you could do it (the inherited scene class is called GraphicsSelectionScene) :
void GraphicsSelectionScene::mousePressEvent(QGraphicsSceneMouseEvent* pMouseEvent) {
QGraphicsItem* pItemUnderMouse = itemAt(pMouseEvent->scenePos().x(), pMouseEvent->scenePos().y());
if (!pItemUnderMouse)
return;
if (pItemUnderMouse->isEnabled() &&
pItemUnderMouse->flags() & QGraphicsItem::ItemIsSelectable)
pItemUnderMouse->setSelected(!pItemUnderMouse->isSelected());
}
Implementing this way, clicking on an item with select it if it is not already, or will unselect it otherwise.
But be careful, implement mousePressEvent is certainly not enough : you'll have to handle the mouseDoubleClickEventas well if you don't want the default behavior.
Your scene will have to be displayed by a QGraphicsView. Here is an example of a view creating it's own scene (MainFrm class is inheriting QGraphicsView) :
#include "mainfrm.h"
#include "ui_mainfrm.h"
#include "graphicsselectionscene.h"
#include <QGraphicsItem>
MainFrm::MainFrm(QWidget *parent) : QGraphicsView(parent), ui(new Ui::MainFrm) {
ui->setupUi(this);
// Create a scene with our own selection behavior
QGraphicsScene* pScene = new GraphicsSelectionScene(this);
this->setScene(pScene);
// Create a few items for testing
QGraphicsItem* pRect1 = pScene->addRect(10,10,50,50, QColor(Qt::red), QBrush(Qt::blue));
QGraphicsItem* pRect2 = pScene->addRect(100,-10,50,50);
QGraphicsItem* pRect3 = pScene->addRect(-200,-30,50,50);
// Make sure the items are selectable
pRect1->setFlag(QGraphicsItem::ItemIsSelectable, true);
pRect2->setFlag(QGraphicsItem::ItemIsSelectable, true);
pRect3->setFlag(QGraphicsItem::ItemIsSelectable, true);
}
maybe it's a hack but it's works for me. In this example you can select multiple items using shift key
void MySceneView::mousePressEvent(QMouseEvent *event)
{
if (event->modifiers() & Qt::ShiftModifier ) //any other condition
event->setModifiers(Qt::ControlModifier);
QGraphicsView::mousePressEvent(event);
}
void MySceneView::mouseReleaseEvent(QMouseEvent *event)
{
if (event->modifiers() & Qt::ShiftModifier ) //any other condition
event->setModifiers(Qt::ControlModifier);
QGraphicsView::mouseReleaseEvent(event);
}
Related
I have to make something like the iOS interface, 3x3 field of icons that can be dragged to change their position, and then remember it in an XML file.
I decided to use Grid class (QWidget is parent) as a container and my own class, inherited from QWidget, as elements.
Now I'm trying to learn how to perform a drag & drop for QWidget, but it seems like you are only able to drop onto a QWidget, but it's impossible to drag it.
Is it impossible? How do I move this thing?
Dragging a widget isn't that easy. Plenty of coding you have to do yourself.
From the Qt Docs:
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton
&& iconLabel->geometry().contains(event->pos())) {
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(commentEdit->toPlainText());
drag->setMimeData(mimeData);
drag->setPixmap(iconPixmap);
Qt::DropAction dropAction = drag->exec();
...
}
}
This means, when your widget receives a message that it is to be dragged, e.g. like with a mousepress, it has to create a QDrag object.
In the QDrag object you can set a pixmap, which represents your dragged widget. If you hide your widget at this point, it looks like as if your mouse pointer 'took' the widget.
Additionally you need a QMimeData object. In this you can put all kinds of data, which describes your widget. In your use case something which allows you to identify your widget. Because, and here comes the difficult part: You have to do the moving yourself.
The widget, which is the grid's parent, receives the drop event and reads from the mime data, which widget wishes to me moved. From the QDropEvent it gets the point where the widget is to be moved. That's what you have to code: The actual repositioning in your grid layout. And don't forget to update the xml.
do a trick then you can able to drag QWidget also,
QPixmap *widgetPixmap = new QPixmap;
yourWidget->render(widgetPixmap); // rendering the current widget to t_iconTPixmap
Now use this widgetPixmap as the pixmap for yourDragObject->setPixmap();
example,
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton
&& iconLabel->geometry().contains(event->pos())) {
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(commentEdit->toPlainText());
QPixmap *widgetPixmap = new QPixmap;
yourWidget->render(widgetPixmap);
drag->setMimeData(mimeData);
drag->setPixmap(widgetPixmap);
Qt::DropAction dropAction = drag->exec();
...
}
}
The Qt wiki contains a QtQuick example on how to emulate the iOS icon interface with less then 80 lines of code.
You can find it here.
I want to change a QWidget in a QMainWindow dynamically. Therefore, the old widget (if it exists) will be deleted, a new one will be constructed and added to the main window.
The widget (_visualization) is a QMainWindow itself that contains a menu bar and a widget that shows an OSG scene graph.
If I donĀ“t call show() on the new widget, I will only see the menu bar, but not the scene graph.
My goal is to call show(), when the user clicks on a button. The button is connected with the outer QMainWindow (MyQMainWindow).
Unfortunately, when I call show() on _visualization in the connected method, the scene graph will not be shown. In contrast to that, the scene graph will be shown, if I call it in the constructor method (loadVisualization(...)).
MyQMainWindow::MyQMainWindow(QWidget *parent ) :
QMainWindow(parent) {
...
loadVisualization(...);
connect(_ui->nextButton, SIGNAL(clicked()), this, SLOT(showNext()));
...
}
void MyQMainWindow::loadVisualization(QString filePath) {
if (_visualization) {
_heightWidgetLayout->removeWidget(_visualization);
delete _visualization;
}
_visualization= new HeightVisualization(0, filePath);
_visualization->setParent(_mainWindowWidget);
_heightWidgetLayout->addWidget(_visualization);
//_visualization->show(); // will work here
}
void MyQMainWindow::showNext() {
_visualization->show(); // does not work here!
}
I know that QT will call setVisible(...) on the widget. This method first tests some states in QT (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)). If I call show() in showNext(), these tests lead to a return without any change.
Is it a problem with connectors and slots? Is there a possibility to show the widget, when the user clicked on a button?
What I have learned is that it is easy to use stackedWidget.
You can then programmatically change it with this
currentPageIndex = (currentPageIndex + 1) % 3;
ui->stackedWidget->setCurrentIndex(0);
The 3 can be the total pages you have in your stack widget. You can either use currentPageIndex, or you can just create some constants with the page ids like I have done with the 0.
Newbie here. I want to have a GUI effect in my dock widget that whenever I click "add more" button or link, a new lineEdit field appears in the bottom.
I saw many software has something like
point-1 (_____,_____)
point-2 (_____,_____)
+ Add More Points
And when you click "+ Add More Points", a new point-3 will show up and wait for the input.
The code I have now is something like this:
#include "perfectPanel.hpp"
perfectPanel::perfectPanel(QWidget *parent) : QWidget(parent)
{
setupUi(this);
readInfo();
connect
(
btn_accept,
SIGNAL(clicked()),
this,
SLOT(readInfo()),
Qt::UniqueConnection
);
}
// Destructor
perfectPanel::~perfectPanel()
{}
void perfectPanel::readInfo()
{
xObject_ = vtkDoubleArray::New();
yObject_ = vtkDoubleArray::New();
xObject_->InsertNextValue( lineEdit_xObject01X->text().toDouble() );
xObject_->InsertNextValue( lineEdit_xObject02X->text().toDouble() );
yObject_->InsertNextValue( lineEdit_yObject01Y->text().toDouble() );
yObject_->InsertNextValue( lineEdit_yObject02Y->text().toDouble() );
}
You'll need to add that + Add More Points button to the perfectPanel class. Let's say you've already done that with this declaration in your class' private data section:
QPushButton* m_AddPoint;
Now, connect the button's clicked() signal to some slot to add the point. From the example code, you seem to already know how to do this, so I won't go into the specifics. Let's say you've connected the button's click event to the addPoint function.
void perfectPanel::addPoint()
{
/* The "this" argument is needed to prevent memory leaks */
QLineEdit* Field = new QLineEdit(this);
/* Your perfectPanel class has some layout where the existing LineEdit rows
are. I'm assuming m_Layout is a pointer to that layout here. */
m_Layout->addWidget(Field);
Field->show();
}
I'd like to be able to deselect items in my QTreeView by clicking in a part of the QTreeView with no items in, but I can't seem to find anyway of doing this. I'd intercept a click that's not on an item, but the QTreeView doesn't have a clicked signal, so I can't work out how to do this.
Based on #Eric's solution, and as it only deselects if the clicked item was selected, here is what I came up with.
This solution also works when you click the blank area of the QTreeView
#ifndef DESELECTABLETREEVIEW_H
#define DESELECTABLETREEVIEW_H
#include "QTreeView"
#include "QMouseEvent"
#include "QDebug"
class DeselectableTreeView : public QTreeView
{
public:
DeselectableTreeView(QWidget *parent) : QTreeView(parent) {}
virtual ~DeselectableTreeView() {}
private:
virtual void mousePressEvent(QMouseEvent *event)
{
QModelIndex item = indexAt(event->pos());
bool selected = selectionModel()->isSelected(indexAt(event->pos()));
QTreeView::mousePressEvent(event);
if ((item.row() == -1 && item.column() == -1) || selected)
{
clearSelection();
const QModelIndex index;
selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select);
}
}
};
#endif // DESELECTABLETREEVIEW_H
Yassir
This is actually quite simple (in PyQt):
class DeselectableTreeView(QtGui.QTreeView):
def mousePressEvent(self, event):
self.clearSelection()
QtGui.QTreeView.mousePressEvent(self, event)
Qt uses mousePressEvent to emit clicked. If you clear the selection before sending on the event, then if an item is clicked it will be selected, otherwise nothing will be selected. Many thanks to Patrice for helping me out with this one :)
The clearSelection does not work in my case. I'm using treeviews with a singleselection mode. Here is what I've coded:
class DeselectableTreeView : public QTreeView
{
public:
DeselectableTreeView(QWidget *parent) : QTreeView(parent) {}
virtual ~DeselectableTreeView() {}
private:
virtual void mousePressEvent(QMouseEvent *event)
{
QModelIndex item = indexAt(event->pos());
bool selected = selectionModel()->isSelected(item);
QTreeView::mousePressEvent(event);
if (selected)
selectionModel()->select(item, QItemSelectionModel::Deselect);
}
};
This works really fine.
Eric
QTreeView inherits from QAbstractView (http://doc.qt.digia.com/4.6/qtreeview.html) which has a clicked signal. The problem is that the signal is emitted only when index is valid so you can not achieve what you want with this signal.
Try to intercept the mousePressEvent instead. In the function you can find where the user has clicked and deselect selected item if needed.
In the answer by #Skilldrick, we risk sending superfluous events. If an item is already selected, and we're clicking it again, we are raising deselected and selected events. Based on other listeners in your application, this might not be what you want.
The solution by #eric-maeker only deselects an item if we click it again while it is already selected. Strictly speaking, this is not the answer to the original question, which was how to deselect the selected item when clicking somewhere else.
#yassir-ennazk gets close, but as pointed out by #adrian-maire, the solution is not optimal. event->pos() is evaluated twice. Also, the mouse event is always evaluated by calling QTreeView::mousePressEvent.
Here's the solution I've come up with, based on the other answers mentioned above. If we're clicking at a point where another tree view item is present, the new item is selected by forwarding the event to the TreeView. If not, we're clearing the selection.
Note that this works for QTreeWidgets as well.
virtual void mousePressEvent(QMouseEvent* event)
{
QModelIndex item = indexAt(event->pos());
if (item.isValid())
{
QTreeView::mousePressEvent(event);
}
else
{
clearSelection();
const QModelIndex index;
selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select);
}
}
To add to #Skilldrick's answer, if you need to apply this to a view that has already been instantiated because you're using Qt Designer, you can do something like this:
import new
def mousePressEvent(self, event):
self.clearSelection()
QtGui.QTableView.mousePressEvent(self, event)
self.ui.tableView.mousePressEvent = new.instancemethod(mousePressEvent, self.ui.tableView, None)
This assumes that your view is self.ui.tableView.
Thanks to this answer: https://stackoverflow.com/a/1647616/1300519
You could try setting a different selection mode for your widget. I don't know if any of them quite covers what it appears you want (single selection, but deselectable).
Since the question is specifically about PyQt, I'd like to add this based on the answer by Nick Pruehs and the comment by ekhumoro, the simplest way to achieve this:
class TreeView(QTreeView):
def __init__(self, *args, **kwds):
QTreeView.__init__(self, *args, **kwds)
def mousePressEvent(self, event):
item = self.indexAt(event.pos())
if not item.isValid():
self.clearSelection()
QTreeView.mousePressEvent(self, event)
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.