Graphics.DrawImage无法用于透明图像C#

时间:2019-06-28 00:35:32

标签: c# winforms

我试图创建一个自定义控件,在该控件中我只需将图像绘制到按钮上即可。调整图像大小以填充按钮的区域,同时仍保持图像的原始比例。在设计和运行模式下,我总是出现一些非常奇怪的行为。

在设计人员中,大多数情况下,图像的透明部分为黑色。如果我使控件足够小,则透明区域将开始填充它们在我的屏幕上可以找到的任何随机内容。在运行模式下,透明度始终填充为黑色(请参见下图)。

我觉得我可能以错误的方式使用winforms控件,但是我对此没有太多经验。我尝试了所有在这里找到的建议:Using Graphics.DrawImage() to Draw Image with Transparency/Alpha Channel,以及其他一些我在网上找到的建议,都无济于事。

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. 在设计模式下:Design Mode Black Background Problem
  2. 在设计模式下:Design Mode Weird Behavior
  3. 在运行模式下:Run Mode Black Background

1 个答案:

答案 0 :(得分:0)

我建议在base.OnPaint(e)覆盖中调用OnPaint,因为除非Button FlatStyle(或者更好的是Button派生的ButtonBase类的样式)类型为FlatStyle.System,则Button控件被视为OwnerDrawn。结果,它是用ControlStyles.UserPaint创建的。这对从ButtonBaseAdapter派生的ButtonBase类分派器绘制控件的方式产生了许多影响,这些分派器决定了呈现样式和内部PaintWorker方法的操作。

此外,正如您在ButtonBase constructor中看到的那样,按钮是用 ControlStyles.Opaque 样式创建的(并且您还可以看到 ControlStyles.OptimizedDoubleBuffer < / strong>样式)。这意味着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()或用{{3}声明这些对象},将在其内部创建一个 try/finally 块,将对象放置在finally部分中。

您可以在此处发布的代码中看到,设置了新图像后,旧图像将立即被处理掉。
using statement方法被重写,以摆脱分配给Field的当前对象,该对象保存您的Button正在显示的位图。这是因为此位图来自嵌入式资源,因此在不再需要它时,最好对其进行废弃处理。

如果您改为创建一个使用非托管资源的类,该类不是从已经处理垃圾回收的另一个类派生的,请实现OnHandledDistroyed接口。

有关此主题的一些文档:

埃里克·利珀特(Eric Lippert)关于垃圾收集和终结处理程序的系列:IDisposable
MSDN:When everything you know is wrong, part one(及后续页面)。

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

  • 请注意,专用字段用于替代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);
    }
}