FullScreenModeTrigger for Universal Windows Platform

14/07/2015 2 comments

Last week we talked about full-screen mode in Windows 10 Universal apps. In this scenario, we could have the need to adjust the UI when we enter full-screen mode. The best way to do so is to create an Adaptive Trigger:

public class FullScreenModeTrigger : StateTriggerBase
{
    public bool IsFullScreen
    {
        get { return (bool)GetValue(IsFullScreenProperty); }
        set { SetValue(IsFullScreenProperty, value); }
    }

    public static readonly DependencyProperty IsFullScreenProperty =
        DependencyProperty.Register("IsFullScreen", typeof(bool), 
        typeof(FullScreenModeTrigger),
        new PropertyMetadata(false, OnIsFullScreenPropertyChanged));

    private static void OnIsFullScreenPropertyChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        var obj = (FullScreenModeTrigger)d;
        if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)
        {
            var isFullScreen = ApplicationView.GetForCurrentView().IsFullScreenMode;
            obj.UpdateTrigger(isFullScreen);
        }
    }

    public FullScreenModeTrigger()
    {
        if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)
            ApplicationView.GetForCurrentView().VisibleBoundsChanged += 
                FullScreenModeTrigger_VisibleBoundsChanged;
    }

    private void FullScreenModeTrigger_VisibleBoundsChanged(ApplicationView sender, 
        object args)
    {
        UpdateTrigger(sender.IsFullScreenMode);
    }

    private void UpdateTrigger(bool isFullScreen)
    {
        SetActive(isFullScreen == IsFullScreen);
    }
}

It defines a dependency property (line 9-12) that specifies if we want to activate the trigger when in full-screen or windowed mode. Then, in the constructor we register on the ApplicationView.VisibleBoundsChanged event (line 28-29). In its event handler (lines 32-36), we check ApplicationView.IsFullScreenMode and activate the trigger based on the value of the IsFullScreen property.

Now we can use this trigger in XAML:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
      <TextBlock x:Name="fullScreenModeStatus"  
                 HorizontalAlignment="Center" 
                 VerticalAlignment="Center" />
      <Button x:Name="fullScreenMode" Content="Toggle Full Screen Mode" 
              Click="fullScreenMode_Click" />
  </StackPanel>

  <VisualStateManager.VisualStateGroups>
      <VisualStateGroup >
          <VisualState x:Name="fullScreen">
              <VisualState.StateTriggers>
                  <triggers:FullScreenModeTrigger IsFullScreen="true" />
              </VisualState.StateTriggers>
              <VisualState.Setters>
                  <Setter Target="fullScreenModeStatus.Text" 
                          Value="App is in full screen mode" />
                  <Setter Target="fullScreenMode.Content" Value="Exit full screen" />
              </VisualState.Setters>
          </VisualState>
          <VisualState x:Name="windowed">
              <VisualState.StateTriggers>
                  <triggers:FullScreenModeTrigger IsFullScreen="false" />
              </VisualState.StateTriggers>
              <VisualState.Setters>
                  <Setter Target="fullScreenModeStatus.Text" 
                          Value="App is in windowed mode" />
                  <Setter Target="fullScreenMode.Content" Value="Enter full screen" />
              </VisualState.Setters>
          </VisualState>
      </VisualStateGroup>
  </VisualStateManager.VisualStateGroups>
</Grid>

We have defined two visual states (lines 12-21 and 22-32), that are activated according to the value of the IsFullScreen property (lines 14 and 24).

Finally, in the code behind of the page let’s insert the code to enter and exit the full-screen mode:

private void fullScreenMode_Click(object sender, RoutedEventArgs e)
{
    var view = ApplicationView.GetForCurrentView();
    var isFullScreenMode = view.IsFullScreenMode;

    if (isFullScreenMode)
        view.ExitFullScreenMode();
    else
        view.TryEnterFullScreenMode();
}

We simply use a button to toggle the full-screen mode, as described in the previous post.

Full Screen Mode in Windows 10 Universal apps

09/07/2015 2 comments

Windows 8/8.1 apps on a Windows 10 Desktop are shown in window, as usual. But, in this case, on the title bar appears a button that allows to put the app in full-screen mode, to resemble the old style.

In fact, we can leverage a new API to show our apps in full-screen mode even if we’re using the new Universal Windows Platform. Note that in this case we haven’t a built-in command to enter/exit this mode, so we need to provide it in our apps.

So, for example, we can write something like this:

private async void toggleFullScreenButton_Click(object sender, RoutedEventArgs e)
{
    var view = ApplicationView.GetForCurrentView();
    if (view.IsFullScreenMode)
    {
        view.ExitFullScreenMode();
    }
    else
    {
        var succeeded = view.TryEnterFullScreenMode();
        if (!succeeded)
        {
            var dialog = new MessageDialog("Unable to enter the full-screen mode.");
            await dialog.ShowAsync();
        }
    }
}

First of all, we check if the app is already in full-screen mode (line 4). If so, we call the ExitFullScreenMode method (line 6) to return to the standard windowed mode. Otherwise, we call TryEnterFullScreenMode (line 10) to attempt to place the app in full-screen mode. This method can fail, so we check its return value to determine whether the app is actually placed in full-screen mode.

