Creating tiled backgrounds in Metro style XAML apps

20Jul

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, http://bgrepeat.com and http://www.repeatxy.com are tileable.
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.

Fake it

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.

Make it

There was no way I was going to convert my application into a JavaScript & HTML app, so I set out to create a control that would look just like a WPF tiled brush. The easiest way to do this is to manually arrange a set of images in a control. I'm very familiar with WPF, and I just knew there'd be some weird inconsistencies in the new WinRT APIs that made this harder than it should be, so I decided to spike this in WPF first. I inherited from Canvas, gave it an ImageSource property, and slapped my code into the OnRenderSizeChanged method. It worked perfectly, here's the code:
[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 :).

Optimising Memory

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.

Almost perfect?

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.

[…] it’s fortunate that there’s now a bit less tiresome way of doing this…”Creating tiled backgrounds in Metro style XAML apps (Rob Fonseca-Ensor)“Textures can make your application beautiful. … So how do you […]

[…] solution that you can download at the end of this post. The one extra thing I’m using is Rob Fonseca-Ensor’s TileCanvas class to create a tiled background on the extended splash screen UserControl, to give a more interesting […]

This doesn’t seem to work when the scale resolution of the display is not 100. Just use the simulator and change the display and you’ll see it looks pretty broken. I’ve been trying to figure out how to fix it but haven’t had any luck. Has anyone else?

I know this is an old post but this technique is still quite handy (until WinRT gets tile support like WPF).

There is a small problem that will show itself when dragging a metro app from a low res monitor to a large res monitor (like from a laptop to an external display). This might also be the problem Jacob had with the emulator when changing scaling (or resolution).

In the Rebuild() method, the bitmap’s PixelWidth/PixelHeight are only valid on the first call.

Simple solution is to add 2 new member variables of type nullable int to store the width/height. Assign them in the ImageOnImageFailed( / ImageOnImageOpened() methods as appropriate and test them in Rebuild().