Thursday, August 9, 2012

Metro App: Adding Settings Charms in your Metro Application

In this post, I am going share code which I used to show Settings charm in my Metro App.

Step 1) Define a style for Back Button which will be displayed on Settings window, you can use this style in your Metro App anywhere while displaying "Back" button.


<Style x:Key="SettingsBackButtonStyle" TargetType="Button">
<Setter Property="MinWidth" Value="0"/>
<Setter Property="FontFamily" Value="Segoe UI Symbol"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="FontSize" Value="26.66667"/>
<Setter Property="AutomationProperties.AutomationId" Value="BackButton"/>
<Setter Property="AutomationProperties.Name" Value="Back"/>
<Setter Property="AutomationProperties.ItemType" Value="Navigation Button"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid" Width="30" Height="30">
<Grid Margin="-3,-5,0,0">
<TextBlock x:Name="BackgroundGlyph" Text="&#xE0D4;" Foreground="{StaticResource BackButtonBackgroundThemeBrush}"/>
<TextBlock x:Name="NormalGlyph" Text="{StaticResource BackButtonSnappedGlyph}"
Foreground="{StaticResource BackButtonForegroundThemeBrush}"/>
<TextBlock x:Name="ArrowGlyph" Text="&#xE0C4;" Foreground="{StaticResource BackButtonPressedForegroundThemeBrush}" Opacity="0"/>
</Grid>
<Rectangle
x:Name="FocusVisualWhite"
IsHitTestVisible="False"
Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}"
StrokeEndLineCap="Square"
StrokeDashArray="1,1"
Opacity="0"
StrokeDashOffset="1.5"/>
<Rectangle
x:Name="FocusVisualBlack"
IsHitTestVisible="False"
Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}"
StrokeEndLineCap="Square"
StrokeDashArray="1,1"
Opacity="0"
StrokeDashOffset="0.5"/>

<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundGlyph" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource BackButtonPointerOverBackgroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NormalGlyph" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource BackButtonPointerOverForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundGlyph" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource BackButtonForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation
Storyboard.TargetName="ArrowGlyph"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
<DoubleAnimation
Storyboard.TargetName="NormalGlyph"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="FocusVisualWhite"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
<DoubleAnimation
Storyboard.TargetName="FocusVisualBlack"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>


Step 2) Add a new control (SettingsFlyout) which will display content of particular command in Settings Window.

Xaml definition of that control will be as show below..

<Style TargetType="local:SettingsFlyout">
<Setter Property="Background" Value="White" />
<Setter Property="HeaderBrush" Value="#004D60" />
<Setter Property="HeaderText" Value="{StaticResource SettingsFlyoutHeaderTextDefault}" />
<Setter Property="MaxWidth" Value="646" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingsFlyout">
<Border BorderBrush="{TemplateBinding HeaderBrush}" BorderThickness="1,0,0,0" VerticalAlignment="Stretch">
<Grid Background="{TemplateBinding Background}" VerticalAlignment="Stretch" >
<!-- Root grid definition -->
<Grid.RowDefinitions>
<RowDefinition Height="80" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- Header area for panel -->
<Grid Background="#004D60" Grid.Row="0">

<Grid Margin="40,35,40,13">

<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>

<Button x:Name="SettingsBackButton" Margin="0,0,0,0" Grid.Column="0" Style="{StaticResource SettingsBackButtonStyle}" HorizontalAlignment="Left" />

<TextBlock TextTrimming="WordEllipsis" Margin="10,-2,0,0" Grid.Column="1" FontFamily="Segoe UI" FontWeight="SemiLight" FontSize="24.6667" Text="{TemplateBinding HeaderText}" HorizontalAlignment="Left" />

<Image Source="{TemplateBinding SmallLogoImageSource}" HorizontalAlignment="Right" Grid.Column="2" Margin="0,0,0,0" />

</Grid>

</Grid>

