要实现的面板的效果如下图所示:
一个面板打开了,其它的面板会自动收起。而且打开的面板会填充所有可用空间。那么这样的效果在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,对于实现一些常见的功能还是很有帮助的。
转载自原文链接, 如需删除请联系管理员。
原文链接:【WPF】实现QQ中的分组面板,转载请注明来源!