Search code examples
wpfvb.netapache-fleximage

9-Slice Images in WPF


I was wondering if anyone knew how to duplicate the 9-slice functionality of Flex/Flash in WPF and VB.Net. I have used 9-slice scaling many times in Flex and it would be a great asset in WPF. I would like to be able to have an image as my background of a Canvas and have it stretch without ruining the rounded corners. Please, does anyone know how to do this?


Solution

  • I'm not aware of any built-in functionality that can do this, but you could write a custom control to do so.

    The salient part of such a control would be a 9 part grid in which 4 parts were of fixed size (the corners), two parts had fixed heights and variable widths (center top and center bottom), two parts had fixed widths and variable heights (left center and right center), and the final part had variable height and width (the middle). Stretching in only one direction (e.g. making a button that only grows horizontally) is as simply as limiting the middle portion's height.

    In XAML, that would be:

    <Grid>
        <Grid.ColumnDefinitions>
            <!-- 20 is the amount of pixel on your image corner -->
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
    </Grid>
    

    Then you'd add objects to paint the images on to (I'll use Rectangles), as well as an object to put content into (ContentPresenter):

    <Rectangle Grid.Row="0" Grid.Column="0" x:Name="TopLeft"/>
    <Rectangle Grid.Row="0" Grid.Column="1" x:Name="TopCenter"/>
    <Rectangle Grid.Row="0" Grid.Column="2" x:Name="TopRight"/>
    
    <Rectangle Grid.Row="1" Grid.Column="0" x:Name="CenterLeft"/>
    <Rectangle Grid.Row="1" Grid.Column="2" x:Name="CenterRight"/>
    
    <Rectangle Grid.Row="2" Grid.Column="0" x:Name="BottomLeft"/>
    <Rectangle Grid.Row="2" Grid.Column="1" x:Name="BottomCenter"/>
    <Rectangle Grid.Row="2" Grid.Column="2" x:Name="BottomRight"/>
    
    <Grid Grid.Row="1" Grid.Column="1" x:Name="Middle">
      <Rectangle/>
      <ContentPresenter x:Name="MiddleContent"/>
    </Grid>
    

    Each of the Rectangles can be painted using an ImageBrush so that they show the correct portion of your source image:

    <Rectangle>
        <Rectangle.Fill>
            <ImageBrush ImageSource="Image.png" TileMode="None" 
                        <!-- Add the settings necessary to show the correct part of the image --> />
        </Rectangle.Fill>
    </Rectangle>
    

    Wrapping all of that up into a custom control, you could produce a pretty usable 9-slice image control:

    <local:NineSliceImage Image="Source.png" Slice="20,20">
        <TextBox Text="Nine Slice Image TextBox!"/>
    </local:NineSliceImage>
    

    Where Slice is a property of type System.Windows.Size, so that you can use it like the Margin/Padding/etc. properties for setting the position of the slices.

    You'll also want to set SnapToDisplayPixels to True on all of the Rectangles; otherwise, you'll see small gaps between the pieces of the image at certain resolutions as WPF tries to interpolate the in-between pixels.

    An alternative, slightly faster way of doing this if you plan to use a lot of these controls is to override OnRender and do it there; I've done this in the past for a 3-slice image control, but it's quite a bit more tricky.

    That should get you most of the way there - if there's anything I'm missing, leave a comment.