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!

Published 03 May 05 12:34 by Adam Nathan
Filed under: ,

Comments

# Erno de Weerd said on May 3, 2005 3:59 PM:
# ShadowChaser said on May 3, 2005 5:07 PM:
Wow... awesome stuff :-)

Please tell me these are going to ship with Longhorn and not as a separate powertoy :D

I wonder how hard it would be to make a replacement for cards.dll. Does XAML support any type of inheritance or reusability?
# Adam Nathan said on May 3, 2005 5:47 PM:
Thanks! To be clear, this is just a demonstration for the sake of developer education. I don't currently know what the plans are for the card games shipping in future versions of Windows.

As for your question (ShadowChaser), there's not much benefit in creating an Avalon-based *binary-compatible* replacement for cards.dll. But you could reuse the controls defined in my assembly in multiple applications. I haven't looked into how easy or hard this is currently, but hopefully we'll learn something as I do this with Internet Hearts!
# RIO - Randektív Informatikai Oldal said on May 4, 2005 8:43 AM:
&lt;p&gt;&amp;lt;ul&amp;gt;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;http://fb2.hu/x10/Articles/MonoForFun.html&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;Mono Mania&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;http://www.imation.com.au/products/disc_stakka/view_demo.htm&amp;quot; target=&amp;quot
# Mike Dimmick said on May 4, 2005 2:29 PM:
So, are you collaborating with Chris Sells on his plans for Longhorn Solitaire?
# Michael Swanson's Blog said on May 4, 2005 9:34 PM:
Adam Nathan's Win32 to WinFX Blog has a good post titled XAML Playing Cards. Adam takes the playing cards...
# Adam Nathan said on May 5, 2005 6:28 PM:
This is a separate effort, although I welcome Chris (or anyone else) to use my cards!
# Mike Carnevale said on May 6, 2005 3:30 PM:
Cool demo, Adam. You mentioned that you drew the artwork in Illustrator, then exported to SVG, and "dealt with it from there." What types of conversion operations are needed to create an XAML document from SVG data? I'm just starting to read about XAML and all of its potential, so apologies if this is an obvious question.
# William Luu said on May 9, 2005 1:34 AM:
Mike, to convert SVG to XAML have a look at:
http://www.xamlon.com/kb/article.aspx?id=10018
http://workspaces.gotdotnet.com/svg2xaml

Adam, those cards look really good.
# Mike Carnevale said on May 10, 2005 9:22 AM:
Thanks for the links, William!
# Adam Nathan's Win32 to WinFX Blog said on May 11, 2005 10:34 AM:
In my previous XAML-related post, Mike asked about my conversion of XAML from SVG.&amp;nbsp; I know of two...
# DarkByte said on May 16, 2005 11:07 PM:
I have to admit, this is an awesome sample.

a simple thing:
I've been getting your sample code on XamlShare, tried to integrate it into a sample library that i created for learning and the PlayingCardHand class simply wont compile. The system refuse to accept Xaml for a class subclassing Viewbox. In the demo here, you provide Grid as a subclass.

Is this a limitation of the compiler ?
Will we be able to create custom controls using any base class in the future ?
# Adam Nathan said on May 18, 2005 10:16 AM:
Ah, it appears that the version I put on XAMLshare is actually for the next Avalon CTP. I've updated it to wrap the Viewbox in a Grid. But yes, that limitation will be removed very shortly!

Thanks!
# Adam Nathan said on May 23, 2005 10:32 PM:
I've posted a Beta 1 RC version of the sample at http://www.pinvoke.net/blog/downloads/CardsBeta1RC.zip.
# Life, Universe and Everything according to Dirk said on May 31, 2005 12:37 AM:
I've been reading a couple of Avalon blogs lately, considering to write my HeroQuest programs in Avalon...
# Anonymous said on June 2, 2005 2:12 PM:
Getting an error when trying to build

A project with an Output Type of Class Library cannot be started directly.

In order to debug this project, add an executable project to this solution which references the library project. Set the Executable project as the startup project.

How do i resolve this issue?
# Adam Nathan said on June 2, 2005 11:24 PM:
You can right-click on the "Demo" project and select "Set as StartUp Project". Then you should be able to launch the app. That setting is stored in the .user file, which seemed wrong to distribute, although I should have just listed the Demo project first in the solution since that's the one VS tries to start up by default.
# The Longhorn Project Team said on June 13, 2005 11:52 AM:
Trying to run your soft (with May RC1 release), I had the following error (with Visual Studio 2005 Beta 2):
System.Resources.MissingManifestResourceException was unhandled
Message="Could not find any resources appropriate for the specified culture or the neutral culture. Make sure \"Demo.g.resources\" was correctly embedded or linked into assembly \"Demo\" at compile time, or that all the satellite assemblies required are loadable and fully signed."
Source="mscorlib"
StackTrace:
at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo culture, Boolean createIfNotExists, Boolean tryParents)

at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo culture, Boolean createIfNotExists, Boolean tryParents)

at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo culture, Boolean createIfNotExists, Boolean tryParents)

at System.Resources.ResourceManager.GetObject(String name, CultureInfo culture, Boolean wrapUnmanagedMemStream)

at System.Resources.ResourceManager.GetStream(String name, CultureInfo culture)

at Demo.MyApp._InitializeThis() in C:\Documents and Settings\Gilles\Bureau\CardsBeta1RC\Demo\obj\Debug\MyApp.g.cs:line 38

at BootStrapper.EntryPoint.Main(String[] args) in C:\Documents and Settings\Gilles\Bureau\CardsBeta1RC\Demo\obj\Debug\Demo.main.g.cs:line 61

Ouch!!
# Mishel said on June 15, 2005 12:44 PM:
weight loss pill http://www.nofatonline.com/
# Venkatarangan's Blog [வெங்கடரங்கன் வலைப்பதிவு] said on June 26, 2005 4:07 AM:
# Venkatarangan's Blog [வெங்கடரங்கன் வலைப்பதிவு] said on June 26, 2005 4:12 AM:
# Venkatarangan's Blog [வெங்கடரங்கன் வலைப்பதிவு] said on June 26, 2005 4:14 AM:
# Erwyn van der Meer said on July 28, 2005 8:57 AM:
After installing Windows Vista and revealing that it has no significant dependency on WinFX or the .NET...
# Frank Gonzalez said on February 9, 2006 6:05 AM:
I totally agree with what you're saying. I wish more people felt this way and took the time to express themselves. Keep up the great work.

Frank Gonzalez
http://www.postcardmaniafun.com
# Venkatarangan's Blog [வெங்கடரங்கன் வலைப்பதிவு] said on March 10, 2006 4:39 AM:
# Mary Anne Martin said on May 4, 2006 11:36 PM:
This blog posting was of great use in learning new information and also in exchanging our views. Thank you.
Mary Anne Martin
http://www.postcardmaniafun.com
# Mary Anne Martin said on May 4, 2006 11:36 PM:
This blog posting was of great use in learning new information and also in exchanging our views. Thank you.
Mary Anne Martin
http://www.postcardmaniafun.com
# dicover credit card said on July 29, 2006 1:34 AM:
dicover credit card <a href=http://dicovercreditcard.artshost.com/>dicover credit card</a>
# dicover credit card said on July 29, 2006 2:55 AM:
dicover credit card <a href=http://dicovercreditcard.isportsdot.com/>dicover credit card</a>
# abhinandan said on July 31, 2006 4:41 PM:
longues peines dans des exils en margeant
# cardgames said on August 9, 2006 12:14 PM:
cardgames <a href=http://cardgames.noneto.com>cardgames</a>
# Erwyn van der Meer said on August 30, 2006 5:33 PM:
After installing Windows Vista and revealing that it has no significant dependency on WinFX or the .NET
# Adam Nathan's Blog said on December 4, 2006 11:50 PM:

I got an email the other day that I thought I'd share (with permission from the author and with names

New Comments to this post are disabled
Page view tracker