Windows DevCenter    
 Published on Windows DevCenter (http://www.windowsdevcenter.com/)
 See this if you're having trouble printing code examples


Building Photo Uploaders with XAML

by Jack Herrington
08/29/2006

Introduction

If you are a user experience junky like I am, then XAML is like interface crack. If you haven't seen XAML demos on Windows Vista, or used the freely available WinFX SDK, then you haven't lived. XAML is a tag-based language that you use to create stunning interfaces quickly and easily.

In this article I'll show you not only how to develop one of these applications, but also how to do something useful with it. For this example I'll be writing a photo Uploader to get image files up to a simple PHP web application.

Building the PHP Web Application

The first stop on the journey is to create the PHP web application that will accept the photos, and show what has been uploaded. Because the PHP application isn't the primary focus of the article, I'll keep it pretty simple and just use a file-based solution to storing and retrieving the files. Obviously, to use this system in practice you will want to have a more robust image storage and retrieval system.

To start I'll create the page that will receive the uploaded files and move them into a sub-directory named "media." This PHP script is shown in Listing 1.

Listing 1. upload.php

<?php
move_uploaded_file( $_FILES['file']['tmp_name'],
  "media/".$_FILES['file']['name'] );
?>

Wow, it doesn't get a lot easier than that. Here I used PHP's move_uploaded_file function to take the uploaded file and move it into the media directory. Clearly the ideal application would check for colliding file names, but since this is just a test application I'll keep it simple.

To see what's been uploaded, I need another script that will show the contents of the media directory as an HTML page. That PHP script is shown in Listing 2.

Listing 2. index.php

<html>
<body>
<table>
<?php
$dir = opendir( "media" );
while ( ( $name = readdir( $dir ) ) ) {
if ( preg_match( "/[.]jpg$/", $name ) ) { 
?>
<tr>
<td><?php echo( $name ); ?></td>
<td><img src="media/<?php echo( $name ); ?>" /></td>
</tr>
<?php echo( $row[1] ); ?>
<?php } } ?>
</table>
</body>
</html>

This is a pretty simple script. It starts by building out the tags for a page and then a table. Then it opens up the directory and creates a row in the table for each file and two cells within each row, one of which holds the name of the file and the other an image tag which will display the image.

Once I have the XAML Uploader application written and some test images uploaded, I'll show the output of this page.

Starting with XAML

The next step is to create the XAML Uploader application. And that starts by installing the WinFX SDK and Visual Studio 2005. Both of these can be installed either on Windows XP or a Windows Vista beta if you are brave enough to run that. The SDK is free and evaluation versions of 2005 are also available. Plan to spend a long time on a fat pipe downloading this stuff because the SDK is several gigabytes.

With the development toolkit installed I can start working on the interface.

The Interface Basics

In my mind's eye I've sketched out a simple layout for the Uploader. Along the top is a line of image thumbnails, and then in the center is a preview of the selected thumbnail. When a thumbnail is selected, an Upload button will appear; when pressed it will send the file to the server.

The first thing to do is create a new XAML application project and title it UploaderProject. Then within that project I create a new XAML page called Uploader. This will create two files: Uploader.xaml and Uploader.xaml.cs. The XAML file defines the objects and the layout and the CS file is the code for the page. It's a C# class that's called when the page is loaded, buttons are clicked, etc. Using Visual Studio I could put together the layout visually. But I like to work in code and the first version of the page is shown in Listing 3.

Listing 3. Uploader.xaml

<Grid 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:d="http://schemas.microsoft.com/expression/interactivedesigner/2006"
  mc:Ignorable="d"
  Background="black" 
  x:Name="DocumentRoot"
  x:Class="UploaderProject.Uploader" 
  Width="640" Height="480">

  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
  </Grid.ColumnDefinitions>
  
  <Grid.RowDefinitions>
    <RowDefinition Height="27*"/>
    <RowDefinition Height="69*"/>
    <RowDefinition Height="4*"/>
  </Grid.RowDefinitions>
  
  <ScrollViewer Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch"
      VerticalAlignment="Top" Width="Auto" Height="Auto"
      HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
    <StackPanel RenderTransformOrigin="0.5,0.5" Orientation="Horizontal" 
      x:Name="ImageList" />
  </ScrollViewer>

  <Image Width="Auto" Height="Auto" Grid.Row="1" Grid.Column="0"
    x:Name="DisplayImage" HorizontalAlignment="Center"
    VerticalAlignment="Center" />

  <Button Click="Upload_Click" Visibility="Hidden" Grid.Row="1" Grid.Column="0"
    HorizontalAlignment="Right" VerticalAlignment="Bottom" x:Name="Upload"
    Content="Upload" FontSize="16" FontWeight="Bold" Margin="0,0,12,14"
    Width="92" BorderBrush="sc#1, 0.175407261, 0.290725321, 0.621118367" >
  </Button>

  <TextBox Grid.Row="2" Background="Black" BorderBrush="{x:Null}"
     Foreground="White" Grid.Column="0" x:Name="PercentDone"
     FontFamily="Tahoma" FontSize="9pt" />
  
</Grid>

I'll base this page on a Grid class. The Grid class is one of many XAML layout managers. It's similar to a table in HTML. In this case I'll define three rows. The first is where the thumbnails will go. The second row, the largest, is where the image preview will go. And the third row will hold a small status text line. If you look at the Grid.RowDefinitions tag you can see these three rows, and their relative widths, laid out.

After that, I define the visual objects. At the top I create a StackPanel within a ScrollViewer to hold the thumbnails. A StackPanel is another organizing class, like a Grid, but in this case it just stacks the contained objects either horizontally or vertically. The ScrollViewer puts a set of scrollbars around the StackPanel.

The Image object is the preview for the selected thumbnail. Below that, the Upload button is in the same Grid cell as the preview but is aligned along the bottom right to give some visual distinction.

Located in the bottom cell is a TextBox that contains the status line.

It's interesting to note the different approach taken in this layout model than what would be done in HTML. In an HTML <table> there would three rows, with one cell each and the content of each cell would be defined with the <td> tag of the cell. With XAML, I define a container and then use special attributes, in this case Grid.Row and Grid.Column to specify where I want the element within the container.

To spice this up, I'm going to add a definition at the top of the file that will provide a glow around an image thumbnail when the mouse rolls over it. This definition is shown in Listing 4.

Listing 4. The mouseover glow definition

  <Grid.Resources>
    <Style TargetType="{x:Type Image}" x:Key="TopImage">
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="BitmapEffect">
            <Setter.Value>
              <OuterGlowBitmapEffect GlowSize="6"/>
            </Setter.Value>
          </Setter>
        </Trigger>
      </Style.Triggers>
    </Style>
  </Grid.Resources>

This is the XAML version of an inline CSS stylesheet. In this case, I'm defining a style that applies to Image types and that triggers when the IsMouseOver property is set to true. A BitmapEffect property is altered to add an outer glow effect. Outer glow adds an anti-aliased glowing visual effect to the image. You can set the color of the glow, the size of the glow, and more.

XAML provides a grab bag of bitmap effects that you can apply not only to images but to almost any object. If you are familiar with Adobe Photoshop and its effects package, then you will see some similarities to what's provided in the XAML toolkit. To demonstrate that, I'll give the upload button a little visual pop by changing its background to have a linear gradient fill, as shown in Listing 5.

Listing 5. Altering the appearance of the Upload button

  <Button Click="Upload_Click" Visibility="Hidden"
    Grid.Row="1" Grid.Column="0"
    HorizontalAlignment="Right" VerticalAlignment="Bottom" x:Name="Upload"
    Content="Upload" FontSize="16" FontWeight="Bold" Margin="0,0,12,14"
    Width="92" BorderBrush="sc#1, 0.175407261, 0.290725321, 0.621118367" >
    <Button.Background>
      <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
        <LinearGradientBrush.GradientStops>
          <GradientStopCollection>
            <GradientStop Offset="0"
              Color="sc#1, 0.2154015, 0.2336096, 0.868529737"/>
            <GradientStop Offset="0.9" Color="#FFF0F0EA"/>
          </GradientStopCollection>
        </LinearGradientBrush.GradientStops>
      </LinearGradientBrush>
    </Button.Background>
  </Button>

Notice that I've changed the background property of the button. All of the UI elements in XAML are composed of XAML elements that you can alter in this way, either at the individual object level, or by specifying a class of elements. For example, all buttons--or all buttons of a specific class--can be given a new background, a new font for the name of the button, a new template for positioning the content of the button, and so on. All while retaining the interactivity of the button. So, for example, creating icon buttons is a snap in XAML.

Wiring Up the Back End

Now with the interface all put together I can start working on the Uploader.xaml.cs class that sits behind the user interface. The first thing I do is create class that will hold information about each image; I call it ImageInfo and it's shown in Listing 6.

Listing 6. The ImageInfo private class

using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Media.Imaging;
using System.Collections;
using System.Configuration;

namespace UploaderProject
{
  public class ImageInfo
  {
    private BitmapImage m_Image = null;
    public BitmapImage Image { get { return m_Image; } }

    private string m_sPath = null;
    public string Path { get { return m_sPath; } }

    private ImageInfo() {
      m_Image = null;
      m_sPath = "";
    }

    public ImageInfo( string sPath, BitmapImage image ) {
      m_sPath = sPath;
      m_Image = image;
    }
  }

The class isn't really important enough to get its own file, so I'm just going to stick it in the same definition file for the Uploader interface code.

To finish off the backend, I write the code for the interface itself as the Uploader class as shown in Listing 7.

Listing 7. The Uploader class behind the interface

  public partial class Uploader
  {
    private Hashtable m_ImageLookup = new Hashtable();
    private WebClient m_Client = new WebClient();
    private ArrayList m_PendingFiles = new ArrayList();
    private ImageInfo m_CurrentlySelected = null;
    private string m_sCurrentDownload = "";

    public Uploader() {
      m_Client.DownloadFileCompleted += new AsyncCompletedEventHandler(
          WebClient_DownloadFileCompleted );
      m_Client.DownloadProgressChanged += new
        DownloadProgressChangedEventHandler(
          WebClient_DownloadProgressChanged );
    }

    void WebClient_DownloadProgressChanged(object sender,
        DownloadProgressChangedEventArgs e) {
      PercentDone.Text = String.Format("{0} - {1}% done",
        m_sCurrentDownload, e.ProgressPercentage);
    }

    void WebClient_DownloadFileCompleted(object sender,AsyncCompletedEventArgs e) {
      PercentDone.Text = "";
      StartUpload();
    }

    void StartUpload() {
      string sPath = (string)m_PendingFiles[0];
      m_sCurrentDownload = Path.GetFileName(sPath);
      m_PendingFiles.RemoveAt(0);

      string sUploadPage = ConfigurationSettings.AppSettings.Get("UploadPage");

      if ( m_Client.IsBusy == false )
        m_Client.UploadFileAsync(new Uri(sUploadPage), "POST", sPath, null);
    }

    protected override void OnInitialized(EventArgs e) {
      string[] sFiles = Directory.GetFiles( Directory.GetCurrentDirectory() );
      foreach( string sFile in sFiles ) AddImageButton( sFile );
    }

    void Upload_Click(object sender, RoutedEventArgs e) {
      if (m_CurrentlySelected != null)
      {
        m_PendingFiles.Add(m_CurrentlySelected.Path);
        StartUpload();
        Upload.Visibility = Visibility.Hidden;
        m_CurrentlySelected = null;
      }
    }

    protected void AddImageButton(string sPath) {
      if (Path.GetExtension(sPath).ToLower() != ".jpg") return;

      BitmapImage image = null;
      try {
        image = new BitmapImage();
        image.BeginInit();
        image.UriSource = new Uri("file://" + sPath);
        image.EndInit();
      } catch { }

      if (image != null)
      {
        int nFixedHeight = 100;

        double dScaleRatio = (double)image.Height / (double)(nFixedHeight - 10);

        Canvas b = new Canvas();
        b.Width = (int)(image.Width / dScaleRatio) + 5;
        b.Height = nFixedHeight;

        Image bi = new Image();
        bi.Source = image;
        bi.Width = (int)(image.Width / dScaleRatio);
        bi.Height = nFixedHeight - 10;
        bi.Style = (Style)DocumentRoot.FindResource("TopImage");
        bi.SetValue(Canvas.LeftProperty, 5.0);
        bi.SetValue(Canvas.TopProperty, 5.0);
        bi.MouseUp += new MouseButtonEventHandler(TopImage_MouseUp);
        b.Children.Add(bi);

        m_ImageLookup[bi] = new ImageInfo(sPath, image);

        ImageList.Children.Add(b);
      }
    }

    void TopImage_MouseUp(object sender, MouseButtonEventArgs e) {
      if (m_ImageLookup[sender] != null)
      {
        m_CurrentlySelected = (ImageInfo)m_ImageLookup[sender];
        DisplayImage.Source = m_CurrentlySelected.Image;
        Upload.Visibility = Visibility.Visible;
      }
    }
  }
}

The bulk of the code sits in the AddImageButton method, which adds an image thumbnail to the display. Its job is to dynamically create a new Canvas object, then within that place an Image object, and add that into the ImageList element in the interface. As with Dynamic HTML, adding new interface items to the display is as easy as adding new nodes into a visual tree.

The other interesting portion of the example is in the Upload_Click method which is called when the Upload button is clicked. It starts the file transfer to the web server using a WebClient object. This WebClient object is the equivalent of a scriptable web browser, you can use it to get the contents of pages, post forms, and it even stores cookies and sessions. So if you have some authentication requirements you can still use the WebClient to first log in, then upload the files.

The value of using the WebClient is that there is no special back door for the Uploader. This mechanism uses exactly the same method for uploading files as a web browser, so the PHP web application doesn't need to change at all to support this mechanism.

One thing the application does need is the URL of the page that will handle the upload of the image. This is set in the XML configuration file for the application as shown in Listing 8.

Listing 8. The configuration file

?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="UploadPage"
      value="http://192.168.5.106/media/upload.php" />
  </appSettings>
</configuration>

In my case, I have the PHP application on a Mac OS X laptop sitting next to my Windows box on the same router, thus the hardcoded address. In production, you would likely use a host name resolved via DNS to an IP address, such as http://mycompany.com/upload.php.

Trying It Out

With the PHP web site done, and the front- and backend of the Uploader written, it's time to give it a spin and see if it works. So, I press the F5 key to launch the debugger and I see something that looks like Figure 1-1.

Figure 1-1
Figure 1-1. The initial display of the Uploader

Next, I select the nice picture of yellow flowers and the C# code sets the image in the middle of the frame to the selected image. This is shown in Figure 1-2.

Figure 1-2
Figure 1-2. After selecting an image

What's kind of fun at this point is to resize the window; notice that the layout of the elements is preserved as the window is stretched. That's part of the advantage of using XAML and, in particular, the grid layout system. Moving on, I hit the Upload button and wait for the upload to complete. Then I browse to the index.php page on the site to see that the file has indeed been uploaded per my request as shown in Figure 1-3.

Figure 1-3
Figure 1-3. Viewing the uploaded image

There you have it, a rudimentary image uploader using some of the sexier features of XAML and the WinFX SDK. It's also a good demonstration of the Windows world playing nice with the open source world.

Conclusion

There has been a lot of talk about Windows Vista recently. Will it ship? When will it ship? What will be in it? I'm of the mind that it will ship, and from my experience with WPF (the Windows Presentation Foundation, which includes XAML) I'm sure Windows will ship as well, first and foremost because it's at the core of Vista. But secondly, because from the projects I've worked on I think it's solid, and the library design is good.

However, I do have one word of caution, and that is to watch out for old reference stuff. The WinFX SDK has gone through some changes. The class names have been altered, the attributes changed, and how the files are written has changed; you can see it when you Google for information on how to use a particular tag or class. Honestly, even on Microsoft sites there is legacy information from previous versions of the SDK. So, keep an eye out as you develop with this stuff and make sure that the code you are copying and pasting still works with the current version of the API.

That being said, I'm impressed with WPF and I think there is a lot of potential for engineers and graphic designers to take some of their interfaces to the next level with it.

Jack Herrington is an engineer, author and presenter who lives and works in the Bay Area. His mission is to expose his fellow engineers to new technologies. That covers a broad spectrum, from demonstrating programs that write other programs in the book Code Generation in Action. Providing techniques for building customer centered web sites in PHP Hacks. All the way writing a how-to on audio blogging called Podcasting Hacks.


Return to the Windows DevCenter.

Copyright © 2009 O'Reilly Media, Inc.