Page 1—Adding Items to the Shopping Cart

With the business classes in place in StoreItems.cs, you're ready to build the first page. Open up Window1.xaml, as shown in Figure 4-4.

Now, edit this file to create a slider for the photographs. You'll do this by adapting the image list created in the previous chapter to display the U.S. presidents.

In that example you had a Grid with a StackPanel, and inside that was a ListBox with ListBox items. In this case, rather than explicitly naming the items in the listbox, you're going to bind the ListBox to a data source (a static resource called Photos). You'll put the ListBox inside a Grid, and you'll put that inside a ViewBox so the user can resize the entire thing. This will be done in two steps, with the first one allowing you to visualize the Grid.

The Photo Cooperative

Figure 4-4. The Photo Cooperative

<Viewbox VerticalAlignment="Top" Stretch="Uniform">

<Grid Margin="20" Width="620" ShowGridLines="True" >
   <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="120" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="250" />
      <RowDefinition Height="15" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
   </Grid.RowDefinitions>
   <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition Width="400" />
      <ColumnDefinition Width="160" />
   </Grid.ColumnDefinitions>
   <TextBlock Grid.Row="0" Grid.ColumnSpan="3"
       Style="{DynamicResource TitleText}">
      <Span>The Photo Co-op: </Span>
      <Span FontStyle="Italic"> your pictures your way</Span>
   </TextBlock>
</Grid>

</Viewbox>

If you run the application now, you should see something like Figure 4-5. You won't want the gridlines to be visible, though—to turn them off, set the ShowGridLines property of the Grid to False.

Running the application with the gridlines on

Figure 4-5. Running the application with the gridlines on

Next, you need to deal with some of the resources that the Window will use (specifically, DynamicResource and PhotoListStyle). It is helpful to understand that you simply need to add these resources to a Window.Resources section. You'll put this section just below your Window class declaration:

<Window x:Class="PhotoCooperative.Window 1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Photo Cooperative" Height="600" Width="800"
    xmlns:sbts="clr-namespace:PhotoCooperative"
>
   <Window.Resources>

      <!-- PHOTOLIST TEMPLATE -->

      <Style x:Key="PhotoListStyle" TargetType="{x:Type ListBox}">
         <Setter Property="Template">
            <Setter.Value>
               <ControlTemplate TargetType="{x:Type ListBox}" >
                  <Border
                      BorderBrush="Gray"
                      BorderThickness="1"
                      CornerRadius="6"
                      Background="{DynamicResource ListBoxGradient}" >
                     <ScrollViewer
                         VerticalScrollBarVisibility="Disabled"
                         HorizontalScrollBarVisibility="Auto">
                        <StackPanel
                            IsItemsHost="True"
                            Orientation="Horizontal"
                            HorizontalAlignment="Left" />
                     </ScrollViewer>
                  </Border>
               </ControlTemplate>
            </Setter.Value>
         </Setter>
      </Style>

      <!-- PHOTOLIST STORYBOARDS -->

      <Style x:Key="PhotoListItem" TargetType="{x:Type ListBoxItem}">
         <Setter Property="MaxHeight" Value="75" />
         <Setter Property="MinHeight" Value="75" />
         <Setter Property="Opacity" Value=".75" />
         <Style.Triggers>
            <EventTrigger RoutedEvent="Mouse.MouseEnter">
               <EventTrigger.Actions>
                  <BeginStoryboard>
                     <Storyboard>
                        <DoubleAnimation
                            Duration="0:0:0.2"
                            Storyboard.TargetProperty="MaxHeight"
                            To="85" />
                        <DoubleAnimation
                            Duration="0:0:0.2"
                            Storyboard.TargetProperty="Opacity"
                            To="1.0" />
                     </Storyboard>
                  </BeginStoryboard>
               </EventTrigger.Actions>
            </EventTrigger>

            <EventTrigger RoutedEvent="Mouse.MouseLeave">
               <EventTrigger.Actions>
                  <BeginStoryboard>
                     <Storyboard>
                        <DoubleAnimation
                            Duration="0:0:1"
                            Storyboard.TargetProperty="MaxHeight" />
                        <DoubleAnimation
                            Duration="0:0:0.2"
                            Storyboard.TargetProperty="Opacity" />
                     </Storyboard>
                  </BeginStoryboard>
               </EventTrigger.Actions>
            </EventTrigger>
         </Style.Triggers>
      </Style>
   </Window.Resources>

At this point you will need to add the ListBox code mentioned earlier to your Window1.xaml file, right below the TextBlock inside the Grid:

<ListBox Style="{DynamicResource PhotoListStyle}"
    Grid.Row="1"
    Grid.ColumnSpan="3"
    Name ="PhotoListBox"
    Margin="0,0,0,20"
    DataContext="{Binding Source={StaticResource Photos}}"
    SelectionChanged ="PhotoListSelection"
    ItemsSource="{Binding }"
    ItemContainerStyle="{DynamicResource PhotoListItem}"
    SelectedIndex="0" />

