3

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

4

4 回答 4

6

我知道,WPF 不支持开箱即用的 9 补丁格式,因此您正在查看自定义控件-也就是说,我想这将是组合在一起的更“合理”的控件之一: 真的你会看设置...嗯,简单来说,9 个不同的图像,每个设置 Stretch to extents,都包裹在一个...哦,让我们为它选择一个 DockPanel。控件模板可能类似于:

(警告:在该死的手机上稍微醉酒和随意处理这个,所以可能无法编译......事实上,现在我想到它,网格可能是一个更明智的选择(3x3 行列,每个单元格一张图片),但我会坚持我原来的建议!)

<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>
于 2012-11-15T03:55:52.740 回答
4

这是一个开箱即用的内容控件,使用单个图像制作 9-patch 背景。
好好享受!

//Code Auther: Mr. Squirrel.Downy (Flithor)
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Flithor_ReusableCodes
{
    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))
        };
        }
    }
}

预览

背景图像: 中心区域:10,10,10,10
在此处输入图像描述

结果:
在此处输入图像描述

于 2019-08-22T06:46:46.287 回答
0

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

我创建了一个新的 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 依赖属性默认具有相应的切片依赖属性...如果您知道更好的方法?

于 2021-03-16T14:12:11.473 回答
-5

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

于 2012-11-08T08:17:32.797 回答