Wednesday 25 February 2015

Streams

Think of a stream as channel from memory to persistent storage and vice versa, it's basically something that's used to write and read date to and from files. the inner mechanism is a bit more involved and something that i don't think i could explain gracefully, so I'll chalk it up to encapsulation, i know it works, i sorta know how it works and if you're really curious i suggest you hunt around for a good explanation:

https://msdn.microsoft.com/en-us/library/k3352a4t(v=vs.110).aspx

basically anything stored in your computer is either a 1 or 0 (quantum computing aside), so whether your data is text, numbers, music, image's videos whatever at the end of the day it's represented by 1's and 0's or binary.

streams just let us stream those 1's and 0's; we can transform them or just read and write them, anyway here are some quick examples, let's start with a UI

<Page
    x:Class="pc.StreamsExample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:pc.StreamsExample"
    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 Grid.Row="0" Text="Streams example" Margin="100 0 0 0"
                   VerticalAlignment="Center" Style="{ThemeResource HeaderTextBlockStyle}" />
       
        <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="100 0 0 0">
            <TextBlock Text="Data to Log:" Style="{ThemeResource  SubheaderTextBlockStyle}" />
            <TextBox Margin="10 0 0 0" x:Name="input_TXT" Width="500"/>
            <Button x:Name="log_BTN" Content="Log" />
            <Button x:Name="read_BTN" Content="Read" />
        </StackPanel>
       
        <TextBox x:Name="Log_TXT" Grid.Row="2" Margin="100 20 50 50"
                 Header="Log Content:" FontSize="24"
                 TextWrapping="Wrap" AcceptsReturn="True" />    
    </Grid>

</Page>

and it should look like

next lets look at our code behind and try to log some data:

using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Security.Cryptography;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace pc.StreamsExample
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            this.log_BTN.Click += log_BTN_Click;
        }

        async void log_BTN_Click(object sender, RoutedEventArgs e)
        {
            var lineItem = String.Format("{0}: {1}\r\n", DateTime.Now.ToString("dd-MM-yy hh:mm:ss"), input_TXT.Text);
            IBuffer line = CryptographicBuffer.ConvertStringToBinary(lineItem, BinaryStringEncoding.Utf8);

            //require picture library capability in our app manifest
            var Pictures = KnownFolders.PicturesLibrary;
            var logFile = await Pictures.CreateFileAsync("log.txt", CreationCollisionOption.OpenIfExists);

            await FileIO.WriteBufferAsync(logFile, line);
        }
    }
}


now this will log our file, but what it's going to do is overwrite our file with each log, so that's not very useful, so instead of writing our buffer let's append our file.

swap the following line
    await FileIO.WriteBufferAsync(logFile, line);
With
       await FileIO.AppendTextAsync(logFile, lineItem);

there we go, with that small change we're now appending our file at the end instead of overwriting it. next let's add our read event in our constructor

public MainPage()
{
    this.InitializeComponent();

    this.log_BTN.Click += log_BTN_Click;
    this.read_BTN.Click += read_BTN_Click;

}

and the FileIO code for reading our text document.

async void read_BTN_Click(object sender, RoutedEventArgs e)
{
    StorageFolder Pictures = KnownFolders.PicturesLibrary;
    StorageFile logFile = await Pictures.GetFileAsync("log.txt");

    Log_TXT.Text = await FileIO.ReadTextAsync(logFile);

}

easy as that, now i like the FileIO class for that exact reason it's easy concise and abstracts all the inner workings away from you, making nicer cleaner code to look at, but there's more then one way to skin a cat, so you may very well see something like the following for working with the streams more directly/

async void log_BTN_Click(object sender, RoutedEventArgs e)
{
    var lineItem = String.Format("{0}: {1}\r\n", DateTime.Now.ToString("dd-MM-yy hh:mm:ss"), input_TXT.Text);
    IBuffer line = CryptographicBuffer.ConvertStringToBinary(lineItem, BinaryStringEncoding.Utf8);

    //require picture library capability in our app manifest
    var Pictures = KnownFolders.PicturesLibrary;
    var logFile = await Pictures.CreateFileAsync("log.txt", CreationCollisionOption.OpenIfExists);

    using (var s = await logFile.OpenStreamForWriteAsync())
    {
        s.Position = s.Length;
        s.Write(line.ToArray(), 0, Convert.ToInt32(line.Length));
    }
}

async void read_BTN_Click(object sender, RoutedEventArgs e)
{
    StorageFolder Pictures = KnownFolders.PicturesLibrary;
    StorageFile logFile = await Pictures.GetFileAsync("log.txt");

    using (var stream = await logFile.OpenReadAsync())
    using (var inputStream = stream.GetInputStreamAt(0))
    {
        var dr = new DataReader(inputStream);
        uint numBytesLoaded = await dr.LoadAsync((uint)stream.Size);

        Log_TXT.Text = dr.ReadString((uint)numBytesLoaded);
    }

}

now that's a bit more verbose in my opinion, but hey why not right, and one more variation

async void log_BTN_Click(object sender, RoutedEventArgs e)
{
    var lineItem = String.Format("{0}: {1}\r\n", DateTime.Now.ToString("dd-MM-yy hh:mm:ss"), input_TXT.Text);
    IBuffer line = CryptographicBuffer.ConvertStringToBinary(lineItem, BinaryStringEncoding.Utf8);

    //requrie picture library capablity in our app manifest
    var Pictures = KnownFolders.PicturesLibrary;
    var logFile = await Pictures.CreateFileAsync("log.txt", CreationCollisionOption.OpenIfExists);

    using (var fileStream = await logFile.OpenStreamForWriteAsync())
    using (var outputStream = fileStream.AsOutputStream())
    {
        fileStream.Position = fileStream.Length;
        DataWriter dw = new DataWriter(outputStream);
        dw.WriteBytes(line.ToArray());
        await dw.StoreAsync();
        await outputStream.FlushAsync();
    }
}

async void read_BTN_Click(object sender, RoutedEventArgs e)
{
    StorageFolder Pictures = KnownFolders.PicturesLibrary;
    StorageFile logFile = await Pictures.GetFileAsync("log.txt");

    using (var stream = await logFile.OpenReadAsync())
    using (var inputStream = stream.GetInputStreamAt(0))
    {
        var dr = new DataReader(inputStream);
        uint numBytesLoaded = await dr.LoadAsync((uint)stream.Size);

        Log_TXT.Text = dr.ReadString((uint)numBytesLoaded);
    }

}

there's many different ways to utilize streams, theses just happen to be the approaches I've come across, the thing to know about streams is you pass data into them and they write them read them or transform them, you can pass streams to each other.


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.