各位好,終於講到自定義Panel了。當系統自帶的幾個Panel比如Gird,StackPanel,RelativePanel不能滿足我們的特定要求時(其實不常見啦),自定義Panel就顯得非常必要,而且因為是針對性的處理,效果也會非常好。更何況自定義Panel其實並不複雜,今天俺們就來學習一下。.....
各位好,終於講到自定義Panel了。當系統自帶的幾個Panel比如Gird,StackPanel,RelativePanel不能滿足我們的特定要求時(其實不常見啦),自定義Panel就顯得非常必要,而且因為是針對性的處理,效果也會非常好。更何況自定義Panel其實並不複雜,今天俺們就來學習一下。
記得上一篇自定義CommandBar在增加占位控制項AppBarEmpty時,採用的是通過Page的SizeChanged事件中計算頁面Width,減去CommandBar中其他控制項Width後再賦值Width給AppBarEmpty的方法。就可行性而言是絕對沒問題的,代碼複雜度也很低,不失為一個好方法。但是復用性不太好,需要在每個Page都寫上一小段代碼。而我們的初衷是希望AppBarEmpty能夠自動撐開,計算自身所需的Width。
遇到的困難來自StackPanel這個控制項,StackPanel在計算自身所需空間時,會非常吝嗇按children元素所需的最小尺寸來申請。就好比申請經費時按最下限申請,這種精神ZF部門應該學習,而公司組織TeamBuilding就應該排斥……)。說到這裡,本篇的主題有了,就是通過自定義一個StackPanelEx來實現讓AppBarEmpty自動撐開的效果。
前面說了,自定義Panel其實並不複雜。只有兩個方法需要override:
// // Summary:(根據子元素測量控制項本身需要的空間) // Provides the behavior for the Measure pass of the layout cycle. Classes can override // this method to define their own Measure pass behavior. // // Parameters: // availableSize:(控制項本身的可用空間。如指定無窮大值,表示控制項的大小將調整為內容的可用大小) // The available size that this object can give to child objects. Infinity can be // specified as a value to indicate that the object will size to whatever content // is available. // // Returns:(控制項根據子元素大小計算得出的所需大小) // The size that this object determines it needs during layout, based on its calculations // of the allocated sizes for child objects or based on other considerations such // as a fixed container size. protected virtual Size MeasureOverride(Size availableSize); // // Summary:(根據上面測量的結果,來對子元素進行佈局) // Provides the behavior for the Arrange pass of layout. Classes can override this // method to define their own Arrange pass behavior. // // Parameters: // finalSize:(控制項用來排列自身及其子元素的最終確定的空間) // The final area within the parent that this object should use to arrange itself // and its children. // // Returns:(使用的實際大小) // The actual size that is used after the element is arranged in layout. protected virtual Size ArrangeOverride(Size finalSize);
聽上去是不是挺繞的?我們來看StackPanelEx的實際代碼:
public class StackPanelEx : Panel { protected override Size MeasureOverride(Size availableSize) { double height = 0; foreach (var child in Children) { // Tell the child control to determine the size needed child.Measure(availableSize); height = child.DesiredSize.Height > height ? child.DesiredSize.Height : height; } return new Size(availableSize.Width,height); } protected override Size ArrangeOverride(Size finalSize) { int count = Children.Count(_ => _ is AppBarEmpty2); double totalLength = Children.Where(_ => _ is AppBarEmpty2 == false).Sum(_ => (_ as FrameworkElement).Width); var emptyWidth = (finalSize.Width - totalLength) / count; double x = 0; foreach (var child in Children) { if (child is AppBarEmpty2) { child.Arrange(new Rect(x, 0, emptyWidth, 0)); x += emptyWidth; } else { child.Arrange(new Rect(x, 0, child.DesiredSize.Width, child.DesiredSize.Height)); x += child.DesiredSize.Width; } } return finalSize; } }
簡單解釋一下,首先在MeasureOverride方法里,告訴每個子元素(這裡是AppBarButton和AppBarEmpty2)去算自己需要多少空間,一會要分地了。然後直接告訴上頭,Width我全要了,Height按我們村裡最高的人給就行了。
緊接著到了ArrangeOverride方法,上級領導比較大方,告訴該控制項Width全給你,還有你Height要的太少,拖了上級Panel的後腿,多給一些Height免得擠到同級的其他控制項……
然後村長及開始分地了。AppBarButton就按他自己申請的給,AppBarEmpty2感覺是村長親戚,剩下的Width全給他們家承包了……
最後向上頭彙報一下分地的情況,這事就算完了。
AppBarEmpty2還是沒變,和上次一樣。
public class AppBarEmpty2 : FrameworkElement, ICommandBarElement { public bool IsCompact { get; set; } }
既然新的StackPanelEx寫好了,就該替換CommandBar原有的StackPanel了,找到CommandBar模板中關於PrimaryItemsControl的部分
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <ContentControl x:Name="ContentControl" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsTabStop="False" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/> <ItemsControl x:Name="PrimaryItemsControl" Grid.Column="0" HorizontalAlignment="Stretch" IsTabStop="False" MinHeight="{ThemeResource AppBarThemeMinHeight}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <!--<StackPanel Orientation="Horizontal"/>--> <local:StackPanelEx ></local:StackPanelEx> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </Grid>
將ItemsPanelTemplate中的StackPanel替換成StackPanelEx,同時將ItemsControl的HorizontalAlignment="Right"改成HorizontalAlignment="Stretch",大功告成,再來看一下Page中的XAML部分:
<CommandBar x:Name="commandBar" Grid.Row="3" Style="{StaticResource CommandBarStyle2}" > <AppBarButton x:Name="appbarButton" Icon="Accept" Label="fdsfdsf" ></AppBarButton> <local:AppBarEmpty2 ></local:AppBarEmpty2> <AppBarButton Icon="Accept" Label="fdsfdsf"></AppBarButton> <local:AppBarEmpty2 ></local:AppBarEmpty2> <AppBarButton Icon="Accept" Label="fdsfdsf" ></AppBarButton> </CommandBar>
是不是非常的清爽,再也不用去寫什麼SizeChanged事件了。實際效果如下圖: