Easily manage the Title Bar in Windows 10 apps

19/05/2015 Leave a comment

Windows 10 apps run in a window like any standard desktop application. This means that now they have a title bar, that by default is gray, has the name of the app on the left and the standard three button on the right. However, if we need, we can customize its appearance and colors.

To reach this goal, we can leverage the ApplicationView.GetForCurrentView().TitleBar property. So, we need to do this via code. For example:

var titleBar = ApplicationView.GetForCurrentView().TitleBar;
titleBar.BackgroundColor = Colors.DarkBlue;
titleBar.ForegroundColor = Colors.White;
titleBar.ButtonBackgroundColor = Colors.Maroon;
titleBar.ButtonForegroundColor = Colors.Yellow;

With these instructions, we obtain the following chrome:

Windows 10 app Title Bar with custom chrome

Windows 10 app Title Bar with custom chrome

This solution works great, but we can improve it by defining the appropriate attached properties on the Page class, so that we can customize Title Bar colors via XAML:

public static class TitleBar
{
    public static readonly DependencyProperty ForegroundColorProperty =
        DependencyProperty.RegisterAttached("ForegroundColor", typeof(Color),
        typeof(TitleBar), 
        new PropertyMetadata(null, OnForegroundColorPropertyChanged));

    public static Color GetForegroundColor(DependencyObject d)
    {
        return (Color)d.GetValue(ForegroundColorProperty);
    }

    public static void SetForegroundColor(DependencyObject d, Color value)
    {
        d.SetValue(ForegroundColorProperty, value);
    }

    private static void OnForegroundColorPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var color = (Color)e.NewValue;
        var titleBar = ApplicationView.GetForCurrentView().TitleBar;
        titleBar.ForegroundColor = color;
    }

    public static readonly DependencyProperty BackgroundColorProperty =
        DependencyProperty.RegisterAttached("BackgroundColor", typeof(Color),
        typeof(TitleBar), 
        new PropertyMetadata(null, OnBackgroundColorPropertyChanged));

    public static Color GetBackgroundColor(DependencyObject d)
    {
        return (Color)d.GetValue(BackgroundColorProperty);
    }

    public static void SetBackgroundColor(DependencyObject d, Color value)
    {
        d.SetValue(BackgroundColorProperty, value);
    }

    private static void OnBackgroundColorPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var color = (Color)e.NewValue;
        var titleBar = ApplicationView.GetForCurrentView().TitleBar;
        titleBar.BackgroundColor = color;
    }

    public static readonly DependencyProperty ButtonForegroundColorProperty =
        DependencyProperty.RegisterAttached("ButtonForegroundColor", typeof(Color),
        typeof(TitleBar), 
        new PropertyMetadata(null, OnButtonForegroundColorPropertyChanged));

    public static Color GetButtonForegroundColor(DependencyObject d)
    {
        return (Color)d.GetValue(ButtonForegroundColorProperty);
    }

    public static void SetButtonForegroundColor(DependencyObject d, Color value)
    {
        d.SetValue(ButtonForegroundColorProperty, value);
    }

    private static void OnButtonForegroundColorPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var color = (Color)e.NewValue;
        var titleBar = ApplicationView.GetForCurrentView().TitleBar;
        titleBar.ButtonForegroundColor = color;
    }

    public static readonly DependencyProperty ButtonBackgroundColorProperty =
        DependencyProperty.RegisterAttached("ButtonBackgroundColor", typeof(Color),
        typeof(TitleBar), 
        new PropertyMetadata(null, OnButtonBackgroundColorPropertyChanged));

    public static Color GetButtonBackgroundColor(DependencyObject d)
    {
        return (Color)d.GetValue(ButtonBackgroundColorProperty);
    }

    public static void SetButtonBackgroundColor(DependencyObject d, Color value)
    {            
        d.SetValue(ButtonBackgroundColorProperty, value);
    }

    private static void OnButtonBackgroundColorPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var color = (Color)e.NewValue;
        var titleBar = ApplicationView.GetForCurrentView().TitleBar;
        titleBar.ButtonBackgroundColor = color;
    }
}

These attached properties allow to set the same properties we have seen before, but now directly in XAML:

