punker76/gong-wpf-dragdrop

Listview datatemplate with combobox has drag issues

osicka opened this issue · 5 comments

Hi,

I have a listview with a datatemplate that has many embedded controls, date picker, textbox, etc.

If I put a combobox in the datatemplate, drop down the list and try and scroll the drop down list with the vertical scrollbar the listviewitem starts dragging instead.

The solution that I found was to add an additional check to the hit tests that passes the original source as follows:

HitTest4Type(e.OriginalSource, e)

There are some quite funny useability issues still though. If you hold your mouse down on an item in the combobox drop down list and move your mouse the listviewitem also starts to drag. I didn't try to solve this issue as the user is less likely to try this.

Thanks
Anthony

Just installed this from nuget and also run into this (using ListBox). I expect that below template should be enough to reproduce.

<ListBox.ItemTemplate>
 <DataTemplate>
  <ComboBox Text="Text">
   <ComboBoxItem>Item1</ComboBoxItem>
   <ComboBoxItem>Item2</ComboBoxItem
  </ComboBox>
 </DataTemplate>
</ListBox.ItemTemplate>

edit: Adding

|| HitTestUtilities.HitTest4Type<ComboBox>(sender, elementPosition)

to

private static void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) 

in DragDrop.cs fixes this.

I used the combobox as an example but this is an issue for any control in the datatemplate that has left mouse button interaction. It would be nice to have a generic solution, otherwise you need to add every possible control to the list of exclusions. It gets even worse, when you have custom controls that would have to also be added.

I will fix this issue as possible as soon

Ah, right. After going through the code and debugging what's is happening, the reason that the current HitTest4Type's need to be quite specific is that the GetVisualAncestor keeps going after it has passed the container. Thus you will get false hits from borders of the window and so on.

public static T GetVisualAncestor<T>(this DependencyObject d) where T : class
-->
public static T GetVisualAncestor<T>(this DependencyObject d, object source = null) where T : class
----
while (item != null) {
-->
while (item != null && item != source) {

private static T GetHitTestElement4Type<T>(object sender, Point elementPosition) where T : UIElement
----
var uiElement = hit.VisualHit.GetVisualAncestor<T>();
-->
var uiElement = hit.VisualHit.GetVisualAncestor<T>(sender);

Then the hit tests can be a bit more generic:

private static void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)

For example both Slider and ScrollBar match RangeBase

|| HitTestUtilities.HitTest4Type<RangeBase>(sender, elementPosition)

Not sure that this will address osicka's problems completely, and the specific hit tests need to be added (such as Selector, RangeBase). Hopefully I included all the changes, tried a few things before this.

That did not work for me, I think because it is doing the hit test on the sender, which doesn't work when the original source is part of a popup (e.g. ComboBox drop down), the hit test needs to be done on the original source.

Because the popup is not attached to the visual tree of the sender, I added a new function to test this by looping back from the original source to the sender and if I didn't end up at the sender stopped the drag:

    static bool IsNotPartOfSender(object sender, MouseButtonEventArgs e)
    {
        HitTestResult hit = VisualTreeHelper.HitTest((Visual)e.OriginalSource, e.GetPosition((IInputElement)e.OriginalSource));

        if (hit == null)
        {
            return false;
        }
        else
        {
            DependencyObject item = VisualTreeHelper.GetParent(e.OriginalSource as DependencyObject);

            while (item != null && item != sender)
            {
                item = VisualTreeHelper.GetParent(item);
            }

            return item != sender;
        }
    }

And in DragSource_PreviewMouseLeftButtonDown I added

|| IsNotPartOfSender(sender, e)

This fixed the majority of my problems, the only remaining problem is my custom controls which inherit from Control so I cannot add them to the exclusion list unless I linking in my custom control library.