As I mentioned in my last article, I recently published a new Windows 8 application called King Poker. One of the major challenges I had was around handling the giant variety of screen sizes that are possible with Windows 8.
In my numerous years of XAML and C# development, I’ve almost always had the good fortune to be working in a constrained box. In Silverlight, I knew exactly what size the control was going to be when it was embedded on a web page. In Windows Phone 7, there was only one resolution: 480 x 800. It allowed me to get lazy.
With Windows 8, laziness is no longer possible. You’re required to support 3 distinct resolutions out of the gate:
- 1024x768 (filled view)
- 1366x768 (minimum full screen resolution)
- 320x768 (snapped view)
I knew going into this project that I was going to be making extensive use of the Grid control. By setting the widths to “*”, I could have my content automatically scale to the size of the container, effectively scaling everything up to the appropriate sizes. I had another problem though: the amount of content I was presenting varied also.
ProTip: When layout a dynamic grid system, figure out what you want your layout to look like in fixed values on a 1366x768 screen. Set every ColumnDefinition and RowDefinition explicitly, so that it is EXACTLY the way you want it to look. Once you know those values, figure out what percentage of the width or height of the screen that takes up. Use these percentages as the dynamic values in your Column and RowDefinitions. Like this:
<RowDefinition Height="38*" />
<RowDefinition Height="36*" />
<RowDefinition Height="7*" />
<RowDefinition Height="5*" />
<RowDefinition Height="12*" />
At the top of each game, there is a “paytable” that indicates how many credits you will win for certain types of poker hands. Some games, like Jacks or Better, have only 9 different hands that pay out. Other games, especially those that include wild cards, can have as many as 15! Here’s two screenshots of my app (before I solved all of my scaling issues.)
|Jacks or Better (9 rows)||White Hot Aces (11 rows)|
As you can see, the paytables are different heights, and there’s a variable amount of space between the cards and the content below them. This was unacceptable, but I couldn’t figure out how to keep my paytable box a consistent height while having the content inside of it scale to fill it appropriately.
Discovering the Viewbox Control
Last night, after fighting with this issue for a couple of weeks, I discovered the Viewbox control. Not sure how this has eluded me for so long, but that time has passed, and I am a HUGE fan. Its simplicity is its genius. Wrap any content inside a Viewbox control, and it will automatically scale it to fill its container. What this meant for me was that I could set my paytable box to be a fixed percentage of the screen, and the text inside that box would simply scale to fill the box. This means that 9 rows can fit in the same box as 15 rows, because it will just make the font size smaller for me automatically.
Sidenote: I had already started writing an elaborate “Page_SizeChanged” method that was going to manually set all of the font sizes, image sizes, etc. based on the resolution of the user’s machine. I am so glad I don’t have to do that.
You don’t need to do this with everything. For example, I’m only using it for my paytable grid and the advertisement. Everything else is able to scale on its own by the default behavior of a Grid. Here’s some screenshots of the new interface, with many more rows, yet more available vertical space:
|Black Jack Bonus Poker (14 rows)||Deuces Wild (10 rows)|
You can see that now, my interface looks identical, regardless of how many rows I add to that paytable box. What’s also cool is that the second image, the Deuces Wild one, is actually at a significantly higher resolution than the Black Jack Bonus Poker one. Yet they look identical. THIS is exactly what I was going for.
Struggling with Dynamic Layout
So it may seem, at this point, that my problem is solved, and we’re done talking about scaling. Wrong. See those 5 card images? Their size is dynamically determined by the size of the grid cells they reside in. This works perfectly fine until I want to swap them out for other graphics (like when a user deals new cards, for example.) When I change their source, for a brief moment, they don’t have a width or height. More specifically, their width and height are zero. My intial solution (so far) to this problem is to determine what size the images are when they are loaded, and then set their MinHeight, MinWidth, MaxHeight, and MaxWidth equal to their “loaded” size, so that even when I swap out their source, they’ll remain the same size. I tried many different events until Tim Heuer suggested I try the ImageOpened event on each image. This is where I landed:
private void Card_ImageOpened(object sender, RoutedEventArgs e)
ResizeSingleCard(sender as Image);
private void ResizeSingleCard(Image i)
i.MinWidth = i.ActualWidth;
i.MaxWidth = i.ActualWidth;
i.MinHeight = i.ActualHeight;
i.MaxHeight = i.ActualHeight;
By doing this, each of our images are “locked” to their size for the duration of the user’s game session. I am also handling some weird stuff for the very unlikely situation that a user changes their resolution during a game session, but that’s not something most of us need to worry about.
In short, getting your app to scale to multiple resolutions is hard, but it’s much easier than I was making it thanks to the Viewbox control. Now to find a way to get this control into my Windows Phone version, and I’ll be set.
A special shout-out needs to go to Ryan Lowdermilk, because without his constant beta-testing and encouragement to clean up my scaling issues, I probably never would have fought through it to get where I am today. (Also, check out his great podcast, The Windows Developer Show. I’m hooked on this weekly broadcast of the latest news for Windows 8 & Windows Phone Developers.)