<Page
    x:Class="FirstDemo.MainPage"
    local:TitleBar.BackgroundColor="DarkBlue"
    local:TitleBar.ForegroundColor="White"
    local:TitleBar.ButtonBackgroundColor="Maroon"
    local:TitleBar.ButtonForegroundColor="Yellow"
    ...>

It’s more elegant and removes the need of code-behind. Note that other properties are available:

  • ButtonHoverBackgroundColor
  • ButtonHoverForegroundColor
  • ButtonInactiveBackgroundColor
  • ButtonInactiveForegroundColor
  • ButtonPressedBackgroundColor
  • ButtonPressedForegroundColor
  • InactiveBackgroundColor
  • InactiveForegroundColor

Using the same approach we have just analyzed, we can easily add the corresponding attached properties.

But we can do more. First of all, we can extend the UI into the Title Bar, making it a “chromeless” app. We need just one line of code:

CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;

Note that in this case we are using CoreApplication.GetCurrentView, while in the previous snippet we set colors with ApplicationView.GetForCurrentView. In particular, we change the value of the ExtendViewIntoTitleBar property.

However, if we remove the title bar at all, we can’t move the window around. Actually, there is an area immediately to the left of title bar buttons which can be grabbed to drag the window, but it’s quite small and users will not expect this behavior.

The correct solution is to use the Window.Current.SetTitleBar method to set an element to act like the default title bar. So, let’s try to add this feature with a Behavior. The first thing to do is to add a reference to the Behavior SDK:

Adding the Behavior SDK to the project

Adding the Behavior SDK to the project

For the moment, we can ignore the compatibility warning. Now define the TitleBarBehavior:

public class TitleBarBehavior : DependencyObject, IBehavior
{
    public DependencyObject AssociatedObject { get; private set; }

    public void Attach(DependencyObject associatedObject)
    {
        var newTitleBar = associatedObject as UIElement;
        if (newTitleBar == null)
            throw new ArgumentException(
                "TitleBarBehavior can be attached only to UIElement");

        Window.Current.SetTitleBar(newTitleBar);
    }

    public void Detach() { }

    public bool IsChromeless
    {
        get { return (bool)GetValue(IsChromelessProperty); }
        set { SetValue(IsChromelessProperty, value); }
    }

    public static readonly DependencyProperty IsChromelessProperty =
        DependencyProperty.Register("IsChromeless",
        typeof(bool),
        typeof(TitleBarBehavior),
        new PropertyMetadata(false, OnIsChromelessChanged));

    private static void OnIsChromelessChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        CoreApplication.GetCurrentView().TitleBar
            .ExtendViewIntoTitleBar = (bool)e.NewValue;
    }
}

In the Attach method (lines 5-13), we verify that the associated object is a UIElement and, if so, we invoke the Window.Current.SetTitleBar method to use it as Title Bar. Now let’s take a look to the following XAML:

<Page
    ...
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    ...>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Border x:Name="customTitleBar" VerticalAlignment="Top" Height="32">
            <Border.Background>
                <LinearGradientBrush>
                    <GradientStop Color="Blue" Offset="0" />
                    <GradientStop Color="WhiteSmoke" Offset="1.0" />
                </LinearGradientBrush>
            </Border.Background>
            <StackPanel Margin="12,5,5,5" Orientation="Horizontal">
                <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" 
                          Foreground="White" VerticalAlignment="Center" Margin="0,0,8,0" />
                <TextBlock Text="My awesome app title" Foreground="White"
                           VerticalAlignment="Center"/>
            </StackPanel>

            <i:Interaction.Behaviors>
                <local:TitleBarBehavior IsChromeless="True"/>
            </i:Interaction.Behaviors>
        </Border>
        <Grid Grid.Row="1">
            ...
        </Grid>
    </Grid>
</Page>

Note that the TitleBarBehavior is attached to the customTitleBar object (lines 25-28). Running the app, we’ll see the following output:

Our app with a custom title bar

Our app with a custom title bar

In this way, Windows will handle input to the title bar XAML, so that we can move the window by dragging the elements in the title bar, or invoke the title bar context menu by right-clicking.

Adaptive Triggers, RelativePanel and DataTemplate in the Universal Windows Platform

12/05/2015 4 comments

When developing for the Universal Windows Platform, we can use Adaptive Triggers and controls like the RelativePanel to dynamically tailor the UI of our apps according to various conditions, in particular the screen resolution. We can for example modify the dimension of an image, the font size of a text, the margin between objects and rearrange them as the screen space changes.

