[英]Graphics.DrawImage not working for transparent images C#
我试图创建一个自定义控件,在该控件中我只需将图像绘制到按钮上即可。 调整图像大小以填充按钮的区域,同时仍保持图像的原始比例。 在设计和运行模式下,我不断收到一些非常奇怪的行为。
在设计人员中,大多数时候图像的透明部分都是黑色的。 如果我使控件足够小,则透明区域将开始填充它们在我的屏幕上可以找到的任何随机内容。 在运行模式下,透明度始终填充为黑色(请参见下图)。
我觉得我可能以错误的方式使用了winforms控件,但是我对此没有太多的经验。 我尝试了所有在这里找到的建议: 使用Graphics.DrawImage()来绘制具有透明度/ Alpha通道的图像 ,以及其他一些我在网上发现的方法,都无济于事。
using System.Windows.Forms;
using System.Drawing;
namespace Tools
{
public class CustomButton : Button
{
public CustomButton()
{
Image = (Image)Properties.Resources.ResourceManager.GetObject("Custom-Logo-Horiz-RGB");
ForeColor = BackColor = Color.FromArgb(88, 88, 88);
DoubleBuffered = true;
}
protected override void OnPaint(PaintEventArgs pevent)
{
DrawCustomImage(pevent.Graphics);
}
private void DrawCustomImage(Graphics graphics)
{
float baseHeight = Image.Height;
float baseWidth = Image.Width;
float maxHeight = (Height - borderWidth * 2);
float maxWidth = (Width - borderWidth * 2);
float newWidth = maxWidth;
float heightToWidth = baseHeight / baseWidth;
float newHeight = heightToWidth * newWidth;
if (newHeight > maxHeight)
{
newHeight = maxHeight;
float widthToHeight = 1 / heightToWidth;
newWidth = widthToHeight * newHeight;
}
graphics.DrawImage(Image, new RectangleF(Width / 2 - newWidth / 2, Height / 2 - newHeight / 2, newWidth, newHeight));
}
#region Settings
private float borderWidth = 6.0F;
public float BorderWidth
{
get { return borderWidth; }
set { borderWidth = value; }
}
#endregion
}
}
图片:
我建议在OnPaint
重写中调用base.OnPaint(e)
,因为,除非Button FlatStyle (或者更好的是Button派生自ButtonBase类的样式)的类型为FlatStyle.System ,否则Button控件将被视为OwnerDrawn。 因此,它是使用ControlStyles.UserPaint创建的。 这对从ButtonBaseAdapter派生的ButtonBase类分派器绘制控件的方式产生很多影响,这些分派器决定了呈现样式和内部PaintWorker方法的操作。
另外,如您在ButtonBase构造函数中所看到的那样,按钮是使用ControlStyles.Opaque
样式创建的(并且您还可以看到使用了ControlStyles.OptimizedDoubleBuffer
样式)。 这意味着Button类不会绘制背景,而是由PaintWorker
调用PaintThemedButtonBackground (如果Application.RenderWithVisualStyles = true
,否则为标准背景),并使用与Button类生成的相同的PaintEventArgs
(您也可以确定DoubleBuffering
为默认情况下启用)。
因此,如果希望控件正确呈现,则需要在重写中调用base.OnPaint(e)
。
对base.OnPaint(e)
的调用还会绘制分配给Image属性的位图(如果有)。
这就是为什么我建议在不设置Image属性的情况下将自己的Bitmap分配给一个字段(或另一个自定义属性)的原因。 如果这样做,图像将被绘制两次:一个按您自己的意愿绘制,另一个由PaintWorker
。
关于处理非托管对象 :
如果从.Net控件派生自定义控件,则实际上不必担心控件本身。 全部由内部处理。 您可以在我在此处发布的代码中看到使用了protected override void Dispose(bool disposing)
:我将其放在此处,因此可以看到仅在应用程序关闭时才调用此方法; 同样,在disposing
参数设置为false
的情况下调用该函数:它是Finalizer
调用的对象,该对象已经被处理,其资源也随之处理。
在创建对象时,您可能需要照顾自己创建的对象,尤其是Graphics对象:立即处理这些对象,在其上调用Dispose()或使用using语句声明这些对象,该语句在幕后将创建一个try/finally
块,将对象放置在finally
部分中。
您可以在此处发布的代码中看到,设置新图像后,将立即丢弃旧图像。
重写OnHandledDistroyed方法,以摆脱分配给Field的当前对象,该对象保存您的Button正在显示的位图。 这是因为此位图来自嵌入式资源,因此在不再需要它时,最好对其进行废弃处理。
如果改为创建一个使用非托管资源的类(该类不是从已经处理垃圾回收的另一个类派生的),则实现IDisposable接口。
关于此主题的一些文件:
埃里克·利珀特(Eric Lippert)关于垃圾收集和终结器的系列文章: 当您所知道的一切都不对时,第一部分
MSDN: 实现Dispose方法 (及后续页面)。
这是一个实现了一些建议的修改后的类:
BackgroundImage
属性。 using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
[DesignerCategory("code")]
public class PayceButton : Button
{
private Image myImage = null;
private float borderWidth = 6.0F;
public PayceButton() => InitializeComponent();
private void InitializeComponent()
{
this.myImage = Properties.[A Resource Image Name];
this.BackColor = Color.FromArgb(88, 88, 88);
}
public float BorderWidth {
get => borderWidth;
set { borderWidth = value; this.Invalidate(); }
}
public override string Text {
get => string.Empty;
set => base.Text = string.Empty;
}
public new Image Image {
get => this.myImage;
set { this.myImage?.Dispose();
this.myImage = value;
Invalidate();
}
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public override Image BackgroundImage {
get => base.BackgroundImage;
set => base.BackgroundImage = null;
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public override ImageLayout BackgroundImageLayout {
get => base.BackgroundImageLayout;
set => base.BackgroundImageLayout = ImageLayout.None;
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
DrawPayceImage(e.Graphics);
}
private void DrawPayceImage(Graphics g)
{
float scale = (Math.Min(this.Height, this.Width) - (borderWidth * 4)) /
Math.Min(myImage.Height, myImage.Width);
var scaledImageSize = new SizeF(this.myImage.Width * scale, myImage.Height * scale);
var imageLocation = new PointF((this.Width - scaledImageSize.Width) / 2,
(this.Height - scaledImageSize.Height) /2);
g.DrawImage(myImage,
new RectangleF(imageLocation, scaledImageSize),
new RectangleF(PointF.Empty, myImage.Size), GraphicsUnit.Pixel);
}
protected override void OnHandleDestroyed(EventArgs e) {
this.myImage?.Dispose();
base.OnHandleDestroyed(e);
}
protected override void Dispose(bool disposing) {
if (disposing) { this.myImage?.Dispose(); }
base.Dispose(disposing);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.