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 :
- Obtenir la position absolue de l'objet par rapport à la fenêtre principale de l'application
- Appelez
VisualTreeHelper.HitTest
sur tous ses coins (en haut à gauche, en bas à gauche, en haut à droite, en bas à droite)
- 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
}
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.