Tuesday, 17 February 2015

File Activation

In winrt apps you can associate an extension with your application, that is when you open a file with the specified extension, it will launch your application with said file as a parameter. we're going to develop a BMI application save values to a file with a .bmi extension then open our application and load our file data when we open the storage file, so firstly lets set our apps capabilities and declarations.

we're going to add the picture library capability, this will allow our application to save files to the picture library.

I chose the picture library because it's just easy to work with, i know where it is, it's easy to navigate too and an acceptable alternative to the documents library.

next let's configure our File Activation, go to the declarations tab

now once we select the file type association option from the available declarations drop down list and add it we need to give it a name, and a file type, we should also give it a logo which will be the icon that the extension will display in our system, but I aint got time for that.

anyway with those out of the way we can get started, here's our interface

<Page
    x:Class="pc.fileActivationExample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:pc.fileActivationExample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >
        <Grid.RowDefinitions>
            <RowDefinition Height="140" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
       
        <TextBlock Text="File Activation Example"
                   Margin="100 0 0 0"
                   VerticalAlignment="Center"
                   Style="{ThemeResource HeaderTextBlockStyle }" />
       
        <StackPanel Grid.Row="1" VerticalAlignment="Top" Margin="100 0 0 0">
            <TextBox x:Name="Height_TXT" Header="Height in meters:" />
            <TextBox x:Name="Weight_TXT" Header="Weight in kilos:" />
            <Button x:Name="Calculate_BTN" Content="Calcualte"/>
            <TextBox x:Name="Output_TXT" Header="BMI" IsEnabled="False" />
        </StackPanel>

        <ListView Margin="100 0 0 0" Grid.Row="2" x:Name="Values_ListView"
                  IsEnabled="False">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <StackPanel.Resources>
                            <Style TargetType="TextBlock" >
                                <Setter Property="FontSize" Value="24"/>
                            </Style>
                        </StackPanel.Resources>
                        <TextBlock Text="{Binding Weight}"/>
                        <TextBlock Text="*" Margin="25 0"/>
                        <TextBlock Text="{Binding Height}"/>
                        <TextBlock Text="^2 =" Margin="25 0"/>
                        <TextBlock Text="{Binding Bmi}"/>
                        <TextBlock Text="{Binding Date}" Margin="25 0"/>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>


and it should render like so.

ok now our code behind, for our code behind, firstly lets create a container for our bmi data, that our listview can use.

public class BmiSnapshot
{
    public double Weight { get; set; }
    public double Height { get; set; }
    public double Bmi { get { return (this.Weight / (this.Height * this.Height)); } }
    public DateTime Date { get; private set; }

    public BmiSnapshot(double Weight, double Height)
    {
        this.Weight = Weight;
        this.Height = Height;
        this.Date = DateTime.Now;
    }

    public BmiSnapshot(string Data)
    {
        var Values = Data.Split('#');

        this.Weight = Convert.ToDouble(Values[0]);
        this.Height = Convert.ToDouble(Values[1]);
        this.Date = Convert.ToDateTime(Values[2]);
    }

    public override string ToString()
    {
        //not a good serialization tactic
        return String.Format("{0}#{1}#{2}\r\n"this.Weight, this.Height, this.Date);
    }

}

with that done let's add a property to our code behind that our listviews item source can be set to and an event receiver for our calculate button

public ObservableCollection<BmiSnapshot> BmiCollection { get; set; }

public MainPage()
{
    this.InitializeComponent();
    this.Calculate_BTN.Click += Calculate_BTN_Click;
    this.BmiCollection = new ObservableCollection<BmiSnapshot>();
    this.Values_ListView.ItemsSource = BmiCollection;

}

now lets actually write our calculate event handler

void Calculate_BTN_Click(object sender, RoutedEventArgs e)
{
    var height = Convert.ToDouble(Height_TXT.Text);
    var weight = Convert.ToDouble(Weight_TXT.Text);

    var bmi = new BmiSnapshot(weight, height);

    Output_TXT.Text = bmi.Bmi.ToString();

    //add to our listview
    BmiCollection.Add(bmi);

    //serialize to our file
    this.SaveBMI(bmi);

}

now by adding our snapshot to the observable collection it'll automatically update the listview, but we still have to write our serialization logic

private async void SaveBMI(BmiSnapshot bmi)
{
    var lineItem = bmi.ToString();
    IBuffer line = CryptographicBuffer.ConvertStringToBinary(lineItem, BinaryStringEncoding.Utf8);

    var Pictures = KnownFolders.PicturesLibrary;
    var bmiFile = await Pictures.CreateFileAsync("log.bmi", CreationCollisionOption.OpenIfExists);

    await FileIO.AppendTextAsync(bmiFile, lineItem);

}

as you can see it's pretty straight forward, we just open or create a file called log.bmi (notice the extension we declared earlier in the declarations), we open the file and just append lines to it using the objects to string function. granted not the best serialization strategy.

