Showing posts with label Application Bar. Show all posts
Showing posts with label Application Bar. Show all posts

28 February 2016

Keeping UI elements above and below the Universal Windows Platform app bars

A little history - or how I messed up

I initially created this behavior  for Windows Phone 8.1, and then ported it to Windows 8.1 store apps. I presume at one point during creating WpWinNl for Universal Windows Platform I found out that whatever I tried back then did not work anymore, and Microsoft seemed to be moving away from the app bar anyway… whatever, I can’t remember. Fact is, the KeepFromBottomBehavior fell by the wayside during the beta times of Windows 10 and I forgot about it. Time moved on, Windows SDKs finalized and the app bar stayed after all… and last week I got an email from Craig Trought who apparently had used this behavior in previous apps and had found out that it was not in the prerelease WpWinNl UWP package, which kind of was a bit unfortunate as he was now trying to make his app run on UWP. Can’t have people being stuck on UWP development because of me dropping a ball, so I took on the challenge to get the behavior to work again. And in the process I discovered that indeed my approach to this solution did not work at all anymore. Apparently the way Microsoft implemented the app bars had changed considerably.I also found out Craig is quite a persistent fellow, who found I had missed a few points in my first two attempts to port it to UWP.

Purpose of the behavior(s)

The idea behind the KeepFromBottomBehavior (and now it’s little brother, KeepFromTopBehavior) is to prevent (parts of) the UI being covered by the app bar, whether it’s in its closed state or being opened. It should also work when the ApplicationView’s DesiredBoundsMode is set to ApplicationViewBoundsMode.UseCoreWindow and take into account that most Windows 10 mobile devices now have a ‘soft keys navigation bar’, that is – there are no separate buttons – either physical or capacitive– on the device anymore, but just a button bar drawn on the  of the screen. To make things more complicated, you can hide this button bar by swiping from bottom to top over it (making the same gesture brings it back, too) to let apps make full use of the screen real estate. Too make a little more clear what I mean, consider these three screen shots of the app in the mobile emulator:

screen1screen2screen3

To the left, you see how the app looks with both app bars closed. In the middle, you see what happens when both app bars are opened – they cover part of the UI. Ugly. The rightmost screenshot shows what happens when the behaviors are applied – key parts of the UI are moved to stay into view.

ApplicationViewBoundsMode? What ApplicationViewBoundsMode?

Usually the  ApplicationView’s DesiredBoundMode is set to  ApplicationViewBoundsMode.UseVisible. In that case, Windows 10 only provides you with the space between the top app bar and the bottom app bar, if those exist – in their closed state. In Windows 10 mobile, if there is no top bar, the area covered by status bar – the thing on top that shows your battery status and stuff like that –  is not available either.

If you, on the other hand, specify ApplicationViewBoundsMode.UseCoreWindow you get all of the screen to draw on, and stuff appears behind app bars, navigation bars and status bars. This mode is best used in moderation, but can be useful if you for instance want to draw a screen filling map (as in my sample) but then it’s your own responsibility to make sure things don’t get covered up. This is one of the use cases of the behavior. The other is to make sure that whenever the app bars get opened, they still don’t cover op part of the UI. To make things extra complicated, it must also support programmatically made changes to the appbar size (from minimal to compact and back) and support navigating back with NavigationCacheMode.Required.

To see how it looks like – more screenshots!

screen4screen5screen6screen7

To the left you see the app with closed app bars, which I intentionally made translucent so you can see what is going on. Although the map nicely covers the whole screen, the rest of the UI looks pretty horrible. The 2nd to left screenshot shows the app with both app bars opened, and that is a complete mess. The 3rd to left shows closed app bars with the behaviors applied, and the rightmost shows both app bars open, also with the behaviors applied. The parts of the UI we want to not be covered by anything, are moved up- and downwards to stay into view. You are welcome ;)

So how does this work?

image

Very differently from the 8.1 era, I can tell you that. Using Microsoft’s new Live Visual Tree explorer (thanks tools team!) I found out something about the innards of the Windows 10 command bars – inside it there’s a popup that appears over the command par when you click the ellipses button. So if the app bar opens, we  only need to move the stuff we want to keep into view a number of pixels equal to the height of the popup minus the height of the app bar’s closed height. That works fine for the ApplicationViewBoundsMode.UseVisible mode, but for the ApplicationViewBoundsMode.UseCoreWindow things are quite a bit more complicated. The base setup of KeepFromBottomBehavior is as follows:

public class KeepFromBottomBehavior : Behavior<FrameworkElement>
{
  protected override void OnAttached()
  {
    if (!Initialize())
    {
      AssociatedObject.Loaded += AssociatedObjectLoaded;
    }
    base.OnAttached();
  }