Let’s see a simple example:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <RelativePanel>
        <Image x:Name="world" Source="/Assets/World.jpg" Height="250" />
        <TextBlock x:Name="welcomeMessage" RelativePanel.Below="world"
                   RelativePanel.AlignHorizontalCenterWith="world"
                   FontSize="28" Text="Hello World!" />
    </RelativePanel>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="narrowView">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="0" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="world.Height" Value="100" />
                    <Setter Target="welcomeMessage.FontSize" Value="14" />
                    <Setter Target="welcomeMessage.(RelativePanel.RightOf)"
                            Value="world" />
                    <Setter 
                        Target="welcomeMessage.(RelativePanel.AlignVerticalCenterWith)"
                        Value="world" />
                    <Setter 
                        Target="welcomeMessage.(RelativePanel.AlignHorizontalCenterWith)"
                        Value="{x:Null}" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="wideView">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="720" />
                </VisualState.StateTriggers>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>

The default UI contains an Image with an Height of 150 pixels (line 3) and a TextBlock with a FontSize of 28 (lines 4-6). These two controls are put inside a RelativePanel (lines 2-7): using the attached properties RelativePanel.Below and RelativePanel.AlignHorizontalCenterWith on the TextBlock (line 4-5), it is placed below the Image and horizontally centered:

The UI when effective pixels are >= 720

The UI when effective pixels are >= 720

Then, in the VisualStateManager section (lines 9-34), we have defined two states based on AdaptiveTrigger.MinWindowWidth condition.

The first one, narrowView, is applied when the screen width is between 0 (line 13) and 719 effective pixels (that is the lower bound limit defined in the next trigger, line 30). In this case, the Image height is set to 150 pixels, the font size of the TextBlock becomes 14 and the RelativePanel attached properties are modified so that the text appears vertically centered on the right of the image:

The UI when effective pixels are < 720

The UI when effective pixels are < 720

The other state, wideView, is used when the effective pixels are grater than or equal to 720. We haven’t defined any Setters, so the original properties are applied. In other words, the UI will be the default one we have defined directly in XAML (see first screenshot).

This approach works great and allows to easily adapt the UI according to the device characteristics. However, there is a particular situation we must keep in mind: we can’t directly use Adaptive Triggers with objects contained in a DataTemplate. Suppose we have the following ListView:

<ListView x:Name="peopleView" SelectionMode="None">
    <ListView.ItemTemplate>
        <DataTemplate>
            <RelativePanel Margin="8">
                <Image x:Name="personImage" Width="120" Height="120" Margin="12"
                       Source="{Binding ImageUrl}" />
                <TextBlock x:Name="personName" RelativePanel.Below="personImage" 
                       RelativePanel.AlignHorizontalCenterWith="personImage"
                       Text="{Binding FullName}"
                       FontSize="16" />
            </RelativePanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

If we want to change properties of personImage and personName based of screen size, we can’t just add a VisualStateManger inside the DataTemplate. Instead, we need to create a User Control and put the DataTemplate content inside it:

<UserControl
    x:Class="FirstDemo.PersonItem"
    ...>

    <RelativePanel Margin="8">
        <Image x:Name="personImage" Width="120" Height="120" Margin="12"
                                       Source="{Binding ImageUrl}" />
        <TextBlock x:Name="personName" RelativePanel.Below="personImage" 
                   RelativePanel.AlignHorizontalCenterWith="personImage"
                   Text="{Binding FullName}"
                   FontSize="16" />

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="narrowView">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="0" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="personImage.Width" Value="64" />
                        <Setter Target="personImage.Height" Value="64" />
                        <Setter Target="personName.FontSize" Value="12" />
                        <Setter Target="personName.(RelativePanel.RightOf)" 
                                Value="personImage" />
                        <Setter 
                            Target="personName.(RelativePanel.AlignVerticalCenterWith)" 
                            Value="personImage" />
                        <Setter 
                            Target="personName.(RelativePanel.AlignHorizontalCenterWith)" 
                            Value="{x:Null}" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="wideView">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="720" />
                    </VisualState.StateTriggers>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    </RelativePanel>
</UserControl>

