Většina UWP aplikací s výhodou využije ovladací prvek CommandBar
pro přehlednou prezentaci dostupných akcí uživateli. Bohužel způsobuje tento prvek někdy neočekávané chování. Dva z těchto problémů si ukážeme v tomto článku.
Akce na levé straně panelu
Ve výchozím stavu je CommandBar
připraven zobrazovat akce na pravé straně panelu. Díky tomu také mohou na menších displejích příkazy "přetéci" do sekundárnho menu (funkcionalita DynamicOverflow
, která byla přidána ve Windows 10 Anniversary Update). Někdy však můžete chtít zobrazit příkazy také na levé straně ovladacího prvku (např. pro globální příkazy nebo speciální úkony). CommandBar má vlastnost Content
ve které můžeme definovat, co bude zobrazeno nalevo. Můžeme zde deklarovat jakýkoliv markup, ale v tomto případě budeme chtít použít tlačítka AppBarButton
. Výsledek může vypadat takto:
<CommandBar> <CommandBar.Content> <StackPanel Orientation="Horizontal"> <AppBarButton Icon="Accept" Label="Accept" /> <AppBarButton Icon="Delete" Label="Delete" /> </StackPanel> </CommandBar.Content> </CommandBar>
Když aplikaci spustíme, první problém se nám ukáže okamžitě - popisky tlačítek vykukují na spodní straně panelu.
Důvod je jednoduchý - výchozí rozložení talčítek je takto navrženo - popisky jsou prostě umístěny tak, že v této situaci jsou částečně vidět. Když je tlačítko v kolekci PrimaryCommands
CommandBaru
, ovladací prvek sám zajistí skrytí popisků když je command bar v normálním stavu a zobrazí je pouze, když je otevřen. Naštěstí je toto chování snadné napodobit nabindováním vlastnosti IsCompact
tlačítek na inverzní hodnotu vlastnosti IsOpen
command baru.
<CommandBar x:Name="CommandBar"> <CommandBar.Content> <StackPanel Orientation="Horizontal"> <AppBarButton Icon="Accept" Label="Accept" IsCompact="{Binding ElementName=CommandBar, Path=IsOpen, Converter={StaticResource InverseConverter}}" /> <AppBarButton Icon="Delete" Label="Delete" IsCompact="{Binding ElementName=CommandBar, Path=IsOpen, Converter={StaticResource InverseConverter}}" /> </StackPanel> </CommandBar.Content> </CommandBar>
InverseConverter
je jednoduchý IValueConverter
který obrací pravdivostní hodnotu.
Poskakující (a mizející) popisky
Příkazy nalevo nyní vypadají a fungují skvěle. Alespoň většinou. Ale ne vždy! Přibližně jednou za deset pokusů narazíme na to, že se popisky talčítek zobrazí napravo, nebo se dokonce nezobrazí vůbec.
Tento problém je velmi zvláštní a trvalo mi nějakou dobu, než se mi podařilo objevit řešení. Po delším hledání jsem narazil na odpověď na Stack Overflow od vývojáře v Microsoftu - Jay Zua, která právě tento problém adresuje. Anniversary update Windows 10 představil novou vlastnost LabelPosition
, která určuje, kde bude popisek tlačítka zobrazen. Ve výchozím stavu se tlačítko podívá výše na svého otce (kterým by měl být command bar) a zkontroluje jeho vlastnost DefaultLabelPosition
, který může nabývat hodnot Bottom
, Right
nebo Collapsed
. Tento proces funguje skvěle, když tlačítka příkazů jsou uvnitř kolekce PrimaryCommands
, ale jakmile jsou umístěna uvnitř jiného ovladacího prvku jakým je například právě StackPanel
, začnou se chovat nepředvídatelně. Řešení problému vyžaduje úpravu výchozího stylu app bar tlačítek a zakomentování visual state pro umístění Right
a Collapsed
a souvisejího resource uvnitř stylu:
<Style x:Key="BottomLabelAppBarButtonStyle" TargetType="AppBarButton"> ... <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="AppBarButton"> <Grid x:Name="Root" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" MaxWidth="{TemplateBinding MaxWidth}" MinWidth="{TemplateBinding MinWidth}"> <!--<Grid.Resources> <Style x:Name="LabelOnRightStyle" TargetType="AppBarButton"> <Setter Property="Width" Value="NaN"/> </Style> </Grid.Resources>--> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="ApplicationViewStates"> ... <!--<VisualState x:Name="LabelOnRight"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="ContentViewbox"> <DiscreteObjectKeyFrame KeyTime="0" Value="12,14,0,14"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="MinHeight" Storyboard.TargetName="ContentRoot"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource AppBarThemeCompactHeight}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="TextLabel"> <DiscreteObjectKeyFrame KeyTime="0" Value="0"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Column)" Storyboard.TargetName="TextLabel"> <DiscreteObjectKeyFrame KeyTime="0" Value="1"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="TextAlignment" Storyboard.TargetName="TextLabel"> <DiscreteObjectKeyFrame KeyTime="0" Value="Left"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="TextLabel"> <DiscreteObjectKeyFrame KeyTime="0" Value="8,15,12,17"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="LabelCollapsed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="MinHeight" Storyboard.TargetName="ContentRoot"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource AppBarThemeCompactHeight}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="TextLabel"> <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState>--> ... </VisualStateManager.VisualStateGroups> ... </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
Zdrojový kód
Ukázkový zdrojový kód pro tento článek je dostupný na mém GitHubu.
Shrnutí
CommandBar
je snadno přizpůsobitelný, ale jeho použití způsobem, na který nebyl navržen může mít nečekané následky. Tyto situaci lze sice vyřešit poměrně přímočaře, ale musíme si jich přesto být při vývoji vědomi.