Showing posts with label Metro. Show all posts
Showing posts with label Metro. Show all posts

Wednesday, 19 June 2013

Deleting Images from Storage Displayed in XAML in Windows 8

Overview

It's possible to display images from storage in XAML using the following URI syntax:

Property:

public string StorageName { get { return "ms-appdata:///local/images/image.jpg"; } }

XAML:

<Image Margin="5" Width="50" Height="50" Source="{Binding StorageName}" />

This can be handy in various scenarios, for me it was to keep a scaled-down copy of a users image for efficiency and to make sure it wasn't deleted.

This is fine but a problem can occur if you want to delete the image when it is displayed on the UI as you get a "System.UnautorizedAccessException" like this:


Solution

I couldn't find a way of stopping this exactly when I wanted to delete the image, so I decided to keep a note of images to delete, then delete them when the application launched and the images were no longer in use.

NoteImageForDeletion

Call this when you want to note an image for deletion

private const string NOTED_FOR_DELETION = "NOTED_FOR_DELETION";

public static void NoteImageForDeletion(string name)
{
  try
  {
    List<string> noted = new List<string>();
    if (ApplicationData.Current.LocalSettings.Values.ContainsKey(NOTED_FOR_DELETION))
    {
      noted = new List<string>((string[])ApplicationData.Current.LocalSettings.Values[NOTED_FOR_DELETION]);
    }

    noted.Add(name);

    ApplicationData.Current.LocalSettings.Values[NOTED_FOR_DELETION] = noted.ToArray();
  }
  catch (Exception)
  {
  }

}


DeleteNotedImages

Call this when the app starts

public async static Task DeleteNotedImages()
{
  List<string> noted = new List<string>();
  if (ApplicationData.Current.LocalSettings.Values.ContainsKey(NOTED_FOR_DELETION))
  {
    noted = new List<string>((string[])ApplicationData.Current.LocalSettings.Values[NOTED_FOR_DELETION]);
  }

  foreach (var n in noted)
    await DeleteImage(n);
}

public static async Task DeleteImage(string name)
{
  try
  {
    var folder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("images", CreationCollisionOption.OpenIfExists);
    StorageFile file = await folder.GetFileAsync(name);
    await file.DeleteAsync();
  }
  catch (FileNotFoundException)
  {
  }
}

Conclusion

This is a work-around, but a good way of decoupling the image deletion process from the UI.

Thursday, 4 October 2012

Windows 8 ItemTapCommand Attached Property

This attached property picks up touch tap events from FrameworkElements and triggers an attached command. This is useful for controls like GridViews where you might click an item or tap it to perform an action.


public class ItemTapCommand
{
  public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ItemTapCommand), new PropertyMetadata(null, CommandPropertyChanged));

  public static void SetCommand(DependencyObject attached, ICommand value)
  {
    attached.SetValue(CommandProperty, value);
  }

  public static ICommand GetCommand(DependencyObject attached)
  {
    return (ICommand)attached.GetValue(CommandProperty);
  }

  private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    // Attach tap handler
    (d as FrameworkElement).Tapped += ItemTapCommand_Tapped;
  }
       
  private static void ItemTapCommand_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
  {
    // Get command
    ICommand command = GetCommand(sender as DependencyObject);

    // Execute command
    command.Execute(null);
  }
}

This example shows it attached to a GridView item (grid) and using a command from the view model via the context of the GridView:

<GridView ItemsSource="{Binding Items}"
  SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionMode="Single">
  <GridView.ItemTemplate>
    <DataTemplate>
      <Grid Width="133" Height="200"
cmd:ItemTapCommand.Command="{Binding ElementName=posterGrid, Path=DataContext.EditCommand}">


Friday, 17 August 2012

Obelisk Windows 8 & WP7 MVVM Persistence Library V2.2 Release

I've just finished the Obelisk V2.2 release. It's now got support for Windows 8 persistence as well as WP7 persistence. There is a basic MVVM pattern example and an MVVM Light example for WP7 and Windows 8 The MSI only contains source for WP7 because I can't create an MSI in VS2012 yet, so you need to download the source to get the samples:

http://obelisk.codeplex.com/

Tuesday, 7 August 2012

Windows 8 Audio Player Demo

Overview

I need to include a podcast player in the app I'm writing, so I decided to write a prototype app to demo the Windows 8 audio capabilities

Source here: https://bitbucket.org/webbercross/win8audioplayer/


Features

I wanted to include the following:

  • Background capabilities
  • Live tile glyph updates
  • A custom re-templatable control
  • Hardware interaction
  • Fully working demo with all required tasks

