tags:

views:

127

answers:

2

I have a list of controls that I am displaying via a WrapPanel and it horizontally oriented.

I have implemented a "Click and Drag" scrolling technique so that the user scrolls with the mouse via clicking and dragging.

Like so:

<Canvas x:Name="ParentCanvas" PreviewMouseDown="Canvas_MouseDown" MouseMove="Canvas_MouseMove">
  <WrapPanel>
    <WrapPanel.RenderTransform>
      <TranslateTransform />
    </WrapPanel.RenderTransform> 

    <!-- controls are all in here ... -->
  </WrapPanel>
</Canvas>

Then in the code behind:

    private Point _mousePosition;
    private Point _lastMousePosition;

    private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
    {
        _lastMousePosition = e.GetPosition(ParentCanvas);
        e.Handled = true;
    }

    private void Canvas_MouseMove(object sender, MouseEventArgs e)
    {
        _mousePosition = e.GetPosition(ParentCanvas);
        var delta = _mousePosition - _lastMousePosition;

        if(e.LeftButton == MouseButtonState.Pressed && delta.X != 0)
        {
            var transform = ((TranslateTransform)_wrapPanel.RenderTransform).Clone();
            transform.X += delta.X;
            _wrapPanel.RenderTransform = transform;
            _lastMousePosition = _mousePosition;
        }
    }

This all works fine

But what I want to do is make it so that when a users clicks to drag, the items within the WrapPanel dont respond (i.e. the user is only browsing), but when the user clicks (as in a full click) then they do respond to the click.

Just like how the iphone works, when you press and drag directly on an app, it does not open the app, but rather scrolls the screen, but when you tap the app, it starts...

I hope this makes sense.

Cheers, Mark

+1  A: 

I believe you'll need to capture the mouse. The problem is you'll be contending with the controls (such as Button) that will also be trying to capture the mouse.

In your MouseDown event (probably PreviewMouseDown actually) you can use e.MouseDevice.Capture(_wrapPanel, CaptureMode.Element). This should direct all mouse input to the _wrapPanel and not any subtree elements.

In your MouseUp event, you'll need to release the capture by calling e.Mousedevice.Capture(null). If no scrolling has taken place you'll want to send a "click" to the control that normally would have received the click which I'm not quite sure about. Perhaps you can use the Automation Peer classes to do this?

The trick is that certain controls will not work properly if you withhold mouse events from them. Consider a slider for example. How would the slider ever be usable inside a panel that works like this?

Josh Einstein
Agreed Josh, that's the idea that I have been following myself, and luckily for me, I wont be needing to use a slider. But im stuck on the section you mentioned about sending the click event, what are the Automation Peer classes?
Mark
See the following: http://stackoverflow.com/questions/728432/how-to-programmatically-click-a-button-in-wpf
Josh Einstein
this feels wrong to me... Im not sure this is the best implementation, but I simply cannot find a better solution yet
Mark
It does feel wrong. But it's ugly business. Even on the Tablet PC and touch-enabled PC's we have the same problem. For example on a tablet there's a gesture called "press and hold" which does a right click. Unfortunately by having this enabled, you can't click and hold a control like normal because the gesture eats the mouse events. If the gesture isn't completed (ie the user lifts before the press and hold duration) Windows must simulate the original mouse events to the control that would have otherwise received them.
Josh Einstein
ewww, sounds yuk. Oh well thanks for the help, looks like this will do for now at least :)
Mark
A: 

Another, and in my opinion better, solution is to:

  1. Add a PreviewMouseDown handler in which you set Handled=true and record the parameters including the position and set a "maybeClick" flag (unless your "recursion" flag is set), and sets a timer.

  2. Add a MouseMove handler that clears the "maybeClick" flag if the mouse moves more than an epsilon away from the position recorded for the PreviewMouseDown.

  3. Add a PreviewMouseUp handler that checks the "maybeClick" flag - if true, it sets the "recursion" flag, does an InputManager.ProcessInput to re-raise the original PreviewMouseDown, and then clears the "recursion" flag.

  4. In the timer, do the same thing as for PreviewMouseUp so the click will only be delayed a moment.

The net effect is to delay a PreviewMouseDown event until you have had time to check whether the mouse moved or not.

Some things to note about this solution:

  • Setting Handled=true on PreviewMouseDownEvent also stops the MouseDownEvent.
  • The recursive call is ignored in the PreviewMouseDown handler because the recursion flag is set
Ray Burns