接受的答案(以及此页面上的其他答案)解决了原始发布者遇到的具体问题,但他们没有对标题中写的问题给出充分的答案,即如何确定是否控件对用户可见。问题是 一个被其他控件覆盖的控件是不可见的,即使它可以呈现并且它位于其容器的边界内,这是其他答案正在解决的问题。
要确定控件是否对用户可见,您有时必须能够确定 WPF UIElement 是否可由用户点击(或在 PC 上鼠标可到达)
我在尝试检查按钮是否可以被用户单击鼠标时遇到了这个问题。困扰我的一个特殊情况是,一个按钮实际上对用户可见,但覆盖有一些透明(或半透明或完全不透明)层,以防止鼠标点击。在这种情况下,控件可能对用户可见但用户无法访问,这有点像它根本不可见。
所以我必须想出自己的解决方案。
编辑 - 我原来的帖子有一个使用 InputHitTest 方法的不同解决方案。然而,它在很多情况下都不起作用,我不得不重新设计它。这个解决方案更加健壮,并且似乎运行良好,没有任何假阴性或阳性。
解决方案:
- 获取对象相对于应用程序主窗口的绝对位置
- 请在所有角落(左上、左下、右上、右下)致电
VisualTreeHelper.HitTest
- 如果从
VisualTreeHelper.HitTest 获得的对象在其所有角上都等于原始对象或它的可视父对象,则我们将对象称为完全可点击,并且对于部分可点击一个或多个角。
请注意#1:完全可点击或部分可点击的定义
可点击并不准确 - 我们只是检查了所有四个角
对象是可点击的。例如,如果一个按钮有 4 个可点击的角,但它是
center 有一个不可点击的点,我们仍将其视为
完全可点击。检查给定对象中的所有点也是
浪费。
请注意#2:有时需要设置对象IsHitTestVisible
属性为 true (然而,这是许多常见的默认值
控件)如果我们希望VisualTreeHelper.HitTest 找到它
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);
}
那么,要确定一个按钮(例如)是否可点击,只需调用:
if (isElementClickable<Button>(Application.Current.MainWindow, myButton, out isPartiallyClickable))
{
// Whatever
}