繁体   English   中英

Graphics.DrawImage不适用于透明图像C#

[英]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
    }
}

图片:

  1. 在设计方式下:设计方式黑色背景问题
  2. 在设计方式下:设计方式怪异行为
  3. 在运行模式下: 运行模式黑色背景

我建议在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方法 (及后续页面)。

这是一个实现了一些建议的修改后的类:

  • 请注意,专用字段用于替代Image属性:Image属性将为null(并且不会绘制),而该属性仍可在Designer中访问,并且您可以分配另一个Image而不会影响结果。
  • 每次都使用旧图像(如果有的话)进行处理,并用新图像替换。
  • 而是隐藏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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM