Doodle Pad

Doodle Pad is a simple drawing package using InkPresenter control in Silverlight for Windows Phone 7, application features Draw Width and Colour plus Open/Save support using LINQ to XML.

www.cespage.com/silverlight/wp7tut23.html

Step 1

Start Microsoft Visual Studio 2010 Express for Windows Phone, then Select File then New Project... Select "Visual C#" then "Silverlight for Windows Phone" and then "Windows Phone Application" from Templates, select a Location if you wish, then enter a name for the Project and then click OK, see below:

New Project

Step 2

A Windows Phone application Page named MainPage.xaml should then appear, see below:

MainPage.xaml

Step 3

Select Project then Add Reference... The "Add Reference" window should appear, select "System.XML.Linq" from the ".NET" List, see below:

Add System.XML.Linq Reference

Step 4

Add the Reference to "System.XML.Linq" by Clicking on OK.
Right Click on the App.xaml Entry in the Solution Explorer and choose "View Code" then in the Code View for App.xaml.css, above the "public App()" line type the following:

public string Filename { get; set;}
public object Content { get; set;}

See below:

App Filename and Content

Step 5

Right Click on the Entry for the Project in the Solution Explorer and choose "Add" then "New Folder", and give it the Name "images" (without quotes), see below:

Project Images Folder

Step 6

Download the following images (delete.png, new.png, open & save.png) by right-clicking on the images below and choose "Save Picture As..." or "Save Image As..." and Save them to a Folder on your computer:

Delete Application Bar Icon New Application Bar Icon Open Application Bar Icon Save Application Bar Icon

Step 7

Right Click on the Entry for the "images" Folder for the Project in Solution Explorer, and choose "Add", then "Existing Item...", then in the "Add Existing Item" dialog select Folder you saved the images, then choose "Add" to add the delete.png, new.png, open.png and save.png to the images folder in the project, see below:

Project Images Folder with Images

Step 8

While still in the Solution Explorer click on the "delete.png" image then goto the Properties box and change the Build Action to Content, do the same for the "new.png", "open.png" and "save.png" images, see below:

Image Properties

Step 9

In the Solution Explorer select the "DoodlePad" entry from the list, below the Solution Entry.
Then select Project then "Add New Item...", and select the "Windows Phone Portrait Page" Template, then change the "Name" to OpenPage.xaml, see below:

OpenPage Windows Phone Portrait Page

Step 10

Click on "Add" to add the new Windows Phone Portrait Page to the Project
Then in the XAML Pane for OpenPage.xaml, above the <Grid x:Name="LayoutRoot" Background="Transparent"> Tag type the following ApplicationBar XAML:

<phone:PhoneApplicationPage.ApplicationBar>
  <shell:ApplicationBar IsVisible="True" IsMenuEnabled="False">
    <shell:ApplicationBar.Buttons>
      <shell:ApplicationBarIconButton Text="open" IconUri="/images/open.png" Click="Open_Click"/>
      <shell:ApplicationBarIconButton Text="delete" IconUri="/images/delete.png" Click="Delete_Click"/>
    </shell:ApplicationBar.Buttons>
  </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

See below:

ApplicationBar XAML

Step 11

While still in the XAML Pane between between the <Grid Grid.Row="1" x:Name="ContentGrid"> and </Grid> lines type the following XAML:

<ListBox Margin="10" FontSize="{StaticResource PhoneFontSizeExtraLarge}" Name="Files"/>

XAML:

OpenPage XAML Pane

Design:

OpenPage Design View

Step 12

Right Click on the Page or the entry for "OpenPage.xaml" in Solution Explorer and choose the "View Code" option. In the Code View above "namespace DoodlePad" type the following:

using System.IO.IsolatedStorage;
using System.Xml.Linq;
using System.Windows.Ink;

Also in the CodeView above "public OpenPage()" type the following Application declaration:

public App app = (App)Application.Current;

See Below:

OpenPage Includes and Application Declaration

Step 13

While still in the Code View for OpenPage.xaml.cs in the "public OpenPage()" Constructor below "InitializeComponent();" type the following:

ApplicationTitle.Text = "DOODLE PAD";
PageTitle.Text = "open";
Loaded += (object sender, RoutedEventArgs e) =>
{
  using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  {
    foreach (string filename in storage.GetFileNames("*.ipr"))
    {
      Files.Items.Add(filename.ToLower());
    }
  }
};

See Below:

OpenPage Constructor

Step 14

While still in the Code View for OpenPage.xaml.cs, below the "}" of the "public OpenPage()" method type the following Event Handlers:

private void Open_Click(object sender, EventArgs e)
{
  if (Files.SelectedItem != null)
  {
    app.Filename = (string)Files.SelectedItem;
    using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
    {
      XElement _xml;
      Stroke _stroke = new Stroke();
      StrokeCollection _strokes = new StrokeCollection();
      DrawingAttributes _drawAttr = new DrawingAttributes();
      IsolatedStorageFileStream location = new IsolatedStorageFileStream(app.Filename,
      System.IO.FileMode.Open, storage);
      System.IO.StreamReader file = new System.IO.StreamReader(location);
      _xml = XElement.Parse(file.ReadToEnd());
      foreach (XElement element in _xml.Elements("Stroke"))
      {
        foreach (XElement item in element.Elements("Stroke.DrawingAttributes")
        .Elements("DrawingAttributes"))
        {
          _drawAttr = new DrawingAttributes
          {
            Color = Color.FromArgb(
              byte.Parse(item.Attribute("Color").Value.Substring(1, 2),
              System.Globalization.NumberStyles.HexNumber),
              byte.Parse(item.Attribute("Color").Value.Substring(3, 2),
              System.Globalization.NumberStyles.HexNumber),
              byte.Parse(item.Attribute("Color").Value.Substring(5, 2),
              System.Globalization.NumberStyles.HexNumber),
              byte.Parse(item.Attribute("Color").Value.Substring(7, 2),
              System.Globalization.NumberStyles.HexNumber)),
            Width = double.Parse(item.Attribute("Width").Value),
            Height = double.Parse(item.Attribute("Height").Value)
          };
        }
        foreach (XElement item in element.Elements("Stroke.StylusPoints"))
        {
          _stroke = new Stroke();
          _stroke.DrawingAttributes = _drawAttr;
          foreach (XElement point in item.Elements("StylusPoint"))
          {
            _stroke.StylusPoints.Add(new StylusPoint
            {
              X = double.Parse(point.Attribute("X").Value),
              Y = double.Parse(point.Attribute("Y").Value)
            });
          }
        _strokes.Add(_stroke);
        }
      }
      app.Content = _strokes;
      file.Dispose();
      location.Dispose();
    }
    NavigationService.GoBack();
  }
}

private void Delete_Click(object sender, EventArgs e)
{
  if (Files.SelectedItem != null)
  {
    string _selected = (string)Files.SelectedItem;
    if (MessageBox.Show("Delete selected Item " + _selected + "?", "Doodle Pad",
    MessageBoxButton.OKCancel) == MessageBoxResult.OK)
    {
      using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
      {
        if (storage.FileExists(_selected))
        {
          storage.DeleteFile(_selected);
        }
      }
      NavigationService.GoBack();
    }
  }
}

See Below:

OpenPage Event Handlers

Step 15

Select Project then "Add New Item...", and select the "Windows Phone Portrait Page" Template, then change the "Name" to SavePage.xaml, see below:

SavePage Windows Phone Portrait Page

Step 16

Click on "Add" to add the new Windows Phone Portrait Page to the Project
Then the Designer View for SavePage.xaml, in the XAML Pane between between the <Grid Grid.Row="1" x:Name="ContentGrid"> and </Grid> lines type the following XAML:

<StackPanel>
  <TextBlock Text="Filename"/>
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Column="0" Name="Filename">
      <TextBox.InputScope>
        <InputScope>
          <InputScopeName NameValue="FileName"/>
        </InputScope>
      </TextBox.InputScope>
    </TextBox>
    <Button Grid.Column="1" Content="save" Click="Save_Click"/>
  </Grid>
