WPF 的 Adorner(裝飾器) 是一種強大的視覺層擴充機制:不改變原控件視覺樹結構,即可在控件上方疊加任意視覺元素(如調整大小手柄、驗證錯誤圖示、拖曳預覽等)。本文深入剖析 AdornerLayer、Adorner 運作原理,並實作自訂遮罩控件。
AdornerLayer 是承載所有 Adorner 的容器,位於視覺樹中被裝飾控件的上層。其獲取方式:
var layer = AdornerLayer.GetAdornerLayer(targetVisual);
內部實現會沿視覺樹向上尋找 AdornerDecorator 或 ScrollContentPresenter(內建於 ScrollViewer、Window 等控件模板中),取得其 AdornerLayer 屬性。若視覺樹中缺乏這類容器(如自訂 Window 模板移除 ScrollViewer),需手動包裹 AdornerDecorator:
<AdornerDecorator>
<TextBox Text="可獲得 AdornerLayer" />
</AdornerDecorator>
透過 Reflector 可見 ScrollContentPresenter.GetVisualChild:
case 0: return base.TemplateChild; // 被裝飾控件
case 1: return this._adornerLayer; // AdornerLayer 永遠是 index 1
WPF 先繪製 index 0 再繪製 index 1,故 Adorner 永遠覆蓋在原控件之上,ZIndex 設定無效。
Adorner,建構子傳入 adornedElement。VisualChildrenCount、GetVisualChild 回傳內部視覺元素(如 TextBlock)。ArrangeOverride 定位內部元素相對於被裝飾控件的位置。public class SimpleTextAdorner : Adorner
{
readonly TextBlock _text = new TextBlock { Foreground = Brushes.Green, Text = "AdornerText" };
public SimpleTextAdorner(UIElement adorned) : base(adorned) { }
protected override int VisualChildrenCount => 1;
protected override Visual GetVisualChild(int index) => _text;
protected override Size ArrangeOverride(Size finalSize)
{
_text.Arrange(new Rect(new Point(-10, 20), _text.DesiredSize));
return base.ArrangeOverride(finalSize);
}
}
var layer = AdornerLayer.GetAdornerLayer(textBlock);
layer.Add(new SimpleTextAdorner(textBlock));
當被裝飾控件 FlowDirection 變更,Adorner 會自動鏡像翻轉。原理在 AdornerLayer.ArrangeOverride 中呼叫 GetDesiredTransform 並套用 AdornerTransform(實際綁定至 RenderTransform),效能優於佈局重算。
AdornerLayer 存在,否則拋 NullReferenceException。解決:外層包裹 AdornerDecorator 或 ScrollViewer。TemplatedAdorner),預設模板寫死於程式碼,可透過 Validation.ErrorTemplate 自訂。場景:資料載入時遮罩特定控件(如 ListView),顯示進度條與百分比,防止使用者操作。
<ListView ItemsSource="{Binding Employees}">
<ctrl:MaskAttach.MaskAttach>
<ctrl:MaskAttach DataContext="{Binding Progress}" Open="{Binding IsLoading}">
<ctrl:MaskAttach.Template>
<DataTemplate>
<Grid>
<Rectangle Fill="Black" Opacity="0.7" Grid.ColumnSpan="2"/>
<TextBlock Grid.Column="1" Foreground="White" Text="{Binding}%"/>
<ProgressBar Value="{Binding}" Height="20" Margin="10"/>
</Grid>
</DataTemplate>
</ctrl:MaskAttach.Template>
</ctrl:MaskAttach>
</ctrl:MaskAttach.MaskAttach>
</ListView>
關鍵技術點:
Dispatcher.BeginInvoke(DispatcherPriority.Render, ...) 中取得 AdornerLayer,確保視覺樹已建立。DependencyPropertyDescriptor 監聽非 FrameworkElement 物件的屬性變更。WPF Decorator 僅能裝飾單一子元素(Border、Viewbox、AdornerDecorator),對應 GoF Decorator 模式:以包裝方式擴充功能,不修改原物件。若需裝飾多元素則使用 Panel。
標準 WPF 拖曳流程:MouseDown 取資料 → DragDrop.DoDragDrop → 目標端 PreviewDrop 取資料 → Drop 處理。跨 ItemsControl 拖曳可參考 Bea Stollnitz 經典實作。
初學 WPF 易陷入「全自繪」誤區,實則善用預設控件組合、樣式、模板、視覺/邏輯樹機制,才是團隊協作與維護的正道。自訂控件應作為最後手段,專注於效能極致或極特殊視覺需求。