  private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
  {
    AssociatedObject.Loaded -= AssociatedObjectLoaded;
    Initialize();
  }

  private bool Initialize()
  {
    var page = AssociatedObject.GetVisualAncestors().OfType<Page>().FirstOrDefault();
    if (page != null)
    {
      AppBar = GetAppBar(page);
      if (AppBar != null)
      {
        OriginalMargin = AssociatedObject.Margin;

        AppBar.Opened += AppBarManipulated;
        AppBar.Closed += AppBarManipulated;
        AppBar.SizeChanged += AppBarSizeChanged;
        UpdateMargin();
        ApplicationView.GetForCurrentView().VisibleBoundsChanged += VisibleBoundsChanged;
        return true;
      }
    }
    return false;
  }

  protected AppBar AppBar { get; private set; }

  protected Thickness OriginalMargin { get; private set; }

  protected virtual AppBar GetAppBar(Page page)
  {
    return page.BottomAppBar;
  }
}

In the AssociatedObjectLoaded I collect the things I am interested in:

  • The current bottom margin of the object that needs to be kept in view
  • The app bar that we are going to watch – if it’s being opened, closed, or changes size we need to act
  • Whether the visible bounds of the current view have changed. This is especially interesting when a phone is being rotated or the navigation bar is being dismissed.

The fact that the app bar is being collected by and overrideable method is to make a child class for keeping stuff from the top more easy. There is more noteworthy stuff: I try to initialize from OnAttached first, then from OnLoading is that fails. This is because if you use NavigationCacheMode.Required, the first time you hit the page OnAttached is called, but the visual tree is not read, so I have to call from OnLoaded. If you move away, then navigate back, OnLoaded is not called, only OnAttached. Navigate away and back again, then both events are called. So it’s “you never know, just try both”. Note also, by the way, the use of GetVisualAncestors – that’s a WpWinNl extension method, not part of the standard UWP API. It finds the page on which this behavior is located, and from that, the app bar we are interested in. So if one of the events we subscribed to fire, we need to recalculate the bottom margin, which is exactly as you see in code:

private void AppBarSizeChanged(object sender, SizeChangedEventArgs e)
{
  UpdateMargin();
}

private void VisibleBoundsChanged(ApplicationView sender, object args)
{
  UpdateMargin();
}

void AppBarManipulated(object sender, object e)
{
  UpdateMargin();
}

private void UpdateMargin()
{
  AssociatedObject.Margin = GetNewMargin();
}

And then we come to the heart of the matter. First we see the simple method GetDeltaMargin, that indeed calculates the difference in height between an opened and closed app bar:

protected double GetDeltaMargin()
{
  var popup = AppBar.GetVisualDescendents().OfType<Popup>().First();
  return popup.ActualHeight - AppBar.ActualHeight;
}

And then comes the real number trickery.