Background Audio

I've previously use the Background Audio Agent in WP7 and was expecting something similar. There's no direct equivalent but the MediaElement is Windows 8 provides new features different to Silverlight. The new MediaElement has an 'AudioCategory' attribute which can be set to 'BackgroundCapableMedia' to allow it to continue playing while the page is not active or when the app is suspended, but not closed.

<MediaElement x:Name="mediaElement" AudioCategory="BackgroundCapableMedia" AutoPlay="False"/>

The manifest needs modifying to have a Background declaration with 'Audio' and 'Control channel' properties:


Additionally, a wide tile must be used.

Live Tile Glyph Updates

I wasn't sure at first if this was done automatically by the task, but it's not, it needs doing manually, so I wrote a helper class to do it:

public enum Glyphs
{
  none,
  activity,
  alert,
  available,
  away,
  busy,
  newMessage,
  paused,
  playing,
  unavailable,
  error,
  attention,
}

public class BadgeHelper
{
  public static void UpdateBadge(Glyphs glyph)
  {
    // Get badge xml content
    var content = BadgeUpdateManager.GetTemplateContent(Windows.UI.Notifications.BadgeTemplateType.BadgeGlyph);
    var element = content.GetElementsByTagName("badge")[0];

    // Set new glyph value
    element.Attributes[0].NodeValue = glyph.ToString();

    // Update badge
    var notification = new BadgeNotification(content);
    BadgeUpdateManager.CreateBadgeUpdaterForApplication().Update(notification);
  }
}

Normal tile:


Tile with play glyph:



Custom Template

This is the same as Silverlight and WPF, I put in some visual states for play and paused mode. The template is in the default Templates\Generic.xaml location.

Hardware Interaction

The MediaControl object has a number of events which can be subscribed to.

The Demo

[Edit] The demo is now finished, enjoy!



Monday, 6 August 2012

Windows 8 MVVM Animation Control

Overview

As we know Windows 8 Metro apps (or whatever they are called now) don't support trigger or behaviours like Silverlight and WPF apps do, even though they are XAML family. This means that once you have crafted an animation in XAML, you can't actually trigger it without code behind. This article demonstrates how to use an attached property which is toggled by a bool dependency property and controls a pair of animations.


Usage

I have a common settings page which is opened via the charm bar which I was originally showing and hiding using a bool property in the view model and a visibility converter. I wanted to have an animation so that the control would slide in and out but wanted to control it through the view model.


Dependency Property

This dependency property is fairly straight forward, it has 3 dependency properties, the first binds to a boolean property and the other two bind to a pair of opposing animations to control the slide in and out:


/// <summary>
/// This attached property binds to a bool and a pair of StoryboardAnimations
/// When the bool is true, the true animation is started
/// When the bool is false, the fals animation is started
/// </summary>
public class BoolBeginStoryBoardTrigger
{
  #region Bool Property

  public static readonly DependencyProperty BoolProperty =
    DependencyProperty.RegisterAttached("Bool", typeof(bool),   typeof(BoolBeginStoryBoardTrigger), new PropertyMetadata(false, BoolPropertyChanged));

  public static void SetBool(DependencyObject attached, bool value)
  {
    attached.SetValue(BoolProperty, value);
  }

  public static bool GetBool(DependencyObject attached)
  {
    return (bool)attached.GetValue(BoolProperty);
  }

  private static void BoolPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    if (GetBool(d))
      GetTrueStoryboard(d).Begin();
    else
      GetFalseStoryboard(d).Begin();
  }

  #endregion

  #region TrueStoryboard Property

  public static readonly DependencyProperty TrueStoryboardProperty =
    DependencyProperty.RegisterAttached("TrueStoryboard", typeof(Storyboard), typeof(BoolBeginStoryBoardTrigger), null);

  public static void SetTrueStoryboard(DependencyObject attached, Storyboard value)
  {
    attached.SetValue(TrueStoryboardProperty, value);
  }

  public static Storyboard GetTrueStoryboard(DependencyObject attached)
  {
    return (Storyboard)attached.GetValue(TrueStoryboardProperty);
  }

  #endregion

  #region FalseStoryboard Property

  public static readonly DependencyProperty FalseStoryboardProperty =
  DependencyProperty.RegisterAttached("FalseStoryboard", typeof(Storyboard), typeof(BoolBeginStoryBoardTrigger), null);

  public static void SetFalseStoryboard(DependencyObject attached, Storyboard value)
  {
    attached.SetValue(FalseStoryboardProperty, value);
  }

  public static Storyboard GetFalseStoryboard(DependencyObject attached)
  {
    return (Storyboard)attached.GetValue(FalseStoryboardProperty);
  }

  #endregion
}

