65 votes

Dans WPF, comment puis-je déterminer si un contrôle est visible pour l'utilisateur ?

J'affiche un très grand arbre avec beaucoup d'éléments. Chacun de ces éléments affiche des informations à l'utilisateur par le biais du contrôle UserControl qui lui est associé, et ces informations doivent être mises à jour toutes les 250 millisecondes, ce qui peut être une tâche très coûteuse puisque j'utilise également la réflexion pour accéder à certaines de leurs valeurs. Ma première approche consistait à utiliser la propriété IsVisible, mais cela ne fonctionne pas comme prévu.

Existe-t-il un moyen de déterminer si un contrôle est "visible" pour l'utilisateur ?

Remarque : j'utilise déjà la propriété IsExpanded pour ignorer la mise à jour des nœuds effondrés, mais certains nœuds comportent plus de 100 éléments et je ne trouve pas de moyen d'ignorer ceux qui sont en dehors de la fenêtre de la grille.

6 votes

J'ai eu un jour un problème similaire. Après avoir écrit du code pour détecter si un contrôle est visible, il s'est avéré que le code de détection était plus lent que la mise à jour effective du contrôle caché. Comparez vos résultats, car il se peut que cela n'en vaille pas la peine.

87voto

Julien Lebosquain Points 20894

Vous pouvez utiliser cette petite fonction d'aide que je viens d'écrire et qui vérifiera si un élément est visible pour l'utilisateur, dans un conteneur donné. La fonction renvoie true si l'élément est partiellement visible. Si vous voulez vérifier s'il est entièrement visible, remplacez la dernière ligne par rect.Contains(bounds) .

private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
    if (!element.IsVisible)
        return false;

    Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
    Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
    return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}

Dans votre cas, element sera votre contrôle d'utilisateur, et container votre fenêtre.

26 votes

Cela ne tient pas compte du cas où l'élément dépasse la taille du conteneur. Renvoyer rect.IntersectsWith(bounds) à la place corrigera ce problème.

5 votes

Avec un grand nombre de données, vous souhaitez généralement utiliser la virtualisation de l'interface utilisateur. Pour cela, vous ne définissez pas vos éléments directement (c.-à-d. ItemsContro.Items.Add(new ...) ) mais plutôt utiliser la liaison de données. Cependant, la liaison de données rompra la hiérarchie visuelle car les enfants ajoutés à votre objet de données (par ex. ObservableList ) n'aura pas de parent. TransformToAncestor (ou TransformToVisual Que devons-nous faire dans ce cas ?

19voto

Andreas Points 468
public static bool IsUserVisible(this UIElement element)
{
    if (!element.IsVisible)
        return false;
    var container = VisualTreeHelper.GetParent(element) as FrameworkElement;
    if (container == null) throw new ArgumentNullException("container");

    Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.RenderSize.Width, element.RenderSize.Height));
    Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
    return rect.IntersectsWith(bounds);
}

0 votes

Cela explique-t-il que l'élément ne soit pas visible parce que la fenêtre est réduite ou cachée derrière d'autres fenêtres ?

7voto

Ofer Barasofsky Points 131

La réponse acceptée (et les autres réponses sur cette page) résout le problème spécifique que l'affiche originale avait mais ne donne pas une réponse adéquate à la question écrite dans le titre, c'est à dire, Comment déterminer si un contrôle est visible pour l'utilisateur ? . Le problème est que Un contrôle couvert par d'autres contrôles n'est pas visible. même s'il peut être rendu et qu'il est dans les limites de son conteneur, ce qui est ce que les autres réponses résolvent.

Pour déterminer si un contrôle est visible pour l'utilisateur, il faut parfois pouvoir déterminer si un UIElement WPF est cliquable (ou atteignable par la souris sur un PC) par l'utilisateur

J'ai rencontré ce problème lorsque j'ai essayé de vérifier si un bouton peut être cliqué par la souris de l'utilisateur. Un scénario particulier qui m'a gêné est qu'un bouton peut être visible pour l'utilisateur mais recouvert d'une couche transparente (ou semi transparente ou non transparente) qui empêche les clics de souris. Dans ce cas, un contrôle peut être visible pour l'utilisateur mais non accessible pour lui, ce qui revient à dire qu'il n'est pas visible du tout.

J'ai donc dû trouver ma propre solution.

EDIT - Mon message original proposait une solution différente qui utilisait la méthode InputHitTest. Cependant, elle ne fonctionnait pas dans de nombreux cas et j'ai dû la remanier. Cette solution est beaucoup plus robuste et semble fonctionner très bien sans aucun faux négatif ou positif.

Solution :

  1. Obtenir la position absolue de l'objet par rapport à la fenêtre principale de l'application
  2. Appelez VisualTreeHelper.HitTest sur tous ses coins (en haut à gauche, en bas à gauche, en haut à droite, en bas à droite)
  3. On appelle un objet Entièrement cliquable si l'objet obtenu à partir de VisualTreeHelper.HitTest égale l'objet original ou un parent visuel de celui-ci pour tous ses angles, et Partiellement cliquable pour un ou plusieurs coins.

Remarque 1 : Les définitions de " entièrement cliquable " et " partiellement cliquable " ne sont pas exactes. cliquable ne sont pas exactes - nous vérifions simplement que les quatre coins de l'objet sont d'un objet sont cliquables. Si, par exemple, un bouton possède 4 coins cliquables mais que son mais que son centre comporte un point qui n'est pas cliquable, nous le considérerons toujours comme Entièrement cliquable. Vérifier tous les points d'un objet donné serait un gaspillage. inutile.

