Microsoft-ui-xaml: Question: How to block a drop using the DragOver event in TreeView or TreeViewItem?

Created on 11 Aug 2019  ·  40Comments  ·  Source: microsoft/microsoft-ui-xaml

I have a TreeView that contains two types of tree items, Leaf and Group.

Leaves can be children of groups.
Groups can be children of groups.
Leaves cannot have children.

Within the same tree, I want to allow my customers to reorder the tree and to move items to different groups by dragging and dropping elements. Based on the rules provided above, I need to be able to conditionally block certain drop operations, and I want to block them during the DragOver event. I have tried the following code, but it does nothing to block the drop:

            <c:TreeView ItemsSource="{x:Bind ViewModel.Nodes}"
                        DragOver="TreeView_DragOver"
                        DragEnter="TreeView_DragEnter"
                        DragItemsCompleted="TreeView_DragItemsCompleted"
                        DragItemsStarting="TreeView_DragItemsStarting">
                <c:TreeView.ItemTemplate>
                    <DataTemplate x:DataType="local:ExplorerItem">
                        <local:MyTreeViewItem ItemsSource="{x:Bind Children}" Content="{x:Bind Name}" AllowDrop="True" DragOver="TreeViewItem_DragOver"
                                        DragEnter="TreeViewItem_DragEnter"
                                        Drop="TreeViewItem_Drop" DropCompleted="TreeViewItem_DropCompleted"/>
                    </DataTemplate>
                </c:TreeView.ItemTemplate>
            </c:TreeView>
        private void TreeViewItem_DragOver(object sender, DragEventArgs e)
        {
            // this does not do anything
            e.AcceptedOperation = DataPackageOperation.None;
        }

        private void TreeView_DragOver(object sender, DragEventArgs e)
        {
            // this does not do anything
            e.AcceptedOperation = DataPackageOperation.None;
        }

        private void TreeViewItem_DragEnter(object sender, DragEventArgs e)
        {
            // this does not do anything
            e.AcceptedOperation = DataPackageOperation.None;
        }

        private void TreeView_DragEnter(object sender, DragEventArgs e)
        {
            // this does not do anything
            e.AcceptedOperation = DataPackageOperation.None;
        }

Am I doing something wrong? Can someone show me how to conditionally block a drop operation?

question

All 40 comments

@dpaulino I tried a few things looks like the easiest way to do this is updating CanReorderItems in DragItemsStarting.
C# private void TreeView_DragItemsStarting(Microsoft.UI.Xaml.Controls.TreeView sender, Microsoft.UI.Xaml.Controls.TreeViewDragItemsStartingEventArgs args) { YourTreeView.CanReorderItems = /*isItemDraggable*/; }

If I were to use the drag items starting event, do I have knowledge of what the drop target is going to be? I would need to know the drop target in order to decide if the drop should be blocked, right?

To clarify my question, here are some scenarios.

Given a tree that looks like this

  • group 1

    • leaf a

    • leaf b

  • leaf c
  • group 2
  1. User can move leaf c into group 2
  2. User can move group 1 into group 2
  3. User can move leaf b into the root of the tree
  4. User cannot move group 2 into leaf b.

You can see that in scenario 4, I would need to block the drop. This is where I would like to use the drag over event. When the user picks up group 2 and hovers it over leaf b, I want the tree view to show the 🚫 icon to express that this drop operation is not allowed.

Does the drag items started event work in this scenario?

If I were to use the drag items starting event, do I have knowledge of what the drop target is going to be? I would need to know the drop target in order to decide if the drop should be blocked, right?

To clarify my question, here are some scenarios.

Given a tree that looks like this

  • group 1

    • leaf a
    • leaf b
  • leaf c
  • group 2
  1. User can move leaf c into group 2
  2. User can move group 1 into group 2
  3. User can move leaf b into the root of the tree
  4. User cannot move group 2 into leaf b.

You can see that in scenario 4, I would need to block the drop. This is where I would like to use the drag over event. When the user picks up group 2 and hovers it over leaf b, I want the tree view to show the 🚫 icon to express that this drop operation is not allowed.

Does the drag items started event work in this scenario?

I see, I think you can move the logic to DragOver in this case
C# private void TreeViewItem_DragOver(object sender, DragEventArgs e) { TreeViewItem treeViewItem = sender as TreeViewItem; var item = YourTreeView.ItemFromContainer(treeViewItem) as YourItemType; TestTreeView.CanReorderItems = item.IsGroup; }

The RequestedOperation and AcceptedOperation didn't really work because TreeView is changing those values internally when CanReorderItems is true, you can take a look at the code and see what's its doing, but setting the value to false should block the reorder/drop completely.