In this way, we can use Adaptive Triggers as we see in our first example to dynamically change the appearance of all the items in the ListView (lines 13-39). Finally, let’s update the ListView definition to use the User Control:

<ListView x:Name="peopleView" SelectionMode="None">
    <ListView.ItemTemplate>
        <DataTemplate>
            <local:PersonItem />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView> 

And now everything will work as expected.

Binding to Enum in Universal apps with localization

19/03/2015 1 comment

When we work with enums, we may want to display description messages associated to symbolic names. For example, suppose we have the following model in our app that uses the MVVM pattern:

public enum Status
{
    Free,
    NotAvailable,
    DoNotDisturb
}

public class Contact
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Status CurrentStatus { get; set; }
}

If we bind the CurrentStatus property of Contact class, the desired result is to have values such as Free, Not Available and Do Not Disturb.

This task can be simply accomplished using Custom Attributes and a Converter. First of all, create a Display attribute and use it to decorate the enum:

public class DisplayAttribute : Attribute
{
    public string Name { get; private set; }

    public DisplayAttribute(string name)
    {
        Name = name;
    }
}

public enum Status
{
    [Display("Free")]
    Free,
    [Display("Not Available")]
    NotAvailable,
    [Display("Do Not Disturb")]
    DoNotDisturb
}

Then, we define the following Converter:

public class EnumToStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value == null || !(value is Enum))
            return null;

        var @enum = value as Enum;
        var description = @enum.ToString();

        var attrib = this.GetAttribute<DisplayAttribute>(@enum);
        if (attrib != null)
            description = attrib.Name;

        return description;
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        string language)
    {
        throw new NotImplementedException();
    }

    private T GetAttribute<T>(Enum enumValue) where T : Attribute
    {
        return enumValue.GetType().GetTypeInfo()
            .GetDeclaredField(enumValue.ToString())
            .GetCustomAttribute<T>();
    }
}

At line 11 we check whether the enum value is decorated with the Display attribute. If so, we retrieve its Name property, that contains the actual string we want to use.

The last thing to do is to use the EnumToStringConverter in our binding declaration. For example:

<TextBlock Text="{Binding CurrentStatus, 
    Converter={StaticResource EnumToStringConverter}}" />

And what’s about localization? In a real world scenario we want strings localized according to user culture. Thanks to the built-in support in WinRT, this is straightforward. We just need to modify a bit our Converter:

public class EnumToStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value == null || !(value is Enum))
            return null;

        var @enum = value as Enum;
        var description = @enum.ToString();

        var attrib = this.GetAttribute<DisplayAttribute>(@enum);
        if (attrib != null)
        {
            var resource = new ResourceLoader();
            if (!string.IsNullOrWhiteSpace(resource.GetString(attrib.Name)))
                description = resource.GetString(attrib.Name);
            else
                description = attrib.Name;
        }

        return description;
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
        string language)
    {
        throw new NotImplementedException();
    }

    private T GetAttribute<T>(Enum enumValue) where T : Attribute
    {
        return enumValue.GetType().GetTypeInfo()
            .GetDeclaredField(enumValue.ToString())
            .GetCustomAttribute<T>();
    }
}

At lines 14-16 we create a ResourceLoader and try to get the string that corresponds to the Name property of the attribute. So, to enable localization of enum values we simply need to add a Resource file (resw) for each language and define resource names accordingly.

If, instead, the string isn’t found with the ResourceLoader, the original Display.Name property is used (line 18), as in the first version of the Converter.

Categories: .NET, C#, MVVM, Windows Phone, WinRT

Windows 10 on ioProgrammo

26/02/2015 Comments off

I have written an article for the n°196 of ioProgrammo (March/April 2015) in which I talk about the latest news on Windows 10 and how they will impact the world of developers.

ioProgrammo March/April 2015

ioProgrammo March/April 2015

Categories: General

An improved NavigationService for MVVM Light in Universal apps

24/02/2015 4 comments

Some times ago we talked about the new NavigationService that has been introduced with MVVM Light 5.

The implementation for Universal apps is very useful as it covers all the basic needs. However, at this time it lacks some important features, like a way to determine whether the navigation can go back and a method to remove the most recent available entry from the back stack.

So, we can extend the built-in NavigationService defining an interface like this:

