背水一戰 Windows 10 之 控制項(自定義控制項): 自定義控制項的 Layout 系統, 自定義控制項的控制項模板和事件處理的相關知識點 ...
背水一戰 Windows 10 (79) - 自定義控制項: Layout 系統, 控制項模板, 事件處理
作者:webabcd
介紹
背水一戰 Windows 10 之 控制項(自定義控制項)
- 自定義控制項的 Layout 系統
- 自定義控制項的控制項模板和事件處理的相關知識點
示例
1、演示自定義控制項的 Layout 系統
/MyControls/MyControl2.cs
/* * 本例通過一個自定義控制項來演示 uwp 中可視元素的 Layout 系統 * * uwp 的 layout 是一個遞歸系統,本 demo 就遞歸的一個過程做說明(步驟順序參見代碼註釋中的序號) * * * Measure() 的作用是測量尺寸 * Arrange() 的作用是排列元素 */ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml; using Windows.Foundation; using System; using System.Linq; using System.Diagnostics; using System.Collections.Generic; namespace MyControls { /// <summary> /// 一個每行都會自動縮進的 Panel /// </summary> public sealed class MyControl2 : Panel { // 相對上一行的縮進值 const double INDENT = 20; public MyControl2() { } // 1、首先爸爸知道自己能夠提供的尺寸 availableSize,然後告訴兒子們 protected override Size MeasureOverride(Size availableSize) // 測量出期待的尺寸並返回 { // 2、兒子們收到 availableSize 後,又結合了自身的實際情況,然後告訴爸爸兒子們所期望的尺寸 desiredSize List<double> widthList = new List<double>(); Size desiredSize = new Size(0, 0); foreach (UIElement child in this.Children) { // 如果 child 是 FrameworkElement 的話,則當調用其 Measure() 方法時會自動調用其 MeasureOverride() 方法 child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); widthList.Add(child.DesiredSize.Width); desiredSize.Height += child.DesiredSize.Height; } if (this.Children.Count > 0) { desiredSize.Width = widthList.Max(); desiredSize.Width += INDENT * (this.Children.Count - 1); } Debug.WriteLine("availableSize: " + availableSize.ToString()); Debug.WriteLine("desiredSize: " + desiredSize.ToString()); return desiredSize; } // 3、爸爸收到兒子們的反饋後,告訴兒子們自己最終提供的尺寸 finalSize protected override Size ArrangeOverride(Size finalSize) // 排列元素,並返回呈現尺寸 { // 4、兒子們根據 finalSize 安排各自的位置,然後爸爸的呈現尺寸也就確定了 renderSize Point childPosition = new Point(0, 0); foreach (UIElement child in this.Children) { // 如果 child 是 FrameworkElement 的話,則當調用其 Arrange() 方法時會自動調用其 ArrangeOverride() 方法 child.Arrange(new Rect(childPosition, new Size(child.DesiredSize.Width, child.DesiredSize.Height))); childPosition.X += INDENT; childPosition.Y += child.DesiredSize.Height; } Size renderSize = new Size(0, 0); renderSize.Width = finalSize.Width; renderSize.Height = childPosition.Y; Debug.WriteLine("finalSize: " + finalSize.ToString()); Debug.WriteLine("renderSize: " + renderSize.ToString()); return finalSize; } } } /* * 輸出結果如下(運行 /Controls/CustomControl/Demo2.xaml 示例) * availableSize: 800,Double.PositiveInfinity * desiredSize: 141,120 * finalSize: 800,120 * renderSize: 800,120 */ /* * 註: * UIElement * 調用 Measure() 方法後會更新 DesiredSize 屬性 * 調用 Arrange() 方法後會更新 RenderSize 屬性 * UpdateLayout() - 強制 layout 遞歸更新 * * FrameworkElement - 繼承自 UIElement * MeasureOverride() - 在 Measure() 中自動調用 * ArrangeOverride() - 在 Arrange() 中自動調用 * ActualWidth 和 ActualHeight 來自 RenderSize,每次 UpdateLayout() 後都會被更新 */ /* * 註: * 1、uwp 的 layout 是一個遞歸系統 * 2、UIElement 的 InvalidateMeasure() 就是遞歸調用自己和子輩門的 Measure() * 3、UIElement 的 InvalidateArrange() 就是遞歸調用自己和子輩門的 Arrange() * * 一個通過 uwp 自帶控制項說明 layout 的示例,請參見:/Controls/BaseControl/UIElementDemo/LayoutDemo.xaml.cs */
Controls/CustomControl/Demo2.xaml
<Page x:Class="Windows10.Controls.CustomControl.Demo2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Windows10.Controls.CustomControl" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:myControls="using:MyControls"> <Grid Background="Transparent"> <StackPanel Margin="10 0 10 10"> <!-- 演示元素的 Layout 系統 本例所用到的自定義控制項請參看:MyControls/MyControl2.cs --> <myControls:MyControl2 Margin="5" Background="Orange" HorizontalAlignment="Left" Width="800"> <myControls:MyControl2.Children> <TextBlock Text="aaaaaaaa" Margin="5" /> <TextBlock Text="bbbbbbbb" Margin="5" /> <TextBlock Text="cccccccc" Margin="5" /> <TextBlock Text="dddddddd" Margin="5" /> </myControls:MyControl2.Children> </myControls:MyControl2> </StackPanel> </Grid> </Page>
Controls/CustomControl/Demo2.xaml.cs
/* * 本例用於演示元素的 Layout 系統 */ using Windows.UI.Xaml.Controls; namespace Windows10.Controls.CustomControl { public sealed partial class Demo2 : Page { public Demo2() { this.InitializeComponent(); } } }
2、演示自定義控制項的控制項模板和事件處理的相關知識點
/MyControls/themes/MyControl3.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:MyControls"> <Style TargetType="local:MyControl3"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:MyControl3"> <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <StackPanel> <TextBlock Name="textBlock" Foreground="White" FontSize="24" /> </StackPanel> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="PointerOver"> <Storyboard> <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="Green" /> </Storyboard> </VisualState> <VisualStateGroup.Transitions> <VisualTransition To="PointerOver" GeneratedDuration="0:0:1"> <VisualTransition.GeneratedEasingFunction> <ElasticEase EasingMode="EaseInOut" /> </VisualTransition.GeneratedEasingFunction> </VisualTransition> </VisualStateGroup.Transitions> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
/MyControls/MyControl3.cs
/* * 開發一個自定義控制項,用於演示控制項模板和事件處理的相關知識點 */ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Input; namespace MyControls { /// <summary> /// 自定義控制項 /// </summary> public sealed class MyControl3 : Control { public MyControl3() { this.DefaultStyleKey = typeof(MyControl3); } // ApplyTemplate() - 強制載入控制項模板,一般不用調用(因為控制項模板會自動載入)。有一種使用場景是:當父控制項應用控制項模板時要求子控制項必須先應用控制項模板以便父控制項使用時,則可以先調用子控制項的此方法 // GetTemplateChild() - 查找控制項模板中的指定名字的元素 // override OnApplyTemplate() - 應用控制項模板時調用 protected override void OnApplyTemplate() { base.OnApplyTemplate(); TextBlock textBlock = (TextBlock)GetTemplateChild("textBlock"); if (this.Background is SolidColorBrush) { textBlock.Text = $"background: {((SolidColorBrush)this.Background).Color}"; } VisualStateManager.GoToState(this, "Normal", false); } // override GoToElementStateCore() - VisualState 轉換時調用(此方法僅在自定義 ContentPresenter 並將其應用於 GridView 或 ListView 的 ItemContainerStyle 時才會被調用) // 參見:/Controls/CollectionControl/ItemsControlDemo/MyItemPresenter.cs protected override bool GoToElementStateCore(string stateName, bool useTransitions) { return base.GoToElementStateCore(stateName, useTransitions); } // 在 Control 中有很多可 override 的事件處理方法,詳見文檔 protected override void OnPointerEntered(PointerRoutedEventArgs e) { VisualStateManager.GoToState(this, "PointerOver", true); } protected override void OnPointerExited(PointerRoutedEventArgs e) { VisualStateManager.GoToState(this, "Normal", false); } } }
Controls/CustomControl/Demo3.xaml
<Page x:Class="Windows10.Controls.CustomControl.Demo3" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Windows10.Controls.CustomControl" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:myControls="using:MyControls"> <Grid Background="Transparent"> <StackPanel Margin="10 0 10 10"> <!-- 演示自定義控制項的控制項模板和事件處理的相關知識點 本例所用到的自定義控制項請參看:MyControls/MyControl3.cs --> <myControls:MyControl3 Background="Blue" BorderBrush="Yellow" BorderThickness="1" HorizontalAlignment="Left" Margin="5" /> </StackPanel> </Grid> </Page>
Controls/CustomControl/Demo3.xaml.cs
/* * 本例用於演示自定義控制項的控制項模板和事件處理的相關知識點 */ using Windows.UI.Xaml.Controls; namespace Windows10.Controls.CustomControl { public sealed partial class Demo3 : Page { public Demo3() { this.InitializeComponent(); } } }
OK
[源碼下載]