[WPF自定义控件库]自定义Expander
原文:[WPF自定义控件库]自定义Expander

1. 前言

上一篇文章介绍了使用Resizer实现Expander简单的动画效果,运行效果也还好,不过只有展开/折叠而缺少了淡入/淡出的动画(毕竟Resizer模仿Expander只是附带的功能)。这篇继续Measure的话题,自定义了一个带有动画的ExtendedExpander。

2. ExtendedExpander的需求

使用Resizer实现的简易Expander没办法在折叠时做淡出动画,因为ControlTemplate中的ExpandSite在Collapsed状态下直接设置为隐藏。一个稍微好看些的Expander的状态改变动画要满足下面的需求:

  • 拉伸
  • 淡入淡出
  • 上面两个效果都可以用XAML定义

最终运行效果如下:

3. 实现思路

模仿SilverlightToolkit,我也用一个带有Percentage属性的ExpandableContentControl控件控制Expander内容的拉伸。(顺便一提,SilverlightToolkit的Expander没有拉伸动画,ExpandableContentControl用在AccordionItem里面)。ExpandableContentControl的Percentage属性控制这个控件的展开的百分比,1为完全展开,0为完全折叠。

在ControlTemplate中使用VisualState控制Expanded/Collapsed的动画。VusialState.Storyboard控制VisualState的最终值,过渡动画由VisualStateGroup.Transitions控制,这在以前的 这篇文章 中有介绍过:

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;

这样Expander及它的ControlTemplate只做了最少的改动就实现了动画效果。主要的代码逻辑都交给ExpandableContentControl。

4. 实现ExpandableContentControl

ExpandableContentControl派生自ContentControl,它的Percentage属性的定义如下:

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

FrameworkPropertyMetadataOptions用于定义依赖属性的行为,其中AffectsMeasure的意思是依赖属性的值改变时要求重新Measure,既然Measure了Arrange也会发生,所以这个AffectsMeasure其实就是要求重新执行两步布局。功能和上一篇文章介绍的InvalidateMeasure差不多。

在MeasureOverride里根据Percentage告诉父元素自己需要多大的空间,那么使用动画操作Percentage属性就可以实现拉伸效果:

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);
}

最后,因为没有使用Arrange限制子元素的大小,子元素的UI一定会超出范围,所以要overrid GetLayoutClip 函数控制当子元素超出自身大小时是否显示超出的部分,可以用ClipToBounds属性控制。

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

之后只要把ExpandableContentControl放到Expander的ControlTemplate中就大功告成了。

5. 模仿Accordion

因为实现起来太简单,内容太少,所以顺便提一下怎么模仿Accordion。

Accordion通常被翻译为手风琴?通常也就程序的左侧导航菜单会用到,用ExpandableContentControl也可以简单地模仿如下:

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是一个空的Grid,上面这段代码用于控制MenuRoot的RowDefinitions根据当前选中的Expander变化。

最终效果如下:

6. 结语

虽然实现了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

posted on 2019-07-25 09:07 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/11242110.html

[WPF自定义控件库]自定义Expander相关推荐

  1. [WPF自定义控件库]使用WindowChrome自定义RibbonWindow

    [WPF自定义控件库]使用WindowChrome自定义RibbonWindow 原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义Ribbo ...

  2. [WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互

    [WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互 原文:[WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互 1. 前言 WPF有一个灵活的 ...

  3. [WPF自定义控件库]以Button为例谈谈如何模仿Aero2主题

    1. 为什么选择Aero2 除了以外观为卖点的控件库,WPF的控件库都默认使用"素颜"的外观,然后再提供一些主题包.这样做的最大好处是可以和原生控件或其它控件库兼容,而且对于大部分 ...

  4. [WPF自定义控件] 开始一个自定义控件库项目

    1. 目标 我实现了一个自定义控件库,并且打算用这个控件库作例子写一些博客.这个控件库主要目标是用于教学,希望通过这些博客初学者可以学会为自己或公司创建自定义控件,并且对WPF有更深入的了解. 控件库 ...

  5. WPF自定义控件与样式(13)-自定义窗体Window 自适应内容大小消息框MessageBox

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 自定义 ...

  6. 【转】WPF自定义控件与样式(13)-自定义窗体Window 自适应内容大小消息框MessageBox...

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: 自定义Window窗体样式: 基于自定义窗体实现自定义MessageB ...

  7. WPF自定义控件与样式(8)-ComboBox与自定义多选控件MultComboBox

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 下拉选 ...

  8. WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展

    原文:WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展 一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐 ...

  9. WPF自定义控件与样式(4)-CheckBox/RadioButton自定义样式

    原文:WPF自定义控件与样式(4)-CheckBox/RadioButton自定义样式 一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等, ...

最新文章

  1. 为什么很多SpringBoot开发者放弃了Tomcat,选择了Undertow?
  2. 腾讯云 已连接到实验云主机 linux 运维基本操作
  3. 浅尝Windows Server 2016——Container 容器:部署
  4. 浅谈Java/Android下的注解
  5. 一次微服务与IoT的深度探秘与实战
  6. Python Dict用法
  7. RPC框架(一)RPC简介
  8. 没有测量,就没有管理
  9. 中国上海量子计算机,首台光量子计算机在上海亮相
  10. Julia 语言可重用性高竟源于缺陷和不完美?
  11. linux+fstab挂载镜像,通过/etc/fstab自动挂载iso镜像的ISO格式问题
  12. MapGuide open source开发心得一:简介
  13. 【转载】白素贞的身世之谜
  14. python 压缩 解压文件
  15. vue-Vant组件上传图片
  16. AQS的前菜—详解CLH队列锁
  17. 加拿大高中计算机学什么内容,加拿大大学计算机科学专业排名情况及学习内容简单介绍...
  18. 华硕飞行堡垒8intel WiFi6 AX201 160mhz网络适配器报错问题解决日志
  19. uniform crossover(均匀交叉),遗传算法(Genetic Algorithm,GA),python
  20. 高通QCC30xx_QCC51xx_如何 DFU升级 OTA升级

热门文章

  1. dhcp服务器批量修改ip租期,dhcp服务器的ip地址租期默认是多久
  2. 通过hsv筛选颜色 python_OpenCV-Python 光流介绍(附代码)
  3. SQL Sever 数据完整性
  4. WPF仿微信保存与查看聊天记录
  5. Spring框架零基础学习(一):IOC|DI、AOP
  6. poj1700快速渡河问题(贪心策略,详细解析)
  7. UnityShader10:CG标准函数库
  8. 牛客国庆集训派对Day2: E. 数据排序(状压DP+记忆化搜索)
  9. bzoj 1671: [Usaco2005 Dec]Knights of Ni 骑士(BFS)
  10. nlogn最长单调递增