背水一战 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
[源码下载]