The second page, shown in Figure 4-10, is pretty straightforward to lay out.
Start by creating the new page, Checkout.xaml. (Be sure to add a new Page, not a new Windows Form—Pages are used for WPF, and Windows Forms are used for .NET 2.x.)
Once again, let's start simple. You'll add the TextBlock
for the header and a Grid
to hold the radio buttons for the credit cards, all of which will be in a Viewbox
so the user can resize the display.
<Window x:Class="PhotoCooperative.Checkout" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Photo Cooperative: Checkout" Height="600" Width="800" xmlns:sbts="clr-namespace:PhotoCooperative" > <Window.Resources> <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="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> <!-- STYLES --> <Style TargetType="{x:Type sbts:Checkout}"> <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> <Style x:Key="CheckoutText" TargetType="{x:Type TextBlock}" > <Setter Property="FontFamily" Value="Segoe Black" /> <Setter Property="FontSize" Value="14px" /> <Setter Property="Foreground" Value="MidnightBlue" /> </Style> <Style x:Key="InputText" TargetType="{x:Type TextBox}"> <Setter Property="Height" Value="25px" /> <Setter Property="FontFamily" Value="Segoe Black" /> <Setter Property="Foreground" Value="#0066CC" /> <Setter Property="FontSize" Value="10pt" /> <Setter Property="Margin" Value="10,10,20,10" /> <Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> <Trigger Property="Validation.HasError" Value="false"> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=ToolTip.Content}"/> </Trigger> </Style.Triggers> </Style> <!-- BUTTON TEMPLATE --> <Style TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border x:Name="OuterBorder" CornerRadius="3" Background="{DynamicResource ButtonGradient}"> <Border x:Name="InnerBorder" CornerRadius="3" Background="{DynamicResource ButtonUpGradient}" Padding="{TemplateBinding Padding}"> <ContentPresenter x:Name="ContentSite" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Border> </Border> <ControlTemplate.Triggers> <Trigger Property="IsPressed" Value="true"> <Setter TargetName="InnerBorder" Property="Background" Value="{DynamicResource ButtonDownGradient}" /> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter TargetName="InnerBorder" Property="Background" Value="{DynamicResource ButtonDisabledGradient}" /> <Setter Property="BorderBrush" Value="Silver" /> <Setter Property="Foreground" Value="SlateGray" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Height" Value="18" /> <Setter Property="Foreground" Value="MidnightBlue" /> </Style> </Window.Resources> <Viewbox VerticalAlignment="Top" Stretch="Uniform"> <Grid Margin="20" Width="650" ShowGridLines="False" > <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="30" /> <RowDefinition Height="50" /> <RowDefinition Height="50" /> <RowDefinition Height="50" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="200" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="50" /> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.ColumnSpan="6" Style="{DynamicResource TitleText}"> <Span>The Photo Co-op: Checkout</Span> </TextBlock> <TextBlock Grid.Row="2" Grid.Column="0" Style="{DynamicResource CheckoutText}" HorizontalAlignment="Right" VerticalAlignment="Center"> <Span>Choose Payment Method:</Span> </TextBlock> <RadioButton Name="AmericanExpress" Grid.Row="2" Grid.Column="1" Click="OnCardSelected" VerticalAlignment="Center"> <Image Source="baseImg/creditcardamex.gif" MaxWidth="38" MaxHeight="24" VerticalAlignment="Center" HorizontalAlignment="Center"/> </RadioButton> <RadioButton Name="Visa" Grid.Row="2" Grid.Column="2" Click="OnCardSelected" VerticalAlignment="Center"> <Image Source="baseImg/creditcardvisa.gif" MaxWidth="38" MaxHeight="24" VerticalAlignment="Center" HorizontalAlignment="Center"/> </RadioButton> <RadioButton Name="MasterCard" Grid.Row="2" Grid.Column="3" Click="OnCardSelected" VerticalAlignment="Center"> <Image Source="baseImg/creditcardmastercard.gif" MaxWidth="38" MaxHeight="24" VerticalAlignment="Center" HorizontalAlignment="Center"/> </RadioButton> <RadioButton Name="Discover" Grid.Row="2" Grid.Column="4" Click="OnCardSelected" VerticalAlignment="Center"> <Image Source="baseImg/creditcarddiscover.gif" MaxWidth="38" MaxHeight="24" VerticalAlignment="Center" HorizontalAlignment="Center"/> </RadioButton> <TextBlock Grid.Row="3" Grid.Column="0" Style="{DynamicResource CheckoutText}" TextAlignment="Right" VerticalAlignment="Center"> <Span>Name on Card:</Span> </TextBlock> <TextBox Style="{StaticResource InputText}" Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="4" Name="nameOnCard" Width="150" VerticalAlignment="Center"> <TextBox.ToolTip>Enter your name.</TextBox.ToolTip> </TextBox> <TextBlock Grid.Row="4" Grid.Column="0" Style="{DynamicResource CheckoutText}" TextAlignment="Right" VerticalAlignment="Center"> <Span>Card Number:</Span> </TextBlock> <TextBox Style="{StaticResource InputText}" Grid.Column="1" Grid.Row="4" Grid.ColumnSpan="4" Name="ccNumber" Width="150" VerticalAlignment="Center"> <TextBox.ToolTip> Enter valid credit card number. </TextBox.ToolTip> </TextBox> <Button Name="ProcessOrder" Grid.ColumnSpan="3" Grid.Column="1" Grid.Row="5" Click="ProcessOrderForCart"> Process my credit card! </Button> <Label Name="ProcessResults" Grid.Column="1" Grid.Row ="6" Grid.ColumnSpan="4" TextBlock.Foreground="Red" /> </Grid> </Viewbox> </Window>
Tip
This code assumes that you downloaded the four .gif files for the four credit cards (creditcardamex.gif, creditcardmastercard.gif, creditcardvisa.gif and creditcarddiscover.gif) into the baseImg directory while you were downloading the .gif files required for the previous section.
You are probably already scanning this code to see what code-behind support you need, and what resources you'll want to add, and no doubt you've discovered these hints:
Click="OnCardSelected" Style="{DynamicResource CheckoutText}" Style="{DynamicResource TitleText}">
First add the two style
s to the Resources
section:
<Window.Resources> <!-- STYLES --> <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> <Style x:Key="CheckoutText" TargetType="{x:Type TextBlock}" > <Setter Property="FontFamily" Value="Segoe Black" /> <Setter Property="FontSize" Value="14px" /> <Setter Property="Foreground" Value="MidnightBlue" /> </Style> </Window.Resources>
Then, in the code-behind, you can stub out the required event handler:
public void OnCardSelected( object sender, EventArgs e ) { }
To see your shopping cart page, return to the first page and fill in the details in the Checkout
event handler that you stubbed out earlier:
private void Checkout( object sender, RoutedEventArgs e ) { if ( ShoppingCart.Count > 0 ) {Checkout co = new Checkout();
co.ShoppingCart = ShoppingCart;
co.Show();
this.Hide();
} }
This code now makes an instance of your new (second) page and sets its ShoppingCart
property to the ShoppingCart
object created on the first page. It then shows the second page and hides the first.
Modify your ShoppingCart
class to have a ShoppingCart
property to match what the first page will set:
public partial class Checkout : System.Windows.Window { private PrintList shoppingCart; public PrintList ShoppingCart { set { shoppingCart = value; } }
Tip
The class PrintList
, you will remember, is defined in your StoreItems.cs file, as an ObservableCollection
of PrintBase
. Print, GreetingCard
, and SShirt
(SweatShirt) all derive from PrintBase
.
The rest of the layout is pretty straightforward. You need a place for the user's name and credit card number, and a button to submit the order:
<TextBlock Grid.Row="3" Grid.Column="0" Style="{DynamicResource CheckoutText}" TextAlignment="Right" VerticalAlignment="Center"> <Span>Name on Card:</Span> </TextBlock> <TextBox Style="{StaticResource InputText}" Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="4" Name="nameOnCard" Width="150" VerticalAlignment="Center"><TextBox.ToolTip>Enter your name.</TextBox.ToolTip>
</TextBox> <TextBlock Grid.Row="4" Grid.Column="0" Style="{DynamicResource CheckoutText}" TextAlignment="Right" VerticalAlignment="Center"> <Span>Card Number:</Span> </TextBlock> <TextBox Style="{StaticResource InputText}" Grid.Column="1" Grid.Row="4" Grid.ColumnSpan="4" Name="ccNumber" Width="150" VerticalAlignment="Center"><TextBox.ToolTip>Enter valid credit card number.</TextBox.ToolTip>
</TextBox> <Button Name="ProcessOrder" Grid.ColumnSpan="3" Grid.Column="1" Grid.Row="5" Click="ProcessOrderForCart">Process my credit card!</Button> <Label Name="ProcessResults" Grid.Column="1" Grid.Row ="6" Grid.ColumnSpan="4" TextBlock.Foreground="Red" />
Notice the use of tooltips attached to the TextBlock
s. When the user hovers over the text entry block, a tooltip will provide additional information, as shown in Figure 4-11.
When the user clicks the "Process my credit card!" button, you'd like to validate the credit card number before submitting the information to the credit card company. You'll do so using the Luhn algorithm (also known as the modulus 10 algorithm), created by IBM scientist Hans Peter Luhn and described in U.S. Patent 2,950,048, filed on January 6, 1954 and granted on August 23, 1960 (according to Wikipedia).
To accomplish this, take the following steps:
Create a new business class,
CreditCardValidator
, as a C# class, and create a new enumerated constant,CardBrand
.When the user clicks on a credit card, you'll set the chosen
CardBrand
.When the user clicks the "Process my credit card!" button, you'll call the static method
Validate()
in your new class.
Here's the listing for CreditCardValidator.cs:
using System; using System.Collections.Generic; using System.Text; namespace PhotoCooperative { public enum CardBrand { NotSelected, MasterCard, BankCard, Visa, AmericanExpress, Discover, DinersClub, JCB }; public static class CreditCardValidator { public static bool Validate(CardBrand cardBrand, string cardNumber) { byte[] number = new byte[16]; // card number to validate // Remove non-digits int length = 0; for (int i = 0; i < cardNumber.Length; i++) { if (char.IsDigit(cardNumber, i)) { if (length == 16) return false; // card has too // many digits number[length++] = byte.Parse(cardNumber[i].ToString()); } } // To validate a card, you need to // test length then prefix... switch (cardBrand) { case CardBrand.BankCard: if (length != 16) return false; if (number[0] != 5 || number[1] != 6 || number[2] > 1) return false; break; case CardBrand.MasterCard: if (length != 16) return false; if (number[0] != 5 || number[1] == 0 || number[1] > 5) return false; break; case CardBrand.Visa: if (length != 16 && length != 13) return false; if (number[0] != 4) return false; break; case CardBrand.AmericanExpress: if (length != 15) return false; if (number[0] != 3 || (number[1] != 4 && number[1] != 7)) return false; break; case CardBrand.Discover: if (length != 16) return false; if (number[0] != 6 || number[1] != 0 || number[2] != 1 || number[3] != 1) return false; break; case CardBrand.DinersClub: if (length != 14) return false; if (number[0] != 3 || (number[1] != 0 && number[1] != 6 && number[1] != 8) || number[1] == 0 && number[2] > 5) return false; break; } // Now we use the classic Luhn algorithm to validate int sum = 0; for (int i = length - 1; i >= 0; i--) { if (i % 2 == length % 2) { int n = number[i] * 2; sum += (n / 10) + (n % 10); } else sum += number[i]; } return (sum % 10 == 0); } } }
When the user clicks on a credit card, the OnCardSelected()
event handler in Checkout.xaml.cs is called:
public void OnCardSelected( object sender, EventArgs e ) { RadioButton rb = sender as RadioButton; string rbName = rb.Name; switch ( rbName ) { case "Visa": selectedCard = CardBrand.Visa; break; case "MasterCard": selectedCard = CardBrand.MasterCard; break; case "AmericanExpress": selectedCard = CardBrand.AmericanExpress; break; default: selectedCard = CardBrand.Discover; break; } }
Finally, when the user clicks the "Process my credit card!" button, the ProcessOrderForCart()
method in Checkout.xaml.cs is called:
public void ProcessOrderForCart( object sender, RoutedEventArgs e ) { String creditCardNumber = ccNumber.Text; if ( selectedCard == CardBrand.NotSelected ) { MessageBox.Show( "Please select a credit card type", "Uh oh", MessageBoxButton.OK, MessageBoxImage.Error ); } else { if ( CreditCardValidator.Validate( selectedCard, creditCardNumber ) ) { ProcessResults.Content = "Validated"; } else { ProcessResults.Content = "Excuse me sir, there's a call for you..."; } } }
That's it! A meaningful WPF application, with all the fixins.
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.