Windows Presentation Foundation (WPF) and AOL Video Search

AOL Video Search

by Shawn Wildermuth
March 16, 2007

Introduction

No one tell Google, but there are internet videos on sites other than YouTube (and GoogleVideo). Since everyone and their brother has started their own video website since the success of YouTube, it would be good if there was a way to search for just videos. Oh wait, there is: AOL Video Search. What's most interesting to me is that AOL Video Search is doing video search across engines. One search can yield results from YouTube, CBS, NBC, etc. No more trying to remember which site had that cool video of the exploding whale--a quick search will help you find it on several sites.

After my last article, in which I showed you how to add video search to your ASP.NET website, I got to thinking that it should be really easy to make a quick Windows Presentation Foundation (WPF) application using the API. Here is what the resulting application will look like:

Figure 1
Figure 1. The Video Library API

Starting a WPF Project

For this article, we will do everything in Visual Studio. Assuming you have installed the .NET 3.0 framework, the updated Windows SDK, and the Visual Studio Extensions for WPF, you will be able to create a new Windows Application (WPF). This project type sets up an application that uses a single window. All the work we will be doing will be in the Window1.xaml file and its code-behind file (Window1.xaml.cs).

Let's start with the design markup for our video searcher application. A new WPF window starts with a top-level Window element and contains a simple Grid element:

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="VideoLibrary.Window1"
  x:Name="mainWindow"
  Title="AOL Video Searcher"
  Width="800" Height="600" >

  <Grid>
  </Grid>

</Window>

For our specific purpose, the Grid container makes the most sense. We will need to determine how our layout should be created on the grid. By breaking out the UI into a grid as seen in Figure 2, we can see that we need three rows and two columns:

Figure 2
Figure 2. The grid

Now that we see what we want, let's make it happen by adding the row and column definitions to our XAML:

<Grid.ColumnDefinitions>
  <ColumnDefinition Width="250" />
  <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
  <RowDefinition Height="38" />
  <RowDefinition Height="*" />
  <RowDefinition Height="20" />
</Grid.RowDefinitions>

In the row and column definitions, we are able to define specific widths for some of the elements, and use an asterisk (*) to indicate "filling" of the available area that remains. The ColumnDefinitions code above specifies that our left column should always be 250 wide and the second column should occupy the rest of the space. Likewise, the RowDefinitions code specifies that the top and bottom rows are fixed sized, with the middle row occupying whatever space remains in the window.

Now that we have the grid set up, we need to put some content in it. First we need the top banner. This banner is made up of a background rectangle with a color gradient, a TextBlock containing the title, and a stack panel containing the search elements. Each of these elements need to be tied to the row and columns they will use, as seen below:

<!-- Top Frame -->
<Rectangle Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
  <Rectangle.Fill>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
      <GradientStop Color="LightGray" Offset="0" />
      <GradientStop Color="SteelBlue" Offset=".6" />
      <GradientStop Color="Silver" Offset="1" />
    </LinearGradientBrush>
  </Rectangle.Fill>
</Rectangle>

<Label FontSize="20" FontWeight="Bold"
       Margin="0,1,0,3"
       Grid.Column="0" Grid.ColumnSpan="2">AOL Video Search</Label>

<!-- Search Box -->
<StackPanel Orientation="Horizontal" Grid.Column="1" 
               HorizontalAlignment="Right">
  <Label FontSize="16" Margin="0,1,0,3"
         VerticalAlignment="Bottom" >Search:</Label>
  <TextBox Name="searchBox"
           Margin="0,9,9,6" MinWidth="150"
           HorizontalAlignment="Stretch" />
  <Button Margin="0,9,9,6" >Search</Button>
</StackPanel>

Notice that the Grid.Column and Grid.Row attributes are attached to the each of these elements to specify where in the grid they should live. On the background gradient rectangle, you will notice a Grid.ColumnSpan attribute that specifies that the rectangle should span across both columns of our design.