Note the SelectionChanged attribute. This indicates that you need an event handler for when the selection changes in the photo display. Put that in Window1.xaml.cs:

private void PhotoListSelection( object sender, RoutedEventArgs e )
{
   String path = ( ( sender as ListBox ).SelectedItem.ToString() );
   BitmapSource img = BitmapFrame.Create( new Uri( path ) );
}

Recall that in StoreItems.cs you declared a class of type PhotoList:

public class PhotoList : ObservableCollection<ImageFile>

In Window1.xaml.cs, create a member variable that is an instance of this class:

public partial class Window1 : System.Windows.Window
{
   public PhotoList Photos;

The business class also defines an ImageFile class (see the listing for StoreItems.cs), but your Resources section needs to define a DataTemplate for binding to an ImageFile:

<!-- DATA TEMPLATES -->

<DataTemplate DataType="{x:Type sbts:ImageFile}">
   <Border VerticalAlignment="Center"
       HorizontalAlignment="Center"
       P adding="4"
       Margin="2"
       Background="White">
      <Image Source="{Binding Image}" />
   </Border>
</DataTemplate>

The rest is just aesthetics. You'll want to define styles both for the Window itself and for the Title text:

<!-- STYLES -->

<Style TargetType="{x:Type sbts:Window1}">
   <Setter Property="Background"
       Value="{DynamicResource WindowGradient}" />
</Style>

<Style x:Key="TitleText"
    TargetType="{x:Type TextBlock}" >
   <Setter Property="FontFamily"
       Value="Segoe Black" />
   <Setter Property="FontSize"
       Value="20px" />
   <Setter Property="Foreground"
       Value="MidnightBlue" />
</Style>

Also, both the ListBox and the Window use gradients, so these must be defined as well. Add these brushes to the top of the Window.Resources section:

<!-- LINEAR GRADIENT BRUSHES -->
<LinearGradientBrush x:Key="WindowGradient"
    StartPoint="0,0.3"
    EndPoint="1,0">
   <GradientStop Color="#B2B6CAFF"
       Offset="0" />
   <GradientStop Color="#BFC3D5FF"
       Offset="0.1" />
   <GradientStop Color="#E0E4F0FF"
       Offset="0.3" />
   <GradientStop Color="#E6EAF5FF"
       Offset="0.5" />
   <GradientStop Color="#CFD7E2FF"
       Offset="0.6" />
   <GradientStop Color="#BFC5D3FF"
       Offset="0.8" />
   <GradientStop Color="#C4CBD8FF"
       Offset="1" />
</LinearGradientBrush>

<LinearGradientBrush x:Key="ListBoxGradient"
    StartPoint="0,0"
    EndPoint="0,1">

