I've been playing with the list view and came across this post: How to detect a CListCtrl selection change?
However the code used there has a major flow, it doesn't work with multiple selection (as pointed out in that thread). So my question is how can I make the code work with multiselection (eg. selection with shift or ctrl)?
I've written a handy function to see if your OnItemChanged notification was due to a selection change:
BOOL IsItemSelChanged(NMLISTVIEW* pNMListView)
{
// call this from your OnItemchangedMyListCtrl function in your dialog class
if(!(pNMListView->uChanged & LVIF_STATE))
{
return(FALSE);
}
if((pNMListView->uOldState & LVIS_SELECTED) == (pNMListView->uNewState & LVIS_SELECTED))
{
return(FALSE);
}
return(TRUE);
}
Handle LVN_ITEMCHANGED and LVN_ODSTATECHANGED, all you needed...
If a list-view control has the LVS_OWNERDATA style, and the user
selects a range of items by holding down the SHIFT key and clicking
the mouse, LVN_ITEMCHANGED notifications are not sent for each
selected or deselected item. Instead, you will receive a single
LVN_ODSTATECHANGED notification, indicating that a range of items has
changed state.
Related
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
I have a tree view (CTreeView) that will show me a pop-up menu after I right-click my mouse on it.
In my context menu there are only 3 items (i.e A, B, C) for selection and my tree view displays a long list of ordered foods designed with check-boxes. I would like to disable menu items A and B if no ordered foods are checked and enable them when any is.
I create CFoodView::OnUpdateItemA(CCmdUI* pCmdUI) //CFoodView inherits CTreeView
and CFoodView::OnUpdateItemB(CCmdUI* pCmdUI) to handle their states like so
CFoodView::OnUpdateItemB(CCmdUI* pCmdUI)
{
if TreeView has no items
{
pCmdUI->Enable(FALSE);
}
else
{
*Search* the tree to get selected items
if None is checked
{
pCmdUI->Enable(FALSE);
}
else there are checked items
pCmdUI->Enable(TRUE);
}
}
Method CFoodView::OnUpdateItemA(CCmdUI* pCmdUI) is the same.
I think this isn't a correct way to handle this GUI feature.
Well, you did not submit all important information. How did you create menu item handlers?
Assuming you insert handlers the proper way, still did not provide any information how you are invoking popup menu.
If all you did was properly done it is the proper way of handling update menu.
The most common mistake is designating view itself as the window that handles popup updates and commands. In order to use MFC menu update mechanism, you have to pass a pointer to the main window not to the tree view:
CWnd *pMainWnd = AfxGetMainWnd();
ASSERT(pMainWnd != nullptr);
pSubMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, pMainWnd);
If this will not work reexamine the way you create handler and/or the place you invoke the TrackPopupMenu function.
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'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);
}
I have 3 list control on one dialog box but only one is showing focus.
if i clicked on 2nd list control then focus disaappear from 1st one.
Means at a time only one list showing focus.
How to make focus remain on all list control on same dialog box?
I don't think that this is technically possible. 'Focus' is an attribute that can only be applied to an individual element.
Think of it in terms of 'focus' is the element that the user is currently interacting with. How would a user be expected to interact with 3 distinct elements at the same time?
As Brian says - focus can only be on one control at time. I'm guessing you are trying to change the other list controls based on the first list box. One way to do it is to associate a variable with each list control, like mListCtrl1, mListCtrl2. Then add a handler for the NM_CLICK event, and have some code like this:
void CTabTestDlg::OnNMClickList3(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pNMItemActivate = (LPNMITEMACTIVATE)(pNMHDR);
// TODO: Add your control notification handler code here
*pResult = 0;
UpdateData(true);
DWORD dwData = mListCtrl1.GetItemData(pNMItemActivate->iItem);
int max = mListCtrl2.GetItemCount();
for (int i=0;i<max;i++)
{
DWORD dwData2 = mListCtrl2.GetItemData(i);
if (dwData==dwData2)
{
mListCtrl2.SetItemState(i,LVIS_SELECTED,LVIS_SELECTED);
break;
}
}
UpdateData(false);
}
Note that I have the control set to "Always show selection", and "Single selection"