Trying to change the SourceRect of a CroppedBitmap at runtime


When I try to change a CroppedBitmap's SourceRect property at runtime, nothing happens. There's no error, and the property value doesn't actually get changed.

I'm trying to do sprite animation. I have a BitmapSource that contains a spritesheet, which is a single bitmap containing a grid of different poses for the sprite. Then I have a CroppedBitmap that has the spritesheet as its Source, and a SourceRect that pulls one of the poses out of the spritesheet. At runtime, when I want to animate, I'm trying to change the CroppedBitmap's SourceRect property, to pull a different pose out of the larger bitmap; but, as noted above, the new property value simply doesn't stick. It's the weirdest thing.

Here's some sample XAML:

    <BitmapImage x:Key="spritesheet" UriSource="Sprites/elf.png"/>
        <CroppedBitmap x:Name="image" Source="{StaticResource spritesheet}"
                       SourceRect="240 640 240 320"/>

And the codebehind tries to do this:

var newRect = new Int32Rect(...);
Debug.WriteLine("             Before: " + image.SourceRect);
Debug.WriteLine("Assigning new value: " + newRect);
image.SourceRect = newRect;
Debug.WriteLine("              After: " + image.SourceRect);

That gives me this debug output:

             Before: 240,640,240,320
Assigning new value: 240,0,240,320
              After: 240,640,240,320

So it's actually assigning the new rectangle (with Y=0) into the property; there's no exception; but afterward, the property value simply didn't change (Y is still 640).

Any ideas about why this happens, and how to fix it?

I eventually found the answer. From the documentation for CroppedBitmap:

CroppedBitmap implements the ISupportInitialize interface to optimize initialization on multiple properties. Property changes can occur only during object initialization. Call BeginInit to signal that initialization has begun and EndInit to signal that initialization has completed. After initialization, property changes are ignored. (emphasis mine)

Just for fun, I tried adding BeginInit()..EndInit() calls in my method, to see if that would make it modifiable. Not surprisingly, I got an InvalidOperationException ("Cannot set the initializing state more than once").

So CroppedBitmap is effectively immutable. (But they ignored their own Freezable system, which would have thrown an exception to tell me I was doing something wrong, and implemented something more surprising instead.)

Which means, no-go on changing the SourceRect property. I'll need to create a separate CroppedBitmap instance for each sub-image within the spritesheet.