自訂控制項、複雜佈局、座標轉換、視覺樹搜尋,都是進階開發的必修課。本篇深入解析 WinRT 佈局系統的 Measure/Arrange 雙階段遞迴機制、GeneralTransform 跨元素座標映射、VisualTreeHelper 視覺樹遍歷與搜尋技巧,並提供可直接參考的自訂 Panel 範例。
availableSize,子元素回報期望尺寸 desiredSize(更新 DesiredSize 屬性)。finalSize,子元素擺放位置(更新 RenderSize、ActualWidth/Height)。public class MyStackPanel : StackPanel
{
// 1. 父傳遞 availableSize 給子元素測量
protected override Size MeasureOverride(Size availableSize)
{
Size desiredSize = base.MeasureOverride(availableSize);
Debug.WriteLine("availableSize: " + availableSize);
Debug.WriteLine("desiredSize: " + desiredSize);
return desiredSize;
/* 自訂邏輯參考:
Size childrenSize = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
childrenSize.Width += child.DesiredSize.Width;
childrenSize.Height += child.DesiredSize.Height;
}
return childrenSize; */
}
// 2. 父決定 finalSize,子元素安排位置
protected override Size ArrangeOverride(Size finalSize)
{
Size renderSize = base.ArrangeOverride(finalSize);
Debug.WriteLine("finalSize: " + finalSize);
Debug.WriteLine("renderSize: " + renderSize);
return renderSize;
/* 自訂邏輯參考:
Point childPos = new Point(0, 0);
foreach (UIElement child in Children)
{
child.Arrange(new Rect(childPos, new Size(child.DesiredSize.Width, child.DesiredSize.Height)));
childPos.Y += child.RenderSize.Height;
}
return finalSize; */
}
}
availableSize: 200,200 // 父給的空間
desiredSize: 150,250 // 子元素想要的空間(超過高度)
finalSize: 200,250 // 父妥協給的最終空間
renderSize: 200,250 // 實際呈現尺寸
| 成員 | 所屬類別 | 說明 |
|---|---|---|
Measure(Size) | UIElement | 觸發測量,更新 DesiredSize |
Arrange(Rect) | UIElement | 觸發排版,更新 RenderSize |
MeasureOverride | FrameworkElement | 自訂測量邏輯 |
ArrangeOverride | FrameworkElement | 自訂排版邏輯 |
UpdateLayout() | UIElement | 強制立即重跑佈局遞迴 |
UIElement.TransformToVisual(UIElement visual) 回傳 GeneralTransform,可將座標從來源元素空間轉換到目標元素空間(或螢幕空間)。
// 1. 元素相對螢幕絕對位置
GeneralTransform gt = element.TransformToVisual(null); // null = 螢幕根座標
Point screenPos = gt.TransformPoint(new Point(0, 0));
Rect screenRect = new Rect(screenPos, new Size(element.ActualWidth, element.ActualHeight));
// 2. 元素 A 相對元素 B 的位置
GeneralTransform gt = elementA.TransformToVisual(elementB);
Point relativePos = gt.TransformPoint(new Point(0, 0));
| 方法 | 用途 |
|---|---|
GetChildrenCount(DependencyObject) | 取得直系子元素數量 |
GetChild(DependencyObject, int index) | 取得指定索引子元素 |
GetParent(DependencyObject) | 取得父元素 |
FindElementsInHostCoordinates(Point, UIElement, bool) | 點擊測試:取得指定點包含的所有元素(含 ControlTemplate 內部) |
FindElementsInHostCoordinates(Rect, UIElement, bool) | 區域測試:取得矩形區域內所有元素 |
private T GetVisualChild<T>(DependencyObject parent, string name) where T : FrameworkElement
{
T child = default(T);
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
DependencyObject obj = VisualTreeHelper.GetChild(parent, i);
child = obj as T;
if (child == null || child.Name != name)
child = GetVisualChild<T>(obj, name); // 遞迴深入
if (child != null) break;
}
return child;
}
// 用法:在 ScrollViewer Template 中找 VerticalScrollBar
ScrollBar scrollBar = GetVisualChild<ScrollBar>(scrollViewer, "VerticalScrollBar");
void container_Tapped(object sender, TappedRoutedEventArgs e)
{
// 點擊點包含的所有元素(含 Template 內部)
var elements = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), container, true);
foreach (var el in elements) Debug.WriteLine(el.GetType().Name);
// 100x100 區域內的所有元素
var elementsRect = VisualTreeHelper.FindElementsInHostCoordinates(
new Rect(e.GetPosition(null), new Size(100, 100)), container, true);
}