ShawnFeatherly
ShawnFeatherly

Reputation: 2638

WP7 discards MouseLeftButtonUp if didn't receive ButtonDown

I'm trying to get an element to fire the MouseUp event when the user clicks/taps outside of it, drags into it, then lets go. This type of functionality works in Silverlight, but not WP7. I can't figure out how to get it to work in WP7.

I created a simple app that demonstrates this. In a brand new WP7 app I added this to the content panel:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> 
  <Grid.RowDefinitions> 
    <RowDefinition /> 
    <RowDefinition /> 
  </Grid.RowDefinitions> 
  <Grid x:Name="g1" MouseLeftButtonUp="Grid_MouseLeftButtonUp" Background="Green" /> 
  <Grid x:Name="g2" Grid.Row="1" MouseLeftButtonUp="Grid_MouseLeftButtonUp" Background="Blue" /> 
</Grid>

Then the Grid_MouseLeftButtonUp handler in the codebehind:

private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  System.Diagnostics.Debug.WriteLine("MouseUp in " + (sender as Grid).Name);
  (sender as Grid).Background = new SolidColorBrush(Colors.Red);
}

Run this app and notice the MouseUp event fires fine if you release the button in the same cell you pressed down, however it doesn't fire if you drag from one cell to the other. How can I make the MouseUp event fire?

P.S. I also posted this on the app-hub forms, but no response yet: http://forums.create.msdn.com/forums/p/98004/584400.aspx

Upvotes: 1

Views: 649

Answers (4)

Kevin Gosse
Kevin Gosse

Reputation: 39007

My solution is a bit like ShawnFeatherly's, but without TouchFrame.

Basically, as he says, if you call MouseCapture from the grid where the MouseDown event occured, the MouseUp will be triggered on the same grid. So we know how to be notified when MouseUp occurs, the only problem left is how to know in which grid the MouseUp actually occured. For this, we're going to use the VisualTreeHelper.FindElementsInHostCoordinates method, as it returns all the elements at a specified coordinate.

So, first add a MouseLeftButtonDown event handler to each of your grids:

    private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        ((Grid)sender).CaptureMouse();
    }

Now in the MouseLeftButtonUp event handler of each of your grids, first release the mouse capture, then retrieve the Grid in which the MouseUp occured:

    private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var grid = (Grid)sender;

        grid.ReleaseMouseCapture();

        var mouseUpGrid = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
            .OfType<Grid>()
            .FirstOrDefault();

        if (mouseUpGrid != null)
        {
            Debug.WriteLine("MouseUp in " + mouseUpGrid.Name);
            mouseUpGrid.Background = new SolidColorBrush(Colors.Red);
        }
    }

Note that a problem may occurs depending on your visual tree: if you have multiple grids and wants to detect the MouseUp only on some, you need a way to identify them. For this, I suggest to use the Tag property. Tag is an all-purpose field available on each control, that you can use however you need. It's especially useful for identification purposes.

Start by adding it to the grids that interest you:

<Grid x:Name="ContentPanel"
      Grid.Row="1"
      Margin="12,0,12,0"
      MouseLeftButtonUp="ContentPanel_MouseLeftButtonUp">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid x:Name="g1"
          Background="Green"
          MouseLeftButtonDown="Grid_MouseLeftButtonDown"
          MouseLeftButtonUp="Grid_MouseLeftButtonUp"
          Tag="dragdrop" />
    <Grid x:Name="g2"
          Grid.Row="1"
          Background="Blue"
          MouseLeftButtonDown="Grid_MouseLeftButtonDown"
          MouseLeftButtonUp="Grid_MouseLeftButtonUp"
          Tag="dragdrop" />
</Grid>

Then use exactly the same logic in code-behind, but this time add a filter when browsing the visual tree:

    private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var grid = (Grid)sender;

        grid.ReleaseMouseCapture();

        var mouseUpGrid = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
            .OfType<Grid>()
            .FirstOrDefault(element => element.Tag is string && (string)element.Tag == "dragdrop");


        if (mouseUpGrid != null)
        {
            Debug.WriteLine("MouseUp in " + mouseUpGrid.Name);
            mouseUpGrid.Background = new SolidColorBrush(Colors.Red);
        }
    }

And you're done! This code should be able to handle complex scenarios like:

<Grid x:Name="ContentPanel"
      Grid.Row="1"
      Margin="12,0,12,0"
      MouseLeftButtonUp="ContentPanel_MouseLeftButtonUp">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid x:Name="g1"
          Background="Green"
          MouseLeftButtonDown="Grid_MouseLeftButtonDown"
          MouseLeftButtonUp="Grid_MouseLeftButtonUp"
          Tag="dragdrop" />
    <Grid x:Name="DummyGrid" Grid.Row="1">
        <Grid x:Name="g2"
              Background="Blue"
              MouseLeftButtonDown="Grid_MouseLeftButtonDown"
              MouseLeftButtonUp="Grid_MouseLeftButtonUp"
              Tag="dragdrop" />
    </Grid>
</Grid>

Upvotes: 2

ShawnFeatherly
ShawnFeatherly

Reputation: 2638

One way to work around this is listening to the TouchFrame for a TouchAction.Up. You'll have to calculate the UIElement the ButtonUp cooresponds to using the TouchPoints' Position property as described here: http://forums.create.msdn.com/forums/p/98004/584465.aspx#584465

Another way is to capture the mouse in the ButtonDown UIElement. This will cause the ButtonUp to correctly fire, however the sender will be the original UIElement that caused the ButtonDown. You can track the elements the mouse moves through using MouseEnter and MouseLeave. The necessity for mouse capture is briefly touched on here: http://forums.create.msdn.com/forums/p/70785/431882.aspx

Upvotes: 2

Claus J&#248;rgensen
Claus J&#248;rgensen

Reputation: 26341

Last time I checked, my phone didn't have a mouse attached.

Use the Tap event instead of MouseLeftButtonUp. For more complicated gestures, use the Silverlight Toolkit GestureListener class.

Upvotes: 1

Related Questions