<!-- Settings Panel Content -->
<Grid x:Name="SettingsFlyoutContentGrid" Background="White" Grid.Row="1" Margin="33,22,40,0" VerticalAlignment="Top" HorizontalAlignment="Left">
<ContentPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>

</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>


and class definition for "SettingsFlyout" control will be

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.ApplicationSettings;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;



// The Templated Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234235
namespace MyApp.Controls


{
public sealed class SettingsFlyout : ContentControl


{
#region Member Variables
private Popup _hostPopup;
private Rect _windowBounds;
private Button _backButton;
private Grid _contentGrid;
const int CONTENT_HORIZONTAL_OFFSET = 100;
#endregion Member Variables

#region Overrides
protected override void OnApplyTemplate()


{
base.OnApplyTemplate();

// make sure we listen at the right time to add/remove the back button event handlers
if (_backButton != null)


{
_backButton.Tapped -= OnBackButtonTapped;
}
_backButton = GetTemplateChild("SettingsBackButton") as Button;
if (_backButton != null)


{
_backButton.Tapped += OnBackButtonTapped;
}
// need to get these grids in order to set the offsets correctly in RTL situations
if (_contentGrid == null)


{
_contentGrid = GetTemplateChild("SettingsFlyoutContentGrid") as Grid;


}
_contentGrid.Transitions = new TransitionCollection();
_contentGrid.Transitions.Add(new EntranceThemeTransition()


{
FromHorizontalOffset = CONTENT_HORIZONTAL_OFFSET // TODO: if left edge need to multiply by -1


});
}
#endregion Overrides

#region Constructor
public SettingsFlyout()


{
this.DefaultStyleKey = typeof(SettingsFlyout);

_windowBounds = Window.Current.Bounds;

this.Loaded += OnLoaded;

_hostPopup = new Popup();
_hostPopup.ChildTransitions = new TransitionCollection();
_hostPopup.ChildTransitions.Add(new PaneThemeTransition());


_hostPopup.Closed += OnHostPopupClosed;
_hostPopup.IsLightDismissEnabled = true;


_hostPopup.Height = _windowBounds.Height;
_hostPopup.Child = this;
_hostPopup.SetValue(Canvas.TopProperty, 0);

this.Height = _windowBounds.Height;


}
private void OnLoaded(object sender, RoutedEventArgs e)


{
Window.Current.Activated += OnCurrentWindowActivated;
_hostPopup.Width = (this.FlyoutWidth == SettingsFlyoutWidth.Wide) ? 646 : 346;
this.Width = _hostPopup.Width;
_hostPopup.SetValue(Canvas.LeftProperty, _windowBounds.Width - (double)this.FlyoutWidth);


}
private void OnBackButtonTapped(object sender, object e)


{
if (_hostPopup != null)


{
_hostPopup.IsOpen = false;


}
SettingsPane.Show();


}
void OnHostPopupClosed(object sender, object e)


{
_hostPopup.Child = null;
Window.Current.Activated -= OnCurrentWindowActivated;
this.Content = null;

if (null != Closed)


{
Closed(this, e);


}
}
void OnCurrentWindowActivated(object sender, Windows.UI.Core.WindowActivatedEventArgs e)


{
if (e.WindowActivationState == Windows.UI.Core.CoreWindowActivationState.Deactivated)


{
this.IsOpen = false;


}
}
#endregion Constructor

#region Dependency Properties
public bool IsOpen


{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }


}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(SettingsFlyout), new PropertyMetadata(false, (obj, args) =>


{
if (args.NewValue != args.OldValue)


{
SettingsFlyout f = (SettingsFlyout)obj;
f._hostPopup.IsOpen = (bool)args.NewValue;


}
}));
public SolidColorBrush HeaderBrush


{
get { return (SolidColorBrush)GetValue(HeaderBrushProperty); }
set { SetValue(HeaderBrushProperty, value); }


}
public static readonly DependencyProperty HeaderBrushProperty =
DependencyProperty.Register("HeaderBrush", typeof(SolidColorBrush), typeof(SettingsFlyout), null);