   <GradientStop Color="#90000000"
       Offset="0" />
   <GradientStop Color="#40000000"
       Offset="0.005" />
   <GradientStop Color="#10000000"
       Offset="0.04" />
   <GradientStop Color="#20000000"
       Offset="0.945" />
   <GradientStop Color="#60FFFFFF"
       Offset="1" />

</LinearGradientBrush>

You're almost ready to run this puppy—you just have to tell the Application what your data source is. In App.xaml, add this code:

<Application x:Class="PhotoCooperative.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sbts="clr-namespace:PhotoCooperative"
    Startup="AppStartup">
   <Application.Resources>
      <ObjectDataProvider x:Name="PhotosODP" x:Key="Photos"
          ObjectType="{x:Type sbts:PhotoList}" />
   </Application.Resources>
</Application>

Then, in App.xaml.cs, add the following:

public partial class App : System.Windows.Application
{
   void AppStartup( object sender, StartupEventArgs args )
   {
      Window1 theWindow = new Window1();
      theWindow.Show();

      ObjectDataProvider dataProvider =
          this.Resources["Photos"] as ObjectDataProvider;

      PhotoList photoList = dataProvider.Data as PhotoList;
      theWindow.Photos = photoList;
      theWindow.Photos.Path = @"..\..\Photos";
   }
}

With all this wired together, you are ready to run your data-bound photo list (shown in Figure 4-6). The only thing left to do is create a Photos folder and put some GIF images in it. To add a new folder, right-click on the project and select Add → New Folder. Rename it Photos, then right-click on it and select Open in Windows Explorer. From there, you should be able to fill the folder with GIFs. If you need some GIFs in a hurry, you can download Alex's from http://tinyurl.com/2jarve.

Image slider

Figure 4-6. Image slider

Displaying the Selected Image

You can add a display of the selected image with just a couple of small changes. To begin, just below the ListBox, add an Image control:

<Image Name="CurrentPhoto"
    Grid.Row="3"
    Grid.Column="1"
    Margin="10"
    MouseDown="OnMouseDown"/>

This depends on two things. The first is an OnMouseDown event handler, which you can stub out in Window1.xaml.cs:

private void OnMouseDown( object sender, MouseButtonEventArgs e )
{
}

The second is setting the CurrentPhoto when the user clicks in the image slider:

private void PhotoListSelection( object sender, RoutedEventArgs e )
{
   String path = ( ( sender as ListBox ).SelectedItem.ToString() );
   Bitmap Source img = BitmapFrame.Create( new Uri( path ) );
   CurrentPhoto.Source = img;
}

Presto! When the user selects an image in the slider, a nice blow-up of the image is shown below it, in the Image control you placed in grid row 3, column 1. Figure 4-7 demonstrates the effect.

The Image control

Figure 4-7. The Image control

Adding Cropping with the Adorner

There are two steps remaining to finish the first page. The first is to add the rubberband adorner discussed earlier, using the code detailed in Example 4-1. (We'll deal with the second task—adding the shopping cart—in the next section.) Begin by adding a RubberbandAdorner class, which you'll place in a new file called CropUtilities.cs.

To tie this into the application, you need to make a few changes in Window1.xaml. First, add the Crop and Undo buttons to the Grid:

<ListBox Style="{DynamicResource PhotoListStyle}"
    Grid.Row="1"
    Grid.ColumnSpan="3"
    Name ="PhotoListBox"
    Margin="0,0,0,20"
    DataContext="{Binding Source={StaticResource Photos}}"
    SelectionChanged ="PhotoListSelection"
    ItemsSource="{Binding }"
    ItemContainerStyle="{DynamicResource PhotoListItem}"
    SelectedIndex="0" />
<StackPanel
    Grid.Row="3"
    Grid.Column="0">

   <Button
       Name="CropButton"
       VerticalAlignment="Bottom"
       HorizontalAlignment="Center"
       Click="Crop"
       Width="55"
       Margin="2">
      Crop
   </Button>

</StackPanel>

<Button Grid.Row="3"
    Grid.Column="0"
    Name="UndoButton"
    VerticalAlignment="Bottom"
    HorizontalAlignment="Center"
    Click="Undo"
    IsEnabled="False"
    Width="55"
    Margin="2">
   Undo
</Button>

<Image Name="CurrentPhoto"
    Grid.Row="3"
    Grid.Column="1"
    Margin="10"
    MouseDown="OnMouseDown" />

These buttons need gradients, which you should now add to the Window.Resources section:

<LinearGradientBrush x:Key="ButtonGradient"
    StartPoint="0,0"
    EndPoint="0,1">

   <GradientStop Color="#FDB6CADF"
       Offset="0" />
   <GradientStop Color="#FCC3C5FF"
       Offset="0.1" />
   <GradientStop Color="#FCC4D0EF"
       Offset="0.3" />
   <GradientStop Color="#FDB7C2DF"
       Offset="0.6" />
   <GradientStop Color="#FE95B3CF"
       Offset="0.8" />
   <GradientStop Color="#FE96AACF"
       Offset="1" />

</LinearGradientBrush>

<LinearGradientBrush x:Key="ButtonUpGradient"
    StartPoint="0,0"
    EndPoint="0,1">

   <GradientStop Color="Transparent"
       Offset="0" />
   <GradientStop Color="#33000000"
       Offset="1" />

</LinearGradientBrush>

<LinearGradientBrush x:Key="ButtonDownGradient"
    StartPoint="0,0"
    EndPoint="0,1">

   <GradientStop Color="#10000000"
       Offset="0" />
   <GradientStop Color="#20000000"
       Offset="1" />

</LinearGradientBrush>

<LinearGradientBrush x:Key="ButtonDisabledGradient"
    StartPoint="0,0"
    EndPoint="0,1">

   <GradientStop Color="#10302A90"
       Offset="0" />
   <GradientStop Color="#10201040"
       Offset="1" />

</LinearGradientBrush>

You'll also need to add some event handlers. One is obvious: you need to capture the OnMouseDown event to begin the rubberband. But you must also initialize the adorner. The best place to do this is in the Loaded event. To fire the Loaded event, add the following to the Window tag:

<Window x:Class="PhotoCooperative.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Photo Cooperative" Height="600" Width="800"
    xmlns:sbts="clr-namespace:PhotoCooperative"
    Loaded="WindowLoaded"
    >

This sets the event handler WindowLoaded() to handle the Loaded event. The event handlers, of course, go in Window1.xaml.cs. Make sure you reference the following namespaces:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Input;
using System.Collections;

Here's the top of that file:

public partial class Window 1 : System.Windows.Window
{
   public PhotoList Photos;
   private Stack UndoStack;
   private RubberbandAdorner CropSelector;

   public Window1()
   {
      InitializeComponent();
      UndoStack = new Stack();
   }

   private void WindowLoaded( object sender, EventArgs e )
   {
      AdornerLayer layer = AdornerLayer.GetAdornerLayer( CurrentPhoto );
      CropSelector = new RubberbandAdorner( CurrentPhoto );
      CropSelector.Window = this;
      layer.Add( CropSelector );
      CropSelector.Rubberband.Visibility = Visibility.Hidden;
   }

The constructor initializes the Undo stack, and then the WindowLoaded() event handler (which runs after the Window is, er, loaded) creates the rubberband. It does so by getting the AdornerLayer (the acetate layer that is placed "on top of" the element it adorns).

Tip

The static method GetAdornerLayer() walks up the "visual tree," starting at CurrentPhoto, and returns the first adorner layer it finds.

CropSelector is a private member variable of type RubberbandAdorner (which itself is defined in CropUtilities.cs), and in WindowLoaded() you call its constructor, passing in the target element to be adorned (the current photo). You then set its Window property to the current window, add the RubberbandAdorner to the AdornerLayer, and set the RubberbandAdorner to invisible (awaiting the user's mouse-down).

MouseDown

When the user clicks in the current photo, the MouseDown event fires. Here, you remember the point where the mouse was clicked. You then capture the mouse through the CropSelector and call its StartSelection() method. You also enable the CropButton:

private void OnMouseDown( object sender, MouseButtonEventArgs e )
{
   Point anchor = e.GetPosition( CurrentPhoto );
   CropSelector.CaptureMouse();
   CropSelector.StartSelection( anchor );
   CropButton.IsEnabled = true;
}

Because the mouse has been captured, the MouseUp event is handled by the RubberbandAdorner (as you saw in Example 4-1, excerpted here):

public RubberbandAdorner( UIElement adornedElement ) : base( adornedElement )
{
   this.adornedElement = adornedElement;
   //...
   MouseMove += new MouseEventHandler( DrawSelection );
   MouseUp += new MouseButtonEventHandler( EndSelection );
}

With this in place, you can expand the PhotoListSelection() method to manage the Undo stack and the CropSelector:

private void PhotoListSelection( object sender, RoutedEventArgs e )
{
   String path = ( ( sender as ListBox ).SelectedItem.ToString() );
   BitmapSource img = BitmapFrame.Create( new Uri( path ) );
   CurrentPhoto.Source = img;
   ClearUndoStack();
   if ( CropSelector != null )
   {
      if ( Visibility.Visible == CropSelector.Rubberband.Visibility )
          CropSelector.Rubberband.Visibility = Visibility.Hidden;
   }
   CropButton.IsEnabled = false;
}

Handling the Crop button

You now need to implement the method to call when the Crop button is clicked, which will crop the current picture to the limits of the rubberband adorner:

private void Crop( object sender, RoutedEventArgs e )
{
   if ( CurrentPhoto.Source != null )
   {
      BitmapSource img = ( BitmapSource ) ( CurrentPhoto.Source );
      UndoStack.Push( img );
      Int32Rect rect = new Int32Rect();
      rect.X = ( int ) ( CropSelector.SelectRect.X *
          img.PixelWidth / CurrentPhoto.ActualWidth );
      rect.Y = ( int ) ( CropSelector.SelectRect.Y *
          img.PixelHeight / CurrentPhoto.ActualHeight );
      rect.Width = ( int ) ( CropSelector.SelectRect.Width *
          img.PixelWidth / CurrentPhoto.ActualWidth );
      rect.Height = ( int ) ( CropSelector.SelectRect.Height *
          img.PixelHeight / CurrentPhoto.ActualHeight );
      CurrentPhoto.Source = new CroppedBitmap( img, rect );

      CropSelector.Rubberband.Visibility = Visibility.Hidden;

      CropButton.IsEnabled = false;
      UndoButton.IsEnabled = true;
   }
}

You start by obtaining the Source property of the CurrentPhoto. CurrentPhoto, you will remember, is defined to be of type Image; its Source property returns the image source. You cast it to a BitMapSource and store it in the local variable img, which you push onto the UndoStack (to be restored if the user clicks the Undo button).

Next, you create a rectangle and obtain its size by asking the CropSelector for its proportions. You set the CurrentPhoto's image source to a new CroppedBitmap, which you created by passing in the original BitmapSource and the rectangle you just sized.

That done, you hide the rubberband, disable the Crop button, and enable the Undo button.

The picture is now cropped, but pressing the Undo button will undo the cropping:

private void Undo( object sender, RoutedEventArgs e )
{
   if ( UndoStack.Count > 0 )
   {
      CurrentPhoto.Source = ( BitmapSource ) UndoStack.Pop();
   }
   if ( UndoStack.Count == 0 )
   {
      UndoButton.IsEnabled = false;
   }
}

Adding the Shopping Cart

To finish the first page, you only need to add the shopping cart and the associated buttons. You want to offer the user the ability, having chosen a picture, to purchase a 5 x 7 photo, a sweatshirt, or a "holiday card."

Begin by adding the shopping cart template and triggers to Window.Resources:

<!-- SHOPPING CART TEMPLATE -->

<Style x:Key="ShoppingCartStyle"
    TargetType="{x:Type ListBox}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type ListBox}" >
            <Border BorderBrush="Gray"
                BorderThickness="1"
                CornerRadius="6"
                Background="{DynamicResource ShoppingCartGradient}" >
               <ScrollViewer>
                  <WrapPanel ItemHeight="70"
                      ItemWidth="70"
                      Margin="0,25,0,0"
                      IsItemsHost="True"
                      Orientation="Horizontal"
                      HorizontalAlignment="Center" />
               </ScrollViewer>
            </Border>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

<!-- SHOPPING CART TRIGGERS -->

<Style x:Key="ShoppingCartItem"
    TargetType="{x:Type ListBoxItem}">
   <Setter Property="BorderBrush"
       Value="Transparent" />
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type ListBoxItem}">
            <Border x:Name="ContentBorder"
                Opacity="0.85">
               <ContentPresenter />
            </Border>
            <ControlTemplate.Triggers>
               <Trigger Property="IsSelected"
                   Value="True">
                  <Setter TargetName="ContentBorder"
                      Property="Opacity"
                      Value="1.0" />
               </Trigger>
            </ControlTemplate.Triggers>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

The ShoppingCart uses a ShoppingCartGradient, so of course you'll need to add that to the Resources section with the other gradient brushes:

<LinearGradientBrush x:Key="ShoppingCartGradient" StartPoint="0,0" EndPoint="0,1">
   <GradientStop Color="#90000000" Offset="0" />
   <GradientStop Color="#40000000" Offset="0.002" />
   <GradientStop Color="#10000000" Offset="0.02" />
   <GradientStop Color="#20000000" Offset="0.98" />
   <GradientStop Color="#60FFFFFF" Offset="1" />
</LinearGradientBrush>

The trigger is set on the listbox, but the listbox it is set on is ShoppingCartItem, which we have not yet added. This is the drop-down from which the user selects an item to add to the cart. Add that combo box to your Window's Resources section now:

<!-- COMBOBOX STYLE -->

<Style TargetType="{x:Type ComboBox}" >
   <Setter Property="Background"
       Value="{DynamicResource ComboBoxGradient}" />
   <Setter Property="BorderThickness"
       Value="0" />
   <Setter Property="Height"
       Value="18px" />
   <Setter Property="Foreground"
       Value="MidnightBlue" />
</Style>

<LinearGradientBrush x:Key="ComboBoxGradient" StartPoint="0,0" EndPoint="0,1">

   <GradientStop Color="#B2B6CAFF" Offset="0" />
   <GradientStop Color="#B0B3C5FF" Offset="0.1" />
   <GradientStop Color="#BEE4E0FF" Offset="0.3" />
   <GradientStop Color="#B0D7E2FF" Offset="0.6" />
   <GradientStop Color="#B0C5D3FF" Offset="0.8" />
   <GradientStop Color="#C4CBD8FF" Offset="1" />

</LinearGradientBrush>

With the styles in place, you only need to add the objects to the Grid. In this case, you'll use an inner Grid:

<Image Name="CurrentPhoto"
    Grid.Row="3"
    Grid.Column="1"
    Margin="10"
    MouseDown="OnMouseDown"/>

   <Grid
       Grid.Row="5"
       Grid.Column="1"
       HorizontalAlignment="Center"
       Margin="0">
      <Grid.RowDefinitions>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
         <ColumnDefinition/>
         <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <ComboBox
          Grid.Row="0"
          Grid.Column="0"
          Margin="0,0,4,0"
          VerticalAlignment="Center"
          Name="PrintTypeComboBox"
          DataContext="{Binding Source={StaticResource PrintTypes}}"
          ItemsSource="{Binding}"
          Width="110"
          SelectedIndex="0" />

      <Button
          Grid.Row="0"
          Grid.Column="1"
          Click="AddToShoppingCart"
          VerticalAlignment="Center"
          Width="100"
          IsDefault="True">
         Add To Cart
      </Button>

      <Button
          Grid.Row="1"
          Grid.Column="1"
          Name="RemoveButton"
          Click="RemoveShoppingCartItem"
          VerticalAlignment="Center"
          IsEnabled="False"
          Width="100"
          Margin="10" >
         Remove Item
      </Button>

   </Grid>

</Grid>

Because you've declared event handlers, you'll have to stub them out in your code-behind:

private void AddToShoppingCart( object sender, RoutedEventArgs e )
{
}

private void RemoveShoppingCartItem( object sender, RoutedEventArgs e )
{
}

You'll also need a few additional data templates for your visual shopping cart. Add them to the Resources section now:

<DataTemplate DataType="{x:Type sbts:Print}">
   <Grid Margin="3">
      <Image Source="baseImg/photoframe.gif" />
      <Image Source="{Binding Photo}"
          MaxWidth="50"
          MaxHeight="70"
          VerticalAlignment="Center"
          HorizontalAlignment="Center"/>
   </Grid>
</DataTemplate>

<DataTemplate DataType="{x:Type sbts:GreetingCard}">
   <Grid Margin="3" >
      <Border VerticalAlignment="Center"
          HorizontalAlignment="Center"
          Background="{DynamicResource GreetingCardGradient}"
          Width="40"
          Height="50"
          BorderBrush="#44000000"
          BorderThickness="1" >
         <Border.RenderTransform>
            <SkewTransform AngleY="-10" />
         </Border.RenderTransform>
      </Border>
      <Border VerticalAlignment="Center"
          HorizontalAlignment="Center"
          Background="White"
          Width="50"
          Height="50"
          BorderBrush="#66000000"
          BorderThickness="1" >
         <Image Margin="3"
             Source="{Binding Photo}" />
      </Border>
   </Grid>
</DataTemplate>

<DataTemplate DataType="{x:Type sbts:SShirt}">
   <Grid Margin="3">
      <Image Source="baseImg/sweatshirt-front.gif"/>
      <Image Source="{Binding Photo}"
          MaxWidth="20"
          MaxHeight="22.5"
          VerticalAlignment="Center"
          HorizontalAlignment="Center"/>
   </Grid>
</DataTemplate>

These new types need to be identified. Do that in the App.xaml file:

<Application x:Class="PhotoCooperative.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sbts="clr-namespace:PhotoCooperative"
    Startup="AppStartup"
    >

    <Application.Resources>
        <ObjectDataProvider x:Name="PhotosODP"
            x:Key="Photos"
            ObjectType="{x:Type sbts:PhotoList}" />
        <ObjectDataProvider x:Name="ShoppingCartODP"
            x:Key="ShoppingCart"
            ObjectType="{x:Type sbts:PrintList}" />
        <ObjectDataProvider x:Name="PrintTypesODP"
            x:Key="PrintTypes"
            ObjectType="{x:Type sbts:PrintTypeList}" />
    </Application.Resources>
</Application>

Then add the associated code to AppStartup() in App.xaml.cs:

dataProvider = this.Resources["ShoppingCart"] as ObjectDataProvider;
PrintList printList = dataProvider.Data as PrintList;
theWindow.ShoppingCart = printList;

And don't forget to add the ShoppingCart private member variable to Window1.xaml.cs:

public partial class Window1 : System.Windows.Window
{
   public PhotoList Photos;
   public PrintList ShoppingCart;

With this additional code, your first page is 80% complete. Figure 4-8 shows the result.

Combo box and buttons added

Figure 4-8. Combo box and buttons added

Adding scroll bars

To finalize the page, you need to add the shopping cart to it. You'll give the cart scroll bars so that users can scroll through the items they've added and, if desired, select items to remove from the cart.

As demonstrated earlier, you can begin by adding the resources you anticipate needing. Alternatively, you can add the widget and then add the resources it needs.

Let's begin by adding the shopping cart inside a Grid and the associated upload button and progress bar within a StackPanel:

<Grid Grid.Row="3"
    Grid.Column="2">
   <Grid.RowDefinitions>
      <RowDefinition Height="20" />
      <RowDefinition />
   </Grid.RowDefinitions>
   <TextBlock Grid.Row="0"
       Foreground="MidnightBlue"
       FontSize="13px"
       Margin="2"
       HorizontalAlignment="Center">
      Shopping Cart
   </TextBlock>
   <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
       Style="{DynamicResource ShoppingCartStyle}"
       Name="ShoppingCartListBox"
       Grid.Row="1"
       Width="160"
       DataContext="{Binding Source={StaticResource ShoppingCart}}"
       ItemContainerStyle="{DynamicResource ShoppingCartItem}"
       ItemsSource="{Binding}" />
</Grid>

<StackPanel
    Grid.Row="5"
    Grid.Column="2" >
   <Button
       Name="UploadButton"
       Click="Checkout"
       VerticalAlignment="Bottom"
       HorizontalAlignment="Center"
       Width="100"
       Margin="2"
       IsEnabled="False">
      Checkout
   </Button>

   <ProgressBar
       Name="UploadProgressBar"
       Grid.Row="6"
       Grid.Column="2"
       VerticalAlignment="Top"
       Margin="0,10,0,0" />
</StackPanel>

The shopping cart itself is the ListBox inside the Grid. You need a ShoppingCartStyle, a DataContext, and the ShoppingCartItem resource, all of which you've already created. What you need to create now are the scroll bar and progress bar resources:

<!-- PROGRESS BAR STYLE -->

<Style TargetType="{x:Type ProgressBar}" >
   <Setter Property="Background"
       Value="{DynamicResource ComboBoxGradient}" />
   <Setter Property="BorderThickness"
       Value="1" />
   <Setter Property="BorderBrush"
       Value="Gray" />
   <Setter Property="Foreground"
       Value="MidnightBlue" />
</Style>

<!--SCROLL BAR TEMPLATES -->

<Style x:Key="Scrollbar_LineButton"
    TargetType="{x:Type RepeatButton}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type RepeatButton}">
            <Border BorderBrush="Transparent"
                BorderThickness="1"
                CornerRadius="6"
                Background="{DynamicResource ButtonGradient}">
               <ContentPresenter x:Name="ContentSite" />
            </Border>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
   <Setter Property="MinHeight"
       Value="12" />
   <Setter Property="MinWidth"
       Value="12" />
   <Setter Property="Foreground"
       Value="Gray" />
   <Setter Property="FontSize"
       Value="6pt" />
   <Setter Property="FontWeight"
       Value="Bold" />
   <Setter Property="FontFamily"
       Value="Lucida Sans" />
   <Setter Property="VerticalAlignment"
       Value="Center" />
   <Setter Property="HorizontalAlignment"
       Value="Center" />
</Style>

<Style x:Key="ScrollBar_TrackRepeater"
    TargetType="{x:Type RepeatButton}">
   <Setter Property="IsTabStop"
       Value="false" />
   <Setter Property="Focusable"
       Value="false" />
   <Setter Property="Command"
       Value="ScrollBar.PageUpCommand" />
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type RepeatButton}">
            <Rectangle Fill="Transparent" />
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

<Style x:Key="ScrollBar_UpTrack"
    BasedOn="{StaticResource ScrollBar_TrackRepeater}"
    TargetType="{x:Type RepeatButton}">
   <Setter Property="Command"
       Value="ScrollBar.PageUpCommand" />
</Style>

<Style x:Key="ScrollBar_DownTrack"
    BasedOn="{StaticResource ScrollBar_TrackRepeater}"
    TargetType="{x:Type RepeatButton}">
   <Setter Property="Command"
       Value="ScrollBar.PageDownCommand" />
</Style>

<Style x:Key="ScrollBar_LeftTrack"
    BasedOn="{StaticResource ScrollBar_TrackRepeater}"
    TargetType="{x:Type RepeatButton}">
   <Setter Property="Command"
       Value="ScrollBar.PageLeftCommand" />
</Style>

<Style x:Key="ScrollBar_RightTrack"
    BasedOn="{StaticResource ScrollBar_TrackRepeater}"
    TargetType="{x:Type RepeatButton}">
   <Setter Property="Command"
       Value="ScrollBar.PageRightCommand" />
</Style>

<Style x:Key="ScrollBar_VerticalThumb"
    TargetType="{x:Type Thumb}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type Thumb}">
            <Border CornerRadius="6"
                BorderBrush="Transparent"
                BorderThickness="1"
                Background="{DynamicResource VerticalScrollGradient}" />
         </ControlTemplate>
      </Setter.Value>
   </Setter>
   <Setter Property="MinHeight"
       Value="10" />
   <Setter Property="MinWidth"
       Value="10" />
</Style>

<Style x:Key="ScrollBar_HorizontalThumb"
    TargetType="{x:Type Thumb}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type Thumb}">
            <Border CornerRadius="6"
                BorderBrush="Transparent"
                BorderThickness="1"
                Background="{DynamicResource ButtonGradient}" />
         </ControlTemplate>
      </Setter.Value>
   </Setter>
   <Setter Property="MinHeight"
       Value="10" />
   <Setter Property="MinWidth"
       Value="10" />
</Style>

<Style TargetType="{x:Type ScrollBar}">
   <Setter Property="Background"
       Value="Transparent" />
   <Setter Property="MinWidth"
       Value="10" />
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type ScrollBar}">
            <Grid>
               <Grid.ColumnDefinitions>
                  <ColumnDefinition Width="10"/>
               </Grid.ColumnDefinitions>
               <Grid.RowDefinitions>
                  <RowDefinition Height="10"/>
                  <RowDefinition Height="*"/>
                  <RowDefinition Height="10"/>
               </Grid.RowDefinitions>
               <Border Grid.Row="1"
                   BorderThickness="0"
                   Background="Transparent"
                   CornerRadius="4"/>
               <RepeatButton Grid.Row="0"
                   Style="{DynamicResource Scrollbar_LineButton}"
                   Command="ScrollBar.LineUpCommand"
                   Content=" ^"/>
               <Track Grid.Row="1"
                   Name="PART_Track"
                   IsDirectionReversed="True">
                  <Track.IncreaseRepeatButton>
                     <RepeatButton Style="{DynamicResource ScrollBar_DownTrack}"/>
                  </Track.IncreaseRepeatButton>
                  <Track.DecreaseRepeatButton>
                     <RepeatButton Style="{DynamicResource ScrollBar_UpTrack}"/>
                  </Track.DecreaseRepeatButton>
                  <Track.Thumb>
                     <Thumb Style="{DynamicResource ScrollBar_VerticalThumb}"/>
                  </Track.Thumb>
               </Track>
               <RepeatButton Grid.Row="2"
                   Style="{DynamicResource Scrollbar_LineButton}"
                   Command="ScrollBar.LineDownCommand"
                   Content=" v"/>
            </Grid>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
   <Style.Triggers>
      <Trigger Property="Orientation"
          Value="Horizontal" >
         <Setter Property="Background"
             Value="Transparent" />
         <Setter Property="MinHeight"
             Value="10" />
         <Setter Property="Template">
            <Setter.Value>
               <ControlTemplate TargetType="{x:Type ScrollBar}">
                  <Grid>
                     <Grid.RowDefinitions>
                        <RowDefinition Height="12"/>
                     </Grid.RowDefinitions>
                     <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="12" />
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="12" />
                     </Grid.ColumnDefinitions>
                     <Border Grid.Column="1"
                         BorderThickness="0"
                         Background="Transparent"
                         CornerRadius="4"/>
                     <RepeatButton Grid.Column="0"
                         Style="{DynamicResource Scrollbar_LineButton}"
                         Command="ScrollBar.LineLeftCommand"
                         Content=" &lt;"/>
                     <Track Grid.Column="1"
                         Name="PART_Track">
                        <Track.IncreaseRepeatButton>
                           <RepeatButton Style=
                               "{DynamicResource ScrollBar_RightTrack}"/>
                        </Track.IncreaseRepeatButton>
                        <Track.DecreaseRepeatButton>
                           <RepeatButton Style=
                               "{DynamicResource ScrollBar_LeftTrack}"/>
                        </Track.DecreaseRepeatButton>
                        <Track.Thumb>
                           <Thumb Style=
                               "{DynamicResource ScrollBar_HorizontalThumb}"/>
                        </Track.Thumb>
                     </Track>
                     <RepeatButton Grid.Column="2"
                         Style="{DynamicResource Scrollbar_LineButton}"
                         Command="ScrollBar.LineRightCommand"
                         Content=" &gt;"/>

                  </Grid>
               </ControlTemplate>
            </Setter.Value>
         </Setter>
      </Trigger>
   </Style.Triggers>
</Style>

Now it's time to fill in the stubbed-out methods and add the CheckOut() method, shown in Example 4-3.

Example 4-3. Shopping cart methods for Window1.xaml.cs

private void AddToShoppingCart( object sender, RoutedEventArgs e )
{
   if ( PrintTypeComboBox.SelectedItem != null )
   {
      PrintBase item;
      switch ( PrintTypeComboBox.SelectedIndex )
      {
         case 0:
            item = new Print( CurrentPhoto.Source as BitmapSource );
            break;
         case 1:
            item = new GreetingCard( CurrentPhoto.Source as BitmapSource );
            break;
         case 2:
            item = new SShirt( CurrentPhoto.Source as BitmapSource );
            break;
         default:
            return;
      }
      ShoppingCart.Add( item );
      ShoppingCartListBox.ScrollIntoView( item );
      ShoppingCartListBox.SelectedItem = item;
      if ( false == UploadButton.IsEnabled )
         UploadButton.IsEnabled = true;
      if ( false == RemoveButton.IsEnabled )
         RemoveButton.IsEnabled = true;
   }
}

private void RemoveShoppingCartItem( object sender, RoutedEventArgs e )
{
   if ( null != ShoppingCartListBox.SelectedItem )
   {
      PrintBase item = ShoppingCartListBox.SelectedItem as PrintBase;
      ShoppingCart.Remove( item );
      ShoppingCartListBox.SelectedIndex = ShoppingCart.Count - 1;
   }
   if ( ShoppingCart.Count == 0 )
   {
      RemoveButton.IsEnabled = false;
      UploadButton.IsEnabled = false;
   }

private void Checkout( object sender, RoutedEventArgs e )
{
   if ( ShoppingCart.Count > 0 )
   {
      // go to checkout page
      // to be created later
   }
}

Finally, to finish off this page, you'll need to add a baseImg folder to your project, then download the sweatshirt-front.gif and photoframe.gif images from our web sites so as to be able to populate the shopping cart (go to http://www.jliberty.com and click on "Books," or go to http://alexhorovitz.com/books/programming3.5/).

If you look carefully, you'll see that the sweatshirts in the shopping cart have the appropriate photo imposed on them, as shown in Figure 4-9. This is accomplished in the AddToShoppingCart() event handler, where, for example, a new SShirt object is instantiated, passing in the current photo as a BitMapSource to the constructor. The SShirt constructor is in StoreItems.cs; it passes the bitmap to its base class, where it is assigned to the private member variable of type BitMapSource and rendered appropriately.

Get Programming .NET 3.5 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.