Note 2 : il est parfois nécessaire de définir un objet. IsHitTestVisible à la propriété vrai (cependant, il s'agit de la valeur par défaut pour de nombreux contrôles courants). courants) si nous souhaitons VisualTreeHelper.HitTest pour le trouver

    private bool isElementClickable<T>(UIElement container, UIElement element, out bool isPartiallyClickable)
    {
        isPartiallyClickable = false;
        Rect pos = GetAbsolutePlacement((FrameworkElement)container, (FrameworkElement)element);
        bool isTopLeftClickable = GetIsPointClickable<T>(container, element, new Point(pos.TopLeft.X + 1,pos.TopLeft.Y+1));
        bool isBottomLeftClickable = GetIsPointClickable<T>(container, element, new Point(pos.BottomLeft.X + 1, pos.BottomLeft.Y - 1));
        bool isTopRightClickable = GetIsPointClickable<T>(container, element, new Point(pos.TopRight.X - 1, pos.TopRight.Y + 1));
        bool isBottomRightClickable = GetIsPointClickable<T>(container, element, new Point(pos.BottomRight.X - 1, pos.BottomRight.Y - 1));

        if (isTopLeftClickable || isBottomLeftClickable || isTopRightClickable || isBottomRightClickable)
        {
            isPartiallyClickable = true;
        }

        return isTopLeftClickable && isBottomLeftClickable && isTopRightClickable && isBottomRightClickable; // return if element is fully clickable
    }

    private bool GetIsPointClickable<T>(UIElement container, UIElement element, Point p) 
    {
        DependencyObject hitTestResult = HitTest< T>(p, container);
        if (null != hitTestResult)
        {
            return isElementChildOfElement(element, hitTestResult);
        }
        return false;
    }               

    private DependencyObject HitTest<T>(Point p, UIElement container)
    {                       
        PointHitTestParameters parameter = new PointHitTestParameters(p);
        DependencyObject hitTestResult = null;

        HitTestResultCallback resultCallback = (result) =>
        {
           UIElement elemCandidateResult = result.VisualHit as UIElement;
            // result can be collapsed! Even though documentation indicates otherwise
            if (null != elemCandidateResult && elemCandidateResult.Visibility == Visibility.Visible) 
            {
                hitTestResult = result.VisualHit;
                return HitTestResultBehavior.Stop;
            }

            return HitTestResultBehavior.Continue;
        };

        HitTestFilterCallback filterCallBack = (potentialHitTestTarget) =>
        {
            if (potentialHitTestTarget is T)
            {
                hitTestResult = potentialHitTestTarget;
                return HitTestFilterBehavior.Stop;
            }

            return HitTestFilterBehavior.Continue;
        };

        VisualTreeHelper.HitTest(container, filterCallBack, resultCallback, parameter);
        return hitTestResult;
    }         

    private bool isElementChildOfElement(DependencyObject child, DependencyObject parent)
    {
        if (child.GetHashCode() == parent.GetHashCode())
            return true;
        IEnumerable<DependencyObject> elemList = FindVisualChildren<DependencyObject>((DependencyObject)parent);
        foreach (DependencyObject obj in elemList)
        {
            if (obj.GetHashCode() == child.GetHashCode())
                return true;
        }
        return false;
    }

    private IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
    {
        if (depObj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                if (child != null && child is T)
                {
                    yield return (T)child;
                }

                foreach (T childOfChild in FindVisualChildren<T>(child))
                {
                    yield return childOfChild;
                }
            }
        }
    }

    private Rect GetAbsolutePlacement(FrameworkElement container, FrameworkElement element, bool relativeToScreen = false)
    {
        var absolutePos = element.PointToScreen(new System.Windows.Point(0, 0));
        if (relativeToScreen)
        {
            return new Rect(absolutePos.X, absolutePos.Y, element.ActualWidth, element.ActualHeight);
       }
        var posMW = container.PointToScreen(new System.Windows.Point(0, 0));
        absolutePos = new System.Windows.Point(absolutePos.X - posMW.X, absolutePos.Y - posMW.Y);
        return new Rect(absolutePos.X, absolutePos.Y, element.ActualWidth, element.ActualHeight);
   }

Il suffit alors d'appeler pour savoir si un bouton (par exemple) est cliquable :

 if (isElementClickable<Button>(Application.Current.MainWindow, myButton, out isPartiallyClickable))
 {
      // Whatever
 }

5voto

Timoi Points 29

Utilisez ces propriétés pour le contrôle contenant :

VirtualizingStackPanel.IsVirtualizing="True" 
VirtualizingStackPanel.VirtualizationMode="Recycling"

et ensuite connecter l'écoute des abonnés INotifyPropertyChanged.PropertyChanged de votre élément de données comme suit

    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            Console.WriteLine(
               "WPF is listening my property changes so I must be visible");
        }
        remove
        {
            Console.WriteLine("WPF unsubscribed so I must be out of sight");
        }
    }

Pour des informations plus détaillées, voir : http://joew.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DWPF

2 votes

L'événement Initialized est beaucoup plus approprié que cela. Notez que la virtualisation peut initialiser et câbler votre objet bien avant qu'il ne soit visible, donc de toute façon, cette méthode ne garantit pas que votre objet soit visible.

1 votes

Le lien ci-dessus est cassé. Pouvez-vous le remplacer par un autre ? Merci !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X