Skip to content

Instantly share code, notes, and snippets.

@wldevries
Last active June 20, 2018 02:09
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wldevries/14c6b07edf40459579cf to your computer and use it in GitHub Desktop.
Save wldevries/14c6b07edf40459579cf to your computer and use it in GitHub Desktop.
Image.NineGrid for WPF
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NineGridImage">
<Style TargetType="Rectangle">
<Setter Property="SnapsToDevicePixels" Value="True"/>
</Style>
<Style TargetType="{x:Type local:NineGridImage}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NineGridImage}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle x:Name="PART_topleft">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush1"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="1" Grid.Row="0" x:Name="PART_topmid">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush2"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="2" Grid.Row="0">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush3"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Row="1" x:Name="PART_midleft">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush4"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="1" Grid.Row="1">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush5"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="2" Grid.Row="1">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush6"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Row="2">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush7"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="1" Grid.Row="2">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush8"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="2" Grid.Row="2" x:Name="PART_bottomright">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Path=Source, RelativeSource={RelativeSource AncestorType=local:NineGridImage}}"
x:Name="brush9"/>
</Rectangle.Fill>
</Rectangle>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace NineGridImage
{
[TemplatePart(Name = "brush1", Type = typeof(ImageBrush))]
[TemplatePart(Name = "brush2", Type = typeof(ImageBrush))]
[TemplatePart(Name = "brush3", Type = typeof(ImageBrush))]
[TemplatePart(Name = "brush4", Type = typeof(ImageBrush))]
[TemplatePart(Name = "brush5", Type = typeof(ImageBrush))]
[TemplatePart(Name = "brush6", Type = typeof(ImageBrush))]
[TemplatePart(Name = "brush7", Type = typeof(ImageBrush))]
[TemplatePart(Name = "brush8", Type = typeof(ImageBrush))]
[TemplatePart(Name = "brush9", Type = typeof(ImageBrush))]
[TemplatePart(Name = PART_topleft, Type = typeof(Rectangle))]
[TemplatePart(Name = PART_midleft, Type = typeof(Rectangle))]
[TemplatePart(Name = PART_topmid, Type = typeof(Rectangle))]
[TemplatePart(Name = PART_bottomright, Type = typeof(Rectangle))]
public class NineGridImage : Control
{
private const string PART_topleft = "PART_topleft";
private const string PART_midleft = "PART_midleft";
private const string PART_topmid = "PART_topmid";
private const string PART_bottomright = "PART_bottomright";
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(ImageSource), typeof(NineGridImage), new UIPropertyMetadata(null, PropertyChanged));
public static readonly DependencyProperty OffsetsProperty =
DependencyProperty.Register("Offsets", typeof(Thickness), typeof(NineGridImage), new PropertyMetadata(new Thickness(), PropertyChanged));
private ImageBrush[] image = new ImageBrush[9];
private Rectangle topleft, midleft, topmid, bottomright;
static NineGridImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NineGridImage), new FrameworkPropertyMetadata(typeof(NineGridImage)));
}
public NineGridImage()
{
this.SizeChanged += NineGridImage_SizeChanged;
}
void NineGridImage_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.UpdateNines();
this.UpdateSizes();
}
public ImageSource Source
{
get { return (ImageSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public Thickness Offsets
{
get { return (Thickness)GetValue(OffsetsProperty); }
[TypeConverter(typeof(ThicknessConverter))]
set { SetValue(OffsetsProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
for (int i = 0; i < 9; i++)
{
image[i] = GetTemplateChild("brush" + (i + 1)) as ImageBrush;
}
this.topleft = GetTemplateChild(PART_topleft) as Rectangle;
this.topmid = GetTemplateChild(PART_topmid) as Rectangle;
this.midleft = GetTemplateChild(PART_midleft) as Rectangle;
this.bottomright = GetTemplateChild(PART_bottomright) as Rectangle;
this.UpdateSizes();
this.UpdateNines();
}
public static void PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs a)
{
var ng = sender as NineGridImage;
ng.MinWidth = ng.Offsets.Left + ng.Offsets.Right;
ng.MinHeight = ng.Offsets.Bottom + ng.Offsets.Top;
ng.UpdateNines();
}
public void UpdateNines()
{
if (this.image.Any(i => i == null) || this.Source == null)
{
return;
}
double left = this.Offsets.Left / this.Source.Width;
if (left > 1) left = 1;
double right = this.Offsets.Right / this.Source.Width;
if (right > 1) right = 1;
double top = this.Offsets.Top / this.Source.Height;
if (top > 1) top = 1;
double bottom = this.Offsets.Bottom / this.Source.Height;
if (bottom > 1) bottom = 1;
double hmid = 1 - left - right;
if (hmid < 0) hmid = 0;
double vmid = 1 - bottom - top;
if (vmid < 0) vmid = 0;
this.image[0].Viewbox = new Rect(0, 0, left, top);
this.image[1].Viewbox = new Rect(left, 0, hmid, top);
this.image[2].Viewbox = new Rect(1 - right, 0, right, top);
this.image[3].Viewbox = new Rect(0, top, left, vmid);
this.image[4].Viewbox = new Rect(left, top, hmid, vmid);
this.image[5].Viewbox = new Rect(1- right, top, right, vmid);
this.image[6].Viewbox = new Rect(0, 1 - bottom, left, bottom);
this.image[7].Viewbox = new Rect(left, 1 - bottom, hmid, bottom);
this.image[8].Viewbox = new Rect(1 - right, 1 - bottom, right, bottom);
}
private void UpdateSizes()
{
if (this.topleft == null || this.bottomright == null || this.topmid == null || this.midleft == null)
return;
this.topleft.Width = this.Offsets.Left;
this.topleft.Height = this.Offsets.Top;
this.bottomright.Width = this.Offsets.Right;
this.bottomright.Height = this.Offsets.Bottom;
this.topmid.Width = Math.Max(this.ActualWidth - this.Offsets.Left - this.Offsets.Right, 0);
this.midleft.Height = Math.Max(this.ActualHeight - this.Offsets.Bottom - this.Offsets.Top, 0);
this.MinWidth = this.topleft.Width + this.bottomright.Width;
this.MinHeight = this.topleft.Height + this.bottomright.Height;
}
}
}
@SickLab
Copy link

SickLab commented Jun 20, 2018

I found a problem about the sizing.

In the UpdateSizes() method, these 2 lines are obsolete and prevent the automatic resize of the control when something changes

this.topmid.Width = Math.Max(this.ActualWidth - this.Offsets.Left - this.Offsets.Right, 0);
this.midleft.Height = Math.Max(this.ActualHeight - this.Offsets.Bottom - this.Offsets.Top, 0);

It fixed my issue, so maybe it could help someone else too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment