XAML Playing Cards

Looking at the card games that ship with Windows XP, I think one of the most obvious opportunities for improvement is the look & feel of the playing cards.  These games use a shared library known as cards.dll to draw cards that look like... um... this:

Don't those look so 20th century?  To prepare for migrating Internet Hearts, I've created an Avalon Playing Card control with built-in animations:

It can be set to any of the 52 cards (although no jokers currently).  I've also created a "Hand" control that mimics the hand dealt in the Win32 picture above:

And, of course, since they're vector graphics, they look just as good no matter how much you zoom in:

You can get the source code here, which is compatible with the March CTP of Avalon.  I'll post new versions as new builds of Avalon get released.

The .zip file contains a Visual Studio solution with two projects:

  • Cards, which builds Cards.dll containing the Card and Hand controls pictured above
  • Demo, a simple application demonstrating the use of the controls

In the Cards project, Cards.xaml defines the playing card control, which is just a button styled to look like a card:

 <Button Name="CardButton" Style="{StaticResource Card}" />

The style consists of a Viewbox (to enable scaling while maintaining the proper aspect ratio) and two rectangles: one painted with the desired card face and one painted with a shadow.  This painting is done with DrawingBrushes defined in the same file.  There are 53 of them: one per card face and one for the shadow.  For example, here's my DrawingBrush for the 2 of Hearts:

 <DrawingBrush x:Key="H2" Stretch="Uniform" Viewbox="0 0 1468 2053">
  <DrawingBrush.Drawing>
   <DrawingGroup>
    <DrawingGroup.Children>
     <GeometryDrawing Brush="#FFFFFF" Geometry="M1467.5,1852.5c0,110-90,200-200,200h-1067c-110,0-200-90-200-200v-1652 c0-110,90-200,200-200h1067c110,0,200,90,200,200V1852.5z" />
     <GeometryDrawing Geometry="M1467.5,1852.5c0,110-90,200-200,200h-1067c-110,0-200-90-200-200v-1652 c0-110,90-200,200-200h1067c110,0,200,90,200,200V1852.5z">
      <GeometryDrawing.Pen><Pen Brush="#000000" /></GeometryDrawing.Pen>
     </GeometryDrawing>
     <GeometryDrawing Geometry="M1080.5,1402.5c0,110-90,200-200,200h-293 c-110,0-200-90-200-200v-797c0-110,90-200,200-200h293c110,0,200,90,200,200V1402.5z">
      <GeometryDrawing.Pen><Pen Brush="#636BC1" Thickness="27" /></GeometryDrawing.Pen>
     </GeometryDrawing>
     <GeometryDrawing Brush="#FF0000" Geometry="M63.709,718.157c-5.067-10.677-13.2-26.4-15.6-37.2c-7.2-27.6-14.4-60-6-88.8 c13.2-45.6,69.6-62.4,111.6-44.4c18,7.2,27.6,26.4,32.4,45.6c1.2,2.4,4.8,2.4,6,0c3.6-25.2,21.6-46.8,45.6-51.6 c48-8.4,88.8,26.4,94.8,72c4.8,38.4-8.4,74.4-27.6,108c-37.2,67.2-75.6,129.6-116.4,193.2 C140.51,854.957,97.31,788.957,63.709,718.157z" />
     <GeometryDrawing Brush="#FF0000" Geometry="M1406.117,1334.843c5.066,10.677,13.199,26.4,15.6,37.199 c7.199,27.601,14.4,60,6,88.801c-13.2,45.6-69.6,62.399-111.6,44.4c-18-7.201-27.601-26.4-32.4-45.601 c-1.2-2.399-4.801-2.399-6,0c-3.6,25.2-21.6,46.8-45.6,51.601c-48,8.399-88.801-26.4-94.801-72c-4.8-38.4,8.4-74.4,27.6-108 c37.201-67.201,75.601-129.601,116.4-193.2C1329.316,1198.042,1372.517,1264.042,1406.117,1334.843z" />
     <GeometryDrawing Brush="#FF0000" Geometry="M97.5,210.5L56.25,215c-1.5-8.414-2.25-16.43-2.25-24.039 c0-23.313,4.664-43.594,14.008-60.836s23.734-31.492,43.172-42.742S152.227,70.5,175.992,70.5 c21.836,0,41.578,4.891,59.234,14.656c17.656,9.773,30.945,22.414, 39.875,37.914c8.93,15.508,13.398,31.82,13.398,48.938 c0,17.281-3.164,33.438-9.492,48.453c-7.016,16.797-18.461,33.031-34.344,48.695c-15.883,15.672-47.891,40.781-96.016,75.344 h69.313c11.281,0,18.43-0.68,21.461-2.047c3.023-1.367,5.633-4.063,7.836-8.086s4.055-10.945,5.57-20.766l1.648-11.102H294.5 l-18.734,124h-27.18l-2.469-12H56.5v-46.766c27.875-21.32,63.242-52.82,106.086-94.5c21.969-21.32,36.602-39.734,43.883-55.242 c5.352-11.313,8.031-23.188,8.031-35.625c0-14.375-5.063-26.523-15.188-36.461S175.648,131,158.703,131 c-12.539,0-23.422,2.711-32.656,8.125s-16.461,13.133-21.695,23.148S96.5,182.875,96.5,194.016 C96.5,198.063,96.828,203.555,97.5,210.5z" />
     <GeometryDrawing Brush="#FF0000" Geometry="M1370.5,1842.5l41.25-4.5c1.5,8.414,2.25,16.43,2.25,24.039 c0,23.313-4.664,43.594-14.008,60.836s-23.734,31.492-43.172,42.742s-41.047,16.883-64.813,16.883 c-21.836,0-41.578-4.891-59.234-14.656c-17.656-9.773-30.945-22.414-39.875-37.914c-8.93-15.508-13.398-31.82-13.398-48.938 c0-17.281,3.164-33.438,9.492-48.453c7.016-16.797,18.461-33.031,34.344-48.695c15.883-15.672,47.891-40.781,96.016-75.344 h-69.313c-11.281,0-18.43,0.68-21.461,2.047c-3.023,1.367-5.633,4.063-7.836,8.086s-4.055,10.945-5.57,20.766l-1.648,11.102 H1173.5l18.734-124h27.18l2.469,12H1411.5v46.766c-27.875,21.32-63.242,52.82-106.086,94.5 c-21.969,21.32-36.602,39.734-43.883,55.242c-5.352,11.313-8.031,23.188-8.031,35.625c0,14.375,5.063,26.523,15.188,36.461 s23.664,14.906,40.609,14.906c12.539,0,23.422-2.711,32.656-8.125s16.461-13.133,21.695-23.148s7.852-20.602,7.852-31.742 C1371.5,1854.938,1371.172,1849.445,1370.5,1842.5z" />
     <GeometryDrawing Brush="#FF0000" Geometry="M675.605,575.327c-2.534-5.338-6.6-13.199-7.8-18.6c-3.601-13.8-7.2-30-3-44.4 c6.6-22.799,34.8-31.199,55.8-22.199c9,3.6,13.8,13.199,16.2,22.8c0.6,1.2,2.399,1.2,3,0c1.8-12.601,10.799-23.399,22.799-25.8 c24-4.2,44.4,13.199,47.4,36c2.399,19.199-4.199,37.199-13.799,54c-18.602,33.6-37.801,64.8-58.2,96.6 C714.005,643.728,692.405,610.728,675.605,575.327z" />
     <GeometryDrawing Brush="#FF0000" Geometry="M801.395,1432.672c2.533,5.338,6.6,13.199,7.801,18.6c3.6,13.8,7.199,30,3,44.4 c-6.6,22.799-34.801,31.199-55.801,22.199c-9-3.6-13.799-13.199-16.199-22.8c-0.6-1.2-2.4-1.2-3,0 c-1.8,12.601-10.799,23.399-22.799,25.8c-24,4.2-44.4-13.199-47.4-36c-2.4-19.199,4.199-37.199,13.799-54 c18.602-33.6,37.801-64.799,58.2-96.6C762.994,1364.271,784.596,1397.271,801.395,1432.672z" />
    </DrawingGroup.Children>
   </DrawingGroup>
  </DrawingBrush.Drawing>
 </DrawingBrush>

In case you're wondering, I didn't create that XAML by hand! :)  I started with Adobe Illustrator, saved the file as SVG, then dealt with it from there.

The button style also contains an event trigger for Mouse.MouseEnter and another for Mouse.MouseLeave. Each of these points to a "storyboard" that handles the card animations (such as scaling and rotating).  The only piece of code I needed to write was the property that sets the face:

 // The user (via code or XAML) can simply specify the
// name of the face, which must match the name of one of the
// DrawingBrushes in Card.xaml.
 public string Face
{
   get { return face.ToString(); }
   set
{
     // Let exception happen on bad input. The system handles it well.
     face = value;
CardButton.Background = (Brush)Resources[face];
   }
 }
 private string face;

For now, the Hand control in Hand.xaml just hardcodes a set of cards in specific positions and rotations on a Canvas (again wrapped in a Viewbox for uniform scaling). For example:

 <c:Card Width="100" Canvas.Left="10" Face="C7" >
  <c:Card.RenderTransform>
   <RotateTransform Center="50,140" Angle="310" />
  </c:Card.RenderTransform>
 < /c:Card>

Enjoy, and don't hesitate to give me feedback!