Not sure if you deleted your previous comment but I got an email asking about the sender parameter...

Sender should be the item you are currently hovering over.

Try adding the following code to your TreeViewTest project and you should be able to drag "first" into "second" but not vice versa.

    private void TreeViewItem_DragOver(object sender, DragEventArgs e)
    {
        MUXC.TreeViewItem treeViewItem = sender as MUXC.TreeViewItem;
        var item = TestTreeView.ItemFromContainer(treeViewItem) as ExplorerItem;
        TestTreeView.CanReorderItems = item.Name.Equals("second");
    }

From: Daniel Paulino notifications@github.com
Sent: Wednesday, August 14, 2019 7:18 PM
To: microsoft/microsoft-ui-xaml microsoft-ui-xaml@noreply.github.com
Cc: Kai Guo Guo.Kai@microsoft.com; Assign assign@noreply.github.com
Subject: Re: [microsoft/microsoft-ui-xaml] Question: How to block a drop using the DragOver event in TreeView or TreeViewItem? (#1154)

I just tried your suggestion, but I realize there's something missing. The DragOver event does not seem to contain information about the target item that we are hovering over.

I need to be able to determine the target item in order to decide whether or not I should allow or disallow the drop.

In your previous comment, the item variable comes from the sender parameter. But it seems that sender represents the item being dragged, not the item we are hovering over.

How can I determine the target item while reordering items in the WinUI TreeView?


You are receiving this because you were assigned.
Reply to this email directly, view it on GitHubhttps://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fmicrosoft-ui-xaml%2Fissues%2F1154%3Femail_source%3Dnotifications%26email_token%3DABBYFCQQZKLZJQXKVTSMNV3QES4JFA5CNFSM4IK3BVCKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4KUU7Q%23issuecomment-521489022&data=02%7C01%7CGuo.Kai%40microsoft.com%7C21c0a8e7565a4434d2ef08d72126ef33%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637014323409254610&sdata=RdeI4F9sgF2G1iOo5nIUdpRbPziN%2BaLpRKSm5P32ewI%3D&reserved=0, or mute the threadhttps://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FABBYFCXGKMN6VJUIYYDWMDDQES4JFANCNFSM4IK3BVCA&data=02%7C01%7CGuo.Kai%40microsoft.com%7C21c0a8e7565a4434d2ef08d72126ef33%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637014323409264604&sdata=tZ6QXh5%2ByN4qxUVDBDBdMpdafo4ZfSnc5xzDej46uXA%3D&reserved=0.

I deleted my previous comment because I found out I was doing something wrong.

But now I'm back with a new comment. I have tried this approach:

private void TreeViewItem_DragOver(object sender, DragEventArgs e)
{
        TreeViewItem treeViewItem = sender as TreeViewItem;
        var item = YourTreeView.ItemFromContainer(treeViewItem) as YourItemType;
        TestTreeView.CanReorderItems = item.IsGroup;
}

It sort of works, but it seems I am no longer able to reorder the list. Example below.

Say I have a tree that looks like this:

  • Leaf 1
  • Leaf 2
  • Leaf 3
  • Leaf 4

Using the approach you provided, I am unable to move Leaf 1 to between Leaf 2 and 3. The reason seems to be that in the DragOver event, the Tree.CanReorderItems is set to false as the event handler is completing, so I am unable to reorder the leaves.

Any ideas on how to allow reordering but still keep all the previous requirements? Also, is there a fully functional drag drop sample with conditional dropping somewhere? That would make things a lot easier if someone could just make a fully functional sample for a TreeView that can perform conditional dropping.

Try setting CanReorderItems back to true in DragLeave?

C# private void TreeViewItem_DragLeave(object sender, DragEventArgs e) { TestTreeView.CanReorderItems = true; }

Hmm, looks like that doesn't work. You might have to override those drag&drop functions and handle everything on your own. Let me double check tomorrow and see if there are better alternatives...

Yeah I have tried that and it does not work.

Would you or someone else be able to build a small sample that uses the TreeView with ItemsSource bound to an ObservableCollection and with conditional drop? I've been trying to make this work for 3 months and I have just had so many problems. I'm desperate to have this ability because my customers have been complaining so much about this.

I made a sample app here: https://github.com/kaiguo/TreeViewConditionalReorderSample

Updating AcceptedOperation in event callbacks doesn't work because the handler is triggered after the actual event and TreeViewItem has already done a bunch of stuff when you get to the callback. But overriding the events in TreeViewItem does work since you get a chance to make changes before TreeViewItem executing its own code.

This sample looks promising. I just tried it and I got a crash while performing a drag. However, I can no longer reproduce it... I'm going to experiment with this a bit more and I will report back if I can isolate the crash.

Hmm. When I perform a drop onto a group, the observable collection is not being updated. Can you confirm?

Hmm. When I perform a drop onto a group, the observable collection is not being updated. Can you confirm?

Looks like it's working for me. I added a button in the sample app to dump out ItemsSource, you can take a look.

Ah I see what's going on. Your sample is using the prerelease package. When I reverted your sample to the regular release package, the observable collection was not being updated.

image

So that bug was fixed in the prerelease package. Let me try updating my app to the prerelease package and I'll try this again.

Weird, I'm getting a C++ exception on startup after I updated to that prerelease package. Here's the tail end of the debug output

'Nightingale.exe' (Win32): Loaded 'C:\Windows\System32\CryptoWinRT.dll'. 
'Nightingale.exe' (Win32): Loaded 'C:\Users\kid_j\Source\Repos\Nightingale\Package\bin\x64\Debug\AppX\Microsoft.UI.Xaml.Markup.winmd'. 
'Nightingale.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Users\kid_j\Source\Repos\nightingale\Package\bin\x64\Debug\AppX\Microsoft.UI.Xaml.Markup.winmd'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
The thread 0x2bbc has exited with code 0 (0x0).
'Nightingale.exe' (Win32): Loaded 'C:\Users\kid_j\Source\Repos\Nightingale\Package\bin\x64\Debug\AppX\Microsoft.UI.Xaml.winmd'. Module was built without symbols.
'Nightingale.exe' (Win32): Unloaded 'C:\Users\kid_j\Source\Repos\Nightingale\Package\bin\x64\Debug\AppX\Microsoft.UI.Xaml.winmd'
'Nightingale.exe' (Win32): Loaded 'C:\Users\kid_j\Source\Repos\Nightingale\Package\bin\x64\Debug\AppX\Microsoft.UI.Xaml.winmd'. Module was built without symbols.
'Nightingale.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Users\kid_j\Source\Repos\nightingale\Package\bin\x64\Debug\AppX\Microsoft.UI.Xaml.winmd'. Module was built without symbols.
'Nightingale.exe' (Win32): Loaded 'C:\Users\kid_j\Source\Repos\Nightingale\Package\bin\x64\Debug\AppX\Microsoft.UI.Xaml.dll'. 
'Nightingale.exe' (Win32): Loaded 'C:\Program Files\WindowsApps\Microsoft.VCLibs.140.00.Debug_14.0.27508.1_x64__8wekyb3d8bbwe\vcruntime140_1_app.dll'. 
'Nightingale.exe' (Win32): Loaded 'C:\Program Files\WindowsApps\Microsoft.VCLibs.140.00.Debug_14.0.27508.1_x64__8wekyb3d8bbwe\msvcp140_app.dll'. 
'Nightingale.exe' (Win32): Loaded 'C:\Program Files\WindowsApps\Microsoft.UI.Xaml.2.1_2.11906.6001.0_x64__8wekyb3d8bbwe\Microsoft.UI.Xaml.dll'. 
'Nightingale.exe' (Win32): Loaded 'C:\Windows\System32\Windows.StateRepositoryPS.dll'. 
onecoreuap\base\mrt\runtime\com\dllsrv\mrtresourcemanager.cpp(228)\MrmCoreR.dll!00007FFF9973619E: (caller: 00007FFF8CD3FB48) ReturnHr(1) tid(341c) 80073B1F ResourceMap Not Found.
onecoreuap\base\mrt\runtime\com\dllsrv\mrtresourcemanager.cpp(228)\MrmCoreR.dll!00007FFF9973619E: (caller: 00007FFF8CD3FB48) ReturnHr(2) tid(341c) 80073B1F ResourceMap Not Found.
Exception thrown at 0x00007FFFA735A839 (KernelBase.dll) in Nightingale.exe: WinRT originate error - 0x80004005 : 'Cannot locate resource from 'ms-appx://Microsoft.UI.Xaml.2.2/Microsoft.UI.Xaml/Themes/19h1_themeresources.xaml'.'.
onecore\com\combase\winrt\error\restrictederror.cpp(1014)\combase.dll!00007FFFA89B50E0: (caller: 00007FFFA89AF19C) ReturnHr(1) tid(341c) 8007007E The specified module could not be found.
Exception thrown at 0x00007FFFA735A839 in Nightingale.exe: Microsoft C++ exception: winrt::hresult_error at memory location 0x0000008467CFDFD0.

The error seems to be: 'Cannot locate resource from 'ms-appx://Microsoft.UI.Xaml.2.2/Microsoft.UI.Xaml/Themes/19h1_themeresources.xaml'. Any ideas?

Weird. I seem to have fixed it by cleaning the solution and then rebuilding.

The observable collection is being updated now. Going to do a few more tests before I close this issue.

Looks good. Closing issue. Thanks for all your help.

@kaiguo I just found a crash and it's due to a requirement that I incorrectly stated at the start of this issue: groups cannot actually contain other groups.

The crash occurs when I have something looking like this:

  • group 1
  • group 2

    • leaf a

If I drag group 1 to hover in the space between group 2 and leaf a, a drop is allowed and it will lead to a crash for my app due to an invalid cast. In my scenario, the invalid cast is correct because groups cannot contain other groups. How could the sample you provided be modified to support this scenario?

@kaiguo friendly ping. Any thoughts on my previous comment?

We probably need some new APIs for this to get the drop position.

As a workaround for now, I think you can use InsertionPanel to get the item above/below the dropping position, which should help you figure out whether the drop is valid or not (e.g. block the drop if dragged item is a group, and the item above or its parent is a group). I put something here to show you how to get the above/below item by using InsertionPanel.

https://github.com/kaiguo/TreeViewConditionalReorderSample/blob/insertionpanel-test/TreeViewConditionalReorderSample/MyTreeViewItem.cs#L20

I'll try this out and report back soon. I appreciate your help.

Unfortunately, this does not work.

When I hover a group item in the space between a parent group and a child life (see image below), the OnDrop and OnDragOver events are not fired. Thus, I cannot use the InsertionPanel to determine if I can block the drop.

For reference, when I say hovering a group item in the space between, I mean like below:
image

(My request is a child of the group with the chevron pointing down).

As for next steps, should I open a proposal issue or a bug issue to request this new API?

As for next steps, should I open a proposal issue or a bug issue to request this new API?

I think we can just use this issue to track it. I will do some exploration and see what we can do to make this easier.

There is already a proposal for this: #381

I hope it will be considered, because it is a very common scenario that you want to prevent specific drag/drop operations, depending on source and target nodes.

@lukasf thanks for referencing that proposal. I would like to see this feature added ASAP though rather than wait for WinUI 3.0. My customers have been demanding proper drag and drop abilities for the past 4 months, and I cannot implement it properly due to this treeview limitation.

WinUI team, please consider addressing this as soon as possible.

Just to throw my support onto this as well. I raised #381 a while ago. The drag and drop APIs feel very incomplete and inflexible.

Do we have an ETA on support for this?

It's something I'm hearing the need for a lot, as well.

Do we have an ETA on support for this?

I'm on some other lifted XAML work but I'll try and see if I can add something in 2.3 release (no guarantee😅).

I'm on some other lifted XAML work but I'll try and see if I can add something in 2.3 release (no guarantee😅).

@kaiguo that would be awesome.

I think I have the same issue (in #381) as @dpaulino, I want to be able to enforce the order of items (I.e. have pinned items at the top of the tree view) and block moving certain items and make certain items not be drop targets.

Thanks!

@dpaulino I updated the sample app to use the latest TreeView changes, I think this should solve the issue you mentioned in this comment.

Thanks for the update. Is this available in a prerelease package that I could try out?

Is this available in a prerelease package that I could try out?

Yeah, you can find the prerelease package in the sample app here.

Small question on this topic. Is this change approved for stable releases? Wasnt able to work with the sample app provided on this topic.

I think the change should be in the latest (pre)release version of WinUI.

Well, the latest pre-release i found is 2.4.0-prerelease.200506001 where there is a 2.4.0 stable release. The topic was opened on an 2.3 and i'm still missing the "NewParent" property on TreeViewDragItemsCompletedEventArgs

The "NewParent" property is still marked as preview, so it currently should only be available in prereleases.

as said, i tested with several 2.4 pre releases, an wasn't able to access it

It was renamed with #1692, the property is called "NewParentItem" now. Sorry about the confusion.

Namingconversion are no problem in general... but, i cant find any. There are only two acessable properties, Items and DropResult. Call me dumb, but i'm not able to get a working solution.

I've updated the linked project from @kaiguo to use the newest prerelease. Can you clone the following project and check if it compiles for you?

The updated sample: https://github.com/chingucoding/TreeViewConditionalReorderSample

Yes, it does and works like a charme. Well, obivously i missed out my usings...
Both, Microsoft.UI.Xaml.Controls as Windows.UI.Xaml.Controls have an own CompletedEventArgs.... never saw this discrepancy -.- thanks for pushing me into right direction

Glad to hear that we found the issue, happy that I could help you :)

Was this page helpful?
0 / 5 - 0 ratings