2010-09-19 30 views
2

j'ai le contrôle de l'utilisateur suivant: un point et son nom:WPF UserControl HitTest

<UserControl x:Class="ShapeTester.StopPoint" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    mc:Ignorable="d" 
    d:DesignHeight="25" d:DesignWidth="100"> 

    <StackPanel> 
     <Ellipse Stroke="DarkBlue" Fill="LightBlue" Height="10" Width="10"/> 
     <TextBlock Text="Eiffel Tower"/>   
    </StackPanel> 
</UserControl> 

C'est cool.

Maintenant, j'ai un panneau, dans la sorcière je dois récupérer mes StopPoints que j'ai frappé avec la souris:

public partial class StopsPanel : UserControl 
{ 
    private List<StopPoint> hitList = new List<StopPoint>(); 
    private EllipseGeometry hitArea = new EllipseGeometry(); 

    public StopsPanel() 
    { 
     InitializeComponent(); 
     Initialize(); 
    } 

    private void Initialize() 
    { 
     foreach (StopPoint point in StopsCanvas.Children) 
     { 
      point.Background = Brushes.LightBlue; 
     } 
    } 

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
    { 
     // Initialization: 
     Initialize(); 
     // Get mouse click point: 
     Point pt = e.GetPosition(StopsCanvas); 
     // Define hit-testing area: 
     hitArea = new EllipseGeometry(pt, 1.0, 1.0); 
     hitList.Clear(); 
     // Call HitTest method: 
     VisualTreeHelper.HitTest(StopsCanvas, null, 
     new HitTestResultCallback(HitTestCallback), 
     new GeometryHitTestParameters(hitArea)); 
     if (hitList.Count > 0) 
     { 
      foreach (StopPoint point in hitList) 
      { 
       // Change rectangle fill color if it is hit: 
       point.Background = Brushes.LightCoral; 
      } 
      MessageBox.Show(string.Format(
       "You hit {0} StopPoint(s)", hitList.Count)); 
     } 
    } 

    public HitTestResultBehavior HitTestCallback(HitTestResult result) 
    { 
     if (result.VisualHit is StopPoint) 
     { 
      // 
      //-------- NEVER ENTER HERE!!! :(
      // 

      // Retrieve the results of the hit test. 
      IntersectionDetail intersectionDetail = 
      ((GeometryHitTestResult)result).IntersectionDetail; 
      switch (intersectionDetail) 
      { 
       case IntersectionDetail.FullyContains: 
       // Add the hit test result to the list: 
        hitList.Add((StopPoint)result.VisualHit); 
        return HitTestResultBehavior.Continue; 
       case IntersectionDetail.Intersects: 
       // Set the behavior to return visuals at all z-order levels: 
        return HitTestResultBehavior.Continue; 
       case IntersectionDetail.FullyInside: 
       // Set the behavior to return visuals at all z-order levels: 
        return HitTestResultBehavior.Continue; 
       default: 
        return HitTestResultBehavior.Stop; 
      } 
     } 
     else 
     { 
      return HitTestResultBehavior.Continue; 
     } 
    } 
} 

Donc, comme vous pouvez le voir, le problème qui le HitTest identifie jamais un UserControl (StopPoint) comme il est, mais plutôt ses composants (TextBlock, Ellipse ou même Bordure). Comme j'associe l'objet métier à l'élément StopPoint, j'ai besoin de l'obtenir lors de MouseHitting, et non de ses éléments de composition.

Existe-t-il un moyen de le faire?

EDIT:

Utiliser le filtre (maintenant, il ne pénètre pas du tout dans le HitTestCallback):

using System.Collections.Generic; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Input; 
using System.Windows.Media; 

namespace ShapeTester 
{ 
    /// <summary> 
    /// Interaction logic for StopsPanel.xaml 
    /// </summary> 
    public partial class StopsPanel : UserControl 
    { 
     private List<StopPoint> hitList = new List<StopPoint>(); 
     private EllipseGeometry hitArea = new EllipseGeometry(); 

     public StopsPanel() 
     { 
      InitializeComponent(); 
      Initialize(); 
     } 

     private void Initialize() 
     { 
      foreach (StopPoint point in StopsCanvas.Children) 
      { 
       point.Background = Brushes.LightBlue; 
      } 
     } 

