Hit Testing in WPF

There’s a lot that can be written about hit testing in WPF so I won’t try to cover everything but there are some subtleties that bear mentioning. A common issue that I see people encounter is that fact that the default background for many elements is null and null (at least in terms of hit testing) is treated separately from Transparent. Take the following snippet for example.

<Page
  <Page.Resources>
      <Style TargetType=”{x:Type Grid}”>
          <Style.Triggers>
              <Trigger Property=”IsMouseOver” Value=”True”>
                  <Setter Property=”Background” Value=”Yellow” />
              </Trigger>
          </Style.Triggers>
      </Style>
  </Page.Resources>
  <Grid> 
      <TextBlock
          Background=”Red”
          Text=”Test”
          TextAlignment=”Center”
          HorizontalAlignment=”Center”
          VerticalAlignment=”Center”
          Width=”100″ />
  </Grid>
</Page>
 
There is a trigger for the Grid that changes its Background to yellow when IsMouseOver is true. If you run this and move the mouse over the Grid without going over the TextBlock, the background of the Grid will remain the same. However, once you move the mouse over the TextBlock, the background of the Grid will change. Then something interesting happens – once the mouse leaves the TextBlock buts remains within the Grid the background of the Grid will remain Yellow. This basically comes back to the fact that the default background of the Grid is null. If you were to add a setter to the Style set the Background to Transparent (don’t set it explicitly or the mouse over won’t work since you’ll be providing a local value which will take predence over the style trigger) then the background would have changed to yellow as soon as the mouse entered the Grid.
 
     <Style TargetType=”{x:Type Grid}”>
          <Setter Property=”Background” Value=”Transparent” />
          <Style.Triggers>
              <Trigger Property=”IsMouseOver” Value=”True”>
                  <Setter Property=”Background” Value=”Yellow” />
              </Trigger>
          </Style.Triggers>
      </Style>

An interesting thing to note here is that the TextBlock doesn’t have this issue. So if you were to modify the original source and explicitly set the TextBlock’s Background to {x:Null} or not set it (since it defaults to null) and rerun the original test, you will find that the background of the Grid still changes to yellow when you go over the TextBlock (and not just when you go over the actual rendered text). The reason that this occurs is because the TextBlock explicitly overrides UIElement.HitTestCore and returns true if the specified point is within its rect. There are actually a few other elements that do this as well including ScrollViewer and InkCanvas. For TextBlock I think the main reason is that you probably don’t want the IsMouseOver changing as you move across the text in between characters but this is just a guess.

Another property that affects hit testing in WPF is the IsHitTestVisible. This property determines if the element and its descendants should be hidden from hit testing. I phrased it in this way because if you set IsHitTestVisible to false on the Grid in the sample above, it wouldn’t matter what the IsHitTestVisible state of the descendants is set to since the hit test that is performed to evaluate the IsMouseOver state is going to skip the Grid element and not traverse into its children.

Next time I’ll get to the real reason I started writing about hit testing today – to discuss the methods available for performing hit testing in code.

About these ads

13 Responses to “Hit Testing in WPF”

  1. 2008 September 17 - Links for today « My (almost) Daily Links Says:

    [...] other properties Greg Schecter gives a great intro to Multi-Input Shader Effects Andrew Smith on Hit Testing in WPF Possibly related posts: (automatically generated)Delighted!!Inspiring video about peace – please [...]

  2. Yasser Says:

    Hi Greg, just noticed your reply on the baml viewer comment-Good to know you’re still around. You’re posts are really helpful! I’ve managed to find myself now in a situation in which I’ve got a ScrollViewer housing a grid housing an ItemsControl and I’d like it to work such that Hit Testing is only apparent on the Items in the ItemsControl because there is content behind (in z-order) the ScrollViewer that I’d like to still hit. So I guess I’m looking to undo the ScrollViewer HitTestCore override that you mention in this post. There’s not too much on this method so my guess at this point is to do something like this in order to mimic the default behavior of a grid:

    protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
    {
    Point pt = hitTestParameters.HitPoint;
    return new PointHitTestResult((Visual)this.Content, pt);
    }

    any thoughts?

  3. agsmith Says:

    Hi Yasser. Actually my name is Andrew. :-)

    Unfortunately I don’t think there is a simple way to call the original base. I have one workaround which uses the VisualTreeHelper.HitTest and an anti recursion flag returning null if the mouse is only over the scroll viewer.

        public class ScrollViewerEx : ScrollViewer
        {
            private bool _isHitTesting;
     
            protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
            {
                if (_isHitTesting)
                    return base.HitTestCore(hitTestParameters);
     
                try
                {
                    _isHitTesting = true;
                    HitTestResult result = VisualTreeHelper.HitTest(this, hitTestParameters.HitPoint);
     
                    return null != result && null != result.VisualHit && this != result.VisualHit
                        ? new PointHitTestResult(this, hitTestParameters.HitPoint)
                        : null;
                }
                finally
                {
                    _isHitTesting = false;
                }
            }
        }

  4. Yasser Says:

    Sorry about the name mixup Andrew :) I was looking at that trackback post above that mentioned Greg Schechter and accidentally typed in his name.

    Prior to your post, I ended up just overriding the Scrollviewer’s HitTestCore to always return null and that seemed to do the trick. Though I do believe that your implementation is more proper and elegant- Although it does looks like result.VisualHit will always be equal to this ScrollViewerEx instance, I’m wondering under what circumstances would we enter that HitTestCore method and not have the result be == this…

  5. agsmith Says:

    I guess returning null makes sense since that is what an element that wasn’t rendering anything would have done. I made the assumption that the hittest had to return a positive result to recurse into its children.

  6. PiterJankovich Says:

    My name is Piter Jankovich. oOnly want to tell, that your blog is really cool
    And want to ask you: is this blog your hobby?
    P.S. Sorry for my bad english

  7. rdeetz Says:

    What if you also wanted to change the text in the TextBlock when IsMouseOver is true?

    Thanks!

  8. ge spacemaker microwave Says:

    ge spacemaker microwave…

    [...]Hit Testing in WPF « Andrew Smith[...]…

  9. malaysia taman negara Says:

    malaysia taman negara…

    [...]Hit Testing in WPF « Andrew Smith[...]…

  10. Gann Trading University Says:

    Gann Trading University…

    [...]Hit Testing in WPF « Andrew Smith[...]…

  11. les paul Says:

    les paul…

    [...]Hit Testing in WPF « Andrew Smith[...]…

  12. bajar peso Says:

    bajar peso…

    [...]Hit Testing in WPF « Andrew Smith[...]…

  13. YOU SHITHOLE Says:

    ROFL THIS ARTIVEL IS CRAPOLA
    WHAT DUMB NGGR SOB POSTED THIS WORTHLESS PIECE OF CRAP ARTICLE

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: