要實現的面板的效果如下圖所示:
一個面板打開了,其它的面板會自動收起。而且打開的面板會填充所有可用空間。那麼這樣的效果在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,對於實現一些常見的功能還是很有幫助的。