</StackPanel>

XAML:

SavePage XAML Pane

Design:

OpenPage Design View

Step 17

Right Click on the Page or the entry for "SavePage.xaml" in Solution Explorer and choose the "View Code" option. In the Code View above "namespace DoodlePad" type the following:

using System.IO.IsolatedStorage;
using System.Xml.Linq;
using System.Windows.Ink;

Also in the CodeView above "public SavePage()" type the following Application declaration:

public App app = (App)Application.Current;

See Below:

SavePage Includes and Application Declaration

Step 18

While still in the Code View for SavePage.xaml.cs in the "public SavePage()" Constructor below "InitializeComponent();" type the following:

ApplicationTitle.Text = "DOODLE PAD";
PageTitle.Text = "save";
Loaded += (object sender, RoutedEventArgs e) =>
{
  if (app.Filename == null || app.Filename == "")
  {
    Filename.Text = "untitled.ipr";
  }
  else
  {
    Filename.Text = app.Filename;
  }
};

See Below:

SavePage Constructor

Step 19

While still in the Code View for SavePage.xaml.cs, below the "}" of the "public SavePage()" method type the following Event Handlers:

private void Save_Click(object sender, RoutedEventArgs e)
{
  if (Filename.Text != "")
  {
    try
    {
      app.Filename = Filename.Text.Trim().ToLower();
      using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
      {
        XDocument _doc = new XDocument();
        StrokeCollection _strokes = new StrokeCollection();
        _strokes = (StrokeCollection)app.Content;
        XElement _strokecollection = new XElement("StrokeCollection");
        foreach (Stroke item in _strokes)
        {
          XElement _stroke = new XElement("Stroke");
          XElement _strokeAttr = new XElement("Stroke.DrawingAttributes");
          XElement _attributes = new XElement("DrawingAttributes");
          XAttribute _color = new XAttribute("Color", item.DrawingAttributes.Color);
          XAttribute _width = new XAttribute("Width", item.DrawingAttributes.Width);
          XAttribute _height = new XAttribute("Height", item.DrawingAttributes.Height);
          _attributes.Add(_color, _width, _height);
          _strokeAttr.Add(_attributes);
          XElement _points = new XElement("Stroke.StylusPoints");
          foreach (StylusPoint point in item.StylusPoints)
          {
            XElement _point = new XElement("StylusPoint");
            XAttribute _x = new XAttribute("X", point.X);
            XAttribute _y = new XAttribute("Y", point.Y);
            _point.Add(_x, _y);
            _points.Add(_point);
          }
          _stroke.Add(_strokeAttr, _points);
          _strokecollection.Add(_stroke);
        }
        _doc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), _strokecollection);
        IsolatedStorageFileStream location = new IsolatedStorageFileStream(app.Filename,
        System.IO.FileMode.Create, storage);
        System.IO.StreamWriter file = new System.IO.StreamWriter(location);
        _doc.Save(file);
        app.Content = null;
        file.Dispose();
        location.Dispose();
      }
      NavigationService.GoBack();
    }
    catch
    {
      // Ignore Errors
    }
  }
}

See Below:

SavePage Event Handlers

Step 20

Select the "MainPage.xaml" Tab, then in the XAML Pane for MainPage.xaml, above the <Grid x:Name="LayoutRoot" Background="Transparent"> Tag type the following ApplicationBar XAML:

<phone:PhoneApplicationPage.ApplicationBar>
  <shell:ApplicationBar IsVisible="True" IsMenuEnabled="False">
    <shell:ApplicationBar.Buttons>
      <shell:ApplicationBarIconButton Text="new" IconUri="/images/new.png" Click="New_Click"/>
      <shell:ApplicationBarIconButton Text="open" IconUri="/images/open.png" Click="Open_Click"/>
      <shell:ApplicationBarIconButton Text="save" IconUri="/images/save.png" Click="Save_Click"/>
    </shell:ApplicationBar.Buttons>
  </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

See below:

ApplicationBar XAML

Step 21

Then in while still in the XAML Pane, between the <Grid x:Name="ContentGrid" Grid.Row="1"> and </Grid> lines type the following XAML:

<Grid x:Name="ContentMain">
  <Grid.RowDefinitions>
    <RowDefinition Height="90"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <Grid x:Name="Toolbar" Grid.Row="0">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <!-- Toolbar -->
  </Grid>
  <!-- Content -->
</Grid>

XAML:

MainPage XAML Pane

Design:

MainPage Design View

Step 22

While still in the XAML Pane within the "ContentMain" Grid, below <!-- Toolbar --> type the following ListBox XAML:

<ListBox Grid.Column="0" Name="Size"
SelectionChanged="Size_SelectionChanged">
  <ListBoxItem Tag="1">
    <Rectangle Margin="10" Width="340" Height="1" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="2">
    <Rectangle Margin="10" Width="340" Height="2" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="5" IsSelected="True">
    <Rectangle Margin="10" Width="340" Height="5" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="10">
    <Rectangle Margin="10" Width="340" Height="10" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="15">
    <Rectangle Margin="10" Width="340" Height="15" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="20">
    <Rectangle Margin="10" Width="340" Height="20" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="25">
    <Rectangle Margin="10" Width="340" Height="25" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="30">
    <Rectangle Margin="10" Width="340" Height="30" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="40">
    <Rectangle Margin="10" Width="340" Height="40" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
  <ListBoxItem Tag="50">
    <Rectangle Margin="10" Width="340" Height="50" Fill="{StaticResource PhoneAccentBrush}"/>
  </ListBoxItem>
</ListBox>
 
<ListBox Grid.Column="1" Width="120" Name="Colour"
SelectionChanged="Colour_SelectionChanged">
  <ListBoxItem Tag="FF000000" IsSelected="True">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Black"/>
  </ListBoxItem>
  <ListBoxItem Tag="FF808080">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Gray"/>
  </ListBoxItem>
  <ListBoxItem Tag="FFFF0000">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Red"/>
  </ListBoxItem>
  <ListBoxItem Tag="FFFFA500">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Orange"/>
  </ListBoxItem>
  <ListBoxItem Tag="FFFFFF00">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Yellow"/>
  </ListBoxItem>
  <ListBoxItem Tag="FF008000">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Green"/>
  </ListBoxItem>
  <ListBoxItem Tag="FF00FFFF">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Cyan"/>
  </ListBoxItem>
  <ListBoxItem Tag="FF0000FF">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Blue"/>
  </ListBoxItem>
  <ListBoxItem Tag="FFFF00FF">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Magenta"/>
  </ListBoxItem>
  <ListBoxItem Tag="FF800080">
    <Rectangle Margin="5" Width="120" Height="20" Fill="Purple"/>
  </ListBoxItem>
</ListBox>

XAML:

MainPage ListBoxes XAML

Design:

MainPage ListBoxes Design View

Step 23

While still in the XAML Pane for MainPage.xaml below the <!-- Content --> line, type the following InkPresenter XAML:

<InkPresenter Grid.Row="1" Background="White" Name="Surface" 
MouseLeftButtonDown="Surface_MouseLeftButtonDown" 
MouseLeftButtonUp="Surface_MouseLeftButtonUp" 
MouseMove="Surface_MouseMove" />

XAML:

MainPage InkPresenter XAML Pane

Design:

MainPage InkPresenter Design View

Step 24

Select the "MainPage.xaml" Tab then Right Click on the Page or the entry for "MainPage.xaml" in Solution Explorer and choose the "View Code" option. In the Code View above "namespace DoodlePad" type the following:

using System.Xml.Linq;
using System.Windows.Ink;

Also in the CodeView above "public MainPage()" type the following declarations:

public App app = (App)Application.Current;
public Stroke drawStroke;
public Color drawColour = Colors.Black;
public Size drawSize = new Size { Height = 5, Width = 5 };

See Below:

MainPage Includes and Application Declaration

Step 25

While still in the Code View for MainPage.xaml.cs in the "public MainPage()" Constructor below "InitializeComponent();" type the following:

ApplicationTitle.Text = "DOODLE PAD";
PageTitle.Text = "untitled.ipr";
Loaded += (object sender, RoutedEventArgs e) =>
{
  if (app.Content != null)
  {
    Surface.Strokes.Clear();
    Surface.Strokes = (StrokeCollection)app.Content;
  }
  if (app.Filename == null || app.Filename == "")
  {      
    PageTitle.Text = "untitled.ipr";
  }
  else
  {
    PageTitle.Text = app.Filename;
  }
};

See Below:

MainPage Constructor

Step 26

While still in the Code View for MainPage.xaml.cs, below the "}" of the "public MainPage()" method type the following Event Handlers:

private void New_Click(object sender, EventArgs e)
{
  if (MessageBox.Show("Start a new Doodle?", "Doodle Pad",
  MessageBoxButton.OKCancel) == MessageBoxResult.OK)
  {
    PageTitle.Text = "untitled.ipr";
    Surface.Strokes.Clear();
    app.Filename = PageTitle.Text;
    app.Content = Surface.Strokes;
  }
}

private void Open_Click(object sender, EventArgs e)
{
  app.Content = Surface.Strokes;
  NavigationService.Navigate(new Uri("/OpenPage.xaml", UriKind.Relative));
}

private void Save_Click(object sender, EventArgs e)
{
  app.Content = Surface.Strokes;
  NavigationService.Navigate(new Uri("/SavePage.xaml", UriKind.Relative));
}

private void Surface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
  drawStroke = new Stroke();
  drawStroke.DrawingAttributes.Color = drawColour;
  drawStroke.DrawingAttributes.Width = drawSize.Width;
  drawStroke.DrawingAttributes.Height = drawSize.Height;
  drawStroke.StylusPoints.Add(new StylusPoint 
  {
    X = e.GetPosition(Surface).X, 
    Y= e.GetPosition(Surface).Y
  });
  Surface.Strokes.Add(drawStroke);
}

private void Surface_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  drawStroke = null;
}

private void Surface_MouseMove(object sender, MouseEventArgs e)
{
  if (drawStroke != null)
  {
    drawStroke.StylusPoints.Add(new StylusPoint
    {
      X = e.GetPosition(Surface).X,
      Y = e.GetPosition(Surface).Y
    });
  }
}

private void Size_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  if (Size != null)
  {
    double _drawWidth = double.Parse(((ListBoxItem)Size.SelectedItem).Tag.ToString());
    drawSize = new Size { Height = _drawWidth, Width = _drawWidth };
  }
}

private void Colour_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  if (Colour != null)
  {
    string _colour = (string)((ListBoxItem)Colour.SelectedItem).Tag;
    drawColour = Color.FromArgb(
      byte.Parse(_colour.Substring(0, 2),
      System.Globalization.NumberStyles.HexNumber),
      byte.Parse(_colour.Substring(2, 2),
      System.Globalization.NumberStyles.HexNumber),
      byte.Parse(_colour.Substring(4, 2),
      System.Globalization.NumberStyles.HexNumber),
      byte.Parse(_colour.Substring(6, 2),
      System.Globalization.NumberStyles.HexNumber));
  }
}

See Below:

MainPage Event Handlers

Step 27

Save the Project as you have now finished the Windows Phone Silverlight application. Select the Windows Phone Emulator option then Select Debug then Start Debugging or click on Start Debugging:

Start Debugging

After you do, the following will appear in the Windows Phone Emulator after it has been loaded:

Application Running

Step 28

Scroll down the "Colour" ListBox then Tap a Colour for example Blue to select a Draw Colour, select a Draw Width the same way then Tap-and-hold to draw something, which can then be saved, see below:

Doodle Pad

Step 29

You can then Stop the application by selecting the Visual Studio 2010 application window and clicking on the Stop Debugging button:

Stop Debugging

This is a simple Doodle Pad example using the InkPresenter and LINQ-to-XML to Save and Open the "strokes" used to draw with, try adding more features such as more Draw Widths and Colours - make it your own!