Next, we need a list box to show each result. A ListBox element allows you to specify the template for how to show the data, but for now we will just place it in the grid and come back after we have searched for videos. We also need to place a Frame element in the grid. The Frame element allows for different types of content to be placed. If the Source of a Frame is a valid URI, it will show the result as a web page. We will use it to show our actual resulting videos. The ListBox and Frame should look like this:

<!-- Results Pane -->
<ListBox Name="resultBox" HorizontalAlignment="Stretch"
         VerticalAlignment="Stretch"
         Grid.Row="1" />

<!-- Video Pane -->
<Frame Name="videoFrame"
       Grid.Row="1" Grid.Column="1" />

Lastly, we need to create our bottom frame. Again, we are going to use a spanning rectangle and a stack panel:

<!-- Bottom Frame -->
<Rectangle Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
  <Rectangle.Fill>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
      <GradientStop Color="SteelBlue" Offset="0" />
      <GradientStop Color="LightGray" Offset=".6" />
      <GradientStop Color="SteelBlue" Offset="1" />
    </LinearGradientBrush>
  </Rectangle.Fill>
</Rectangle>

<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" 
               Grid.ColumnSpan="2" Grid.Row="2" Margin="0,0,9,0">
  <TextBlock FontSize="12" FontWeight="Bold" Text="Total Found: "/>
  <TextBlock FontSize="12" />
</StackPanel>

Searching for Videos

The Video Search API has not changed since the last article. We will continue to use the getVideos call on the XML API. Again in this article, I will be using YOURAPPID as the API key. You will need to replace this string with your actual application ID before the example will work.

In this example we will be using some of the same code from the ASP.NET example in that we will continue to call a PerformSearch method that will construct the URL to do our search. The big difference in this example is that we will let data binding do all our work for us. In the ASP.NET example, we not only constructed the URL, but we executed it and converted the result into a form that we could use. Instead of doing all that work, we can use an XmlDataProvider element to data bind across all the elements in our window design. An XmlDataProvider is placed inside the resources of the window itself to allow us to use it throughout the window. To bind to it, we will need to specify a key to use to refer to it (e.g., x:Key). In this case we are going to call it SearchResult:

<Window
  ...
  >
  <Window.Resources>
    
    <!-- XML Data Source -->
    <!-- We're binding directly to the search API Results -->
    <XmlDataProvider x:Key="SearchResult">
    </XmlDataProvider>
    
  </Window.Resources>
  
  ...
  
</Window>

Normally an XmlDataProvider specifies a source of where the XML comes from (or it could be a data island inside the XAML). But we are going to construct the search URI in code, so we can leave the provider contents blank. To construct the search URI, we can use almost the same code we did in the last article. Our PerformSearch method will construct the request URL based on the search term. In the earlier example, we used the Server object to encode our search term for the URL. Since we are not using ASP.NET, we will need to use a class called HttpUtility that contains a UrlEncode method (in fact, the same code that the Server object exposed). Before we can use this class, we will need a reference to the System.Web assembly.

Another change to our PerformSearch method is to remove all the code that actually performs the search for us. We can simply comment out all the code after the construction of the request URL. Finally, we need to set our XmlDataProvider source to be the request URL we just created. We can set the source by retrieving the XmlDataProvider from the resources as seen at the end of the PerformSearch method below:

