I have a QTreeWidget and I want certain rows to be non select-able, which can be achieved by QTreeWidgetItem::setFlags(treeWidgetItem->flags() & ~Qt::ItemIsSelectable).
The problem is that I have an existing row that is already selected and later I click on the non select-able row, selectedItems() returns an empty list. I want the selected row to keep its selection if the user tries to select a non select-able row.
Should I keep track of the selection and handle this scenario in the code, or this can be achieved somehow else. I'd rather not reinvent the wheel.
Thank you.
Cause
Calling QTreeView::mousePressEvent(event) clears the selection when clicked on a non-selectable item if the selection mode is set to QAbstractItemView::SingleSelection.
Solution
My solution would be to either:
Set the selection mode to QAbstractItemView::MultiSelection,
or (in case this is not desired):
Reimplement the mouse events in a subclass of QTreeWidget in order to bypass the default behavior.
Note: In either case, use the QItemSelectionModel::selectionChanged signal to get the list of the selected items.
Example
Here is an example re-implementation of the mouse events in MyTreeWidget preventing the selection of being cleared by clicking a non-selectable item. The top item is expanded/collapsed on a double click:
void MyTreeWidget::mousePressEvent(QMouseEvent *event)
{
if (indexAt(event->pos())->flags() & Qt::ItemIsSelectable)
QTreeWidget::mousePressEvent(event);
}
void MyTreeWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QTreeWidget::mouseDoubleClickEvent(event);
QTreeWidgetItem *item = itemAt(event->pos());
if (item && item->childCount())
item->setExpanded(!item->isExpanded());
}
The modified in the described manner version of the provided example is available on GitHub.
Improvements
Special thanks to #eyllanesc for making this example more waterproof by:
adding a check if item is not NULL
replacing itemAt with indexAt
Related
I have created Qt GUI application. It consists of QGraphicsScene, and items (QGraphicsItems) are added to them by pressing or triggering pushbuttons. Each item added to the scene are members of different classes derived from QGraphicsItem. Now, my challenge is to delete an added item off the scene through one of the following mechanisms:
1) Right click an added item, create a context menu, and then use
scene->removeItem(addedItem);
2) Double click the item which deletes the item
3) Select an item using the flag ItemIsSelectable, and then delete the item by pressing the delete key on the keyboard
But having said that, as a newbie to Qt, I'm unable to do number 1 since the context menu doesn't show up when right clicked. In the case of number 2, I used signals and slots, a single emitted whenever an item is double clicked, and a slot in the mainWindow absorbs the signal and removes the item. But this way, the programs fails to compile because of the error "duplicate symbol found" when I add a Q_OBJECT macro to the header file of the item's class.
So my final option is to select an item on the screen and propane the keyboard signal to delete the item by pressing delete. How can be this done? Please give me advice if any of the above methods can be easily done in case I might be doing it completely wrong.
P.S. : I know there a lot of queries regarding deleting QGraphicsItem off QGraphicsScene, but none of them document a solid answer.
... I'm unable to do number 1 since the context menu doesn't show up when right clicked.
There are two possible methods to accomplish this:
Create a QWidget based menu, attached to the QGraphicsView.
Create your own menu item, derived from a QGraphicsItem.
Whilst the 2nd method will take more time, it's probably a better system in my opinion, as it will feel more integrated with the item you're deleting in the scene. The first method is also possible and if it's not working, then you could post an example question on SO.
2, I used signals and slots, ... because of the error "duplicate symbol found" when I add a Q_OBJECT macro to the header file
It sounds like you're trying to add the signal / slot functionality to a class derived from QGraphicsItem. You don't need to do this. Qt provides the QGraphicsObject class, which you can derive from, instead of QGraphicsItem, if you want signals and slots on items in a QGraphicsScene.
propane the keyboard signal to delete the item by pressing delete.
I assume you mean to 'propagate' keyboard signals. By overriding the QGraphicsScene and its keyPressEvent or keyReleaseEvent, you can get a list of selected items and delete them from the scene. Here's a skeleton example: -
class MyScene : public QGraphicsScene
{
protected:
void keyReleaseEvent(QKeyEvent * keyEvent);
};
void MyScene::keyReleaseEvent(QKeyEvent * keyEvent)
{
if(keyEvent->key() == Qt::Key_Backspace)
{
QList<QGraphicsItem*> selectedItems = selectedItems(); // get list of selected items
foreach(QGraphicsItem* item, selectedItems)
{
removeItem(item);
delete item;
}
}
}
You're seeking a lot of answers, Not so much how to handle QGraphicsItem or QGraphicsScene.
1) Right click an added item, create a context menu, and then use scene->removeItem(addedItem); here.
2) Double click the item, which deletes the item - you'll need to handle double clicks, and hit-testing the QGraphicsItems, you'll have to implement mouseDoubleClickEvent(QMouseEvent *e) and pass e's pos() to this to determine if a QGraphicsItem was clicked or not.
3) Select an item using the flag ItemIsSelectable and then delete the item by pressing the delete key on the keyboard - I'm not sure about the ItemIsSelectable flag. However, you'll need #2. And to learn how to handle keyboard input, by overriding this:
void QWidget::keyPressEvent( QKeyEvent *k ){
switch ( tolower(k->ascii()) ) {
case '\x08': \\backspace
break;
case '\x7F': \\delete
break;
}
}
There's also the Qt::key enumeration, which has Key_Backspace, and Key_Delete. It can be tested against the QKeyEvent::Key()'s return if you don't like dealing with ASCII character codes.
I am new to Qt. It seems the default for multiple selection of qgraphicsitem is to press ctrl button.
But is it possible to disable this function? Or reload this function?
This is controlled by the items' flags. To disable selection for a particular item, do
item->setFlag(QGraphicsItem::ItemIsSelectable, false);
If you want to completly disable selecting items for a QGraphicsScene regardless of the item flags I would recommend to connect QGraphicsScene::selectionChanged to QGraphicsScene::clearSelection.
If you want to disable multiple selection I suggest the following:
Subclass QGraphicsScene and keep a pointer lastSelection to a QGraphicsItem around
Create a slot connected to QGraphicsScene::selectionChanged
Check selectedItems:
it's empty: nothing to do (=nothing selected)
contains only lastSelection: nothing to do (=selection didn't really change)
contains one item, not lastSelection: set lastSelection to that item (=one item selected for the first time)
contains two items: One must be lastSelection. Remove that one from the selection (lastSelection->setSelected(false);), set lastSelection to the remaining item. (=another item was selected, move selection to it)
You might need to block signals during modifying the selection inside the slot.
The simple way to disable multiple selection is:
Create your own Dirived class from QGraphicsItem.
Overload the protected mousePressEvent function and disable ControlModifier:
protected:
void YourOwnQGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) Q_DECL_OVERRIDE
{
if(mouseEvent->modifiers() & Qt::ControlModifier)
{
mouseEvent->ignore();
}
else
{
QGraphicsItem::mousePressEvent(mouseEvent);
//Do what you want...
}
}
I'm trying to select an item in my QTreeView with the arrow keys but cannot find a method belonging to this class that returns the index of the highlighted item.
Up to now, i have only been able to select item with a click of the mouse :
connect(m_QTreeView, SIGNAL(clicked(QModelIndex)), this, SLOT(ItemTreeClicked(QModelIndex)));
but if I try to then change the focus with the keyboard arrows the current index is not updated.
To be honest, even my "mouse click" version isn't perfect because i have had use a Boolean that is initialized to false and passes to true when I enter the slot ItemTreeClicked. I had to do this because when i try to delete an item from the list, if I haven't clicked anything yet my app crashes.
I am coding in c++ on QtCreator 4.7.4
Any help or example code would be greatly appreciated.
Cheers.
Maybe you could subclass QTreeView and override the keyPressEvent method so it changes the current index?
myqtreeview::keyPressEvent(QKeyEvent* event){
QModelIndex qmi = this->currentIndex();
if(event->key() == Qt::Key_Down){
this->setCurrentIndex(QAbstractItemModel::createIndex(qmi->row()+1, qmi->column()));
}else{
...
}
QTreeView::keyPressEvent(event);
}
Note: This is just an idea, I can't test it at the moment so you probably have to adjust this a bit if you want to try it out
I have one QTableWidget with some QTableWidgetsItems on it. Some items use checkboxes. I've added the checkboxes using the follow code:
QTableWidgetsItem->setCheckState(Qt::Checked);
I would like now to call some function when this checkbox state change. Using a signal for example.
What may be the easiest way to accomplish this?
The easiest way to do this is to capture signal(s) of QTableWidget with slot(s) in the class that contains QTableWidget. While it would seem that QTableWidget::itemActivated might be our best bet, it is uncertain whether or not this is emitted when the Qt::CheckState is equal to Qt::Checked. In addition, even if this was true, the signal would not provide you the capabilities of handling item unchecking which your application may need to do.
So, here is my proposed solution. Capture the QTableWidget::itemPressed and QTableWidget::itemClicked signals with slots defined in the class that contains the QTableWidget. As itemPressed should be called BEFORE the mouse button is released, and itemClicked should be called AFTER the mouse button is released, the Qt::CheckState for that QTableWidgetItem should only be set in between these two signal emissions. Thus, you can determine exactly when a QTableWidgetItem's checkState has changed with low memory overhead.
Here is an example of what these slots could look like:
void tableItemPressed(QTableWidgetItem * item)
{
// member variable used to keep track of the check state for a
// table widget item currently being pressed
m_pressedItemState = item->checkState();
}
void tableItemClicked(QTableWidgetItem * item)
{
// if check box has been clicked
if (m_pressedItemState != item->checkState())
{
// perform check logic here
}
}
And the signals/ slots would be connected as follows:
connect(m_tableWidget,SIGNAL(itemPressed(QTableWidgetItem *)),this,SLOT(tableItemPressed(QTableWidgetItem *)));
connect(m_tableWidget,SIGNAL(itemClicked(QTableWidgetItem *)),this,SLOT(tableItemClicked(QTableWidgetItem *)));
Where m_tableWidget is the QTableWidget * you associate with your table widget.
I've got a custom model inherited from QTreeView. I've enabled drag and drop and can currently drop an item onto the tree. However, you can currently drop onto either an existing item or between items. I would like to restrict this so that you can only drop onto existing items.
I've set DragDropOverwriteMode to true (actually this is the default for QTreeView). However, this doesn't stop you from dropping between items - it just means you can also drop onto existing items.
I know that I can ignore the "insert" drops in dropMimeData (which I am reimplementing), by checking whether row and column are valid (drops onto existing items have row and column set to -1 and parent set to the current item) and I am doing this. However, I would prefer not to get these drops. Ie. I would like it so that you are always dropping over either the item above or the item below, never between items.
Any ideas?
Thanks for any advice,
Giles
You'' need to catch the drag enter events by reimplementing the dragEnterEvent method in your custom view. The example from the Qt docs is:
void Window::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/plain"))
event->acceptProposedAction();
}
In your case, you would probably need to compare the x and y position in the event with the x and y position of the closest item or something similar and reject or accept the proposed action based on that information.
From the QAbstractItemModel::dropMimeData documentation:
It is the responsibility of the view to provide a suitable location for where the data should be inserted.
Which I have interpreted to mean that the view should reject the drop if it's not something that's supported by an underlying model, such as yours.
As of Qt 5.4 (and I assume this was true even in Qt 4.8), setting DragDropOverwriteMode to true will correctly cause the drags to be droppable only on existing items and prevents the "above/below items" drop targets from appearing.
Also, unlike what the question claims, DragDropOverwriteMode is set to false by default for QTreeView (I didn't check, maybe it's newer Qt versions), so it needs to be set to true manually.
However it is still useful to be able to calculate on what positions the item can be dropped. For example in QTreeView, one cannot drop a dragged thing on the left margin of the items, i.e the red area below:
If something is dropped in the invalid red area, dropMimeData will be called with a parent argument set to NULL. So it would be useful to ignore the dragMoveEvent in advance to show a 'you can't drop here' cursor to the user so they know they can't drop there. Qt doesn't implement changing the mouse cursor on invalid areas while dragging (as of Qt 5.4), but we can do it like this:
bool SubclassedTreeView::dropResultsInValidIndex(const QPoint& pos)
{
QTreeWidgetItem* item = itemAt(pos);
if (item == NULL || !indexFromItem(item).isValid())
return false;
return visualRect(indexFromItem(item)).adjusted(-1, -1, 1, 1).contains(pos, false);
}
virtual void SubclassedTreeView::dragMoveEvent(QDragMoveEvent* event)
{
QTreeWidget::dragMoveEvent(event);
if (!event->isAccepted())
return;
if (dropResultsInValidIndex(event->pos()))
event->accept();
else
event->ignore(); //Show 'forbidden' cursor.
}
virtual bool SubclassedTreeView::dropMimeData(QTreeWidgetItem* parent, int index, const QMimeData* data, Qt::DropAction action)
{
Q_UNUSED(index);
//Modify the following action and data format checks to suit your needs:
if (parent == NULL || action != Qt::CopyAction || !data->hasFormat("my/preferred-type"))
return false;
QModelIndex modelIndex = indexFromItem(parent);
//modelIndex is where the data is dropped onto. Implement your custom drop action here...
return true;
}
The above code contains a little part visualRect….adjusted(-1, -1, 1, 1) which was stolen from QAbstractItemViewPrivate::position sources. Actually the sources of this function can be used to calculate the overwrite/insert/invalid areas for the item when QAbstractItemViewPrivate::position is false too.
I would like to suggest a solution based on the current position of the drop indicator (QAbstractItemView::DropIndicatorPosition). It's pretty simple to implement, but unfortunately requires the drop indicator to be shown. However, this might be acceptable in some cases.
TreeView::TreeView(QWidget* parent) : QTreeView(parent)
{
setDropIndicatorShown(true);
}
void TreeView::dragMoveEvent(QDragMoveEvent* event)
{
QTreeView::dragMoveEvent(event);
if (dropIndicatorPosition() != QTreeView::OnItem)
event->setDropAction(Qt::IgnoreAction);
}