我试图制作ColorableImage
自定义控件,但我不确定我尝试做的是否可行。这个课本身很简单:
public class ColorableImage : Image
{
private bool ChangedByColor { get; set; }
public static readonly DependencyProperty ColorProperty;
public static readonly DependencyProperty UncoloredSourceProperty;
static ColorableImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorableImage), new FrameworkPropertyMetadata(typeof(ColorableImage)));
SourceProperty.OverrideMetadata(typeof(ColorableImage), new FrameworkPropertyMetadata(new PropertyChangedCallback(SourceChanged)));
ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(ColorableImage), new FrameworkPropertyMetadata(new PropertyChangedCallback(ColorChanged)));
UncoloredSourceProperty = DependencyProperty.Register("UncoloredSource", typeof(ImageSource), typeof(ColorableImage));
}
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public ImageSource UncoloredSource
{
get { return (ImageSource)GetValue(UncoloredSourceProperty); }
protected set { SetValue(UncoloredSourceProperty, value); }
}
private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ColorableImage cimg = (ColorableImage)d;
if (!cimg.ChangedByColor)
{
cimg.UncoloredSource = cimg.Source;
if (cimg.Color != null && !cimg.Color.ToString().Equals("#00000000"))
{
ColorChanged(d, e);
}
}
}
private static void ColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ColorableImage cimg = (ColorableImage)d;
// This will call the SourceChanged which we don't want (since it will change UncoloredImage)
cimg.ChangedByColor = true;
cimg.Source = ColorSource(cimg.Source, cimg.Color);
cimg.ChangedByColor = false;
}
private static ImageSource ColorSource(ImageSource source, Color color)
{
BitmapSource bitmapSource = (BitmapSource)source;
if (bitmapSource.Format.BitsPerPixel != 32 && bitmapSource.Format != System.Windows.Media.PixelFormats.Bgra32)
{
bitmapSource = new FormatConvertedBitmap(bitmapSource, System.Windows.Media.PixelFormats.Bgra32, null, 0);
}
WriteableBitmap wbmp = new WriteableBitmap(bitmapSource);
// Get necessary image info to copy into the pixel array
var bytesPerPixel = (wbmp.Format.BitsPerPixel + 7) / 8;
var stride = wbmp.PixelWidth * bytesPerPixel;
var arraySize = stride * wbmp.PixelHeight;
// Copy the image into the pixel array
var pixelArray = new byte[arraySize];
wbmp.CopyPixels(pixelArray, stride, 0);
// Convert the System.Windows.Media.Color to a System.Drawing.Color
System.Drawing.Color newColor = System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B);
// Put the new pixels into the pixel array
for (int i = 0; i < pixelArray.Length; i += bytesPerPixel)
{
// Only color those pixels that have no transparency
// This is a very simple alogirthm; you can pretty much only have one color in the image, with the rest of it being completely transparent; it is primarily meant for small, simple icons
if (pixelArray[i + 3] == 255)
{
pixelArray[i] = newColor.B;
pixelArray[i + 1] = newColor.G;
pixelArray[i + 2] = newColor.R;
pixelArray[i + 3] = newColor.A;
}
}
// Remake the image
wbmp.WritePixels(new Int32Rect(0, 0, wbmp.PixelWidth, wbmp.PixelHeight), pixelArray, stride, 0);
return wbmp;
}
}
着色算法很简单,其目的是能够用给定的Color
替换每个不透明的像素,这是控件的依赖属性。控件上还有一个名为UncoloredSource
的只读属性,它会返回原始图像源,因为实际的Source
因着色而发生了变化。
但是,我认为我正在做的是打破Image
控件对其Source
属性的某种内部绑定。我正在使用ListViewItems
对此进行测试,如下所示:
<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
<local:ColorableImage x:Name="img" Source="plus-black.png" Color="Red"></local:ColorableImage>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="img" Property="Color" Value="Blue" />
<Setter TargetName="img" Property="Source" Value="minus-black.png" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
...
<ListView>
<ListViewItem Template="{DynamicResource ListBoxItemControlTemplate1}" />
<ListViewItem Template="{DynamicResource ListBoxItemControlTemplate1}" />
<ListViewItem Template="{DynamicResource ListBoxItemControlTemplate1}" />
</ListView>
图片会成功更改颜色,但我发现SourceChanged
事件在Color
更改后永远不会再次触发。我唯一的猜测是因为我在Source
中重新分配代码隐藏中的ColorChanged
属性,这会混淆触发器...但我很惊讶这会弄乱它。
另外,如果我稍微切换一下触发器,这就证实了我的想法。如果我没有设置初始颜色,然后在触发器中更改Source
第一个和Color
秒,那么源和颜色会发生变化,但是当我选择第二个列表项时,前一个列表项会完全消失,当我选择第三个,第二个完全消失等等,我认为这意味着触发器无法再访问旧源以恢复它(不确定原因)。