Props to @icebat, sprite animation was the way to go.
Excellent example here: http://www.spottedzebrasoftware.com/blog/xaml-spritesheet-animation.html
Essentially, you get a sprite sheet with the character a different stages of the animation. Then put a WPF element that can be filled with an ImageBrush on the page and apply a TranslateTransform to the ImageBrush to show different images on the sprite sheet at regular intervals.
For posterity, the code from the example looks something like:
XAML
<Grid Grid.Row="1" x:Name="ContentRoot" Margin="19,9.5,19,0">
<Rectangle Width="52" Height="40">
<Rectangle.Fill>
<ImageBrush ImageSource="/Assets/Images/animations/sprite_sheet.png"
Stretch="None"
AlignmentX="Left"
AlignmentY="Top">
<ImageBrush.Transform>
<TranslateTransform x:Name="SpriteSheetOffset" X="0" Y="0" />
</ImageBrush.Transform>
</ImageBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
Code behind
private const int NumberOfColumns = 1;
private const int NumberOfFrames = 8;
private const int FrameWidth = 54;
private const int FrameHeight = 40;
public static readonly TimeSpan TimePerFrame = TimeSpan.FromSeconds(1 / 60f);
private int currentFrame;
private TimeSpan timeTillNextFrame;
private void OnUpdate(object sender, object e)
{
this.timeTillNextFrame += TimeSpan.FromSeconds(1 / 60f);
if (this.timeTillNextFrame > TimePerFrame)
{
this.currentFrame = (this.currentFrame + 1 + NumberOfFrames) % NumberOfFrames;
var row = this.currentFrame % NumberOfColumns;
var column = this.currentFrame / NumberOfColumns;
this.SpriteSheetOffset.X = -column * FrameWidth;
this.SpriteSheetOffset.Y = -row * FrameHeight;
this.timeTillNextFrame = TimeSpan.FromSeconds(0);
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.navigationHelper.OnNavigatedTo(e);
CompositionTarget.Rendering += this.OnUpdate;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
this.navigationHelper.OnNavigatedFrom(e);
CompositionTarget.Rendering -= this.OnUpdate;
}