From 4dd6b16cfdabe75d9c46c5aa37ebcf4162415499 Mon Sep 17 00:00:00 2001 From: Kai Guo Date: Sat, 23 Nov 2019 13:07:04 -0800 Subject: [PATCH] Update TreeView to support blocking drag&drop for specific items (#1646) * Check 'Handled' for drag drop events * Add NewParent property in TreeViewDraItemsCompletedEventArgs * CR feedback * CR feedback2 * Dummy commit to reset status checks --- .../InteractionTests/TreeViewTests.cs | 12 +- dev/TreeView/TestUI/TreeViewPage.xaml.cs | 9 +- dev/TreeView/TreeView.cpp | 22 ++- dev/TreeView/TreeView.idl | 5 + .../TreeViewDragItemsCompletedEventArgs.cpp | 11 +- .../TreeViewDragItemsCompletedEventArgs.h | 6 +- .../TreeViewDragItemsStartingEventArgs.cpp | 4 +- .../TreeViewDragItemsStartingEventArgs.h | 2 +- dev/TreeView/TreeViewItem.cpp | 23 ++- dev/TreeView/TreeViewList.cpp | 186 +++++++++--------- 10 files changed, 165 insertions(+), 115 deletions(-) diff --git a/dev/TreeView/InteractionTests/TreeViewTests.cs b/dev/TreeView/InteractionTests/TreeViewTests.cs index 29c612e434..6645e8a5e8 100644 --- a/dev/TreeView/InteractionTests/TreeViewTests.cs +++ b/dev/TreeView/InteractionTests/TreeViewTests.cs @@ -1898,7 +1898,7 @@ private void TreeViewDragItemTest(bool isContentMode = false) ClickButton("SetupDragDropHandlersForApiTest"); UIObject dragUIObject = LabelFirstItem(); InputHelper.DragDistance(dragUIObject, dragUIObject.BoundingRectangle.Height, Direction.South); - Verify.AreEqual("DragItemsStarting:Root->DragItemsCompleted:Root", ReadResult()); + Verify.AreEqual("DragItemsStarting:Root\nDragItemsCompleted:Root", ReadResult()); } } @@ -1918,8 +1918,8 @@ public void TreeViewDragItemTest_ContentMode() private void TreeViewDragMultipleItemsTest(bool isContentMode = false) { - if (IsPhoneDevice() || IsLowerThanRS2() || - (isContentMode && IsLowerThanRS5())) + // InputHelper.DragToTarget() does not work properly on lower versions + if (IsLowerThanRS5()) { return; } @@ -1951,8 +1951,10 @@ private void TreeViewDragMultipleItemsTest(bool isContentMode = false) UIObject dragUIObject = FindElement.ByName("Root.0"); Verify.IsNotNull(dragUIObject, "Verifying Root.0 is found"); - InputHelper.DragDistance(dragUIObject, dragUIObject.BoundingRectangle.Height / 2, Direction.South); - Verify.AreEqual("DragItemsStarting:Root.0|Root.1->DragItemsCompleted:Root.0|Root.1", ReadResult()); + UIObject dropUIObject = FindElement.ByName("Root.2"); + Verify.IsNotNull(dropUIObject, "Verifying Root.2 is found"); + InputHelper.DragToTarget(dragUIObject, dropUIObject); + Verify.AreEqual("DragItemsStarting:Root.0|Root.1\nDragItemsCompleted:Root.0|Root.1\nNewParent: Root.2", ReadResult()); } } diff --git a/dev/TreeView/TestUI/TreeViewPage.xaml.cs b/dev/TreeView/TestUI/TreeViewPage.xaml.cs index 8ccb8c4a87..f02f3010c8 100644 --- a/dev/TreeView/TestUI/TreeViewPage.xaml.cs +++ b/dev/TreeView/TestUI/TreeViewPage.xaml.cs @@ -721,7 +721,14 @@ private void DragItemsStartingForApiTest(TreeView sender, TreeViewDragItemsStart private void DragItemsCompletedForApiTest(TreeView sender, TreeViewDragItemsCompletedEventArgs args) { - Results.Text += "->DragItemsCompleted:" + GetDraggedItemsNames(args.Items); + Results.Text += "\nDragItemsCompleted:" + GetDraggedItemsNames(args.Items); + + var parent = args.NewParent; + if (parent != null) + { + var parentName = IsInContentMode() ? (parent as TreeViewItemSource).Content : (parent as TreeViewNode).Content.ToString(); + Results.Text += "\nNewParent: " + parentName; + } } private String GetDraggedItemsNames(IEnumerable items) diff --git a/dev/TreeView/TreeView.cpp b/dev/TreeView/TreeView.cpp index ef0820fbf5..e57821131b 100644 --- a/dev/TreeView/TreeView.cpp +++ b/dev/TreeView/TreeView.cpp @@ -249,15 +249,29 @@ void TreeView::OnPropertyChanged(const winrt::DependencyPropertyChangedEventArgs void TreeView::OnListControlDragItemsStarting(const winrt::IInspectable& sender, const winrt::DragItemsStartingEventArgs& args) { - auto treeViewArgs = winrt::make_self(); - treeViewArgs->DragItemsStartingEventArgs(args); + const auto treeViewArgs = winrt::make_self(args); m_dragItemsStartingEventSource(*this, *treeViewArgs); } void TreeView::OnListControlDragItemsCompleted(const winrt::IInspectable& sender, const winrt::DragItemsCompletedEventArgs& args) { - auto treeViewArgs = winrt::make_self(); - treeViewArgs->DragItemsCompletedEventArgs(args); + const auto newParent = [items = args.Items(), listControl = ListControl(), rootNode = m_rootNode.get()]() + { + if (listControl && items && items.Size() > 0) + { + if (const auto draggedNode = listControl->NodeFromItem(items.GetAt(0))) + { + const auto parentNode = draggedNode.Parent(); + if (parentNode && parentNode != rootNode) + { + return listControl->ItemFromNode(parentNode); + } + } + } + return static_cast(nullptr); + }(); + + const auto treeViewArgs = winrt::make_self(args, newParent); m_dragItemsCompletedEventSource(*this, *treeViewArgs); } diff --git a/dev/TreeView/TreeView.idl b/dev/TreeView/TreeView.idl index fd891e0749..f6b09e495c 100644 --- a/dev/TreeView/TreeView.idl +++ b/dev/TreeView/TreeView.idl @@ -87,6 +87,11 @@ runtimeclass TreeViewDragItemsCompletedEventArgs { Windows.ApplicationModel.DataTransfer.DataPackageOperation DropResult{ get; }; Windows.Foundation.Collections.IVectorView Items{ get; }; + + [WUXC_VERSION_PREVIEW] + { + Object NewParent{ get; }; + } } [WUXC_VERSION_RS4] diff --git a/dev/TreeView/TreeViewDragItemsCompletedEventArgs.cpp b/dev/TreeView/TreeViewDragItemsCompletedEventArgs.cpp index c718ca5db1..f5c8cfd738 100644 --- a/dev/TreeView/TreeViewDragItemsCompletedEventArgs.cpp +++ b/dev/TreeView/TreeViewDragItemsCompletedEventArgs.cpp @@ -6,11 +6,13 @@ #include "Vector.h" #include "TreeViewDragItemsCompletedEventArgs.h" -void TreeViewDragItemsCompletedEventArgs::DragItemsCompletedEventArgs(const winrt::DragItemsCompletedEventArgs& args) +TreeViewDragItemsCompletedEventArgs::TreeViewDragItemsCompletedEventArgs(const winrt::DragItemsCompletedEventArgs& args, const winrt::IInspectable& newParent) { m_dragItemsCompletedEventArgs = args; + m_newParent = newParent; } + DataPackageOperation TreeViewDragItemsCompletedEventArgs::DropResult() const { return m_dragItemsCompletedEventArgs.DropResult(); @@ -19,4 +21,9 @@ DataPackageOperation TreeViewDragItemsCompletedEventArgs::DropResult() const winrt::IVectorView TreeViewDragItemsCompletedEventArgs::Items() { return m_dragItemsCompletedEventArgs.Items(); -} \ No newline at end of file +} + +winrt::IInspectable TreeViewDragItemsCompletedEventArgs::NewParent() +{ + return m_newParent; +} diff --git a/dev/TreeView/TreeViewDragItemsCompletedEventArgs.h b/dev/TreeView/TreeViewDragItemsCompletedEventArgs.h index 76908e32c7..7ddf1bbfff 100644 --- a/dev/TreeView/TreeViewDragItemsCompletedEventArgs.h +++ b/dev/TreeView/TreeViewDragItemsCompletedEventArgs.h @@ -10,10 +10,12 @@ class TreeViewDragItemsCompletedEventArgs : public ReferenceTracker { public: - void DragItemsCompletedEventArgs(const winrt::DragItemsCompletedEventArgs& args); + TreeViewDragItemsCompletedEventArgs(const winrt::DragItemsCompletedEventArgs& args, const winrt::IInspectable& newParent); DataPackageOperation DropResult() const; winrt::IVectorView Items(); + winrt::IInspectable NewParent(); private: winrt::DragItemsCompletedEventArgs m_dragItemsCompletedEventArgs{ nullptr }; -}; \ No newline at end of file + winrt::IInspectable m_newParent; +}; diff --git a/dev/TreeView/TreeViewDragItemsStartingEventArgs.cpp b/dev/TreeView/TreeViewDragItemsStartingEventArgs.cpp index fb90fc2b31..788f3c51eb 100644 --- a/dev/TreeView/TreeViewDragItemsStartingEventArgs.cpp +++ b/dev/TreeView/TreeViewDragItemsStartingEventArgs.cpp @@ -6,7 +6,7 @@ #include "Vector.h" #include "TreeViewDragItemsStartingEventArgs.h" -void TreeViewDragItemsStartingEventArgs::DragItemsStartingEventArgs(const winrt::DragItemsStartingEventArgs& args) +TreeViewDragItemsStartingEventArgs::TreeViewDragItemsStartingEventArgs(const winrt::DragItemsStartingEventArgs& args) { m_dragItemsStartingEventArgs = args; } @@ -29,4 +29,4 @@ DataPackage TreeViewDragItemsStartingEventArgs::Data() const winrt::IVector TreeViewDragItemsStartingEventArgs::Items() { return m_dragItemsStartingEventArgs.Items(); -} \ No newline at end of file +} diff --git a/dev/TreeView/TreeViewDragItemsStartingEventArgs.h b/dev/TreeView/TreeViewDragItemsStartingEventArgs.h index 2651b536f3..02f9f26cc1 100644 --- a/dev/TreeView/TreeViewDragItemsStartingEventArgs.h +++ b/dev/TreeView/TreeViewDragItemsStartingEventArgs.h @@ -9,7 +9,7 @@ class TreeViewDragItemsStartingEventArgs : public ReferenceTracker { public: - void DragItemsStartingEventArgs(const winrt::DragItemsStartingEventArgs& args); + TreeViewDragItemsStartingEventArgs(const winrt::DragItemsStartingEventArgs& args); bool Cancel() const; void Cancel(const bool value); DataPackage Data() const; diff --git a/dev/TreeView/TreeViewItem.cpp b/dev/TreeView/TreeViewItem.cpp index 422a45fc3b..0918d10181 100644 --- a/dev/TreeView/TreeViewItem.cpp +++ b/dev/TreeView/TreeViewItem.cpp @@ -66,7 +66,7 @@ void TreeViewItem::OnKeyDown(winrt::KeyRoutedEventArgs const& e) void TreeViewItem::OnDrop(winrt::DragEventArgs const& args) { - if (args.AcceptedOperation() == winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Move) + if (!args.Handled() && args.AcceptedOperation() == winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Move) { winrt::TreeViewItem droppedOnItem = *this; auto treeView = AncestorTreeView(); @@ -127,7 +127,7 @@ void TreeViewItem::OnDrop(winrt::DragEventArgs const& args) void TreeViewItem::OnDragOver(winrt::DragEventArgs const& args) { auto treeView = AncestorTreeView(); - if (treeView) + if (treeView && !args.Handled()) { auto treeViewList = treeView->ListControl(); winrt::TreeViewItem draggedOverItem = *this; @@ -179,7 +179,7 @@ void TreeViewItem::OnDragEnter(winrt::DragEventArgs const& args) args.DragUIOverride().IsGlyphVisible(true); auto treeView = AncestorTreeView(); - if (treeView && treeView->CanReorderItems()) + if (treeView && treeView->CanReorderItems() && !args.Handled()) { auto treeViewList = treeView->ListControl(); winrt::TreeViewNode draggedNode = treeViewList->DraggedTreeViewNode(); @@ -230,15 +230,18 @@ void TreeViewItem::OnDragEnter(winrt::DragEventArgs const& args) void TreeViewItem::OnDragLeave(winrt::DragEventArgs const& args) { - if (auto treeView = AncestorTreeView()) + if (!args.Handled()) { - auto treeViewList = treeView->ListControl(); - treeViewList->SetDraggedOverItem(nullptr); - } + if (auto treeView = AncestorTreeView()) + { + auto treeViewList = treeView->ListControl(); + treeViewList->SetDraggedOverItem(nullptr); + } - if (m_expandContentTimer) - { - m_expandContentTimer.get().Stop(); + if (m_expandContentTimer) + { + m_expandContentTimer.get().Stop(); + } } __super::OnDragLeave(args); diff --git a/dev/TreeView/TreeViewList.cpp b/dev/TreeView/TreeViewList.cpp index f8abfcfce8..8be1b617c4 100644 --- a/dev/TreeView/TreeViewList.cpp +++ b/dev/TreeView/TreeViewList.cpp @@ -170,122 +170,129 @@ void TreeViewList::MoveNodeInto(winrt::TreeViewNode const& node, winrt::TreeView void TreeViewList::OnDragOver(winrt::DragEventArgs const& args) { - args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::None); - winrt::IInsertionPanel insertionPanel = ItemsPanelRoot().as(); - - // reorder is only supported with panels that implement IInsertionPanel - if (insertionPanel && m_draggedTreeViewNode && CanReorderItems()) + if (!args.Handled()) { - int aboveIndex = -1; - int belowIndex = -1; - auto itemsSource = ListViewModel(); - int size = itemsSource->Size(); - auto point = args.GetPosition(insertionPanel.as()); - insertionPanel.GetInsertionIndexes(point, aboveIndex, belowIndex); - - // a value of -1 means we're inserting at the end of the list or before the last item - if (belowIndex == -1) - { - // this allows the next part of this code to test if we're dropping before or after the last item - belowIndex = static_cast(size - 1); - } + args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::None); + winrt::IInsertionPanel insertionPanel = ItemsPanelRoot().as(); - // if we have an insertion point - // a value of 0 means we're inserting at the beginning - // a value greater than size - 1 means we're inserting at the end as items are collapsing - if (belowIndex > static_cast(size - 1)) + // reorder is only supported with panels that implement IInsertionPanel + if (insertionPanel && m_draggedTreeViewNode && CanReorderItems()) { - // don't go out of bounds - // since items might be collapsing as we're dragging - m_emptySlotIndex = static_cast(size - 1); - } - else if (belowIndex > 0 && m_draggedTreeViewNode) - { - m_emptySlotIndex = -1; - unsigned int draggedIndex; - winrt::TreeViewNode tvi = m_draggedTreeViewNode.get(); - if (ListViewModel()->IndexOfNode(tvi, draggedIndex)) + int aboveIndex = -1; + int belowIndex = -1; + auto itemsSource = ListViewModel(); + int size = itemsSource->Size(); + auto point = args.GetPosition(insertionPanel.as()); + insertionPanel.GetInsertionIndexes(point, aboveIndex, belowIndex); + + // a value of -1 means we're inserting at the end of the list or before the last item + if (belowIndex == -1) { - const int indexToUse = (static_cast(draggedIndex) < belowIndex) ? belowIndex : (belowIndex - 1); - if (auto item = ContainerFromIndex(indexToUse)) - { - auto treeViewItem = item.as(); - auto pointInsideItem = args.GetPosition(treeViewItem.as()); + // this allows the next part of this code to test if we're dropping before or after the last item + belowIndex = static_cast(size - 1); + } - // if the point is in the top half of the item - // we need to insert before that item - if (pointInsideItem.Y < treeViewItem.ActualHeight() / 2) - { - m_emptySlotIndex = belowIndex - 1; - } - else + // if we have an insertion point + // a value of 0 means we're inserting at the beginning + // a value greater than size - 1 means we're inserting at the end as items are collapsing + if (belowIndex > static_cast(size - 1)) + { + // don't go out of bounds + // since items might be collapsing as we're dragging + m_emptySlotIndex = static_cast(size - 1); + } + else if (belowIndex > 0 && m_draggedTreeViewNode) + { + m_emptySlotIndex = -1; + unsigned int draggedIndex; + winrt::TreeViewNode tvi = m_draggedTreeViewNode.get(); + if (ListViewModel()->IndexOfNode(tvi, draggedIndex)) + { + const int indexToUse = (static_cast(draggedIndex) < belowIndex) ? belowIndex : (belowIndex - 1); + if (auto item = ContainerFromIndex(indexToUse)) { - m_emptySlotIndex = belowIndex; + auto treeViewItem = item.as(); + auto pointInsideItem = args.GetPosition(treeViewItem.as()); + + // if the point is in the top half of the item + // we need to insert before that item + if (pointInsideItem.Y < treeViewItem.ActualHeight() / 2) + { + m_emptySlotIndex = belowIndex - 1; + } + else + { + m_emptySlotIndex = belowIndex; + } } } } - } - else - { - // top of the list - m_emptySlotIndex = 0; - } + else + { + // top of the list + m_emptySlotIndex = 0; + } - bool allowReorder = true; - if (IsFlatIndexValid(m_emptySlotIndex)) - { - auto insertAtNode = NodeAtFlatIndex(m_emptySlotIndex); - if (IsMultiselect()) + bool allowReorder = true; + if (IsFlatIndexValid(m_emptySlotIndex)) { - // If insertAtNode is in the selected items, then we do not want to allow a dropping. - auto selectedItems = ListViewModel()->GetSelectedNodes(); - for (unsigned int i = 0; i < selectedItems.Size(); i++) + auto insertAtNode = NodeAtFlatIndex(m_emptySlotIndex); + if (IsMultiselect()) { - auto selectedNode = selectedItems.GetAt(i); - if (selectedNode == insertAtNode) + // If insertAtNode is in the selected items, then we do not want to allow a dropping. + auto selectedItems = ListViewModel()->GetSelectedNodes(); + for (unsigned int i = 0; i < selectedItems.Size(); i++) { - allowReorder = false; - break; + auto selectedNode = selectedItems.GetAt(i); + if (selectedNode == insertAtNode) + { + allowReorder = false; + break; + } } } - } - else - { - if (m_draggedTreeViewNode && insertAtNode && insertAtNode.Parent()) + else { - auto insertContainer = ContainerFromNode(insertAtNode.Parent()); - if (insertContainer) + if (m_draggedTreeViewNode && insertAtNode && insertAtNode.Parent()) { - allowReorder = insertContainer.as().AllowDrop(); + auto insertContainer = ContainerFromNode(insertAtNode.Parent()); + if (insertContainer) + { + allowReorder = insertContainer.as().AllowDrop(); + } } } } - } - else - { - // m_emptySlotIndex does not exist in the ViewModel - don't allow the reorder. - allowReorder = false; - } + else + { + // m_emptySlotIndex does not exist in the ViewModel - don't allow the reorder. + allowReorder = false; + } - if (allowReorder) - { - args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Move); - } - else - { - m_emptySlotIndex = -1; - args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::None); - args.Handled(true); + if (allowReorder) + { + args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Move); + } + else + { + m_emptySlotIndex = -1; + args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::None); + args.Handled(true); + } } + + UpdateDropTargetDropEffect(false, false, nullptr); } - UpdateDropTargetDropEffect(false, false, nullptr); __super::OnDragOver(args); } void TreeViewList::OnDragEnter(winrt::DragEventArgs const& args) { - UpdateDropTargetDropEffect(false, false, nullptr); + if (!args.Handled()) + { + UpdateDropTargetDropEffect(false, false, nullptr); + } __super::OnDragEnter(args); } @@ -293,7 +300,10 @@ void TreeViewList::OnDragLeave(winrt::DragEventArgs const& args) { m_emptySlotIndex = -1; __super::OnDragLeave(args); - UpdateDropTargetDropEffect(false, true, nullptr); + if (!args.Handled()) + { + UpdateDropTargetDropEffect(false, true, nullptr); + } } // IItemsControlOverrides