From c189be711b8c43e900665cb193d0d8800fd9fb68 Mon Sep 17 00:00:00 2001 From: michael-hawker Date: Fri, 9 Nov 2018 14:17:53 -0800 Subject: [PATCH 1/4] Add additional XamlDragAndDrop sample to show how to preserve user position when dragging to ListView. --- .../cs/DragAndDropSampleManaged.csproj | 7 + .../XamlDragAndDrop/cs/SampleConfiguration.cs | 3 +- .../cs/Scenario4_MoveBetweenListView.xaml | 71 +++++++++ .../cs/Scenario4_MoveBetweenListView.xaml.cs | 150 ++++++++++++++++++ 4 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml create mode 100644 Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs diff --git a/Samples/XamlDragAndDrop/cs/DragAndDropSampleManaged.csproj b/Samples/XamlDragAndDrop/cs/DragAndDropSampleManaged.csproj index 7429c060c4..fa82f12c1f 100644 --- a/Samples/XamlDragAndDrop/cs/DragAndDropSampleManaged.csproj +++ b/Samples/XamlDragAndDrop/cs/DragAndDropSampleManaged.csproj @@ -99,6 +99,9 @@ Properties\AssemblyInfo.cs + + Scenario4_MoveBetweenListView.xaml + Scenario1_ListView.xaml @@ -125,6 +128,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/Samples/XamlDragAndDrop/cs/SampleConfiguration.cs b/Samples/XamlDragAndDrop/cs/SampleConfiguration.cs index dcd0fd14fd..d6fc57e4ce 100644 --- a/Samples/XamlDragAndDrop/cs/SampleConfiguration.cs +++ b/Samples/XamlDragAndDrop/cs/SampleConfiguration.cs @@ -24,7 +24,8 @@ public partial class MainPage : Page { new Scenario() { Title="ListView Drag and Drop and Reorder", ClassType=typeof(Scenario1_ListView)}, new Scenario() { Title="Drag UI Customization", ClassType=typeof(Scenario2_DragUICustomization)}, - new Scenario() { Title="StartDragAsync", ClassType=typeof(Scenario3_StartDragAsync)} + new Scenario() { Title="StartDragAsync", ClassType=typeof(Scenario3_StartDragAsync)}, + new Scenario() { Title="Move Between ListViews", ClassType=typeof(Scenario4_MoveBetweenListView)} }; } diff --git a/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml new file mode 100644 index 0000000000..7e8837773b --- /dev/null +++ b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs new file mode 100644 index 0000000000..09834fd6ff --- /dev/null +++ b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs @@ -0,0 +1,150 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; +using SDKTemplate; +using System; +using System.Collections.ObjectModel; +using Windows.ApplicationModel.DataTransfer; +using System.Linq; +using System.Collections.Generic; + +namespace DragAndDropSampleManaged +{ + /// + /// This sample shows how to drag a single item between listviews and maintain the user's desired location. + /// + public sealed partial class Scenario4_MoveBetweenListView : Page + { + private MainPage rootPage; + + ObservableCollection _source; + ObservableCollection _target; + string _deletedItem; + + public Scenario4_MoveBetweenListView() + { + this.InitializeComponent(); + + _source = GetSampleData(); + _target = new ObservableCollection(); + SourceListView.ItemsSource = _source; + TargetListView.ItemsSource = _target; + } + private ObservableCollection GetSampleData() + { + return new ObservableCollection + { + "My Research Paper", + "Electricity Bill", + "My To-do list", + "TV sales receipt", + "Water Bill", + "Grocery List", + "Superbowl schedule", + "World Cup E-ticket" + }; + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + rootPage = MainPage.Current; + } + + /// + /// DragItemsStarting is called when the Drag and Drop operation starts + /// We take advantage of it to set the content of the DataPackage + /// as well as indicate which operations are supported + /// + /// + /// + private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) + { + // Prepare a string with one dragged item per line + // Set the content of the DataPackage + e.Data.SetText(e.Items.FirstOrDefault() as string); + // As we want our Reference list to mutate, we only allow Move + e.Data.RequestedOperation = DataPackageOperation.Move; + } + + /// + /// DragOver is called when the dragged pointer moves over a UIElement with AllowDrop=True + /// We need to return an AcceptedOperation != None in either DragOver or DragEnter + /// + /// + /// + private void ListView_DragOver(object sender, DragEventArgs e) + { + // Our list only accepts text + e.AcceptedOperation = (e.DataView.Contains(StandardDataFormats.Text)) ? DataPackageOperation.Move : DataPackageOperation.None; + } + + /// + /// We need to return the effective operation from Drop + /// This is not important for our source ListView, but it might be if the user + /// drags text from another source + /// + /// + /// + private async void ListView_Drop(object sender, DragEventArgs e) + { + // This test is in theory not needed as we returned DataPackageOperation.None if + // the DataPackage did not contained text. However, it is always better if each + // method is robust by itself + if (e.DataView.Contains(StandardDataFormats.Text)) + { + // We need to take a Deferral as we won't be able to confirm the end + // of the operation synchronously + var def = e.GetDeferral(); + var text = await e.DataView.GetTextAsync(); + + // Get the lists we need to work with. + var sourceList = (sender == TargetListView ? _source : _target); + var targetList = (sender == TargetListView ? _target : _source); + + // Remove item from other list + sourceList.Remove(text); + + // Get position in List to drop to + var listview = sender as ListView; + var panel = listview.ItemsPanelRoot; + var point = e.GetPosition(panel); + + // Use insertion panel interface (available on StackPanel) to help us. + if (panel is IInsertionPanel ipanel) + { + ipanel.GetInsertionIndexes(point, out int aboveIndex, out int belowIndex); + + // Top of List + if (aboveIndex == -1 && belowIndex >= 0) + { + targetList.Insert(0, text); + } + // Between two items + else if (aboveIndex >= 0 && belowIndex >= 0) + { + targetList.Insert(aboveIndex, text); + } + // Bottom of List + else if (aboveIndex >= 0 && belowIndex == -1) + { + targetList.Add(text); + } + } + + e.AcceptedOperation = DataPackageOperation.Move; + def.Complete(); + } + } + } +} From ca979d8686f64debe013dcd89101b82c51dba70e Mon Sep 17 00:00:00 2001 From: michael-hawker Date: Fri, 9 Nov 2018 19:10:07 -0800 Subject: [PATCH 2/4] Fix implementation to use visuals to determine location for better results. --- .../cs/Scenario4_MoveBetweenListView.xaml.cs | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs index 09834fd6ff..80428c76e9 100644 --- a/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs +++ b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs @@ -17,7 +17,7 @@ using System.Collections.ObjectModel; using Windows.ApplicationModel.DataTransfer; using System.Linq; -using System.Collections.Generic; +using Windows.Foundation; namespace DragAndDropSampleManaged { @@ -115,33 +115,35 @@ private async void ListView_Drop(object sender, DragEventArgs e) // Remove item from other list sourceList.Remove(text); - // Get position in List to drop to + // First we need to get the position in the List to drop to var listview = sender as ListView; - var panel = listview.ItemsPanelRoot; - var point = e.GetPosition(panel); + var index = -1; - // Use insertion panel interface (available on StackPanel) to help us. - if (panel is IInsertionPanel ipanel) + // Determine which items in the list our pointer is inbetween. + for (int i = 0; i < listview.Items.Count; i++) { - ipanel.GetInsertionIndexes(point, out int aboveIndex, out int belowIndex); + var item = listview.ContainerFromIndex(i) as ListViewItem; - // Top of List - if (aboveIndex == -1 && belowIndex >= 0) - { - targetList.Insert(0, text); - } - // Between two items - else if (aboveIndex >= 0 && belowIndex >= 0) - { - targetList.Insert(aboveIndex, text); - } - // Bottom of List - else if (aboveIndex >= 0 && belowIndex == -1) + var p = e.GetPosition(item); + + if (p.Y - item.ActualHeight < 0) { - targetList.Add(text); + index = i; + break; } } + if (index < 0) + { + // We didn't find a transition point, so we're at the end of the list + targetList.Add(text); + } + else if (index < listview.Items.Count) + { + // Otherwise, insert at the provided index. + targetList.Insert(index, text); + } + e.AcceptedOperation = DataPackageOperation.Move; def.Complete(); } From f44cc45a4779cf081a4aee1ceb94e793c029ac7d Mon Sep 17 00:00:00 2001 From: michael-hawker Date: Mon, 12 Nov 2018 12:20:55 -0800 Subject: [PATCH 3/4] Clean-up for C# sample to not override ItemsPanel --- .../cs/Scenario4_MoveBetweenListView.xaml | 18 ++---------------- .../cs/Scenario4_MoveBetweenListView.xaml.cs | 1 - 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml index 7e8837773b..77944496fd 100644 --- a/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml +++ b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml @@ -43,14 +43,7 @@ AllowDrop="True" CanReorderItems="True" CanDragItems="True" DragOver="ListView_DragOver" Drop="ListView_Drop" - DragItemsStarting="ListView_DragItemsStarting"> - - - - - - - + DragItemsStarting="ListView_DragItemsStarting"/> @@ -59,13 +52,6 @@ AllowDrop="True" CanReorderItems="True" CanDragItems="True" DragOver="ListView_DragOver" Drop="ListView_Drop" - DragItemsStarting="ListView_DragItemsStarting"> - - - - - - - + DragItemsStarting="ListView_DragItemsStarting"/> diff --git a/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs index 80428c76e9..70abfbc947 100644 --- a/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs +++ b/Samples/XamlDragAndDrop/cs/Scenario4_MoveBetweenListView.xaml.cs @@ -30,7 +30,6 @@ public sealed partial class Scenario4_MoveBetweenListView : Page ObservableCollection _source; ObservableCollection _target; - string _deletedItem; public Scenario4_MoveBetweenListView() { From 8218f1eccc7cd842403ea7a936f7930109ad4b62 Mon Sep 17 00:00:00 2001 From: michael-hawker Date: Mon, 12 Nov 2018 12:22:50 -0800 Subject: [PATCH 4/4] Add C++ Sample for Moving between ListViews --- .../cpp/DragAndDropSampleNative.vcxproj | 15 +- .../DragAndDropSampleNative.vcxproj.filters | 1 + .../cpp/SampleConfiguration.cpp | 3 +- .../cpp/Scenario4_MoveBetweenListView.xaml | 57 +++++++ .../Scenario4_MoveBetweenListView.xaml.cpp | 148 ++++++++++++++++++ .../cpp/Scenario4_MoveBetweenListView.xaml.h | 38 +++++ 6 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml create mode 100644 Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml.cpp create mode 100644 Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml.h diff --git a/Samples/XamlDragAndDrop/cpp/DragAndDropSampleNative.vcxproj b/Samples/XamlDragAndDrop/cpp/DragAndDropSampleNative.vcxproj index 104df5e012..cb28e36aee 100644 --- a/Samples/XamlDragAndDrop/cpp/DragAndDropSampleNative.vcxproj +++ b/Samples/XamlDragAndDrop/cpp/DragAndDropSampleNative.vcxproj @@ -101,10 +101,10 @@ - - $(VC_IncludePath);$(UniversalCRT_IncludePath);$(WindowsSDK_IncludePath);..\..\..\SharedContent\cpp - - + + $(VC_IncludePath);$(UniversalCRT_IncludePath);$(WindowsSDK_IncludePath);..\..\..\SharedContent\cpp + + /bigobj %(AdditionalOptions) 4453;28204 @@ -158,6 +158,9 @@ Scenario3_StartDragAsync.xaml + + Scenario4_MoveBetweenListView.xaml + @@ -172,6 +175,7 @@ Styles\Styles.xaml + @@ -203,6 +207,9 @@ Scenario3_StartDragAsync.xaml + + Scenario4_MoveBetweenListView.xaml + diff --git a/Samples/XamlDragAndDrop/cpp/DragAndDropSampleNative.vcxproj.filters b/Samples/XamlDragAndDrop/cpp/DragAndDropSampleNative.vcxproj.filters index 9c3efd46c9..c99fbe24a7 100644 --- a/Samples/XamlDragAndDrop/cpp/DragAndDropSampleNative.vcxproj.filters +++ b/Samples/XamlDragAndDrop/cpp/DragAndDropSampleNative.vcxproj.filters @@ -39,6 +39,7 @@ + diff --git a/Samples/XamlDragAndDrop/cpp/SampleConfiguration.cpp b/Samples/XamlDragAndDrop/cpp/SampleConfiguration.cpp index 59ac22360a..8dd04c12df 100644 --- a/Samples/XamlDragAndDrop/cpp/SampleConfiguration.cpp +++ b/Samples/XamlDragAndDrop/cpp/SampleConfiguration.cpp @@ -19,5 +19,6 @@ Platform::Array^ MainPage::scenariosInner = ref new Platform::Array + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml.cpp b/Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml.cpp new file mode 100644 index 0000000000..d5aead4065 --- /dev/null +++ b/Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml.cpp @@ -0,0 +1,148 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* +#include "pch.h" +#include "Scenario4_MoveBetweenListView.xaml.h" + +using namespace concurrency; +using namespace SDKTemplate; +using namespace Platform; +using namespace Platform::Collections; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Controls::Primitives; +using namespace Windows::UI::Xaml::Data; +using namespace Windows::UI::Xaml::Input; +using namespace Windows::UI::Xaml::Media; +using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::ApplicationModel::DataTransfer; + +Scenario4_MoveBetweenListView::Scenario4_MoveBetweenListView() : rootPage(MainPage::Current) +{ + InitializeComponent(); + + _source = GetSampleData(); + _target = ref new Vector(); + SourceListView->ItemsSource = _source; + TargetListView->ItemsSource = _target; +} + +Vector^ Scenario4_MoveBetweenListView::GetSampleData() +{ + return ref new Vector( + { + ref new String(L"My Research Paper"), + ref new String(L"Electricity Bill"), + ref new String(L"My To-do list"), + ref new String(L"TV sales receipt"), + ref new String(L"Water Bill"), + ref new String(L"Grocery List"), + ref new String(L"Superbowl schedule"), + ref new String(L"World Cup E-ticket") + }); +} + +/// +/// DragItemsStarting is called when the Drag and Drop operation starts +/// We take advantage of it to set the content of the DataPackage +/// as well as indicate which operations are supported +/// +/// +/// +void Scenario4_MoveBetweenListView::ListView_DragItemsStarting(Platform::Object^ sender, Windows::UI::Xaml::Controls::DragItemsStartingEventArgs^ e) +{ + // Prepare a string with one dragged item per line + // Set the content of the DataPackage + if (e->Items->Size > 0) + { + e->Data->SetText(dynamic_cast(e->Items->GetAt(0))); + // As we want our Reference list to say intact, we only allow Move + e->Data->RequestedOperation = DataPackageOperation::Move; + } +} + +/// +/// DragOver is called when the dragged pointer moves over a UIElement with AllowDrop=True +/// We need to return an AcceptedOperation != None in either DragOver or DragEnter +/// +/// +/// +void Scenario4_MoveBetweenListView::ListView_DragOver(Platform::Object^ sender, Windows::UI::Xaml::DragEventArgs^ e) +{ + // Our list only accepts text + e->AcceptedOperation = (e->DataView->Contains(StandardDataFormats::Text)) ? DataPackageOperation::Move : DataPackageOperation::None; +} + +/// +/// We need to return the effective operation from Drop +/// This is not important for our source ListView, but it might be if the user +/// drags text from another source +/// +/// +/// +void Scenario4_MoveBetweenListView::ListView_Drop(Platform::Object^ sender, Windows::UI::Xaml::DragEventArgs^ e) +{ + // This test is in theory not needed as we returned DataPackageOperation.None if + // the DataPackage did not contained text. However, it is always better if each + // method is robust by itself + if (e->DataView->Contains(StandardDataFormats::Text)) + { + // We need to take a Deferral as we won't be able to confirm the end + // of the operation synchronously + auto def = e->GetDeferral(); + create_task(e->DataView->GetTextAsync()).then([def, this, sender, e](String^ s) + { + // Get the lists we need to work with. + auto sourceList = (sender->Equals(TargetListView) ? _source : _target); + auto targetList = (sender->Equals(TargetListView) ? _target : _source); + + // Remove item from other list + unsigned j; + if (sourceList->IndexOf(s, &j)) + { + sourceList->RemoveAt(j); + } + + // First we need to get the position in the List to drop to + auto listview = dynamic_cast(sender); + int index = -1; + + // Determine which items in the list our pointer is inbetween. + for (int i = 0; i < listview->Items->Size; i++) + { + auto item = dynamic_cast(listview->ContainerFromIndex(i)); + + auto p = e->GetPosition(item); + + if (p.Y - item->ActualHeight < 0) + { + index = i; + break; + } + } + + if (index < 0) + { + // We didn't find a transition point, so we're at the end of the list + targetList->Append(s); + } + else if (index < listview->Items->Size) + { + // Otherwise, insert at the provided index. + targetList->InsertAt(index, s); + } + + e->AcceptedOperation = DataPackageOperation::Copy; + def->Complete(); + }); + } +} diff --git a/Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml.h b/Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml.h new file mode 100644 index 0000000000..265c285edd --- /dev/null +++ b/Samples/XamlDragAndDrop/cpp/Scenario4_MoveBetweenListView.xaml.h @@ -0,0 +1,38 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#pragma once + +#include "Scenario4_MoveBetweenListView.g.h" +#include "MainPage.xaml.h" + +namespace SDKTemplate +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + [Windows::Foundation::Metadata::WebHostHidden] + public ref class Scenario4_MoveBetweenListView sealed + { + public: + Scenario4_MoveBetweenListView(); + private: + MainPage^ rootPage; + + Platform::Collections::Vector^ GetSampleData(); + Platform::Collections::Vector^ _source; + Platform::Collections::Vector^ _target; + + void ListView_DragItemsStarting(Platform::Object^ sender, Windows::UI::Xaml::Controls::DragItemsStartingEventArgs^ e); + void ListView_DragOver(Platform::Object^ sender, Windows::UI::Xaml::DragEventArgs^ e); + void ListView_Drop(Platform::Object^ sender, Windows::UI::Xaml::DragEventArgs^ e); + }; +}