AppBarButtons and the Visual State

Please note: This article is only available in English.
Currently the templates for Windows Store apps have one or the other problem. One of the problems is that the AppBar buttons do not switch their state automatically.

Currently the templates for Windows Store apps have one or the other problem. One of the problems is that the AppBar buttons do not switch their state automatically. However, the interesting thing is that this behavior can be attacked quite easily using the provided classes, like the LayoutAwarePage class.

Most useful (i.e. standard) styles of the Windows store apps design (formerly known as Metro) can be found in the file StandardStyles.xaml. This file also defines a AppBarButtonStyle style as a StaticResource. However, using the code in XAML like

<Page.BottomAppBar>
    <AppBar x:Name="MoreAppBar" Padding="10,0,10,0" AutomationProperties.Name="App Bar">
        <StackPanel x:Name="RightPanel" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="Help" Style="{StaticResource HelpAppBarButtonStyle}" Tag="Help" />
            <Button x:Name="About" Style="{StaticResource AboutAppBarButtonStyle}" Tag="About" />
        </StackPanel>
    </AppBar>
</Page.BottomAppBar>

will give you a headache if you switch states from Filled (or something similar) to Snapped (as an example). The reason is the following: In the XAML defined by Microsoft you already see the declarations for the VisualStateManager, however, nothing appears to happen. The reason for this is the code-behind - the control (obviously) was not registered to change the state.

Now I suppose that one has either done all the required event handling (i.e. what do to on resizing etc.) or one uses already the LayoutAwarePage class that you get for free from Microsoft if you open one of the pre-made templates or add a certain pre-made template-page to your project.

To make the long story short - nothing will happen. And this is due to the intrinsic behavior of the AppBar. But I can suggest a few small changes in the code to include any Button that has been added to the BottomAppBar property of the page.

The following change is required in the constructor of the LayoutAwarePage class:

//This stays
this.StartLayoutUpdates(sender, e);
//This will be added right afterwards
this.StartAppBarUpdates();

This change is supposed to happen in the handler for the Loaded event.

Now we just need the method I've just called. The code goes as follows:

void StartAppBarUpdates()
{
	if (BottomAppBar == null || BottomAppBar.Content == null)
		return;

	foreach (var child in FindAllChildren<Button>(BottomAppBar.Content as DependencyObject))
		child.Loaded += StartLayoutUpdates;
}

Ok, so we are just binding the Loaded event for each button... This works, because the event will be fired once the Visual tree will be constructed. This will happen after the page's loaded event occured. So basically we are fine, but we still need to know the body of the method FindAllChildren(). This is the method that will find all corresponding buttons. It is a generic (pun-intended) method. The code looks like:

public IEnumerable FindAllChildren(DependencyObject root) where T : DependencyObject
{
	if (root == null)
		yield break;

	var children = VisualTreeHelper.GetChildrenCount(root);

	for (int i = 0; i < children; i++)
	{
		var child = VisualTreeHelper.GetChild(root, i);

		if (child is T)
			yield return (T)child;
		else
		{
			foreach (var subchild in FindAllChildren(child))
				yield return subchild;
		}
	}
}

So we are just iterating over all children. This search is also continued recursively, if the current child does not match the requested type.

The cool thing is that we can also use this trick if we want to create and add a button to the AppBar in the code behind. Consider the following method:

void CreateButton(string style, TappedEventHandler callback)
{
	var bt = new Button();
	bt.Loaded += StartLayoutUpdates;
	bt.Style = Application.Current.Resources[style] as Style;
	bt.Tapped += callback;
	RightPanel.Children.Add(bt);//In this example RightPanel has been named in the XAML above
}

So here we are just creating a button in the code-behind. The button is then added to a container that sits in the AppBar. All we need to do is to apply the appropriate Loaded handler for the button. And that's it! This method could now be used like

CreateButton("CropAppBarButtonStyle", (s, e) =>
{
	//What to do on tapped?!
});
Created . Last updated .

References

Sharing is caring!