Note that the full-screen APIs is available also on phones. In this case, a full-screen app has no statusbar and no “soft navigation buttons”.

EnumStateTrigger for Universal Windows Platform

07/07/2015 1 comment

The new Universal Windows Platform introduces Adaptive Triggers, that allow to use declaratives rules in XAML to define transitions between visual states, as described in a previous post.

The most interesting thing about this new concept is that we can define our triggers to handle specific scenarios: we simply need to inherit from the StateTriggerBase class and call its SetActive method to activate it.

Suppose for example that we want to show or hide certain UI elements based on some access rules, i.e., if the current user is logged or anonymous, or if he has read/write rights, etc. In this case, we can define an EnumStateTrigger like the following one:

public class EnumStateTrigger : StateTriggerBase
{
    public object Value
    {
        get { return (object)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(object), 
        typeof(EnumStateTrigger),
        new PropertyMetadata(null, ValuePropertyChanged));

    public string ActiveValues
    {
        get { return (string)GetValue(ActiveValuesProperty); }
        set { SetValue(ActiveValuesProperty, value); }
    }

    public static readonly DependencyProperty ActiveValuesProperty =
        DependencyProperty.Register("ActiveValues", typeof(string), 
        typeof(EnumStateTrigger),
        new PropertyMetadata(null, ValuePropertyChanged));

    private static void ValuePropertyChanged(object sender,
        DependencyPropertyChangedEventArgs e)
    {
        var obj = (EnumStateTrigger)sender;
        obj.UpdateTrigger();
    }

    private void UpdateTrigger()
    {
        if (Value == null || ActiveValues == null)
        {
            SetActive(false);
        }
        else
        {
            var currentStates = Value.ToString().ToLower()
                .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(s => s.Trim()).ToList();

            var stateStrings = ActiveValues.ToString().ToLower()
                .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(s => s.Trim()).ToList();

            var isActive = currentStates.Intersect(stateStrings).Any();
            SetActive(isActive);
        }
    }
}

It defines two dependency properties: Value is the current value that must be checked, while ActiveValues are comma separated enum values that activate the trigger. Then, in the UpdateTrigger method (line 32-51), we check if current values interesect active values and activate the trigger correspondingly. We use this tecnique to handle also the | operator for enum flags.

Now we can use this trigger in XAML:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
        <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
            <Button x:Name="accessLevelNone" Content="None" 
                    Click="accessLevelNone_Click" />
            <Button x:Name="accessLevelRead" Content="Read"
                    Click="accessLevelRead_Click" />
            <Button x:Name="accessLevelWrite" Content="Write"
                    Click="accessLevelWrite_Click" />
        </StackPanel>

        <TextBlock x:Name="enumStatus"         
                   HorizontalAlignment="Center" 
                   VerticalAlignment="Center" />
    </StackPanel>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup >
            <VisualState x:Name="none">
                <VisualState.StateTriggers>
                    <triggers:EnumStateTrigger 
                        Value="{x:Bind SelectedAccessLevel, Mode=OneWay}" 
                        ActiveValues="None" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="enumStatus.Text" Value="Access level: none" />
                    <Setter Target="accessLevelNone.FontWeight" Value="Bold" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="readerOrWriter">
                <VisualState.StateTriggers>
                    <triggers:EnumStateTrigger 
                        Value="{x:Bind SelectedAccessLevel, Mode=OneWay}" 
                        ActiveValues="Reader, Writer" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="enumStatus.Text" 
                            Value="Access level: reader or writer" />
                    <Setter Target="accessLevelRead.FontWeight" Value="Bold" />
                    <Setter Target="accessLevelWrite.FontWeight" Value="Bold" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>

We have defined two visual states (lines 19-29 and 30-42), that are activated according to the value of the bound SelectedAccessLevel property (lines 21-23 and 32-34): the former is related to the None enum value, the latter to Reader or Writer.

And finally the code-behind for the page:

public sealed partial class MainPage : Page, INotifyPropertyChanged
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    private AccessLevel selectedAccessLevel = AccessLevel.None;
    public AccessLevel SelectedAccessLevel
    {
        get { return selectedAccessLevel; }
        set
        {
            if (selectedAccessLevel != value)
            {
                selectedAccessLevel = value;
                PropertyChanged?.Invoke(this, 
                    new PropertyChangedEventArgs(nameof(SelectedAccessLevel)));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void accessLevelNone_Click(object sender, RoutedEventArgs e)
    {
        SelectedAccessLevel = AccessLevel.None;
    }

    private void accessLevelRead_Click(object sender, RoutedEventArgs e)
    {
        SelectedAccessLevel = AccessLevel.Reader;
    }

    private void accessLevelWrite_Click(object sender, RoutedEventArgs e)
    {
        SelectedAccessLevel = AccessLevel.Writer;
    }
}

public enum AccessLevel
{
    None,
    Reader,
    Writer
}

When we click one the the buttons (line 25-38), the SelectedAccessLevel property changes, so triggers that are bound to it are re-evaluated, causing the transition to the corresponding visual states.

Easily manage the Title Bar in Windows 10 apps

19/05/2015 2 comments

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 5 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
Follow

Get every new post delivered to your Inbox.

Join 44 other followers