void PerformSearch()
{

  const string appId = "YOURAPPID"; // Your AppID here const 
                                    // string method = "truveo.videos.getVideos";
  const string method = "truveo.videos.getVideos";
  const int pageSize = 50;

  // Construct the Search and Search URL
  string searchTerm = HttpUtility.UrlEncode(searchBox.Text);
  string requestUrl = 
string.Format("http://beta.searchvideo.com/apiv3?appid={0}&method={1}&query={2}&results={3}",
    appId,
    method,
    searchTerm, 
    pageSize);

  // We don't need any of this code because we can ask WPF 
  // to use an XML DataSource and data binding to do 
  // all the real work.

  // Perform an Synchronous Search
  //HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(requestUrl);
  //WebResponse response = req.GetResponse();

  // The Stream of the Search Response
  //Stream strm = response.GetResponseStream();

  // Deserialize the results into our XSD Generated Class
  //XmlSerializer ser = new XmlSerializer(typeof(Aol.Search.Video.Response));
  //Response res = (Response)ser.Deserialize(strm);

  // If there are results, then show them
  //theDataList.DataSource = res.VideoSet.Video;
  //theDataList.DataBind();

  // Show the number of results
  //numResultsLabel.Text = string.Format("Found {0} result(s)",
  //  res.VideoSet.totalResultsAvailable);

  // Change the Source of the XML Data Provider
  XmlDataProvider xmlData = (XmlDataProvider)this.Resources["SearchResult"];
  xmlData.Source = new Uri(requestUrl);

}

Data Binding the Search