public interface INavigationServiceEx : INavigationService
{
    bool CanGoBack { get; }
    
    bool RemoveBackEntry();       
}

And the corresponding implementation:

public class NavigationServiceEx : NavigationService, INavigationServiceEx
{
    public bool CanGoBack
    {
        get
        {
            var frame = this.GetMainFrame();
            if (frame != null)
                return frame.CanGoBack;

            return false;
        }
    }

    public bool RemoveBackEntry()
    {
        var frame = this.GetMainFrame();
        if (frame.CanGoBack)
        {
            frame.BackStack.RemoveAt(frame.BackStackDepth - 1);
            return true;
        }

        return false;
    }

    private Frame GetMainFrame()
    {
        return (Frame)Window.Current.Content;
    }
}

In this class, we simply get the current Frame and then access its CanGoBack and BackStack property.

Now we can use the new NavigationServiceEx in the usual way:

static ViewModelLocator()
{
    ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

    var navigationService = CreateNavigationService();
    SimpleIoc.Default.Register<INavigationServiceEx>(() => navigationService);

    // ...
}

private static INavigationServiceEx CreateNavigationService()
{
    var navigationService = new NavigationServiceEx();
    navigationService.Configure(Constants.SecondPage, typeof(SecondPage));

    return navigationService;
}

Of course, all ViewModels need to refer the new INavigationServiceEx interface (instead of the default INavigationService provided by MVVM Light).

Categories: C#, MVVM, Windows Phone, WinRT

How to open and close Flyouts in Universal apps using MVVM

15/01/2015 5 comments

Some times ago we talked about how to open attached Flyouts and close them using custom behaviors and MVVM. This was necessary because the Flyout control doesn’t expose properties to control its visibility, so we need to use its ShowAt and Hide methods explicitly.

While this approach works correctly, we can obtain the same result in a more elegant way with two simple Attached Properties. The following is a generic version of the implementation shown in the article Using Windows 8.1 Flyout with MVVM and works with every control, not only buttons (remember, in fact, that we can attach a Flyout on any FrameworkElement).

Let’s start creating a FlyoutHelper class:

public static class FlyoutHelper
{
    public static readonly DependencyProperty IsVisibleProperty = 
        DependencyProperty.RegisterAttached(
        "IsOpen", typeof(bool), typeof(FlyoutHelper), 
        new PropertyMetadata(true, IsOpenChangedCallback));

    public static readonly DependencyProperty ParentProperty = 
        DependencyProperty.RegisterAttached(
        "Parent", typeof(FrameworkElement), typeof(FlyoutHelper), null);

    public static void SetIsOpen(DependencyObject element, bool value)
    {
        element.SetValue(IsVisibleProperty, value);
    }

    public static bool GetIsOpen(DependencyObject element)
    {
        return (bool)element.GetValue(IsVisibleProperty);
    }

    private static void IsOpenChangedCallback(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        var fb = d as FlyoutBase;
        if (fb == null)
            return;

        if ((bool)e.NewValue)
        {
            fb.Closed += flyout_Closed;
            fb.ShowAt(GetParent(d));
        }
        else
        {
            fb.Closed -= flyout_Closed;
            fb.Hide();
        }
    }

    private static void flyout_Closed(object sender, object e)
    {
        // When the flyout is closed, sets its IsOpen attached property to false.
        SetIsOpen(sender as DependencyObject, false);
    }

    public static void SetParent(DependencyObject element, FrameworkElement value)
    {
        element.SetValue(ParentProperty, value);
    }

    public static FrameworkElement GetParent(DependencyObject element)
    {
        return (FrameworkElement)element.GetValue(ParentProperty);
    }
}

This code adds an IsOpen Attached Property to flyout objects. When it changes (lines 22-39), we check whether we want to open or close the Flyout: in the first case, we call the ShowAt method (lines 32), that actually displays it. Since this method requires a FrameworkElement that represents the flyout’s placement target, we need also to specify the target control for the popup, so we have defined a Dependecy Property named Parent that holds the container of the Flyout. If, instead, we are about to dismiss the control, when IsOpen is set to false, we invoke its Hide method (line 37).

Note that we also need to register for the Closed event (line 31), that occurs when the flyout is hidden. The Flyout, in fact, implements the light-dismiss behavior: it can be closed simply touching anywhere on the screen outside of it, so it is necessary to handle the Closing event and set the IsOpen property to false, to reflect the correct state of the Flyout.

