[WPF Custom Control Library] custom Expander

Original: [WPF Custom Control Library] custom Expander

1 Introduction

Previous article describes the use Resizer achieve Expander simple animation effects, operating results is also good, but only expand / collapse and the lack of a fade-in / fade-out animation (After all Resizer imitate Expander only incidental function). This topic continues to Measure, a customized ExtendedExpander with animation.

2. ExtendedExpander demand

Easy to use Expander Resizer did not realize the way to do fade animation when folded, because ControlTemplate in ExpandSite directly in the lower Collapsed state is hidden. A little look better state of change animation Expander to meet the following requirements:

  • Tensile
  • fade in and fade out
  • The above two effects can be defined in XAML

The final operating results are as follows:

3. The realization of ideas

Imitate SilverlightToolkit, I also use ExpandableContentControl control with a property of control Percentage stretch Expander content. (Incidentally, SilverlightToolkit Expander no stretching of animation, ExpandableContentControl used AccordionItem inside). Percentage of ExpandableContentControl attribute control the percentage of control deployment, fully deployed 1, 0 is fully folded.

Use VisualState control Expanded / Collapsed animation in the ControlTemplate. VusialState.Storyboard control VisualState final value of VisualStateGroup.Transitions transition animation control, which in the previous article has introduced:

lt;Border BorderBrush=quot;{TemplateBinding BorderBrush}quot; BorderThickness=quot;{TemplateBinding BorderThickness}quot; Background=quot;{TemplateBinding Background}quot; CornerRadius=quot;3quot; SnapsToDevicePixels=quot;truequot;gt;
    lt;VisualStateManager.VisualStateGroupsgt;
        lt;VisualStateGroup x:Name=quot;ExpansionStatesquot;gt;
            lt;VisualStateGroup.Transitionsgt;
                lt;VisualTransition GeneratedDuration=quot;0:0:0.3quot;gt;
                    lt;VisualTransition.GeneratedEasingFunctiongt;
                        lt;QuarticEase EasingMode=quot;EaseOutquot;/gt;
                    lt;/VisualTransition.GeneratedEasingFunctiongt;
                lt;/VisualTransitiongt;
            lt;/VisualStateGroup.Transitionsgt;
            lt;VisualState x:Name=quot;Expandedquot;/gt;
            lt;VisualState x:Name=quot;Collapsedquot;gt;
                lt;Storyboardgt;
                    lt;DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=quot;(UIElement.Opacity)quot; Storyboard.TargetName=quot;ExpandableContentControlquot;gt;
                        lt;EasingDoubleKeyFrame KeyTime=quot;0quot; Value=quot;0quot;/gt;
                    lt;/DoubleAnimationUsingKeyFramesgt;
                    lt;DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=quot;Percentagequot; Storyboard.TargetName=quot;ExpandableContentControlquot;gt;
                        lt;EasingDoubleKeyFrame KeyTime=quot;0quot; Value=quot;0quot;/gt;
                    lt;/DoubleAnimationUsingKeyFramesgt;
                lt;/Storyboardgt;
            lt;/VisualStategt;
        lt;/VisualStateGroupgt;
    lt;/VisualStateManager.VisualStateGroupsgt;
    lt;DockPanelgt;
        lt;ToggleButton x:Name=quot;HeaderSitequot; ContentTemplate=quot;{TemplateBinding HeaderTemplate}quot; ContentTemplateSelector=quot;{TemplateBinding HeaderTemplateSelector}quot; Content=quot;{TemplateBinding Header}quot; DockPanel.Dock=quot;Topquot; Foreground=quot;{TemplateBinding Foreground}quot; FontWeight=quot;{TemplateBinding FontWeight}quot; FocusVisualStyle=quot;{StaticResource ExpanderHeaderFocusVisual}quot; FontStyle=quot;{TemplateBinding FontStyle}quot; FontStretch=quot;{TemplateBinding FontStretch}quot; FontSize=quot;{TemplateBinding FontSize}quot; FontFamily=quot;{TemplateBinding FontFamily}quot; HorizontalContentAlignment=quot;{TemplateBinding HorizontalContentAlignment}quot; IsChecked=quot;{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}quot; Margin=quot;1quot; MinWidth=quot;0quot; MinHeight=quot;0quot; Padding=quot;{TemplateBinding Padding}quot; Style=quot;{StaticResource ExpanderDownHeaderStyle}quot; VerticalContentAlignment=quot;{TemplateBinding VerticalContentAlignment}quot;/gt;
        lt;Primitives:ExpandableContentControl x:Name=quot;ExpandableContentControlquot; HorizontalContentAlignment=quot;{TemplateBinding HorizontalContentAlignment}quot; VerticalContentAlignment=quot;{TemplateBinding VerticalContentAlignment}quot;
                                             Margin=quot;{TemplateBinding Padding}quot; ClipToBounds=quot;Truequot;gt;
            lt;ContentPresenter x:Name=quot;ExpandSitequot; DockPanel.Dock=quot;Bottomquot; Focusable=quot;falsequot; HorizontalAlignment=quot;{TemplateBinding HorizontalContentAlignment}quot;  VerticalAlignment=quot;{TemplateBinding VerticalContentAlignment}quot;/gt;
        lt;/Primitives:ExpandableContentControlgt;
    lt;/DockPanelgt;
lt;/Bordergt;
lt;ControlTemplate.Triggersgt;
    lt;Trigger Property=quot;IsExpandedquot; Value=quot;falsequot;gt;
        lt;Setter Property=quot;IsHitTestVisiblequot; TargetName=quot;ExpandableContentControlquot; Value=quot;Falsequot;/gt;
    lt;/Triggergt;
    ...
lt;/ControlTemplate.Triggersgt;