When you execute a search with the Search API, it returns an XML document that looks something like this (I've omitted unnecessary parts of the document):

<?xml version="1.0" encoding="utf-8"?>
<Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xmlns="http://beta.searchvideo.com" 
          xsi:schemaLocation="http://beta.searchvideo.com apiv3.xsd">
  <method>truveo.videos.getVideos</method>
  <query>baby kungfu</query>
  <VideoSet>
    ...
  </VideoSet>
</Response>

Of particular note is the xmlns attribute on the Response tag. This attribute specifies the default namespace. Because there is a default namespace, we have to tell the XmlDataProvider about the namespace so that our XPath expressions work. Unfortunately, we cannot tell the XmlDataProvider that there is a default namespace. Instead, we have to specify a prefix. Inside the XmlDataProvider, you will need to add an XmlNamespaceCollection inside the XmlDataProvider.NamespaceManager property like so:

<!-- XML Data Source -->
<!-- We're binding directly to the search API Results -->
<XmlDataProvider x:Key="SearchResult">

  <XmlDataProvider.XmlNamespaceManager>
    <XmlNamespaceMappingCollection>
      <XmlNamespaceMapping Uri="http://beta.searchvideo.com" Prefix="aol" />
    </XmlNamespaceMappingCollection>
  </XmlDataProvider.XmlNamespaceManager>

</XmlDataProvider>

Now that we have our XmlDataSource set up, we can start binding data. We should bind the entire Grid to the data source so we can do bindings across the entire user interface. To do this, add the DataContext attribute to the Grid:

<Grid DataContext="{StaticResource SearchResult}">

Let's make sure our search is working correctly. To do so, we should start with a simple binding. In the footer of the XAML, there is a TextBlock that holds the number of results. It's the last TextBlock in the XAML. We can set the Text of that TextBlock using an XPath expression. In this case, we want to find the total results, which can be reached with the XPath of "/aol:Response/aol:VideoSet/aol:totalResultsAvailable". We can bind that text box as shown below:

<TextBlock FontSize="12" 
Text="{Binding XPath=/aol:Response/aol:VideoSet/aol:totalResultsAvailable}" />

To test to see if the bindings are working, we need to go to the button and add an event handler for the Click event. You can do this by adding a Click attribute to the button in the header:

<Button Margin="0,9,9,6" Click="search_Clicked">Search</Button>

In the code-behind, you will want to create the handler:

public void search_Clicked(object source, RoutedEventArgs args)
{
  PerformSearch();
}

If you run the application, put in a result, and press the button you should see the number of results appear at the end of the page.

Completing the Application

Now that we have all the wiring hooked up, we need to simply add the rest of the data binding to the application. First, the ListBox we created earlier to show the results on the left side of the user interface needs to be bound. To do this, we first need to set the entire ListBox to be bound to the list of videos returned. You can do this by binding the ItemsSource to the list of videos:

<!-- Results Pane -->
<ListBox Name="resultBox" HorizontalAlignment="Stretch"
      VerticalAlignment="Stretch"
      Grid.Row="1"
      ItemsSource="{Binding XPath=/aol:Response/aol:VideoSet/aol:Video}" />

Next we need to define a DataTemplate for each item in the ListBox. Data templating is too large a topic to explain here (but there is a link to a good article on MSDN in the References section below). In a nutshell, a DataTemplate is a set of XAML that is used for each item in the ListBox. As it binds to individual pieces of XAML, the DataTemplate will be used to create the ListBox item. We use data binding in the template to create a thumbnail of the video, the title of the video, and other information about the video, as seen below:

 <!-- Template for the list box -->
    <DataTemplate x:Key="videoTemplate">
      <Grid HorizontalAlignment="Stretch"
            ToolTip="{Binding XPath=aol:description}">
        <Grid.RowDefinitions>
          <RowDefinition/>
          <RowDefinition/>
          <RowDefinition/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="100" MaxWidth="100"/>
          <ColumnDefinition Width="120" MaxWidth="120"/>
        </Grid.ColumnDefinitions>
        <TextBlock FontSize="14" FontWeight="Bold" 
                   Width="235" ClipToBounds="True" 
                   Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" 
                   Text="{Binding XPath=aol:title}" />
        <Image Source="{Binding XPath=aol:thumbnailUrl}" 
               Width="100" Height="50" 
               Grid.Row="1" Grid.Column="0" Grid.RowSpan="3" 
               HorizontalAlignment="Left" />
        <TextBlock Grid.Row="1" Grid.Column="1"
                   ClipToBounds="True"
                   Text="{Binding XPath=aol:channel}" />
        <TextBlock Grid.Row="2" Grid.Column="1"
                   ClipToBounds="True"
                   Text="{Binding XPath=aol:dateProduced}" />
        <TextBlock Grid.Row="3" Grid.Column="1"
                   ClipToBounds="True"
                   Text="{Binding XPath=aol:userRating}" />
      </Grid>
    </DataTemplate>

You will notice that when we data bind individual elements, we do not use the entire XPath path, but only the relative path. This is because each list box item will be passed the element of Video from the resulting XML file. The thumbnailUrl, channel, title, and other data bound properties are all child elements of the Video element, so we can bind to them by just specifying their names (with the namespace prefix).

The DataTemplate is stored in the Window's resources like the XmlDataProvider was. In this way we can refer to it from the ListBox by specifying the ItemTemplate like so:

 <!-- Results Pane -->
    <ListBox Name="resultBox" HorizontalAlignment="Stretch"
             VerticalAlignment="Stretch"
             Grid.Row="1"
             ItemsSource="{Binding XPath=/aol:Response/aol:VideoSet/aol:Video}"
             ItemTemplate="{StaticResource videoTemplate}"  />

Finally, we can bind our Frame element to the selected item of the ListBox. We do this by specifying an ElementName in the binding to the Source of the Frame element. The ElementName allows us to bind to other controls in the XAML document. The final step to make this work is to specify a complex path (note: not XPath but path, since we're dealing with simple CLR types instead of XML elements). This path is going to get the SelectedItem of the ListBox, and then get the videoUrl element of the selected Video node. Finally, we can finish the Path with the InnerText of the the videoUrl element to specify the source of the Frame.

<!-- Video Pane -->
<Frame Name="videoFrame"
    Source="{Binding ElementName=resultBox, Path=SelectedItem[videoUrl].InnerText}"
    Grid.Row="1" Grid.Column="1" />

Conclusion

Using WPF's native XML data binding makes creating your own video search application quite simple. Creating a Windows application that allows searching for videos on the internet becomes a fairly trivial affair. Because the AOL Video Search API is a simple URL that returns XML, you can let WPF data binding do all the heavy lifting instead of parsing out the results by hand and creating controls.

Resources

References

 


Enable the Subscriptions block here!