如何在Wpf-app中使用9补丁图像

时间:2012-11-08 08:12:15

标签: c# android wpf nine-patch

我需要在WPF-app中创建和使用9-patch image(* .9.png,就像在android中一样)! 只有一个问题 - 如何?

4 个答案:

答案 0 :(得分:5)

WPF不支持开箱即用的9-patch格式,所以你正在寻找一个自定义控件 - 我想这是一个比较“合理”的控件放在一起:真的,你要设置好......好吧,简单,9个不同的图像,每个设置Stretch到范围,所有包裹在...哦,让我们选择一个DockPanel为它的地狱。控件模板可能类似于:

(警告:在血腥的手机上略微醉酒和自由处理这个,所以可能无法编译......事实上,现在我想起来了,网格可能是一个更明智的选择(3x3行cols,每个细胞都带有图像),但我会坚持原来的建议!)

<DockPanel>
    <DockPanel DockPanel.Dock="Top">
        <Image DockPanel.Dock="Left" Source="{Binding LeftTopImage}"/>
        <Image DockPanel.Dock="Right" Source="{Binding RightTopImage}"/>
        <Image Source="{Binding CenterTopImage}"/>
    </DockPanel>
    <DockPanel DockPanel.Dock="Left">
        <Image Source="{Binding CenterLeftImage}"/>
    </DockPanel>
    <DockPanel DockPanel.Dock="Right">
        <Image Source="{Binding CenterRightImage}"/>
    </DockPanel>
    <DockPanel DockPanel.Dock="Bottom">
        <Image DockPanel.Dock="Left" Source="{Binding LeftBottomImage}"/>
        <Image DockPanel.Dock="Right" Source="{Binding RightBottomImage}"/>
        <Image Source="{Binding CenterBottomImage}"/>
    </DockPanel>
    <DockPanel>
        <Image Source="{Binding CenterImage}"/>
    </DockPanel>
</DockPanel>

答案 1 :(得分:1)

享受吧!您还可以修改代码以执行更多操作!

public class NinePatchPanel : ContentControl
{
    ImageSource[] patchs;

    [Category("Background")]
    [Description("Set nine patch background image")]
    public ImageSource BackgroundImage
    {
        get { return (ImageSource)GetValue(BackgroundImageProperty); }
        set { SetValue(BackgroundImageProperty, value); }
    }
    public static readonly DependencyProperty BackgroundImageProperty =
        DependencyProperty.Register("BackgroundImage", typeof(ImageSource), typeof(NinePatchPanel), new PropertyMetadata(null, SetImg));

    [Category("Background")]
    [Description("Set the center split area")]
    public Int32Rect CenterArea
    {
        get { return (Int32Rect)GetValue(CenterAreaProperty); }
        set { SetValue(CenterAreaProperty, value); }
    }
    public static readonly DependencyProperty CenterAreaProperty =
        DependencyProperty.Register("CenterArea", typeof(Int32Rect), typeof(NinePatchPanel), new PropertyMetadata(Int32Rect.Empty, SetArea));

    protected override void OnRender(DrawingContext dc)
    {
        if (patchs != null)
        {
            double x1 = patchs[0].Width, x2 = Math.Max(ActualWidth - patchs[2].Width, 0);
            double y1 = patchs[0].Height, y2 = Math.Max(ActualHeight - patchs[6].Height, 0);
            double w1 = patchs[0].Width, w2 = Math.Max(x2 - x1, 0), w3 = patchs[2].Width;
            double h1 = patchs[0].Height, h2 = Math.Max(y2 - y1, 0), h3 = patchs[6].Height;
            var rects = new[]
            {
                new Rect(0, 0, w1, h1),
                new Rect(x1, 0, w2, h1),
                new Rect(x2, 0, w3, h1),
                new Rect(0, y1, w1, h2),
                new Rect(x1, y1, w2, h2),
                new Rect(x2, y1, w3, h2),
                new Rect(0, y2, w1, h3),
                new Rect(x1, y2, w2, h3),
                new Rect(x2, y2, w3, h3)
            };
            for (int i = 0; i < 9; i++)
            {
                dc.DrawImage(patchs[i], rects[i]);
            }
        }
        base.OnRender(dc);
    }

