【WPF】实现QQ中的分组面板
要实现的面板的效果如下图所示:
一个面板打开了,其它的面板会自动收起。而且打开的面板会填充所有可用空间。那么这样的效果在WPF里应该如何实现呢?
1. 多个面板,排成一排,感觉可以用ListBox。
2. 然后里面的东西,点一下打开,再点一下收起。感觉就是一个Expander嘛。
3. 一个打开,其它所有的收起。可以把Expander的IsExpanded与SelectedItem绑定。
第一步:ListBox + Expander + Style
上面所有的功能都可以用现有的控件做到,没有必要做个自定义控件,一个Style就可以搞定了。
为了让代码窄一点。所以分成了几个部分。Style部分如下所示。
<ControlTemplate x:Key="ExpandListItemTemplate" TargetType="{x:Type ListBoxItem}"> <Border BorderThickness="1"> <Border CornerRadius="3" Padding="2,1,2,2" BorderThickness="1" BorderBrush="#FF666670"> <Expander Header="Header" Content="{TemplateBinding Content}" IsExpanded="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}}"/> </Border> </Border> </ControlTemplate> <Style x:Key="ExpandListItemStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="Template" Value="{StaticResource ExpandListItemTemplate}"/> </Style> <Style x:Key="MostSimple" TargetType="{x:Type ListBox}"> <Setter Property="SelectionMode" Value="Single"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/> <Setter Property="ItemContainerStyle" Value="{StaticResource ExpandListItemStyle}"/> </Style>
主体部分如下所示。
<ListBox Style="{StaticResource MostSimple}"> <ListBoxItem> <ListBox> <Button Content="OK"/> <Button Content="Cancel"/> <Button Content="Yes"/> <Button Content="No"/> </ListBox> </ListBoxItem> <ListBoxItem> <ListBox> <CheckBox Content="Close"/> <CheckBox Content="Open"/> <CheckBox Content="Copy"/> <CheckBox Content="Paste"/> </ListBox> </ListBoxItem> <ListBoxItem> <ListBox> <RadioButton Content="Stay"/> <RadioButton Content="Leave"/> <RadioButton Content="Stay"/> <RadioButton Content="Leave"/> </ListBox> </ListBoxItem> </ListBox>
我们来看一下效果。
看上去差不多了,点一下如何?
完了,打开的Expander没有填充。由于ListBoxItem默认是放在VirtualizedStackPanel中的,所以里面的Item都是向上对齐,不填充。我的第一感觉就是用个会填充的当ItemsPanel就可以了。记得DockPanel有LastChildFill的功能。试试。
<ItemsPanelTemplate x:Key="ExpandListItemsPanelTemplate"> <DockPanel/> </ItemsPanelTemplate> <Style x:Key="ExpandListItemStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="DockPanel.Dock" Value="Top"/> <Setter Property="Template" Value="{StaticResource ExpandListItemTemplate}"/> </Style> <Style x:Key="MostSimple" TargetType="{x:Type ListBox}"> <Setter Property="SelectionMode" Value="Single"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/> <Setter Property="ItemContainerStyle" Value="{StaticResource ExpandListItemStyle}"/> <Setter Property="ItemsPanel" Value="{StaticResource ExpandListItemsPanelTemplate}"/> </Style>
看看效果。
寒,还没有点就展开了。早该想到的。
想了半天如何动态地改变Last Child是哪个。但是没有想出啥好办法,一个方法是把Item一个个取出来重新放回去。这也太变态了。再想想好像也没有可用的Panel了。完了,又要写一个自定义Panel了。还好这个Panel的功能不复杂。
这个面板要做的就是把其中的一个item填充。其它的就按其期望的最小大小放上去。顺序不能乱就可以了。
想了一个简单的逻辑。就是LargestChildFill,找出最大的Child,让它填充剩下的所有空间。
/// <summary> /// The logic is very simple. Make the largest child fill. /// </summary> public class GroupPanel : Panel { private UIElement largestChild; private double totalHeight; protected override Size MeasureOverride(Size availableSize) { totalHeight = 0; double width = 0; largestChild = null; foreach (UIElement child in Children) { child.Measure(availableSize); if (largestChild == null || child.DesiredSize.Height >= largestChild.DesiredSize.Height) { largestChild = child; } totalHeight += child.DesiredSize.Height; if (child.DesiredSize.Width > width) { width = child.DesiredSize.Width; } } return new Size(width, totalHeight); } protected override Size ArrangeOverride(Size finalSize) { double yOffset = 0; foreach (UIElement child in Children) { if (child == largestChild) { double finalHeight = child.DesiredSize.Height + finalSize.Height - totalHeight; child.Arrange(new Rect(0, yOffset, finalSize.Width, finalHeight)); yOffset += finalHeight; } else { child.Arrange(new Rect(0, yOffset, finalSize.Width, child.DesiredSize.Height)); yOffset += child.DesiredSize.Height; } } return finalSize; } }
然后把ItemsPanelTemplate改一下。
<ItemsPanelTemplate x:Key="ExpandListItemsPanelTemplate"> <c:GroupPanel/> </ItemsPanelTemplate>
搞定。看下效果。
就是想要的效果。目前这些面板可以全部折叠起来,如果你想让至少一个展开。一个简单的方法就是。当展开后,把Expander的Header里的ToggleButton禁用。
再常见一些的需求就是这个面板的展开过程要有动画什么的。这里就不再演示了。
目前为到止,写在博客里的自定义Panel就有仨了。其实我想说的是,WPF自带的几个Panel的功能实在是很基本的。 了解如何实现自定义Panel,对于实现一些常见的功能还是很有帮助的。