各位好,再次回到UWP開發入門系列,剛回歸可能有些不適應,所以今天我們講個簡單的,自定義CommandBar,說通俗點就是自定義類似AppBarButton的東西,然后扔到CommandBar中使用。
話說為了在公司次世代平台級戰略層產品上實現與水果和機器人一致的用戶體驗,美工把Win10 APP的AppBar也畫成左右分開的了,好看是好看了,問題原生的ComandBar控件不支持這么排列啊……頭疼歸頭疼,只能再次展開山寨之路……
初步思路是在CommandBar中塞進占位用的空白元素,我管它叫AppBarEmpty。那么能放進CommandBar中的元素需要符合什么樣的規則呢?首先看一下CommandBar的用法:
<CommandBar> <AppBarToggleButton Icon="Shuffle" Label="Shuffle" Click="AppBarButton_Click" /> <AppBarToggleButton Icon="RepeatAll" Label="Repeat" Click="AppBarButton_Click"/> <AppBarSeparator/> <AppBarButton Icon="Back" Label="Back" Click="AppBarButton_Click"/> <AppBarButton Icon="Stop" Label="Stop" Click="AppBarButton_Click"/> <AppBarButton Icon="Play" Label="Play" Click="AppBarButton_Click"/> <AppBarButton Icon="Forward" Label="Forward" Click="AppBarButton_Click"/> <CommandBar.SecondaryCommands> <AppBarButton Icon="Like" Label="Like" Click="AppBarButton_Click"/> <AppBarButton Icon="Dislike" Label="Dislike" Click="AppBarButton_Click"/> </CommandBar.SecondaryCommands> <CommandBar.Content> <TextBlock Text="Now playing..." Margin="12,14"/> </CommandBar.Content> </CommandBar>
上面這個常規的Command顯示效果如下圖:
規律還是比較明顯的,菜單“like”,“Dislike”均屬於SecondaryCommands這個集合,和我們今天的主題關系不大。而最左側的文字描述是放到CommandBar的Content屬性中,也無需理睬。我們需要實現的是將AppButton左右分開,這部分的按鈕均屬於PrimaryCommands這個集合,XAML沒有看到是因為簡化寫法的緣故。
既然屬於PrimaryCommands集合的元素會被呈現在按鈕區域,那么我們就檢查一下該集合的類型定義:
public IObservableVector<ICommandBarElement> PrimaryCommands { get; }
很明顯該集合的元素需要實現接口ICommandBarElement,而該接口又非常簡單:
// // Summary: // Defines the compact view for command bar elements. [ContractVersion(typeof(UniversalApiContract), 65536)] [GuidAttribute(1737592347, 62165, 17617, 139, 132, 146, 184, 127, 128, 163, 80)] [WebHostHidden] public interface ICommandBarElement { // // Summary: // Gets or sets a value that indicates whether the element is shown with no label // and reduced padding. // // Returns: // true if the element is shown in its compact state; otherwise, false. The default // is false. System.Boolean IsCompact { get; set; } }
這樣我們的AppBarEmpty就好寫了,僅僅需要實現一個IsCompact屬性即可,而占位用的AppBarEmpty其實是沒有內容的,連IsCompact的效果其實都省了……
public class AppBarEmpty : FrameworkElement, ICommandBarElement { public bool IsCompact { get; set; } }
是不是有種騙錢的感覺?別走啊,這里還是有講究的,首先為什么是繼承自FrameworkElement呢?
1.AppEmpty是一個占位控件,基本不具備功能,應該從輕量級的基類繼承
2.FrameworkElement提供了占位所有的Width和Height等屬性,更基礎的UIElement則沒有這些
3.從FrameworkElement才開始支持的Binding
搞清楚了以上這些之后,我們興沖沖的把XAML補完了:
<CommandBar> <AppBarButton Icon="Accept" Label="Accept"></AppBarButton> <AppBarButton Icon="Accept" Label="Accept"></AppBarButton> <local:AppBarEmpty></local:AppBarEmpty> <AppBarButton Icon="Accept" Label="Accept"></AppBarButton> </CommandBar>
行雲流水編譯一次通過,嗷嗷的按下F5運行……結果傻眼了……什么效果也沒有……介個是為什么呢?
只能通過Live Tree View來檢查了(后面會寫一篇介紹Live Tree View,這貨簡直是神器,現在沒有它都不知道怎么調試了……),檢查發現AppBarEmpty的Widht是0,而PrimaryCommands集合里的這些按鈕又都是放在StackPanel里橫向順序排開的,悲劇的是StackPanle的Width就是幾個AppBarButton的Width總和,完全沒有留給AppBarEmpty一絲絲的寬度……設置AppBarEmpty的HorizontalAlignment=Stretch對StackPanel是然並卵……
CommanBar模板PrimaryCommands部分節選:
<ItemsControl x:Name="PrimaryItemsControl" HorizontalAlignment="Right" MinHeight="{ThemeResource AppBarThemeMinHeight}" IsTabStop="False" Grid.Column="1"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl>
悲劇啊!既然HorizontalAlignment不管用,無法自行撐開。那我們就只有給AppBarEmpty綁定一個確實的Width了。對應的XAML如下:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition x:Name="RowBottom" Height="Auto"></RowDefinition> </Grid.RowDefinitions> <TextBox Grid.Row="1"> </TextBox> <CommandBar x:Name="commandBar" Grid.Row="2"> <AppBarButton x:Name="appbarButton" Icon="Accept" Label="Accept"></AppBarButton> <AppBarButton Icon="Accept" Label="Accept"></AppBarButton> <local:AppBarEmpty Width="{x:Bind TestWidth,Mode=OneWay}"></local:AppBarEmpty> <AppBarButton Icon="Accept" Label="Accept"></AppBarButton> </CommandBar> </Grid>
為AppBarEmpty做了XBind到TestWidth屬性上,相應的Page的代碼里定義了該屬性,並在Page的SizeChanged事件中計算了AppBarEmpty實際需要的寬度。
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public double TestWidth { get; set; }
public MainPage()
{
this.InitializeComponent();
this.SizeChanged += MainPage_SizeChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
private void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
{
int count = this.commandBar.PrimaryCommands.Count-1;
double width = (this.commandBar.PrimaryCommands[0] as AppBarButton).ActualWidth;
TestWidth = e.NewSize.Width - count* width -48;
this.OnPropertyChanged("TestWidth");
}
public void OnPropertyChanged([CallerMemberName]string name = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
代碼中減掉的48是CommandBar最右邊“…”按鈕的寬度。實際顯示如下圖:
這里仍然存在幾個問題:
1.有童鞋說為什么不自己寫個RelativePanel,然后里面的按鈕就可以隨便對齊定位了。
該方法確實可行,但等於拋棄了CommandBar,也無法集成到Page的TopAppBar、BottomAppBar中使用。有點偏離了我們自定義CommadBar的初衷。並且要想完整實現一套CommandBar的工作量還是不小的
2.實現的方式不夠優美,竟然使用了SizeChanged事件來計算Width。簡直返古到了WinForm的時代。
哼哼,兄弟我不想好解決方案敢寫這篇被你們罵?當然是已經准備好了完美的解決方案才來寫這篇釣魚咯,乖乖給我點個推薦,然后我們下周見……