Now we can define a ViewModel like this:

public class MainViewModel : ViewModelBase
{
    private bool isOpen;
    public bool IsOpen
    {
        get { return isOpen; }
        set { this.Set(ref isOpen, value); }
    }

    public RelayCommand OpenCommand { get; set; }
    public RelayCommand CloseCommand { get; set; }

    public MainViewModel()
    {
        OpenCommand = new RelayCommand(() => IsOpen = true);
        CloseCommand = new RelayCommand(() => IsOpen = false);
    }
}

We have a command to open the Flyout (setting the IsOpen variable to true, line 15) and, similarly, a command to close it (line 16). In this way, in the View we can use the following XAML:

<Button Content="Open Flyout" x:Name="button" Command="{Binding OpenCommand}">
    <FlyoutBase.AttachedFlyout>
        <Flyout controls:FlyoutHelper.IsOpen="{Binding IsOpen, Mode=TwoWay}" 
                controls:FlyoutHelper.Parent="{Binding ElementName=button}">
            <StackPanel>
                <TextBlock Text="Awesome Flyout!" Margin="10" />
                <Button Content="Close" Command="{Binding CloseCommand}" 
                        HorizontalAlignment="Right" />
            </StackPanel>
        </Flyout>
    </FlyoutBase.AttachedFlyout>
</Button>

Moveover, using the Behavior SDK, we can use the same approach to attach a Flyout to any control:

<Page
    ...
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    ...>

    ...
    <Image x:Name="image" Source="/Assets/Logo.scale-100.png" 
           Height="100" Width="100">
        <FlyoutBase.AttachedFlyout>
            <Flyout controls:FlyoutHelper.IsOpen="{Binding IsOpen, Mode=TwoWay}" 
                    controls:FlyoutHelper.Parent="{Binding ElementName=image}">
                <StackPanel>
                    <TextBlock Text="Awesome Flyout!" Margin="10" />
                    <Button Content="Close" Command="{Binding CloseCommand}" 
                            HorizontalAlignment="Right" />
                </StackPanel>
            </Flyout>
        </FlyoutBase.AttachedFlyout>
        <i:Interaction.Behaviors>
            <core:EventTriggerBehavior EventName="Tapped">
                <core:InvokeCommandAction Command="{Binding OpenCommand}" />
            </core:EventTriggerBehavior>
        </i:Interaction.Behaviors>
    </Image>
    ...
</Page>

The EventTriggerBehavior declared at lines 21-23 invokes the OpenCommand action as soon as the Tapped event raises. As seen before, this sets the IsOpen property of the ViewModel to true, so the Flyout attached property displays the popup.

Categories: .NET, C#, MVVM, Windows Phone, WinRT

Using “AndContinue” methods in Windows Phone Store apps with MVVM

07/01/2015 1 comment

As MSDN says, “Certain memory-intensive API on Windows Phone 8.1 contain AndContinue methods. When you call an AndContinue method, your app is deactivated while the operation completes in order to conserve memory. On low-memory devices, your app might even be terminated. Because of this possibility, you have to call different methods in a Windows Phone Store app than you call in a Windows Store app to continue your app after these operations.”.

This happens, for example, when we want to select an image using the FilePicker. On Windows Phone Store apps, we need to call the PickSingleAndContinue method. In this case, we have the problem to continue our apps after calling this method.

The topic How to continue your Windows Phone Store app after calling an AndContinue method on MSDN introduces the ContinuationManager, a class that works together with the SuspensionManager to make it easier to continue our app.

This code, however, is based on a code-behind approach. If we’re following the MVVM pattern, we need to change it in order to invoke continuation methods on a ViewModel. So, we can rewrite the Continue method as follows:

public class ContinuationManager
{
    // ...
    
    private FrameworkElement GetCurrentView()
    {
        var frame = Window.Current.Content as Frame;
        if (frame != null)
            return frame.Content as FrameworkElement;

        return Window.Current.Content as FrameworkElement;
    }

    internal void Continue(IContinuationActivatedEventArgs args)
    {
        var view = this.GetCurrentView();
        if (view == null)
            return;

        this.Continue(args, view.DataContext);
    }

