在MeasureOverride里根据Percentage告诉父元素自己需要多大的空间,那么使用动画操作Percentage属性就可以实现拉伸效果:
protected override Size MeasureOverride(Size constraint) { int count = VisualChildrenCount; Size childConstraint = new Size(Double.PositiveInfinity, Double.PositiveInfinity); UIElement child = (count > 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)); else return null; }之后只要把ExpandableContentControl放到Expander的ControlTemplate中就大功告成了。
5. 模仿Accordion因为实现起来太简单,内容太少,所以顺便提一下怎么模仿Accordion。
Accordion通常被翻译为手风琴?通常也就程序的左侧导航菜单会用到,用ExpandableContentControl也可以简单地模仿如下:
private void OnLoaded(object sender, RoutedEventArgs e) { var expanders = new List<KinoExpander>(); Expander firstExpander = null; for (int i = 0; i < 10; i++) { var expander = new KinoExpander() { Header = "This is AccordionItem " + i }; if (i == 0) firstExpander = expander; Grid.SetRow(expander, i); var panel = new StackPanel(); panel.Children.Add(new CheckBox { Content = "Calendar" }); panel.Children.Add(new CheckBox { Content = "中国节假日" }); panel.Children.Add(new CheckBox { Content = "Birthdays" }); 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) => { var lastExpander = expanders.Where(p => p.IsExpanded && p != s).FirstOrDefault(); if (lastExpander != null) lastExpander.IsExpanded = false; MenuRoot.RowDefinitions[index].Height = new GridLength(1, GridUnitType.Star); }; expander.Collapsed += (s, args) => { if (expanders.Any(p => 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