public SettingsFlyoutWidth FlyoutWidth


{
get { return (SettingsFlyoutWidth)GetValue(FlyoutWidthProperty); }
set { SetValue(FlyoutWidthProperty, value); }


}
public static readonly DependencyProperty FlyoutWidthProperty =
DependencyProperty.Register("FlyoutWidth", typeof(SettingsFlyoutWidth), typeof(SettingsFlyout), new PropertyMetadata(SettingsFlyoutWidth.Narrow));



 
public string HeaderText


{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }


}
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof(string), typeof(SettingsFlyout), new PropertyMetadata(null));



 
public ImageSource SmallLogoImageSource


{
get { return (ImageSource)GetValue(SmallLogoImageSourceProperty); }
set { SetValue(SmallLogoImageSourceProperty, value); }


}
public static readonly DependencyProperty SmallLogoImageSourceProperty =
DependencyProperty.Register("SmallLogoImageSource", typeof(ImageSource), typeof(SettingsFlyout), null);

#endregion Dependency Properties

#region Events
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Runtime EventHandler")]
public event EventHandler<object> Closed;



#endregion
#region Enums
public enum SettingsFlyoutWidth


{
Narrow = 346,
Wide = 646
}
#endregion Enums


}
}


Step 3) Now in the constructor of your MainWindow, you register for an event handler which will be executed when "Windows Key + I" is pressed together as show below.

private void MainPage
{
SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested

}

Step 4) Now in Event Handler method you can add commands and their corresponding views that you want in Setting Charms as show below.

SettingsCommand changeplayersnamecmd = new SettingsCommand("sample", "Change Player's Name", (x) =>


{
SettingsFlyout settings = new SettingsFlyout();
settings.FlyoutWidth = SettingsFlyout.SettingsFlyoutWidth.Narrow;
settings.HeaderText = "Player's Names";
BitmapImage bmp = new BitmapImage(new Uri("ms-appx:///Assets/Logo.png"));


settings.SmallLogoImageSource = bmp;
settings.Content = settingschangeplayersname;
settings.Closed += ChangePlayersName_Closed;
settings.IsOpen = true;



 
});

args.Request.ApplicationCommands.Add(changeplayersnamecmd);

 
SettingsCommand changenumberofplayerscmd = new SettingsCommand("sample", "Change Number of Players", (x) =>


{
SettingsFlyout settings = new SettingsFlyout();
settings.FlyoutWidth = SettingsFlyout.SettingsFlyoutWidth.Narrow;
settings.HeaderText = "Number of Players";
BitmapImage bmp = new BitmapImage(new Uri("ms-appx:///Assets/Logo.png"));


settings.SmallLogoImageSource = bmp;
settings.Content = settingschangenumberofplayers;
settings.Closed += ChangeNumberOfPlayers_Closed;
settings.IsOpen = true;



 
});

args.Request.ApplicationCommands.Add(changenumberofplayerscmd);

 
SettingsCommand helpcmd = new SettingsCommand("sample", "Help", (x) =>


{
              this.Frame.Navigate(typeof(HelpWindow), "AllGroups");


});
           args.Request.ApplicationCommands.Add(helpcmd);

 
SettingsCommand aboutscmd = new SettingsCommand("sample", "About", (x) =>


{
SettingsFlyout settings = new SettingsFlyout();
settings.FlyoutWidth = SettingsFlyout.SettingsFlyoutWidth.Narrow;
settings.HeaderText = "About";
BitmapImage bmp = new BitmapImage(new Uri("ms-appx:///Assets/Logo.png"));


settings.SmallLogoImageSource = bmp;
settings.Content = settingsaboutwindow;
settings.IsOpen = true;



 
});

args.Request.ApplicationCommands.Add(aboutscmd);