    private static void SetArea(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var np = d as NinePatchPanel;
        if (np == null) return;
        var bm = np.BackgroundImage as BitmapSource;
        if (bm != null) SetPatchs(np, bm);
    }
    private static void SetImg(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var np = d as NinePatchPanel;
        if (np == null) return;
        var bm = np.BackgroundImage as BitmapSource;
        if (np.CenterArea == Int32Rect.Empty)
        {
            var w1_3 = bm.PixelWidth / 3;
            var h1_3 = bm.PixelHeight / 3;
            np.CenterArea = new Int32Rect(w1_3, h1_3, w1_3, h1_3);
        }
        else
        {
            SetPatchs(np, bm);
        }
    }

    private static void SetPatchs(NinePatchPanel np, BitmapSource bm)
    {
        int x1 = np.CenterArea.X, x2 = np.CenterArea.X + np.CenterArea.Width;
        int y1 = np.CenterArea.Y, y2 = np.CenterArea.Y + np.CenterArea.Height;
        int w1 = np.CenterArea.X, w2 = np.CenterArea.Width, w3 = bm.PixelWidth - np.CenterArea.X - np.CenterArea.Width;
        int h1 = np.CenterArea.Y, h2 = np.CenterArea.Height, h3 = bm.PixelHeight - np.CenterArea.Y - np.CenterArea.Height;
        np.patchs = new[] {
            new CroppedBitmap(bm, new Int32Rect(0, 0, w1, h1)),
            new CroppedBitmap(bm, new Int32Rect(x1, 0, w2, h1)),
            new CroppedBitmap(bm, new Int32Rect(x2, 0, w3, h1)),
            new CroppedBitmap(bm, new Int32Rect(0, y1, w1, h2)),
            new CroppedBitmap(bm, new Int32Rect(x1, y1, w2, h2)),
            new CroppedBitmap(bm, new Int32Rect(x2, y1, w3, h2)),
            new CroppedBitmap(bm, new Int32Rect(0, y2, w1, h3)),
            new CroppedBitmap(bm, new Int32Rect(x1, y2, w2, h3)),
            new CroppedBitmap(bm, new Int32Rect(x2, y2, w3, h3))
        };
    }
}

预览

BackgroundImage:enter image description here

中心区域:10,10,10,10

知道:

enter image description here

答案 2 :(得分:0)

我有类似的要求,最后来到这里和其他网站(特别是 here,因为我的回答受他的 code 影响很大)

我创建了一个新的 UserControl(在 Visual Studio 上,右键单击项目并选择 [Add new Element ...] -> WPF -> UserControl)

(注意:将 {YOUR_PROJECT_NAMESPACE} 替换为您的个人项目命名空间)

这是我的新 UserControl 的 XAML 代码:

<UserControl x:Class="{YOUR_PROJECT_NAMESPACE}.NineSliceImage"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Simple_DNS_Changer"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">

<Grid>        
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="{Binding ColRowSize.Left}"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="{Binding ColRowSize.Right}"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="{Binding ColRowSize.Top}"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="{Binding ColRowSize.Bottom}"/>
    </Grid.RowDefinitions>
    <!--
    ­­▓░░
    ░░░
    ░░░
    -->
    <Rectangle SnapsToDevicePixels="True">
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[0]}"/>
        </Rectangle.Fill>
    </Rectangle>
    <!--
    ­­░▓░
    ░░░
    ░░░        
    -->
    <Rectangle SnapsToDevicePixels="True" Grid.Column="1"><!--HorizontalAlignment="Stretch"-->
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[1]}"/>
        </Rectangle.Fill>
    </Rectangle>
    <!--
    ­­░░▓
    ░░░
    ░░░        
    -->
    <Rectangle SnapsToDevicePixels="True" Grid.Column="2">
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[2]}"/>
        </Rectangle.Fill>
    </Rectangle>
    <!--
    ­­░░░
    ▓░░
    ░░░
    -->
    <Rectangle SnapsToDevicePixels="True" Grid.Row="1"><!--VerticalAlignment="Stretch"-->
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[3]}"/>
        </Rectangle.Fill>
    </Rectangle>
    <!--
    ­­░░░
    ░▓░
    ░░░
    -->
    <Rectangle SnapsToDevicePixels="True" Grid.Row="1" Grid.Column="1"><!--HorizontalAlignment="Stretch" VerticalAlignment="Stretch"-->
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[4]}" Stretch="Uniform"/>
        </Rectangle.Fill>
    </Rectangle>
    <!--
    ­­░░░
    ░░▓
    ░░░
    -->
    <Rectangle SnapsToDevicePixels="True" Grid.Row="1" Grid.Column="2"><!--VerticalAlignment="Stretch"-->
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[5]}"/>
        </Rectangle.Fill>
    </Rectangle>
    <!--
    ­­░░░
    ░░░
    ▓░░
    -->
    <Rectangle SnapsToDevicePixels="True" Grid.Row="2">
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[6]}"/>
        </Rectangle.Fill>
    </Rectangle>
    <!--
    ­­░░░
    ░░░
    ░▓░
    -->
    <Rectangle SnapsToDevicePixels="True" Grid.Row="2" Grid.Column="1"><!--HorizontalAlignment="Stretch"-->
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[7]}"/>
        </Rectangle.Fill>
    </Rectangle>
    <!--
    ­­░░░
    ░░░
    ░░▓
    -->
    <Rectangle SnapsToDevicePixels="True" Grid.Row="2" Grid.Column="2">
        <Rectangle.Fill>
            <ImageBrush ImageSource="{Binding ImgSource}" Viewbox="{Binding ViewBoxes[8]}"/>
        </Rectangle.Fill>
    </Rectangle>
</Grid></UserControl>

然后是后面的代码:

