各位好,再次回到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的时代。
哼哼,兄弟我不想好解决方案敢写这篇被你们骂?当然是已经准备好了完美的解决方案才来写这篇钓鱼咯,乖乖给我点个推荐,然后我们下周见……