开发者

How would I implement a multi-image auto-adjusting background in WPF...similar to Nine Patch on Android?

I'm new to WPF.

I have an app that is split into two "panes", a master pane and a detail pane. The master pane is tall and narrow and left-aligned. The detail pane is wide and takes up most of the app's area and is right-aligned.

My goal here is to implement a re-sizable image background for each pane. The concept will be similar to Android's Nine Patch mechanism (see Nine Patch documentation). In fact, this WPF app will use the exact same source PNGs that are used in the Android version of the same app. As you can see, this is a cross-platform app. It shares its business objects and logic across both platforms, but the UI implementation is where the apps diverge.

When you read the Nine Patch docs, ignore the fact that the docs only illustrate this concept with buttons. It can be implemented for any kind of view. I already have the concept working in iOS, including the app's background. Now I just need to port the concept to WPF and Silverlight.

The root container of each pane is a System.Windows.Controls.Grid. From what I can tell, the best way to deal with this is to split the Grid into a 3 by 3 structure, and then apply each part of the Nine Patch image to each appropriate grid section via WPF property bindings...probably with an ImageBrush. However, I'm not entirely familiar with constructing Grids in WPF, or the binding feature for that matter. And how would re-sizing of the window be handled?

Any advice on this would be greatly appreciated. Thanks.

EDIT: I should also mention that I don't have the convenience of XAML at my disposal in this case. This must all be done programatically.

EDIT: Just want to show off the fruits of loxxy's advice and ask for more guidance on the next step. Here's my implementation results so far (I have more questions below, so please read on):

How would I implement a multi-image auto-adjusting background in WPF...similar to Nine Patch on Android?

NinePatchImage npi = new NinePatchImage([some params]);
Grid backgroundGrid = new Grid();
backgroundGrid.ColumnDefinitions.Clear();
backgroundGrid.RowDefinitions.Clear();
backgroundGrid.ColumnDefinitions.Add(
    // first column
    new ColumnDefinition()
    {
        Width = new GridLength(1, GridUnitType.Auto),
    });
backgroundGrid.ColumnDefinitions.Add(
    // second column
    new ColumnDefinition()
    {
        Width = new GridLength(1, GridUnitType.Star)
    });
backgroundGrid.ColumnDefinitions.Add(
    // third column
    new ColumnDefinition()
    {
        Width = new GridLength(1, GridUnitType.Auto)
    });
backgroundGrid.RowDefinitions.Add(
    // first row
    new RowDefinition()
    {
        Height = new GridLength(1, GridUnitType.Auto),
    });
backgroundGrid.RowDefinitions.Add(
    // second row
    new RowDefinition()
    {
        Height = new GridLength(1, GridUnitType.Star)
    });
backgroundGrid.RowDefinitions.Add(
    // third row
    new RowDefinition()
    {
        Height = new GridLength(1, GridUnitType.Auto)
    });

var imageTopLeft = new Image() { Source = (npi.TopLeft != null) ? npi.TopLeft : null, Stretch = Stretch.None };
var imageTopCenter = new Image() { Source = (npi.TopCenter != null) ? npi.TopCenter : null, Stretch = Stretch.None };
var imageTopRight = new Image() { Source = (npi.TopRight != null) ? npi.TopRight : null, Stretch = Stretch.None };
var imageMiddleLeft = new Image() { Source = (npi.MiddleLeft != null) ? npi.MiddleLeft : null, Stretch = Stretch.None };
var imageMiddleCenter = new Image() { Source = (npi.MiddleCenter != null) ? npi.MiddleCenter : null, Stretch = Stretch.None };
var imageMiddleRight = new Image() { Source = (npi.MiddleRight != null) ? npi.MiddleRight : null, Stretch = Stretch.None };
var imageBottomLeft = new Image() { Source = (npi.BottomLeft != null) ? npi.BottomLeft : null, Stretch = Stretch.None };
var imageBottomCenter = new Image() { Source = (npi.BottomCenter != null) ? npi.BottomCenter : null, Stretch = Stretch.None };
var imageBottomRight = new Image() { Source = (npi.BottomRight != null) ? npi.BottomRight : null, Stretch = Stretch.None };

