30 April 2014

A screen-size independent scroll-into-view pane for Windows Phone and Windows Store apps

In this article I introduced MoveObjectBehavior and a way to use it to make Facebook-app like scroll-into-view panes. All very nice, but with a minor drawback – it basically needs a fixed screen size. That’s OK under Windows Phone 8 (albeit it does not look so good on a Lumia 1520) but still – you got away with it.

That is no longer the case in Windows Phone 8.1, and it never was the case in Windows 8. You have to take multiple screen sizes into account now, and in the case of Windows 8 the screen size can change while the app is running – as the user can arbitrarily change the horizontal space an app is using while it’s running.

Like this:

Scroll-into-view pane for Windows 8

Now MoveObjectBehavior has an ActivatedXValue Property and of course I could try, in stead of setting a fixed value, to bind that to the value of ActualWidth of the Page. Unfortunately that is not a Dependecy Property, so although it works initially, changes to it’s value are not populated. Yet, as you can see above, it can be done, and with a pretty animation as well.

This, of course, is were a second behavior comes in handy. Meet SizeListenerBehavior:

using Windows.UI.Xaml;

namespace WpWinNl.Behaviors
{

  /// <summary>
  /// This behavior listens to a size change of the attached object
  /// and puts the resulting size into two bindable dependecy properties
  /// </summary>
  public class SizeListenerBehavior : SafeBehavior<FrameworkElement>
  {
    protected override void OnSetup()
    {
      AssociatedObject.SizeChanged += AssociatedObjectSizeChanged;
      base.OnSetup();
      PropagateSizes();
    }

    protected override void OnCleanup()
    {
      AssociatedObject.SizeChanged -= AssociatedObjectSizeChanged;
      base.OnCleanup();
    }

    void AssociatedObjectSizeChanged(object sender, SizeChangedEventArgs e)
    {
      PropagateSizes();
    }

    private void PropagateSizes()
    {
      WatchedObjectHeight = AssociatedObject.ActualHeight;
      WatchedObjectWidth = AssociatedObject.ActualWidth;
    }
  }
}

It is very simple - it listens to the SizeChanged event that does get triggered on any size change, then populates that to two dependency properties. And those are bindable. I omitted both properties from the code above as they are fairly standard. So if you give the behavior a name you can use Element binding to bind this properties to MoveObjectBehavior.

Which is done like this:

<Page
    x:Class="SizeListener.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
    xmlns:behaviors="using:WpWinNl.Behaviors"
    mc:Ignorable="d" 
DataContext="{Binding Source={StaticResource DemoViewModelDataSource}}"> <Page.BottomAppBar > <CommandBar x:Name="BottomBar"> <AppBarButton Icon="Help" Command="{Binding MyCommand}"> </AppBarButton> </CommandBar> </Page.BottomAppBar > <interactivity:Interaction.Behaviors> <behaviors:SizeListenerBehavior x:Name="pageRootSizeListener"/> </interactivity:Interaction.Behaviors> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" > <Grid> <interactivity:Interaction.Behaviors> <behaviors:MoveObjectBehavior Activated="{Binding ShowSettings}" Duration="1000" ActivatedXValue="{Binding WatchedObjectWidth, ElementName=pageRootSizeListener}"/> </interactivity:Interaction.Behaviors> <Grid Background="Green"> <Grid Margin="50"> <TextBlock FontSize="50">THIS IS MY MAIN PAGE</TextBlock> </Grid> </Grid> <Grid RenderTransformOrigin="0,0.5" Background="Blue"> <Grid.RenderTransform> <CompositeTransform ScaleX="-1"/> </Grid.RenderTransform> <Grid> <Grid RenderTransformOrigin="0.5,0.5" Margin="50" > <Grid.RenderTransform> <CompositeTransform ScaleX="-1"/> </Grid.RenderTransform> <TextBlock FontSize="50">THIS IS MY MESSAGE PAGE</TextBlock> </Grid> </Grid> </Grid> </Grid> </Grid> </Page>

You see the parts that are actually do the work underlined, red and bold (so I hope that this is clear for everyone, even color blind people).

The trick with the mirroring grids to put stuff to the left (and the right) of your screen is described in my article, Aligning XAML elements outside the screen using scaling (for a Facebook-like GUI), so I won’t repeat that here.

The demo solution can be found here. If you are looking for the source in the code, you can keep looking, as it’s been sitting in my WpWinNl library on CodePlex for a while – but I just did not have the time to blog about it. If you want just the behavior’s source, that’s here.

No comments: