0
votes

First things first: I have a WPF project which uses C# and MVVM (MVVM Light), in Visual Studio 2010.

I have a Canvas control inside which is a ListBox - each ListBoxItem can be moved around with mouse drag (each has a DataTemplate contains a Thumb control to allow each item to be dragged).

That looks as follows:

<DataTemplate>
                    <Grid Background="Transparent">
                        <Thumb Name="myThumb" Template="{StaticResource NodeVisualTemplateRegular}">

                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="DragDelta">
                                    <cmd:EventToCommand Command="{Binding ChatNodeListViewModel.DragDeltaCommand, Source={StaticResource Locator}}" PassEventArgsToCommand="True"/>
                                </i:EventTrigger>

                            </i:Interaction.Triggers>
                        </Thumb>
                    </Grid> </DataTemplate>

As you can see, there's a command which handles the dragging part (DeltaDragCommand).

None of this is a problem, but then I wanted to add the ability to pan around the Canvas control with a click and drag motion. This is where things come into conflict with the above stated dragging of ListBoxItems.

The Canvas pan stuff is handled in the code behind and the subscriptions for the events look like this:

NodeDragScrollViewer.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
        NodeDragScrollViewer.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
        NodeDragScrollViewer.MouseMove += OnMouseMove;
        NodeDragScrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;

        NodeDragScrollViewer.ScrollChanged += OnScrollViewerScrollChanged;

The events we see here are run on the ScrollViewer (a control in which the Canvas control sits). The problem is that these events sit 'on top of' the ListBoxItems - the click for the pan of the canvas will activate before the click for the drag of the ListBoxItem.

Now a solution I've seen is to use the VisualTreeHelper HitTest method to find out what was clicked on. If I can find out if a ListBoxItem was clicked on, then I can ignore the Canvas pan actions and let things progress as before.

The problem I'm facing is that I just can't get this to work. I have seem people explain how this should work and have borrowed code for this action. I present that code here:

Point pt = new Point();
        VisualTreeHelper.HitTest(NodeDragCanvas, null,
                     new HitTestResultCallback(MyHitTestResult),
                     new PointHitTestParameters(pt));

private HitTestResultBehavior MyHitTestResult(HitTestResult result)
    {
        var p = FindParent<ListBoxItem>(result.VisualHit);


        //Set the behavior to return visuals at all z-order levels. 
        return HitTestResultBehavior.Continue;
    }

public static T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
        //get parent item
        DependencyObject parentObject = VisualTreeHelper.GetParent(child);

        //we've reached the end of the tree
        if (parentObject == null) return null;

        //check if the parent matches the type we're looking for
        T parent = parentObject as T;
        if (parent != null)
            return parent;
        else
            return FindParent<T>(parentObject);
    }

If you look at the contents of HitTestResultBehaviour, this is where I check for the control that I've clicked on. I'm hoping it will be a ListBoxItem... but that always returns 'null'. I am aware that the method can fire multiple times, but I've never seen the var p (in this case) be anything other than a null value. If, however, I attempt to use FindParent<ListBox>, I will get returned the ListBox. It has the right amount of items in it and seems to be as I would expect. But this doesn't work for me since my ListBox is the size of the Canvas.

It seems this is the key point, but I don't know what else to try and all avenues of research seem to lead back to this method.

Can anyone offer any guidance? Thanks

1
Why are you using the Preview events in the first place? - Clemens
Well if I change them to the regular non-preview versions, the events don't fire. - TheFaithfulLearner

1 Answers

1
votes

Yes, obviously you need to use the PreviewXXX events on the Canvas, or you wouldn't get the mouse events for the ListBoxItems in the Canvas because the ListBoxItem already ate them.

You might want to consider restructuring your code so that instead of using a ListBox, you use a MyListBox (or whatever you want to call it) and trap the ListBoxItem mouse events there and handle just the Canvas dragging in the Canvas. The way you have it now, you are intermingling both of those cases when they are really completely separate. The canvas should handle its thing and the list box should handle its thing.

If you want to keep the code how you have it now, take a look at the MouseEventArgs, original source, sender, etc. One of those might have the original sender, but it'll probably be a TextBlock rather then a ListBoxItem since that's the top level item, so...

Google around for a well known class VisualTreeExtensions and use that. When you get the TextBlock or whatever, you can just do something like ((DependencyObject)e.OriginalSource).GetVisualAncestor<ListBoxItem>() and if it's not null, you know you're inside of a ListBox.