protected virtual Thickness GetNewMargin()
{
  var currentMargin = AssociatedObject.Margin;
  var baseMargin = 0.0;
  if (ApplicationView.GetForCurrentView().DesiredBoundsMode == 
       ApplicationViewBoundsMode.UseCoreWindow)
  {
    var visibleBounds = ApplicationView.GetForCurrentView().VisibleBounds;
    baseHeight = CoreApplication.GetCurrentView().CoreWindow.Bounds.Height - 
                   visibleBounds.Height + AppBar.ActualHeight;

    if(AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Mobile")
    {
      baseMargin -= visibleBounds.Top;
    }
  }

  return new Thickness(currentMargin.Left, currentMargin.Top, currentMargin.Right,
                          OriginalMargin.Bottom + 
                            (AppBar.IsOpen ? GetDeltaMargin() + baseMargin : baseMargin));

}

Right. If the simple flow is followed ( that is, for ApplicationViewBoundsMode.UseVisible), we simply need to add the difference in height of a closed and an opened app bar to the bottom margin. On the other hand, if ApplicationViewBoundsMode.UseCoreWindow was used, we need to calculate the base margin of the element we need to keep above the app bar, whether this bar is closed or not, for Windows doesn’t take care of that for us now. You wanted the whole window, you get the whole window. So the base margin is the whole height of the AppBar itself plus the difference between the windows height and the visible height -  that is, the height of that portion of the windows that would be used if we were just letting Windows take care of things. In addition, if we are on Windows 10 mobile, we have to subtract the visible top to take care of the status bar.

Also very important – the cleaning up if we leave the page! After all, we might return to a cached version of it! So all events are detached, and the original margin of the UI element handled by this behavior is restored.

protected override void OnDetaching()
{
  AppBar.Opened -= AppBarManipulated;
  AppBar.Closed -= AppBarManipulated;
  AppBar.SizeChanged -= AppBarSizeChanged;
  ApplicationView.GetForCurrentView().VisibleBoundsChanged -= VisibleBoundsChanged;
  ResetMargin();
  base.OnDetaching();
}

private void ResetMargin()
{
  AssociatedObject.Margin = OriginalMargin;
}

KeepFromTopBehavior

Basically we only have to override GetAppBar, GetOrginalMargin and of course GetNewMargin. This is a tiny bit simpler than KeepFromBottomBehavior – as we are setting the top we can now directly relate to the top margin differences, and don’t need to worry about differences between Windows 10 and Windows 10 mobile:

public class KeepFromTopBehavior : KeepFromBottomBehavior
{
  protected override Thickness GetNewMargin()
  {
    var currentMargin = AssociatedObject.Margin;
    var baseMargin = 0.0;
    if (ApplicationView.GetForCurrentView().DesiredBoundsMode ==
       ApplicationViewBoundsMode.UseCoreWindow)
    {
      var visibleBounds = ApplicationView.GetForCurrentView().VisibleBounds;
      baseMargin = visibleBounds.Top - 
        CoreApplication.GetCurrentView().CoreWindow.Bounds.Top + AppBar.ActualHeight;
    }
    return new Thickness(currentMargin.Left,
      OriginalMargin.Top + 
       (AppBar.IsOpen ? GetDeltaMargin() + baseMargin : baseMargin), 
        currentMargin.Right, currentMargin.Bottom);
  }

  protected override AppBar GetAppBar(Page page)
  {
    return page.TopAppBar;
  }
}

Concluding remarks

This is the first time I actually had to resort to checking for device family for UI purposes; the U in UWP makes application development so universal indeed that these kinds of things usually get abstracted pretty much away. As always, you can have a look at the sample solution, which is still the same as in the Windows 8.1 article, only I have added a UWP project. If you want to observe the difference between ApplicationViewBoundsMode.UseVisible and ApplicationViewBoundsMode.UseCoreWindows yourself, go to the App.Xaml.cs and uncomment the line that sets UseCoreWindow (line 68).

Oh by the way, this all works on Windows 10 desktop of course as well but making screenshots for mobile is a bit easier. The code for this is also in WpWinNl on Github already (be sure to head for the UWP branch), but I am still dogfooding the rest of the package so that is still not released as a NuGet package.

I want to thank Craig for being such a thorough tester – although he excused himself for being “such a pain” I would love to have more behaviors being put through the grinder like this, it really improves code quality.

23 October 2014

Making the Application Bar display on of top of the UI in Windows Phone 8.1 Store Apps

In Windows Phone 7, 8 and 8.1 Silverlight apps this is very easy: you just set the Opacity of the Application bar to any value less than 1 as described here, and the app does not reserve space for the app bar – it just displays the bar on top of the main UI like this:

image

The problem is, if you set the opacity of what is now called the CommandBar in Windows Phone 8.1 Store apps, the Application bar is translucent indeed - which you can see if you open it - but the apps still reserves space for it in it’s closed state:

image

The fix for this is surprisingly easy, but also pretty hard to find if you don’t know what you are looking for. In the end I got a tip by mail from my famous fellow MVP Rudy Huyn. You just add the following, not very obvious line of code to your app. I added it to my App.Xaml.cs, just before the line “Window.Current.Activate();”

 ApplicationView.GetForCurrentView().SetDesiredBoundsMode(
   ApplicationViewBoundsMode.UseCoreWindow);

Right. And then you get what you want (as is displayed in the first image). And once you enter this line of code into a search engine, then you find hits enough, dating back to even April. But well, that did not help me much. Hopefully this simple article will save someone else a fruitless seach ;)

To make this code compile, you will need to add a “using Windows.UI.ViewManagement;” on top of your file. And although it is in fact an article about code, I will skip the sample download app this time – for just one line of code it feels like a bit of overkill.

Update – be aware that not only the Application Bar now falls over the UI, but the status bar on top as well. So you may want to take that into account when designing your UI: keep some extra space at the top, or make in partially translucent as wel. Thanks to my fellow MVP Shawn Kendrot for pointing me to this article that he has written some time ago

01 February 2012

Behavior to force TextBox model update to prevent trouble with the ApplicationBar

A small quicky this time:

Problem:

  • TextBox, Text property bound to a string in my ViewModel
  • I type text in the TextBox
  • I click a “Save” button on my ApplicationBar
  • The string in my ViewModel is not updated. It never gets updated. WTF???

