TL;DR version: This class will let you tile an image in a Metro-style XAML app. Use an image that's at least 128x128 for best memory efficiency
Textures can make your application beautiful. Here's an example from http://subtlepatterns.com
, which is full of classy textures:
[caption id="attachment_285" align="aligncenter" width="600"]
A nice tile texture[/caption]
These textures can make great backgrounds, but because the texture is usually smaller than your application (especially tiny textures like
), you need to repeat (tile) the image vertically and horizontally. Images that "match up" on opposite sides are ideal. All the images on http://subtlepatterns.com
So how do you program your application to repeat this image across a surface? In HTML, you can use the css style "background-repeat: repeat
" to tile an image across your webpage. In WPF, you can set up the TileBrush properties on an ImageBrush
to tile an image across your application. But in Silverlight, Silverlight for Windows Phone, or Metro style XAML applications, you're out of luck.
The easiest solution is to use Photoshop (or cheaper equivalent) to create a huge image that you can set as a background. Just use the pattern tool to tile your texture onto an image that's bigger than your application's surface will ever be. The question is: can you guarantee a maximum size? With Metro style XAML applications, there is no maximum resolution
. The other problem is that these massive image files tend to use up a LOT of memory. In my case, it was so much memory that I was likely to fail Microsoft's Win8 certification.
[gist id=3077863 file=TileCanvas.cs]
I broke a convention :(
If I was a WPF Purist I would have never extended a Panel class in order to provide a visual control. I'd have built a templated control (Inheriting from Control), and declared the canvas as a template part, and provided a default template that just contained that canvas in my Generic.xaml file. But my whole aim was to keep memory usage low, and in WPF every visual costs you memory, so I decided to pre-optimise. It's also simpler this was as I only have to show you one file :).
A tilebrush in WPF on a 1000x800 surface will happily repeat a 4 pixel image for you, thousands of times (200,000 to be exact), without breaking a sweat. However, with my TileCanvas, we're creating an Image visual every time we want to repeat the tile, so you don't want an ImageSource that's too small. You have to find a happy balance between a huge image (which uses up a lot of memory by itself) and a lot of small images (where you don't keep paying for the image data, but you do pay for the visual tree explosion). I have found that by repeating your pattern, Photoshop style, onto an image that's a sized with a nice round number like 128x128 pixels
is a good compromise. This applies to the WPF spike as well as the following Metro solution.
Make it - Metro Style
If you want to build a Metro Style application, you need to program against the WinRT API. This provides .NET developers with new challenges. Microsoft say that WinRT allows .NET developers to use their existing skills, which is fair. What they don't mention is that you're going to need a lot of new knowedge. WinRT is like a parallel universe - a lot of the things you expect to find are there, but they are a bit different, or they're in a different place
. A lot of operations are now done asyncronously, which should be make apps snappier. Truly a "parallel" universe (sorry).
When I tried to port my WPF TileCanvas over to use the WinRT version of XAML controls, I ran into two differences that changed the way I had to do things:
- There's no longer a RenderSizeChanged method - after a bit of experimenting, I decided on the LayoutUpdated event (although adding images causes this event to be raised, so I had to track the old size in order to avoid stack overflow)
- ImageSources now start with a width and height of 0. It's not until the ImageOpened event is raised that you can find out the width and height of an image. And you don't get an ImageOpened event until your image is loaded into the Visual Tree. To get around this chicken-and-egg situation, I add a single image to the canvas, and then once it's opened and I have the width & height I do a proper layout of the whole screen. I also attach to the ImageFailed event to help you debug, and clean up my event subscriptions once they've fired.
Here is my working Metro-style TileCanvas:
[gist id=3006848 file=TileCanvas.cs]
One thing that I would add in future would be to not throw away the entire canvas of images every time it was built. It would be better to determine just how many images need to be added and removed. At the moment, this isn't a big issue because metro apps are always fullscreen so they don't change size unless you snap/rotate them.
I needed this class because I wanted a background texture in my new Windows 8 game, Chromazone. It's just been accepted my Microsoft to the store, check it out: http://apps.microsoft.com/webpdp/en-US/app/chromazone/fe3aea27-d44f-43e0-8662-1c45178e4dec