我正在显示一个非常大的树,其中有很多项。每个项目通过其关联的UserControl控件向用户显示信息,并且这些信息必须每250毫秒更新一次,这可能是一项非常昂贵的任务,因为我还要使用反射来访问其中一些值。我的第一种方法是使用IsVisible属性,但它并不像我预期的那样工作。
有没有办法确定控件对用户是否“可见”?
注意:我已经使用IsExpanded属性跳过更新折叠节点,但有些节点有100多个元素,无法找到跳过网格视口之外元素的方法。
我正在显示一个非常大的树,其中有很多项。每个项目通过其关联的UserControl控件向用户显示信息,并且这些信息必须每250毫秒更新一次,这可能是一项非常昂贵的任务,因为我还要使用反射来访问其中一些值。我的第一种方法是使用IsVisible属性,但它并不像我预期的那样工作。
有没有办法确定控件对用户是否“可见”?
注意:我已经使用IsExpanded属性跳过更新折叠节点,但有些节点有100多个元素,无法找到跳过网格视口之外元素的方法。
true
。如果要检查元素是否完全可见,请将最后一行替换为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);
}
在你的情况下,element
将是你的用户控件,而container
则是你的窗口。ItemsContro.Items.Add(new...)
),而是使用数据绑定。
然而,数据绑定会破坏可视化层次结构,因为添加到数据对象(例如ObservableList
)中的子元素将没有父级。 TransformToAncestor
(或TransformToVisual
)将无法正常工作。在这种情况下,我们该怎么办呢?! - Shakaronpublic 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);
}
VisualTreeHelper.HitTest
VisualTreeHelper.HitTest
获得的对象等于原始对象或其视觉父对象,我们将称之为完全可点击的对象,对于一个或多个角落而言, 称之为部分可点击。请注意#1:此处关于“完全可点击”或“部分可点击”的定义并不精确-我们只检查一个对象的所有四个角是否可点击。例如,如果一个按钮有4个可点击的角落,但其中心有一个不可点击的点,我们仍将把它视为“完全可点击”。要检查给定对象中的所有点将太浪费时间。
请注意#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
}
使用以下属性控制容器:
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
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");
}
}
更详细的信息请参见: http://joew.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DWPF
(此链接为相关的IT技术内容,如需了解详情,请点击链接)