It appears the TextBox only updates it’s value to a bound string when it loses focus. And a TextBox does not lose focus when you click an ApplicationBar Button. Meh.

I have found a few solutions and workarounds, and in the end rolled my own:  a very small behavior that updates the binding every time you type something in your textbox. That’s a bit wasteful, but it works for me. It builds on the SafeBehavior pattern I wrote about earlier, and it’s so small I post it in one go:

using System.Windows.Controls;

namespace Wp7nl.Behaviors
{
  /// <summary>
  /// A behavior to for text box model update when text changes
  /// </summary>
  public class TextBoxChangeModelUpdateBehavior : SafeBehavior<TextBox>
  {
    protected override void OnSetup()
    {
      AssociatedObject.TextChanged += AssociatedObjectTextChanged;
    }

    protected override void OnCleanup()
    {
      AssociatedObject.TextChanged -= AssociatedObjectTextChanged;
    }

    void AssociatedObjectTextChanged(object sender, TextChangedEventArgs e)
    {
      var binding = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
      if (binding != null)
      {
        binding.UpdateSource();
      }
    }
  }
}

I have read amongst other things Prism has a UpdateTextBindingOnPropertyChanged behavior. Well this one will be in the next version of my #wp7nl library on codeplex 

25 February 2011

Using attached dependency properties to toggle an Application Bar Icon from viewmodel

Application Bar Icon buttons are a look-and-feel-wise very consistent and easy to understand way for every Windows Phone 7 application to give it’s users access to the most important functions. The drawback of this thing is that it’s not a real FrameworkElement, thus you cannot bind commands and other stuff to it, but as I described earlier, this has been solved by the BindableApplicationBar.

But even the BindableApplicationBar has its issues. The IconUri attribute of a BindableApplicationBarIconButton cannot be bound to – for it is no Dependecy Property. Now you can fix that, since Nicolas Humann includes the source code of his excellent solution. But I prefer to threat someone else’s code as third party component, and solved the problem using a very powerful way of extending of Silverlight/Windows Phone 7: attached dependency properties.

The problem I wanted to solve is this: I have changed my MapMania App in such a way that it now tracks your location continually if you press the “Satellite” button. But I want the user to be able to stop tracking as well, using the same button. So what used to be a simple activation button, is now a toggle button. When the user is tracking his own phone, I want the button image to change from the image of of a satellite to an image of a satellite with a big cross through it, indicating “if you press me again, I will stop tracking”. And I wanted this, of course, to be steered from the viewmodel.

This is how I solved it, with three attached dependency properties:

using System;
using System.Windows;
using Phone7.Fx.Preview;

namespace LocalJoost.Utilities
{
  /// <summary>
  /// Supports toggle of a BindableApplicationBarIconButton's icon
  /// </summary>
  public static class AppBarIconFlipper
  {
    #region IconUri
    public static readonly DependencyProperty IconUriProperty =
     DependencyProperty.RegisterAttached("IconUri",
     typeof(Uri),
     typeof(AppBarIconFlipper),
     new PropertyMetadata(IconUriPropertyChanged));

    // Called when Property is retrieved
    public static Uri GetIconUri(DependencyObject obj)
    {
      return obj.GetValue(IconUriProperty) as Uri;
    }

    // Called when Property is set
    public static void SetIconUri(
      DependencyObject obj,
      Uri value)
    {
      obj.SetValue(IconUriProperty, value);
    }

    // Called when property is changed
    private static void IconUriPropertyChanged(
     object sender,
     DependencyPropertyChangedEventArgs args)
    {
      var attachedObject = sender as BindableApplicationBarIconButton;
      if (attachedObject == null) return;
      attachedObject.IconUri = (bool)attachedObject.GetValue(ShowAlernateIconUriProperty)
                     ? (Uri)attachedObject.GetValue(AlernateIconUriProperty)
                     : (Uri)args.NewValue;
    }
    #endregion

    #region AlernateIconUri
    public static readonly DependencyProperty AlernateIconUriProperty =
     DependencyProperty.RegisterAttached("AlernateIconUri",
     typeof(Uri),
     typeof(AppBarIconFlipper),
     new PropertyMetadata(AlernateIconUriPropertyChanged));

    // Called when Property is retrieved
    public static Uri GetAlernateIconUri(DependencyObject obj)
    {
      return obj.GetValue(AlernateIconUriProperty) as Uri;
    }

    public static void SetAlernateIconUri(
      DependencyObject obj,
      Uri value)
    {
      obj.SetValue(AlernateIconUriProperty, value);
    }