    internal void Continue(IContinuationActivatedEventArgs args, object dataContext)
    {
        if (args == null)
            throw new ArgumentNullException("args");

        if (this.args != null)
            throw new InvalidOperationException("Can't set args more than once");

        this.args = args;
        this.id = Guid.NewGuid();

        if (dataContext == null)
            return;

        switch (args.Kind)
        {
            case ActivationKind.PickFileContinuation:
                var fileOpenPickerViewModel = dataContext as IFileOpenPickerContinuable;
                if (fileOpenPickerViewModel != null)
                    fileOpenPickerViewModel.ContinueFileOpenPicker
                        (args as FileOpenPickerContinuationEventArgs);
                break;

            case ActivationKind.PickSaveFileContinuation:
                var fileSavePickerViewModel = dataContext as IFileSavePickerContinuable;
                if (fileSavePickerViewModel != null)
                    fileSavePickerViewModel.ContinueFileSavePicker
                        (args as FileSavePickerContinuationEventArgs);
                break;

            case ActivationKind.PickFolderContinuation:
                var folderPickerViewModel = dataContext as IFolderPickerContinuable;
                if (folderPickerViewModel != null)
                    folderPickerViewModel.ContinueFolderPicker
                        (args as FolderPickerContinuationEventArgs);
                break;

            case ActivationKind.WebAuthenticationBrokerContinuation:
                var wabViewModel = dataContext as IWebAuthenticationContinuable;
                if (wabViewModel != null)
                    wabViewModel.ContinueWebAuthentication
                        (args as WebAuthenticationBrokerContinuationEventArgs);
                break;
        }
    }
}

The GetForCurrentView method, at lines 5-12, retrieves the view (i.e., the page) that is currently shown. Then, in the Continue method at lines 23-67, we check the Kind property of IContinuationActivatedEventArgs (that comes from the OnActivated method of App.xaml.cs file): based on its value, we try to cast the DataContext of the page (i.e., its ViewModel) to a particular interface (that is declared below in the file).

If the conversion succeeds, it means that the ViewModel supports this kind of continuation, so the corresponding method is invoked. For example, in case of PickFileContinuation (lines 39-44), we cast the DataContext to IFileOpenPickerContinuable and then call its ContinueFileOpenPicker method passing information about the selected file.

In this way, our ViewModel looks like (the example is based on MVVM Light):

public class PhotoViewModel : ViewModelBase, IFileOpenPickerContinuable
{
    private string photoPath;
    public string PhotoPath
    {
        get { return photoPath; }
        set { this.Set(ref photoPath, value); }
    }

    public RelayCommand SelectPhotoCommand { get; set; }

    public PhotoViewModel()
    {
        this.CreateCommands();
    }

    private void CreateCommands()
    {
        SelectPhotoCommand = new RelayCommand(() =>
        {
            // Calls the method that opens the FilePicker page.
            // When selected, the method "ContinueFileOpenPicker"
            // from IFileOpenPickerContinuable interface
            // will be invoked.
            var picker = new FileOpenPicker();

            picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
            picker.SettingsIdentifier = "continuationmanagerdemo";
            picker.ViewMode = PickerViewMode.List;
            picker.FileTypeFilter.Add(".jpg");

            picker.PickSingleFileAndContinue();
        });
    }

    public void ContinueFileOpenPicker(FileOpenPickerContinuationEventArgs args)
    {
        // The "args" object contains information about selected file(s).
        if (args.Files.Any())
        {
            var file = args.Files[0];
            PhotoPath = file.Path;
        }
        else
        {
            PhotoPath = null;
        }
    }
}

The SelectPhotoCommand (lines 19-33) calls the FileOpenPicker.PickSingleFileAndContinue method, that opens a page allowing the selection of a file (a JPG image, in this case). Thanks to the ContinuationManager, as the ViewModel implements the IFileOpenPickerContinuable interface, the ContinueFileOpenPicker method will be automatically invoked when the user selects a file. We retrieve the first one and save its Path property, so we can show it, for example, in an Image control that is bound to it.

You can download a working example of the ContinuationManger with MVVM using the link below:

Using AndContinue methods in Windows Phone Store apps with MVVM

Categories: .NET, C#, MVVM, Windows Phone
Follow

Get every new post delivered to your Inbox.

Join 40 other followers