     private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
     { 
      // Initialization: 
      Initialize(); 
      // Get mouse click point: 
      Point pt = e.GetPosition(StopsCanvas); 
      // Define hit-testing area: 
      hitArea = new EllipseGeometry(pt, 1.0, 1.0); 
      hitList.Clear(); 
      // Call HitTest method: 
      VisualTreeHelper.HitTest(StopsCanvas, 
       new HitTestFilterCallback(MyHitTestFilter), 
       new HitTestResultCallback(HitTestCallback), 
       new GeometryHitTestParameters(hitArea)); 

      if (hitList.Count > 0) 
      { 
       foreach (StopPoint point in hitList) 
       { 
        // Change rectangle fill color if it is hit: 
        point.Background = Brushes.LightCoral; 
       } 
       MessageBox.Show(string.Format(
        "You hit {0} StopPoint(s)", hitList.Count)); 
      } 
     } 

     public HitTestResultBehavior HitTestCallback(HitTestResult result) 
     { 
      if (result.VisualHit is StopPoint) 
      { 
       // 
       //-------- NEVER ENTER HERE!!! :(
       // 

       // Retrieve the results of the hit test. 
       IntersectionDetail intersectionDetail = 
       ((GeometryHitTestResult)result).IntersectionDetail; 
       switch (intersectionDetail) 
       { 
        case IntersectionDetail.FullyContains: 
        // Add the hit test result to the list: 
         hitList.Add((StopPoint)result.VisualHit); 
         return HitTestResultBehavior.Continue; 
        case IntersectionDetail.Intersects: 
        // Set the behavior to return visuals at all z-order levels: 
         return HitTestResultBehavior.Continue; 
        case IntersectionDetail.FullyInside: 
        // Set the behavior to return visuals at all z-order levels: 
         return HitTestResultBehavior.Continue; 
        default: 
         return HitTestResultBehavior.Stop; 
       } 
      } 
      else 
      { 
       return HitTestResultBehavior.Continue; 
      } 
     } 

     // Filter the hit test values for each object in the enumeration. 
     public HitTestFilterBehavior MyHitTestFilter(DependencyObject o) 
     { 
      // Test for the object value you want to filter. 
      if (o.GetType() == typeof(StopPoint)) 
      { 
       // Visual object's descendants are 
       // NOT part of hit test results enumeration. 
       return HitTestFilterBehavior.ContinueSkipChildren; 
      } 
      else 
      { 
       // Visual object is part of hit test results enumeration. 
       return HitTestFilterBehavior.Continue; 
      } 
     } 
    } 
} 
+0

Avez-vous essayé d'ajouter un HitTestFilterCallback et de renvoyer ContinueSkipChildren s'il se trouve sur un StopPoint? Je vois que vous passez actuellement null comme rappel de filtre. – Bubblewrap

+0

@Bubblewrap: hmm ... e ... que voulez-vous dire? – serhio

+0

Le 2ème paramètre de VisualTreeHelper.HitTest, vous pouvez spécifier un HitTestFilterCallback. Voir ici: http://msdn.microsoft.com/en-us/library/ms752097.aspx # using_a_hit_test_filter_callback – Bubblewrap

Répondre

0

Pourriez-vous pas ajouter des événements de clic de souris auditeurs aux points, et juste jeter l'expéditeur à StopPoint et tout serait bien? Pas besoin du code de test supplémentaire.

+0

Je ne suis pas sûr ... je vais devoir le déplacer en faisant glisser puis ... – serhio

3

Vous pouvez utiliser VisualTreeHelper pour trouver le point d'arrêt parent:

var element = result.VisualHit; 
while(element != null && !(element is StopPoint)) 
    element = VisualTreeHelper.GetParent(element); 

if(element == null) return; 
3

Je voulais écrire une explication, mais je l'ai déjà trouvé une bonne un:

https://stackoverflow.com/a/7162443/717732

Le point est:

Votre UserControl.HitTestCore() est laissé à l'implémentation par défaut qui renvoie probalement NULL, que cela provoque l'UC doit être ignorée au lieu d'être transmise à resultCallback.

Le comportement par défaut n'est pas un bogue. C'est un design pas évident, intelligent - dans l'ensemble, votre contrôle n'a aucun visuel, il est seulement conteneur pour certains enfants qui ont les formes, donc généralement, il ne sert à rien d'être hittestable et d'encombrer le sentier. Vous pouvez le voir comme une lacune, car la brièveté de votre code pourrait bénéficier de l'UC être hittestable. Cependant, pas la brièveté est le but ici - c'est la vitesse. En fait, c'est une caractéristique importante, car elle réduit vraiment la quantité d'éléments sur lesquels le treewalker doit effectuer les intersections réelles! Donc - soit implémenter HitTestCore et retourner quelque chose de non-null, ou hittest pour les enfants de l'UserControl à la place, et puis quand un résultat correct mais égal à son enfant, utilisez le VisualTreeHelper.GetParent jusqu'à ce que vous marchiez jusqu'à UserControl voulait.