namespace {YOUR_PROJECT_NAMESPACE}
{
/// <summary>
/// Logique d'interaction pour NineSliceImage.xaml
/// </summary>
public partial class NineSliceImage : UserControl
{
    [Description("The source of the image to be '9 sliced'")]
    public ImageSource ImgSource
    {
        get { return (ImageSource)GetValue(ImgSourceProperty); }
        set { SetValue(ImgSourceProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Source.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ImgSourceProperty =
        DependencyProperty.Register("ImgSource", typeof(ImageSource), typeof(NineSliceImage), new PropertyMetadata(null));

    [Description("Where to cut the image to get the resulting 9 slices (Defined as pixel from 'Left,Top,Right,Bottom' edge of the image)")]
    public Thickness Slice
    {
        get { return (Thickness)GetValue(SliceProperty); }
        set { SetValue(SliceProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Slice.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SliceProperty =
        DependencyProperty.Register("Slice", typeof(Thickness), typeof(NineSliceImage), new PropertyMetadata(new Thickness()));

    [Description("Define the size of each new sliced area to be displayed as 'Left,Top,Right,Bottom' for {Left} width, {Right} width, {Top} height and {Bottom} height.\n By default this property is equal to Slice if not defined")]
    public Thickness AreasSize
    {
        get { return (Thickness)GetValue(AreasSizeProperty); }
        set { SetValue(AreasSizeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for GridSize.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AreasSizeProperty =
        DependencyProperty.Register("AreasSize", typeof(Thickness), typeof(NineSliceImage), new PropertyMetadata(new Thickness()));
            
    private Rect[] _viewBoxes;

    public Rect[] ViewBoxes {
        get
        {
            if (_viewBoxes == null || _viewBoxes.Length < 9 || _viewBoxes.Any(x => x==null))
                _viewBoxes = GetViewBoxes();
            return _viewBoxes;
        }
        set => _viewBoxes = value; }

    private Thickness _colRowSize;

    public Thickness ColRowSize {
        get
        {
            if (_colRowSize == null || _colRowSize == new Thickness())
            {
                double left = AreasSize.Left, right = AreasSize.Right, top = AreasSize.Top, bottom = AreasSize.Bottom;
                double parentWidth = 1, parentHeight = 1;
                var parent = HelperUtils.FindClosestParent<FrameworkElement>(this, x => x is FrameworkElement);

                // if left / right size are relative (proportional)
                if (AreasSize.Left + AreasSize.Right <= 1 && parent != null)                        
                    parentWidth = parent.ActualWidth;

                // if top / bottom size are relative (proportional)
                if (AreasSize.Top + AreasSize.Bottom <= 1 && parent != null)
                    parentHeight = parent.ActualHeight;                    
                
                _colRowSize = new Thickness(left*parentWidth, top*parentHeight, right*parentWidth, bottom*parentHeight);
            }
            return _colRowSize;
        }
        set => _colRowSize = value; }

    private Rect[] GetViewBoxes()
    {
        var res = new Rect[9];
        var slice = Slice;
        double left = Slice.Left, right = Slice.Right, top = Slice.Top, bottom = Slice.Bottom;

        if (slice.Left + slice.Right > 1)
        {
            left = slice.Left / this.ImgSource.Width;
            if (left > 1) left = 1;
            right = slice.Right / this.ImgSource.Width;
            if (right > 1) right = 1;
        }

        if (slice.Top + slice.Bottom > 1)
        {
            top = slice.Top / this.ImgSource.Height;
            if (top > 1) top = 1;
            bottom = slice.Bottom / this.ImgSource.Height;
            if (bottom > 1) bottom = 1;
        }


        double horizontalMid = 1 - left - right;
        if (horizontalMid < 0) horizontalMid = 0;
        double verticalMid = 1 - bottom - top;
        if (verticalMid < 0) verticalMid = 0;

        res[0] = new Rect(0, 0, left, top);
        res[1] = new Rect(left, 0, horizontalMid, top);
        res[2] = new Rect(1 - right, 0, right, top);

        res[3] = new Rect(0, top, left, verticalMid);
        res[4] = new Rect(left, top, horizontalMid, verticalMid);
        res[5] = new Rect(1 - right, top, right, verticalMid);

        res[6] = new Rect(0, 1 - bottom, left, bottom);
        res[7] = new Rect(left, 1 - bottom, horizontalMid, bottom);
        res[8] = new Rect(1 - right, 1 - bottom, right, bottom);

        return res;
    }   

    public NineSliceImage()
    {
        InitializeComponent();
        this.DataContext = this;
    }
}

}

在你的 MainWindow 代码后面:

public {YOUR_PROJECT_WINDOW}()
    {
        InitializeComponent();
        this.DataContext = this;

        //Find all custom UserControl NineSliceImage, and set their AreasSize if needed
        var defaultThickness = new Thickness();
        foreach (var nineSliceImg in HelperUtils.FindAllChildren<NineSliceImage>(this, x => x is NineSliceImage))
        {
            if (nineSliceImg.AreasSize == defaultThickness)
                nineSliceImg.AreasSize = nineSliceImg.Slice;
        }
        [...]
    }

并在您的主窗口 XAML 中使用它:

<local:NineSliceImage x:Name="Save9SliceImg" ImgSource="/Assets/SaveBIG.png" Slice="225,225,225,300" AreasSize="25,25,25,0"/>

您可能需要清洁溶液并重新生成溶液。

如您所见,我最终采用了一个有点丑陋的解决方法,使 AreasSize 依赖属性默认具有其对应的切片依赖属性......如果您知道更好的方法吗?

答案 3 :(得分:-4)

您可以使用Android SDK中的draw9patch工具创建9patch图像。 http://developer.android.com/tools/help/draw9patch.html