Such Expander and its ControlTemplate only a minimal change on the realization of animation. The main code logic to ExpandableContentControl.

4. Implement ExpandableContentControl

ExpandableContentControl derived from ContentControl, its definition Percentage properties as follows:

public static readonly DependencyProperty PercentageProperty =
    DependencyProperty.Register(nameof(Percentage),
                                typeof(double),
                                typeof(ExpandableContentControl),
                                new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsMeasure));

FrameworkPropertyMetadataOptions used to define the behavior of dependency properties, which AffectsMeasuremeans that the value changes require re Measure dependency properties, since Measure the Arrange will happen, so this AffectsMeasureis actually a two-step requirement to re-layout. Function and the introduction of an article InvalidateMeasureabout the same.

In MeasureOverride in the parent element according Percentage tell how much space they need, then use the animation operation Percentage properties stretching effect can be achieved:

protected override Size MeasureOverride(Size constraint)
{
    int count = VisualChildrenCount;
    Size childConstraint = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
    UIElement child = (count gt; 0) ? GetVisualChild(0) as UIElement : null;
    var result = new Size();
    if (child != null)
    {
        child.Measure(childConstraint);
        result = child.DesiredSize;
    }

    return new Size(result.Width * Percentage, result.Height * Percentage);
}

Finally, since no size limit Arrange child elements, sub-elements of the UI will be out of range, so overrid GetLayoutClipfunction controls whether the sub-display portion exceeds the elements beyond their size, can ClipToBoundsattribute control.

protected override Geometry GetLayoutClip(Size layoutSlotSize)
{
    if (ClipToBounds)
        return new RectangleGeometry(new Rect(RenderSize));
    else
        return null;
}

After as long as the ExpandableContentControl Expander into the ControlTemplate in and you're done.

5. imitate Accordion

Because too simple to implement, too little content, so the way how to imitate Accordion.

Accordion is usually translated as the accordion? Usually on the left navigation menu of the program also will be used, with ExpandableContentControl can simply imitate as follows:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var expanders = new Listlt;KinoExpandergt;();
    Expander firstExpander = null;
    for (int i = 0; i lt; 10; i++)
    {
        var expander = new KinoExpander() { Header = quot;This is AccordionItem quot; + i };
        if (i == 0)
            firstExpander = expander;

        Grid.SetRow(expander, i);
        var panel = new StackPanel();
        panel.Children.Add(new CheckBox { Content = quot;Calendarquot; });
        panel.Children.Add(new CheckBox { Content = quot;中国节假日quot; });
        panel.Children.Add(new CheckBox { Content = quot;Birthdaysquot; });
        expander.Content = panel;
        MenuRoot.Children.Add(expander);
        MenuRoot.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
        int index = i;
        expander.Expanded += (s, args) =gt;
        {

            var lastExpander = expanders.Where(p =gt; p.IsExpanded amp;amp; p != s).FirstOrDefault();
            if (lastExpander != null)
                lastExpander.IsExpanded = false;

            MenuRoot.RowDefinitions[index].Height = new GridLength(1, GridUnitType.Star);
        };

        expander.Collapsed += (s, args) =gt;
          {
              if (expanders.Any(p =gt; p.IsExpanded) == false)
              {
                  expander.IsExpanded = true;
                  return;
              }

              MenuRoot.RowDefinitions[index].Height = new GridLength(1, GridUnitType.Auto);
          };
        expanders.Add(expander);
    }


    firstExpander.IsExpanded = true;
}

MenuRoot is an empty Grid, the above code is used to control Expander RowDefinitions MenuRoot changes according to the currently selected.

The final results are as follows:

6. Conclusion

虽然实现了Expander,但我想这种方式会影响到Expander中ScrollViewer的计算,所以最好还是不要把ScrollViewer放进Expander。

写完这篇文章才发觉可能把这篇和上一篇调换下比较好,因为这篇的Measure的用法更简单。

其实有不少方案可以实现,但为了介绍Measure搞到有点舍近求远了。例如直接用LayoutTransform就挺好的。

不过这种动画效果不怎么好看,所以很多控件库基本上都实现了自己的带动画的Expander控件,例如Telerik开源了UI for UWP控件库,里面的RadExpanderControl是个漂亮优雅的方案,应该可以轻易地移植到WPF(不过某些情况运行起来卡卡的)。

其它控件库的AccordionItem也可以实现类似的功能,可以当作Expander来用,例如Silverlight Toolkit,移植起来应该也不复杂。

另外有没有从上面ExtendedExpander的ControlTemplate感受到不换行的XAML有多烦?Blend产生的样式默认就是这样的。ExtendedExpander的XAML没有使用之前的每个属性一行的方式写,这样的好处是很容易看清楚结构,但在分辨率不高的显示器,或者在Github上根本看不到后面的属性,很容易因为看不到添加在最后的属性犯错(而且我的博客园主题,代码框里还没有滚动条)。使用哪种格式化见仁见智,这篇文章的样式因为是从别的地方复制的,既然保持了原格式就顺便用来讲解一下格式的这个问题,正好HeaderSite的ToggleButton几乎是PresentationFramework.Aero2主题里最长的一行,感受一下这有多欢乐。最终选择使用哪种方式视乎团队人员的显示器有多大,但为了博客里看起来方便我会尽量选择每个属性一行的格式。

7. 参考

Expander 概述 _ Microsoft Docs

Customizing WPF Expander with ControlTemplate - CodeProject

FrameworkPropertyMetadataOptions Enum (System.Windows) _ Microsoft Docs

FrameworkElement.MeasureOverride(Size) Method (System.Windows) Microsoft Docs.html

8. 源码

Kino.Toolkit.Wpf_Expander at master

Guess you like

Origin www.cnblogs.com/lonelyxmas/p/11242110.html