    private static void AlernateIconUriPropertyChanged(
     object sender,
     DependencyPropertyChangedEventArgs args)
    {
      var attachedObject = sender as BindableApplicationBarIconButton;
      if (attachedObject == null) return;
      attachedObject.IconUri = (bool)attachedObject.GetValue(ShowAlernateIconUriProperty)
                     ? (Uri)args.NewValue
                     : (Uri)attachedObject.GetValue(IconUriProperty);
    }
    #endregion

    #region ShowAlernateIconUri
    public static readonly DependencyProperty ShowAlernateIconUriProperty =
     DependencyProperty.RegisterAttached("ShowAlernateIconUri",
     typeof(bool),
     typeof(AppBarIconFlipper),
     new PropertyMetadata(ShowAlernateIconUriPropertyChanged));

    public static bool GetShowAlernateIconUri(DependencyObject obj)
    {
      return (bool)obj.GetValue(ShowAlernateIconUriProperty);
    }

    public static void SetShowAlernateIconUri(
      DependencyObject obj,
      bool value)
    {
      obj.SetValue(ShowAlernateIconUriProperty, value);
    }

    private static void ShowAlernateIconUriPropertyChanged(
     object sender,
     DependencyPropertyChangedEventArgs args)
    {
      var attachedObject = sender as BindableApplicationBarIconButton;
      if (attachedObject == null) return;
      var value = (bool)args.NewValue;
      attachedObject.IconUri = value
                     ? (Uri)attachedObject.GetValue(AlernateIconUriProperty)
                     : (Uri)attachedObject.GetValue(IconUriProperty);
    }
    #endregion
  }
}
The only interesting code is in the “***Changed” methods, the rest is just the necessary plumbing. Anyway, in stead of setting the IconUri of the BindableApplicationBarIconButton directly, you use the attached dependency properties like this:
<Phone7Fx:BindableApplicationBarIconButton 
 Command="{Binding ShowLocation}" Text="Track" 
 LocalJoostUtils:AppBarIconFlipper.IconUri="/icons/gps.png"
 LocalJoostUtils:AppBarIconFlipper.AlernateIconUri="/icons/gps_stop.png"
 LocalJoostUtils:AppBarIconFlipper.ShowAlernateIconUri="{Binding IsTracking, Mode=TwoWay}"/>

and depending on the value of the Viewmodel property IsTracking the icon will flip from gps.png (false) to gps_stop (true) – or back.

Once again it shows Microsoft’s XAML-based platforms are as flexible as a rubber band, and wherever are holes in binding, you can almost always plug them using attached dependency properties – the super glue of MVVM, IMHO.

08 February 2011

The case for the Bindable Application Bar for Windows Phone 7

I’ve said it once and I’ll say it again:

”Thou shalt not make unto thee any Windows Phone 7 application without MVVMLight

Even when using Application Bar. I strikes me as very odd that people still use that thing for an excuse to bypass the model or even forego MVVM. As early as August a French .NET consultant called Nicolas Humann wrote an excellent blog post about an implementation of a bindable wrapper for the Application Bar. You can simply download the Phone.Fx.Preview project, add this as a project to your Windows Phone 7 solution and then, in stead of  having something like this:

<phone:PhoneApplicationPage.ApplicationBar>
  <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
    <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png"
       Text="Button 1"/>
    <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" 
       Text="Button 2"/>
    <shell:ApplicationBar.MenuItems>
      <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
      <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
  </shell:ApplicationBar.MenuItems>
  </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
and hooking up events to code in the code behind of the XAML, and then doing something complicated with messages or maybe event casting the DataContext of the page to your model type and then calling methods directly - in stead of all this hoopla and duct tape programming you can simply use
<Phone7Fx:BindableApplicationBar x:Name="AppBar" IsVisible="true" >
  <Phone7Fx:BindableApplicationBarIconButton Command="{Binding Command1}" 
    Text="Button 1" IconUri="/Images/appbar_button1.png" />
  <Phone7Fx:BindableApplicationBarIconButton Command="{Binding Command2}" 
    Text="Button 2" IconUri="/Images/appbar_button2.png" />
  <Phone7Fx:BindableApplicationBarMenuItem   
    Command="{Binding Command3}" Text="MenuItem 1" />
  <Phone7Fx:BindableApplicationBarMenuItem   
    Command="{Binding Command3}" Text="MenuItem 2" />  
</Phone7Fx:BindableApplicationBar>

and let the model do the work as it should.

The bindable application bar should be in the toolkit of every Windows Phone 7 developer. Case closed.