25 January 2015

Keeping popups above the bottom app bar in Windows 8.1 store apps

Recently, I wrote about the KeepFromBottomBehavior that helped me to deal with popups that appeared on the bottom of the screen, but were covered by the app bar when using the full screen by applying ApplicationViewBoundsMode.UseCoreWindow. When I started porting parts of Travalyzer to Windows 8.1 as a companion app, I kind of ran into the same problem: popup appearing ‘under’ the bottom app bar.

image

(This time I replaced the map by a simple blue area, to prevent you from needing to install the Bing Maps SDK to run the sample).

So I kind-of ported my KeepFromBottomBehavior to Windows 8.1, and got the following result.image

Which is exactly what I wanted. If the App bar is open, the popup up appears above the App bar, if it is closed, it appears on the very bottom of the screen:

image

The code is actually a lot simpler than the KeepFromBottomBehavior for Windows Phone 8.1,

using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace WpWinNl.Behaviors
{
  public class KeepFromBottomBehavior : SafeBehavior<FrameworkElement>
  {
    private double originalBottomMargin;
    private AppBar bottomAppBar;
    protected override void OnSetup()
    {
      var page = AssociatedObject.GetVisualAncestors().OfType<Page>().First();
      bottomAppBar = page.BottomAppBar;
      bottomAppBar.Opened += BottomAppBarManipulated;
      bottomAppBar.Closed += BottomAppBarManipulated;
      originalBottomMargin = AssociatedObject.Margin.Bottom;
      UpdateBottomMargin();
      base.OnSetup();
    }

    void BottomAppBarManipulated(object sender, object e)
    {
      UpdateBottomMargin();
    }

    protected override void OnCleanup()
    {
      bottomAppBar.Opened -= BottomAppBarManipulated;
      bottomAppBar.Closed -= BottomAppBarManipulated;
      base.OnCleanup();
    }

    private async void UpdateBottomMargin()
    {
      await Task.Delay(1);
      var currentMargins = AssociatedObject.Margin;

      var newMargin = new Thickness(currentMargins.Left, 
currentMargins.Top, currentMargins.Right, originalBottomMargin + (bottomAppBar.IsOpen ?
bottomAppBar.ActualHeight : 0)); AssociatedObject.Margin = newMargin; } public double WindowHeight { get; set; } } }

Here we see this behavior works very different from it’s Windows Phone counterpart. It finds the Page this behavior is on - using the GetVisualAncestors extension methods from WpWinNl - and attaches itself to the bottom AppBar’s Opened and Closed events. When these events are fired, the bottom margin of the whole panel this behavior is attached to is increased with the actual height of the bottom AppBar. Easy as cake. The only weird thing is the Task.Delay(1). I found out that if you omit that, the behavior’s actions will flip-flop, that is, it won’t move the panel when you open the AppBar, but it will move it up when you close it. I think this has something to do with the calculation of the ActualHeight not being done yet. By using Task.Delay, with however small the value, the whole event is asynchronous and thus the calculation is not being blocked by whatever it was being blocked ;)

The WindowHeight is no longer a dependency property and is in fact no longer used, it’s just kept here to keep the behavior’s signature identical to it’s Windows Phone counterpart. In the sample solution you will see this behavior works stand-alone, it does not need SizeListenerBehavior as a companion to bind to.

The sample solution is essentially the same as in the previous article, but contains both code for Windows and Windows Phone now.

No comments: