在項目中經常會遇到類似如下要求的需求,創建允許自由拖動的控制項,這樣的需求可以使用WPF的裝飾器Adorner來實現。 一、什麼是裝飾器? 裝飾器是一種特殊類型的FrameworkElement,裝飾器始終呈現在被裝飾元素的頂部,用於向用戶提供可視化提示。裝飾器可以在不改變原有控制項結構的基礎上,將功能 ...
在項目中經常會遇到類似如下要求的需求,創建允許自由拖動的控制項,這樣的需求可以使用WPF的裝飾器Adorner來實現。
一、什麼是裝飾器?
裝飾器是一種特殊類型的FrameworkElement,裝飾器始終呈現在被裝飾元素的頂部,用於向用戶提供可視化提示。裝飾器可以在不改變原有控制項結構的基礎上,將功能點增加到元素中或元素上提供視覺效果等,如WPF的游標效果,焦點效果等都是通過裝飾器來實現的。 裝飾器是一個始終位於裝飾元素或裝飾元素集合頂部的呈現圖層,其呈現獨立與它所綁定的UIElement,WPF中的裝飾器是在一個單獨的曾AnornerLayer上進行繪製的,該層位於普通控制項元素之上,而且允許多個AdornerLayer進行疊加,當加入AdornerLayer層後,Adorner會預設使用其所裝飾元素的左上角作為原點進行定位。- Adorner 是一個抽象類,所有裝飾器的實現都需要繼承此類,比如ThumbBorderAdorner
- AdornerLayer 一個類,表示一個或多個裝飾元素的裝飾器呈現層
- 利用AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(userControl)函數,來獲取指定控制項是否有裝飾器佈局層
- 利用Adorner[] adorners = adornerLayer.GetAdorners(userControl);,來查看當前控制項的裝飾器個數
- AdornerDecorator 一個類,為可視化樹中的子元素提供AdornerLayer
二、裝飾器的使用場景
- 為現有的元素添加額外的裝飾,如為Border添加8個裝飾矩形
三、如何創建自定義的裝飾器?
- 創建一個類,繼承自Adorner類
- 重寫此類中需要的函數
- OnRender(DrawingContext drawingContext) 在派生類中重寫,參與由佈局系統控制的呈現操作,調用此方法時,不直接使用此元素的呈現指令,而是將其保留供佈局和繪製在以後非同步使用,可以使用drawingContext 來繪製各種形狀以及圖形。
- ArrangeOverride() 為FrameworkElement派生類定位子元素並確定大小,在其中調用Arrange()函數,來定位子元素
- GetVisualChild() //獲取第幾個Thumb控制項,在構造時使用
- 簡單的裝飾可以重寫OnRender()函數,在其中繪製所需要的裝飾,參照BorderAdorner
- 複雜一些的如需要可以定義VisualCollection的集合來存放裝飾器,重寫ArrangeOverride和GetVisualChild函數來實現,參照ThumbBorderAdorner
四、給控制項使用自定義的Adorner
- 添加Adorner
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(userControl); if (adornerLayer != null) { Adorner[] adorners = adornerLayer.GetAdorners(userControl); if (adorners == null || adorners.Count() == 0) { adornerLayer.Add(new ThumbBorderAdorner(userControl) { DragCompletedAction = ThumbBorderAdornerDragCompletedActionFunc }); } }
- 移除 Adorner
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(userControl); if (adornerLayer != null) { Adorner[] adorners = adornerLayer.GetAdorners(userControl); if (adorners != null && adorners.Count() > 0) adornerLayer.Remove(adorners[0]); }
五、處理拖拽Thumb時,導致控制項範圍超出父級容器的情況
- 即在添加裝飾器的控制項中添加UserControl_SizeChanged()來處理控制項大小和和未知變化
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) { int tempMargin = 1; //MarkRectUserControl控制項始終在父級容器的1個像素以內 double left = Canvas.GetLeft(this); if (left < tempMargin) { left = tempMargin; Canvas.SetLeft(this, left); } double top = Canvas.GetTop(this); if (top < tempMargin) { top = tempMargin; Canvas.SetTop(this, top); } if (left + this.ActualWidth > canvasParent.ActualWidth - tempMargin) this.Width = canvasParent.ActualWidth - tempMargin - left; if (top + this.ActualHeight > canvasParent.ActualHeight - tempMargin) this.Height = canvasParent.ActualHeight - tempMargin - top; }
六、測試使用
<UserControl x:Class="BlogDemo.Views.AdornerTestUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:BlogDemo.Views" mc:Ignorable="d" Background="White" d:DesignHeight="450" d:DesignWidth="800" FontSize="20" Foreground="Blue" Loaded="UserControl_Loaded" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Text="請拖拽Thumb控制項來改變,控制項大小。" HorizontalAlignment="Center" Margin="10"/> <Canvas x:Name="Canvas_Main" Grid.Row="1"> <Border x:Name="Border_Text" Canvas.Left="200" Canvas.Top="100" Width="200" Height="100" BorderThickness="2" BorderBrush="Green" SizeChanged="Border_Text_SizeChanged"/> </Canvas> </Grid> </UserControl>
/// <summary> /// AdornerTestUserControl.xaml 的交互邏輯 /// </summary> public partial class AdornerTestUserControl : UserControl { public AdornerTestUserControl() { InitializeComponent(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(Border_Text); if (adornerLayer != null) { Adorner[] adorners = adornerLayer.GetAdorners(Border_Text); if (adorners == null || adorners.Count() == 0) { adornerLayer.Add(new ThumbBorderAdorner(Border_Text)); } } } private void Border_Text_SizeChanged(object sender, SizeChangedEventArgs e) { int tempMargin = 100; double left = Canvas.GetLeft(Border_Text); if (left < tempMargin) { left = tempMargin; Canvas.SetLeft(Border_Text, left); } double top = Canvas.GetTop(Border_Text); if (top < tempMargin) { top = tempMargin; Canvas.SetTop(Border_Text, top); } if (Border_Text.ActualWidth < tempMargin) Border_Text.Width = tempMargin; if (Border_Text.ActualHeight < tempMargin) Border_Text.Height = tempMargin; if (left + Border_Text.ActualWidth > Canvas_Main.ActualWidth - tempMargin) Border_Text.Width = Canvas_Main.ActualWidth - tempMargin - left; if (top + Border_Text.ActualHeight > Canvas_Main.ActualHeight - tempMargin) Border_Text.Height = Canvas_Main.ActualHeight - tempMargin - top; } }
源碼地址:https://gitee.com/LiuShuiRuoBing/code_blog