ok so far we've created an app that calculates bmi values, and saves them to file, so go ahead and create a some,

and if we go to our pictures library


we see our file with the icon i was too lazy to specify earlier, now if we open our file our app will launch but it'll just hang our on the splash screen because it has no clue what to do, but at least know to open

so lets open up our App.xaml.cs class and override the OnFileActivated function.

protected override void OnFileActivated(FileActivatedEventArgs args)
{
    var rootFrame = Window.Current.Content as Frame;

    if (rootFrame == null)
    {              
        rootFrame = new Frame();
        rootFrame.Language = Windows.Globalization.ApplicationLanguages.Languages[0];
        rootFrame.NavigationFailed += OnNavigationFailed;

        Window.Current.Content = rootFrame;
    }

    if (rootFrame.Content == null)
        rootFrame.Navigate(typeof(MainPage), args.Files[0]);
           
    Window.Current.Activate();

}

here we launch our main page and pass our file as a parameter, so now lets' head back to our codebhind of our mainpage.xaml.cs and overwrite the OnNavigatedTo method

 async protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var bmiFile = e.Parameter as StorageFile;

    if (bmiFile != null)
    {
        this.BmiCollection = await this.ParseFileAsync(bmiFile);
        this.Values_ListView.ItemsSource = BmiCollection;
    }
    base.OnNavigatedTo(e);
}

and of coarse we need to implement our parseFileAsync function

async Task<ObservableCollection<BmiSnapshot>> ParseFileAsync(StorageFile BmiFile)
{
    var result = new ObservableCollection<BmiSnapshot>();

    var lineItems = await FileIO.ReadLinesAsync(BmiFile);

    foreach (var line in lineItems)
        result.Add(new BmiSnapshot(line));

    return result;
}


and that's it now when we open our .bmi file our application launches passes the file data to our mainpage and parses our data.

for completeness here's the codebehind with the BmiSnapshot class.

using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.Security.Cryptography;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace pc.fileActivationExample
{
    public class BmiSnapshot
    {
        public double Weight { get; set; }
        public double Height { get; set; }
        public double Bmi { get { return (this.Weight / (this.Height * this.Height)); } }
        public DateTime Date { get; private set; }

        public BmiSnapshot(double Weight, double Height)
        {
            this.Weight = Weight;
            this.Height = Height;
            this.Date = DateTime.Now;
        }

        public BmiSnapshot(string Data)
        {
            var Values = Data.Split('#');

            this.Weight = Convert.ToDouble(Values[0]);
            this.Height = Convert.ToDouble(Values[1]);
            this.Date = Convert.ToDateTime(Values[2]);
        }

        public override string ToString()
        {
            //not a good serialization tactic
            return String.Format("{0}#{1}#{2}\r\n", this.Weight, this.Height, this.Date);
        }
    }

    public sealed partial class MainPage : Page
    {
        public ObservableCollection<BmiSnapshot> BmiCollection { get; set; }

        public MainPage()
        {
            this.InitializeComponent();
            this.Calculate_BTN.Click += Calculate_BTN_Click;
            this.BmiCollection = new ObservableCollection<BmiSnapshot>();
            this.Values_ListView.ItemsSource = BmiCollection;
        }

        void Calculate_BTN_Click(object sender, RoutedEventArgs e)
        {
            var height = Convert.ToDouble(Height_TXT.Text);
            var weight = Convert.ToDouble(Weight_TXT.Text);

            var bmi = new BmiSnapshot(weight, height);

            Output_TXT.Text = bmi.Bmi.ToString();

            //add to our listview
            BmiCollection.Add(bmi);

            //serialize to our file
            this.SaveBMI(bmi);
        }

        private async void SaveBMI(BmiSnapshot bmi)
        {
            var lineItem = bmi.ToString();
            IBuffer line = CryptographicBuffer.ConvertStringToBinary(lineItem, BinaryStringEncoding.Utf8);

            var Pictures = KnownFolders.PicturesLibrary;
            var bmiFile = await Pictures.CreateFileAsync("log.bmi", CreationCollisionOption.OpenIfExists);

            await FileIO.AppendTextAsync(bmiFile, lineItem);
        }

        async Task<ObservableCollection<BmiSnapshot>> ParseFileAsync(StorageFile BmiFile)
        {
            var result = new ObservableCollection<BmiSnapshot>();

            var lineItems = await FileIO.ReadLinesAsync(BmiFile);

            foreach (var line in lineItems)
                result.Add(new BmiSnapshot(line));

            return result;
        }

        async protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            var bmiFile = e.Parameter as StorageFile;

            if (bmiFile != null)
            {
                this.BmiCollection = await this.ParseFileAsync(bmiFile);
                this.Values_ListView.ItemsSource = BmiCollection;
            }
            base.OnNavigatedTo(e);
        }

    }
}


just remember that we need to overwrite the  OnFileActivated function in the App.xaml.cs file to actually navigate to the mainpage with the data as a parameter.