The XAML

The attached property can be put anywhere after where the animations are defined. The 3 properties are set like this:

<Grid Style="{StaticResource LayoutRootStyle}"
        cmd:BoolBeginStoryBoardTrigger.Bool="{Binding IsSettingsVisible}"
        cmd:BoolBeginStoryBoardTrigger.FalseStoryboard="{StaticResource SettingsSlideIn}"
        cmd:BoolBeginStoryBoardTrigger.TrueStoryboard="{StaticResource             SettingsSlideOut}">

If you don't want to bind it to a View Model, it can easily bound to a toggle button in the view like this:


<Grid x:Name="settingsPanel"
          local:BoolBeginStoryBoardTrigger.Bool="{Binding ElementName=toggleButton, Path=IsChecked}"
          local:BoolBeginStoryBoardTrigger.FalseStoryboard="{StaticResource SettingsSlideIn}"
          local:BoolBeginStoryBoardTrigger.TrueStoryboard="{StaticResource SettingsSlideOut}"

Complete Settings Panel XAML

This is bound to the Settings view model which controls the IsSettingsVisible property.

<UserControl
    x:Class="WebberCross.Win8.Demo.View.SettingsPanel"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WebberCross.Win8.Demo.Settings"
    xmlns:cmd="using:WebberCross.Win8.Demo.Commanding"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Width="346" HorizontalAlignment="Right"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="346"   
    x:Name="ThisControl" Visibility="Collapsed"
    DataContext="{Binding Settings, Source={StaticResource Locator}}">
   
    <UserControl.RenderTransform>
        <TranslateTransform x:Name="settingsTranslateX" X="346" />
    </UserControl.RenderTransform>

    <UserControl.Resources>
        <Storyboard x:Key="SettingsSlideOut">
            <DoubleAnimationUsingKeyFrames
                Storyboard.TargetProperty="X"
                Storyboard.TargetName="settingsTranslateX">
                <EasingDoubleKeyFrame KeyTime="0:0:0" Value="346" />
                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0" >
                    <EasingDoubleKeyFrame.EasingFunction>
                        <PowerEase EasingMode="EaseOut"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
            <ObjectAnimationUsingKeyFrames
                Storyboard.TargetProperty="Visibility"
                Storyboard.TargetName="ThisControl">
                <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible" />                  
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>

        <Storyboard x:Key="SettingsSlideIn">
            <DoubleAnimationUsingKeyFrames
        Storyboard.TargetProperty="X"
        Storyboard.TargetName="settingsTranslateX">
                <EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="346" >
                    <EasingDoubleKeyFrame.EasingFunction>
                        <PowerEase EasingMode="EaseIn"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>

    <Grid Style="{StaticResource LayoutRootStyle}"
        cmd:BoolBeginStoryBoardTrigger.Bool="{Binding IsSettingsVisible}"
        cmd:BoolBeginStoryBoardTrigger.FalseStoryboard="{StaticResource SettingsSlideIn}"
        cmd:BoolBeginStoryBoardTrigger.TrueStoryboard="{StaticResource SettingsSlideOut}">
        <Grid.RowDefinitions>
            <RowDefinition Height="140"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- Back button and page title -->
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <ToggleButton  Style="{StaticResource BackToggleButtonStyle}"
                           IsChecked="{Binding IsSettingsVisible, Mode=TwoWay}"/>
            <TextBlock x:Name="pageTitle" Text="{Binding SettingsTitle}" Style="{StaticResource PageSubheaderTextStyle}" Grid.Column="1"/>
        </Grid>

        <Grid Grid.Row="1" Margin="40,0,20,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="20" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <TextBlock Text="{Binding PushNotificationsText, Mode=OneWay}" VerticalAlignment="Center"
                       Style="{StaticResource TitleTextStyle}" />
            <ToggleSwitch Grid.Column="1" IsOn="{Binding IsPushEnabled, Mode=TwoWay}" />

            <TextBlock Grid.Row="2" Text="{Binding FontSizeText, Mode=OneWay}" Style="{StaticResource TitleTextStyle}" />
            <ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding FontSizes}" DisplayMemberPath="Key"
                                SelectedItem="{Binding SelectedFontSize, Mode=TwoWay}" 
                                Grid.ColumnSpan="2"  >
            </ComboBox>
        </Grid>
    </Grid>
</UserControl>


Settings Service

The next article discusses how to implement the settings service.

http://geoffwebbercross.blogspot.co.uk/2012/08/windows-8-mvvm-settings-service.html