backgroundGrid.Children.Add(imageTopLeft); Grid.SetColumn(imageTopLeft, 0); Grid.SetRow(imageTopLeft, 0);
backgroundGrid.Children.Add(imageTopCenter); Grid.SetColumn(imageTopCenter, 1); Grid.SetRow(imageTopCenter, 0);
backgroundGrid.Children.Add(imageTopRight); Grid.SetColumn(imageTopRight, 2); Grid.SetRow(imageTopRight, 0);
backgroundGrid.Children.Add(imageMiddleLeft); Grid.SetColumn(imageMiddleLeft, 0); Grid.SetRow(imageMiddleLeft, 1);
backgroundGrid.Children.Add(imageMiddleCenter); Grid.SetColumn(imageMiddleCenter, 1); Grid.SetRow(imageMiddleCenter, 1);
backgroundGrid.Children.Add(imageMiddleRight); Grid.SetColumn(imageMiddleRight, 2); Grid.SetRow(imageMiddleRight, 1);
backgroundGrid.Children.Add(imageBottomLeft); Grid.SetColumn(imageBottomLeft, 0); Grid.SetRow(imageBottomLeft, 2);
backgroundGrid.Children.Add(imageBottomCenter); Grid.SetColumn(imageBottomCenter, 1); Grid.SetRow(imageBottomCenter, 2);
backgroundGrid.Children.Add(imageBottomRight); Grid.SetColumn(imageBottomRight, 2); Grid.SetRow(imageBottomRight, 2);

Grid contentArea = new Grid() { ColumnDefinitions = { new ColumnDefinition() } };
// setup contentArea here blah blah blah

// a containing grid
Grid container = new Grid();
// add the background grid to the containing grid
container.Children.Add(backgroundGrid);
// add the content grid to the background grid's UIElement collection
backgroundGrid.Children.Add(contentArea);
// set the backgroundGrid's column position
Grid.SetColumn(backgroundGrid, 0);
// set the backgroundGrid's row position
Grid.SetRow(backgroundGrid, 0);
// set the content area's column position
Grid.SetColumn(contentArea, 1);
// set the content area's row position
Grid.SetRow(contentArea, 1);

NOW, I need to get the top and side images to stretch or tile in the proper direction, x or y. The Image WPF element has a stretch property on it with several options, but the stretch occurs in both x and y directions, whereas I need it to stretch in only one of those two dimensions. A开发者_JS百科lso, if necessary (as is the case with the above image), I need to be able to TILE in either x or y. The image element doesn't support this, but I see that the ImageBrush class can be very useful for these kinds of things. Can an ImageBrush be bound directly to an Image element, or any other type of element for that matter??


Why do you think 3x3 grid is needed if two panes (as in the link) is what you want. If its simply about background then ImageBrush is pretty much enough to paint the two panels differently.

Also, I would prefer DockPanel instead, when talking in terms of aligning panels to right,left etc. The last child in a dockpanel is stretched to fill the parent container.

I didn't get the part you are trying to do with the binding, though. Are you talking about binding the content inside the panels Or are you trying to bind the background image with some source??


I did read the ninepatch docs, though I couldn't find that nine patch related to nine different images. I felt ninepatch to be simply a stretchable image with a border.

Anyways, if nine images is what you want as background, you may use images inside a child grid. If you put multiple content in a grid (in the same row and column), the last child occupies the topmost layer of the grid.

<Grid>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <Image Source="Img1.png" Grid.Row="0" Grid.Column="0"/>
                <Image Source="Img2.png" Grid.Row="0" Grid.Column="1"/>
                <Image Source="Img3.png" Grid.Row="0" Grid.Column="2"/>
                <Image Source="Img4.png" Grid.Row="1" Grid.Column="0"/>
                <Image Source="Img5.png" Grid.Row="1" Grid.Column="1"/>
                <Image Source="Img6.png" Grid.Row="1" Grid.Column="2"/>
                <Image Source="Img7.png" Grid.Row="2" Grid.Column="0"/>
                <Image Source="Img8.png" Grid.Row="2" Grid.Column="1"/>
                <Image Source="Img9.png" Grid.Row="2" Grid.Column="2"/>
            </Grid>

            <Grid>
                <Label>This Content is put in front.</Label>
            </Grid>
</Grid>

Now the binding part. Are you dynamically updating the image sources? I mean do the image source to the nine images change?? Then only binding is required. If it's simply a source why not specify the uri directly.

But if you really need a binding,

<Image Source="{Binding ImagePath}" />

where ImagePath is a string. This is using a built in converter, that allows specifying image source as a string.

However to avoid problems (in silverlight binding) you may use your own converter.

public sealed class ImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
                          object parameter, CultureInfo culture)
    {
        try
        {
            return new BitmapImage(new Uri((string)value));
        }
        catch 
        {
            return new BitmapImage();
        }
    }

    public object ConvertBack(object value, Type targetType,
                              object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Implement the converter in the binding as

<Image Source="{Binding Path=ImagePath, Converter=...}" />
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