Implement the Expander control in UWP, uwpexpander
The Expander control in WPF is not provided in the Windows 10 SDK. This article mainly describes how to create such a control in UWP. The effect is as follows:
Public static readonly DependencyProperty ContentProperty = DependencyProperty. register ("Content", typeof (object), typeof (Expander), new PropertyMetadata (null); public static readonly DependencyProperty HeaderProperty = DependencyProperty. register ("Header", typeof (object), typeof (Expander), new PropertyMetadata (null); public static readonly DependencyProperty IsExpandProperty = DependencyProperty. register ("IsExpand", typeof (bool), typeof (Expander), new PropertyMetadata (true )); /// <summary> /// control Content /// </summary> public object Content {get {return (object) GetValue (ContentProperty );} set {SetValue (ContentProperty, value) ;}/// <summary> /// control title /// </summary> public object Header {get {return (object) getValue (HeaderProperty) ;}set {SetValue (HeaderProperty, value );}} /// <summary> /// return or set whether the control is expanded // </summary> public bool IsExpand {get {return (bool) GetValue (IsExpandProperty );} set {SetValue (IsExpandProperty, value );}}
2. Define the UI
In Generic. xaml, locate the <Style TargetType = "controls: Expander"> node and add the following code:
<Style TargetType="controls:Expander"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="controls:Expander"> <Grid x:Name="grid" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <ToggleButton x:Name="toggleButton" Width="32" Height="32" Margin="0,0,4,0" BorderThickness="0" IsChecked="{Binding IsExpand, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"> <Path x:Name="arrow" Width="16" Height="16" Data="M15.289001,0L20.484007,0 31.650999,15.953003 29.055021,19.658005 20.415007,32 15.35501,32 15.289001,31.906998 24.621,18.572998 0,18.572998 0,13.326004 24.621,13.326004z" Fill="#DDFFFFFF" RenderTransformOrigin="0.5,0.5" Stretch="Uniform"> </Path> </ToggleButton> <ContentPresenter VerticalAlignment="Center" Content="{TemplateBinding Header}" /> </StackPanel> <ContentPresenter Grid.Row="1" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" Content="{TemplateBinding Content}" Visibility="{Binding IsExpand, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}, Mode=TwoWay}" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
We can see that:
A) The IsChecked attribute of ToggleButton is bound to the IsExpand attribute of the control. The Binding expression {Binding IsExpand, RelativeSource = {RelativeSource Mode = TemplatedParent} is another method of {TemplateBinding IsExpand, in this write method, we can add other attributes of the Binding object, such as Mode = TwoWay, to implement mutual control between ToggleButton and IsExpand attributes of the control;
B) the Visibility of ContentControl is the same as that of a. a Converter is used to convert Bool and Visibility enumeration;
C) We set a Path for the Content attribute of the ToggleButton control to visually express the current status of Expander.
3. Define VisualState
We define two visualstates for the control, which represent the Normal state and the Expanded state, that is, Normal and Expanded. By switching these two states, we can complete the UI changes of the control, here we mainly set the Content of ToggleButton to be animated.
Add RotateTransform in Path. The Code is as follows:
<Path x:Name="arrow" Width="16" Height="16" Data="M15.289001,0L20.484007,0 31.650999,15.953003 29.055021,19.658005 20.415007,32 15.35501,32 15.289001,31.906998 24.621,18.572998 0,18.572998 0,13.326004 24.621,13.326004z" Fill="#DDFFFFFF" RenderTransformOrigin="0.5,0.5" Stretch="Uniform"> <Path.RenderTransform> <RotateTransform x:Name="pathRotate" /> </Path.RenderTransform> </Path>
Add the VisualState to the Grid. The Code is as follows:
<Grid x:Name="grid" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualStateGroup.Transitions> <VisualTransition From="Normal" GeneratedDuration="0:0:0.2" To="Expanded" /> <VisualTransition From="Expanded" GeneratedDuration="0:0:0.2" To="Normal" /> </VisualStateGroup.Transitions> <VisualState x:Name="Normal"> <Storyboard> <DoubleAnimation Duration="0:0:0" Storyboard.TargetName="pathRotate" Storyboard.TargetProperty="Angle" To="0"> <DoubleAnimation.EasingFunction> <QuinticEase EasingMode="EaseOut" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </VisualState> <VisualState x:Name="Expanded"> <Storyboard> <DoubleAnimation Duration="0:0:0" Storyboard.TargetName="pathRotate" Storyboard.TargetProperty="Angle" To="90"> <DoubleAnimation.EasingFunction> <QuinticEase EasingMode="EaseIn" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> ...
Here, we can see that in addition to two visualstates, we also define two VisualTransition to set the excessive time for switching between these two States.
Tip: You can also control the hiding and display of the Content area by adding an animation to the VisualState. However, in the code above, we use ToggleButton and its IsCheced attribute to control its display and hide. This function is more concise.
Next, we need to control when to switch between the two States in the code, and add the following code in Expander. cs:
Private ToggleButton button; protected override void OnApplyTemplate () {base. onApplyTemplate (); button = GetTemplateChild ("toggleButton") as ToggleButton; button. loaded + = (s, e) =>{ ChangeControlState (false) ;}; button. checked + = (s, e) =>{ ChangeControlState () ;}; button. unchecked + = (s, e) =>{ ChangeControlState ();};} /// <summary> /// change the control's VisualState /// </summary> /// <param name = "useTransition"> whether to use VisualTransition, the default value is </param> private void ChangeControlState (bool useTransition = true) {if (button. isChecked. value) {VisualStateManager. goToState (this, "Expanded", useTransition);} else {VisualStateManager. goToState (this, "Normal", useTransition );}}
We can see that we add event responses for ToggleButton to switch the status. The reason for changing the check and status during Load is that when the Expander control is set to IsExpand to true, the control status is updated to Expanded in time; otherwise, the default value is Normal.
Finally, we add a ContentPropertyAttribute for the control and set its Name to Content. In this way, the Content attribute of the control is used as the Content attribute (ContentPropery) of the control ). In short, you can save the <xxx: Expander. Content> node, just like adding its Content to a Button. The Code is as follows:
[ContentProperty(Name = "Content")] public sealed class Expander : Control
So far, an Expander control has been completed. As for additional and other requirements (such as style modification), you can modify the control on this basis.
If you have any better suggestions or other ideas, please leave a message and communicate with each other.
Source code download
References:
What's the difference between ContentControl and ContentPresenter?
Difference between ContentControl, ContentPresenter, ContentTemplate and ControlTemplate? (Bob_Bao's answer)
What is ContentPropertyAttribute?