[WP7] How to press the mouse on a control, and detect MouseLeftButtonUp on another


Another issue I found on StackOverflow, which is way more tricky that it seems.

Let’s say we have a Silverlight WP7 application, and we want to add a drag&drop scenario. The user first taps an element, drags his finger to another, and raises his finger on another element. Easy enough! Just handle the MouseLeftButtonDown on each element, store which element triggered the event in a property, handle the MouseLeftButtonUp on each element, and then we have the originator and the destination!

… right?

Well, so I would have thought.

Unfortunately, the MouseLeftButtonUp event will only be triggered if the ‘mouse’ left button (or your finger, sir Claus Jørgensen ;o) )1 is released on the very same control that it was pressed on.

So, if we use this XAML:

   1: <Grid x:Name="ContentPanel"
   2:       Grid.Row="1"
   3:       Margin="12,0,12,0"
   4:       MouseLeftButtonUp="ContentPanel_MouseLeftButtonUp">
   5:     <Grid.RowDefinitions>
   6:         <RowDefinition />
   7:         <RowDefinition />
   8:     </Grid.RowDefinitions>
   9:     <Grid x:Name="g1"
  10:           Background="Green"
  11:           MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  12:           MouseLeftButtonUp="Grid_MouseLeftButtonUp"
  13:           Tag="dragdrop" />
  14:     <Grid x:Name="g2"
  15:           Grid.Row="1"
  16:           Background="Blue"
  17:           MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  18:           MouseLeftButtonUp="Grid_MouseLeftButtonUp"
  19:           Tag="dragdrop" />
  20:  
  21: </Grid>

The layout looks like:

image

In the MouseLeftButtonUp event handler, we want to paint the destination grid:

   1: private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   2: {
   3:     var grid = (Grid)sender;
   4:     
   5:     grid.Background = new SolidColorBrush(Colors.Red);
   6: }

Unfortunately, it doesn’t work. Press your finger on the upper green grid, MouseLeftButtonDown is triggered. Drag your finger to the lower blue greed, lift your finger, MouseLeftButtonUp isn’t triggered.

I already encountered a similar issue with Silverlight a few years ago, so I knew about the CaptureMouse method. What is it? Basically it tells a control to keep track of the mouse even if events are triggered outside of the control bounds. Let’s try to use it.

In the MouseLeftButtonDown, simply capture the mouse:

   1: private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
   2: {
   3:     ((UIElement)sender).CaptureMouse();
   4: }

Now the MouseLeftButtonUp event is triggered! Unfortunately, it’s triggered on the control we called CaptureMouse on, so we still don’t know on which control the finger was when released. But we have the mouse coordinates, so we should be able to find it somehow.

And that ‘somehow’ is the ‘VisualTreeHelper.FindElementsInHostCoordinates’ method. It takes a point and a control, and enumerates all the control’s child that are located at the specified coordinates. Sounds good enough.

So now let’s rewrite our MouseLeftButtonUp event handler, without forgetting to release the mouse capture:

   1: private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   2: {
   3:     var grid = (Grid)sender;
   4:     
   5:     grid.ReleaseMouseCapture();
   6:  
   7:     var mouseUpGrid = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
   8:         .OfType<Grid>()
   9:         .FirstOrDefault();
  10:  
  11:     if (mouseUpGrid != null)
  12:     {
  13:         Debug.WriteLine("MouseUp in " + mouseUpGrid.Name);
  14:         mouseUpGrid.Background = new SolidColorBrush(Colors.Red);
  15:     }
  16: }

Test on the emulator, and… it works!

Well, sure it does, but now let’s imagine a more complex scenario:

   1: <Grid x:Name="ContentPanel"
   2:       Grid.Row="1"
   3:       Margin="12,0,12,0">
   4:     <Grid.RowDefinitions>
   5:         <RowDefinition />
   6:         <RowDefinition />
   7:     </Grid.RowDefinitions>
   8:     <Grid x:Name="g1"
   9:           Background="Green"
  10:           MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  11:           MouseLeftButtonUp="Grid_MouseLeftButtonUp" />
  12:     <Grid x:Name="DummyGrid"
  13:           Grid.Row="1"
  14:           Background="Gray">
  15:         <Grid x:Name="g2"
  16:               Margin="100 5 5 5"
  17:               Background="Blue"
  18:               MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  19:               MouseLeftButtonUp="Grid_MouseLeftButtonUp" />
  20:     </Grid>
  21: </Grid>

The layout looks like:

image

We only want to be able to drag from the green grid to the blue one (and the other way around). Unfortunately, using the previous code, the drag&drop will be detected even if we lift our finger on the gray grid. So we need a way to opt-in to the drag&drop detection, rather than detect it on all the grids.

The dirty way would be to store the name of the blue and green grids, and check the name in the MouseLeftButtonDown event. But we can make something more generic using the Tag property.

What is Tag? It’s an object property available on every control. What is stored in this property? Nothing. It’s meant to be used by you, and only by you, to store whichever object you want to personalize the control.

In our XAML, let’s add the “dragdrop” string in the Tag of the green and blue grids:

   1: <Grid x:Name="ContentPanel"
   2:       Grid.Row="1"
   3:       Margin="12,0,12,0">
   4:     <Grid.RowDefinitions>
   5:         <RowDefinition />
   6:         <RowDefinition />
   7:     </Grid.RowDefinitions>
   8:     <Grid x:Name="g1"
   9:           Background="Green"
  10:           MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  11:           MouseLeftButtonUp="Grid_MouseLeftButtonUp"
  12:           Tag="dragdrop" />
  13:     <Grid x:Name="DummyGrid"
  14:           Grid.Row="1"
  15:           Background="Gray">
  16:         <Grid x:Name="g2"
  17:               Margin="100 5 5 5"
  18:               Background="Blue"
  19:               MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  20:               MouseLeftButtonUp="Grid_MouseLeftButtonUp"
  21:               Tag="dragdrop" />
  22:     </Grid>
  23: </Grid>

Then, in the MouseLeftButtonUp event handler, it’s only a matter of filtering which controls have the appropriate tag:

   1: private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   2: {
   3:     var grid = (Grid)sender;
   4:     
   5:     grid.ReleaseMouseCapture();
   6:  
   7:     var mouseUpGrid = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
   8:         .OfType<Grid>()
   9:         .FirstOrDefault(element => element.Tag is string && (string)element.Tag == "dragdrop");
  10:  
  11:     if (mouseUpGrid != null)
  12:     {
  13:         Debug.WriteLine("MouseUp in " + mouseUpGrid.Name);
  14:         mouseUpGrid.Background = new SolidColorBrush(Colors.Red);
  15:     }
  16: }

Now the gray grid is excluded, as expected.

And that’s how a seemingly easy problem turns out on a solution requiring the use of Tag, VisualTreeHelper.FindElementsInHostCoordinates, and CaptureMouse. Quite instructive if you ask me.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值