C#中的Picturebox透明背景似乎无法正常工作

24

我需要在一个项目中显示带有透明背景的图像。我制作了一些具有透明背景的.png图片(为了检查这一点,我在Photoshop中打开了它们)。现在我有了一个扩展了PictureBox类的类:

class Foo : PictureBox
{
    public Foo(int argument)
        : base()
    {
        Console.WriteLine(argument);//different in the real application of course.
        //MyProject.Properties.Resources.TRANSPARENCYTEST.MakeTransparent(MyProject.Properties.Resources.TRANSPARENCYTEST.GetPixel(1,1)); //<-- also tried this
        this.Image = MyProject.Properties.Resources.TRANSPARENCYTEST;
        ((Bitmap)this.Image).MakeTransparent(((Bitmap)this.Image).GetPixel(1, 1));
        this.SizeMode = PictureBoxSizeMode.StretchImage;
        this.BackColor = System.Drawing.Color.Transparent;
    }
}

然而,这只会显示一个带有白色背景的PictureBox,我似乎无法让它具有透明背景。


1
你能否提供一个图片链接给我吗?最好是你最基本的那一张。 - Jeff Mercado
http://www.imgload.nl/upload/1301737511BLUE.png <-- 这是我使用的其中一张图片 - teuneboon
我依稀记得(从我的WinForm时代开始),当控件的背景标记为“透明”时,它并不是真正的透明渲染。相反,它会以容器的背景颜色呈现。你的容器背景颜色是白色吗? - Stephen Chung
@Stephen:我很确定那就是它。在WinForms中,图像并没有真正的透明度(默认情况下没有)。如果你的控件重叠,你会看到它。 - Jeff Mercado
@Stepheb 那我开始越来越讨厌C#了,没有透明度?它唯一的容器就是一个带有白色背景的表单。 - teuneboon
1
除了表单(白色)背景透过透明区域显示外,你还能期望什么?我甚至不理解这里的问题是什么... Hans 的答案非常正确。(还要注意这与 C# 语言没有任何关系。C# 没有用户界面;你正在使用 WinForms 来包装本机 Windows API。它并不是为透明度而设计的。你不是在构建网页,而是桌面应用程序。) - Cody Gray
6个回答

44

如果你想叠加图片在图片上(而不是在表单上叠加图片),这个方法可以解决:

overImage.Parent = backImage;
overImage.BackColor = Color.Transparent;
overImage.Location = thePointRelativeToTheBackImage;

其中overImage和backImage是带有PNG(透明背景)的PictureBox。

这是因为,如前所述,图像的透明度是使用父容器的背景颜色呈现的。PictureBox没有“Parent”属性,因此您必须手动创建它(或者当然可以创建自定义控件)。


1
只是为了澄清一下(不确定类型),我使用了overImage.Location = new Point(x,y),一切都很好。非常感谢! - Sash
它的工作正常,需要定义相应于父容器的点。根据这个例子,backImage是父容器。 - ramya
.BackColor设置为Color.Transparent是关键。谢谢! - ashes999

20

它可能完美地工作。你看到的是图片框控件背后的内容,即表单。表单的BackColor(背景颜色)可能是白色。您可以设置表单的BackgroundImage属性以确保,在图片框中应该能够看到图像。就像这样:

输入图像描述

如果要穿过图片框和表单两者,您需要一个更大的武器——Form.TransparencyKey。


2
这不是真正的透明度。如果picturebox后面有另一个控件,它将不会显示在透明区域中。相反,窗体背景将覆盖该控件。 - P. Kouvarakis
是的,我们知道,窗口本来就是不透明的设计。你可以看到父控件将自己绘制为背景。你也可以让其他控件进行绘制,但那需要一些工作(参见https://support.microsoft.com/en-us/kb/943454)。使用Paint事件或WPF在彼此之上堆叠绘画层。 - Hans Passant

12

在CodeProject网站上有一个很好的解决方案,位于:

Making Transparent Controls - No Flickering

本质上的技巧是重写paintbackground事件,以便循环遍历所有位于picturebox下面的控件并重新绘制它们。该函数为:

protected override void OnPaintBackground(PaintEventArgs e)
       // Paint background with underlying graphics from other controls
   {
       base.OnPaintBackground(e);
       Graphics g = e.Graphics;

       if (Parent != null)
       {
           // Take each control in turn
           int index = Parent.Controls.GetChildIndex(this);
           for (int i = Parent.Controls.Count - 1; i > index; i--)
           {
               Control c = Parent.Controls[i];

               // Check it's visible and overlaps this control
               if (c.Bounds.IntersectsWith(Bounds) && c.Visible)
               {
                   // Load appearance of underlying control and redraw it on this background
                   Bitmap bmp = new Bitmap(c.Width, c.Height, g);
                   c.DrawToBitmap(bmp, c.ClientRectangle);
                   g.TranslateTransform(c.Left - Left, c.Top - Top);
                   g.DrawImageUnscaled(bmp, Point.Empty);
                   g.TranslateTransform(Left - c.Left, Top - c.Top);
                   bmp.Dispose();
               }
           }
       }
   }

很不幸,那篇代码项目文章已于2013年10月2日被删除。 - Wyck
2
这篇文章至今仍然具有参考价值... 不能等待老板批准转换到 WPF :( - Zohar Peled

4
我知道您的问题是关于C#的,但由于VB.NET的相似性和易于转换,我将添加一个完整的VB版本,同时允许在移动控件时更新其背景。您已经有了答案,但这是为那些通过搜索引擎找到此帖子并想要VB版本或者只是需要在C#中使用它的人提供一个完整可转换的示例。
创建一个新的自定义控件类,并将以下内容粘贴到其中...覆盖默认的类内容:
自定义控件类:
Public Class TransparentPictureBox
    Private WithEvents refresher As Timer
    Private _image As Image = Nothing

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        refresher = New Timer()
        'refresher.Tick += New EventHandler(AddressOf Me.TimerOnTick)
        refresher.Interval = 50
        refresher.Start()

    End Sub

    Protected Overrides ReadOnly Property CreateParams() As CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            cp.ExStyle = cp.ExStyle Or &H20
            Return cp
        End Get
    End Property

    Protected Overrides Sub OnMove(ByVal e As EventArgs)
        MyBase.OnMove(e)
        MyBase.RecreateHandle()
    End Sub

    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)

        'Add your custom paint code here
        If _image IsNot Nothing Then
            e.Graphics.DrawImage(_image, CInt(Width / 2) - CInt(_image.Width / 2), CInt(Height / 2) - CInt(_image.Height / 2))
        End If
    End Sub


    Protected Overrides Sub OnPaintBackground(ByVal e As System.Windows.Forms.PaintEventArgs)
        ' Paint background with underlying graphics from other controls
        MyBase.OnPaintBackground(e)
        Dim g As Graphics = e.Graphics

        If Parent IsNot Nothing Then
            ' Take each control in turn
            Dim index As Integer = Parent.Controls.GetChildIndex(Me)
            For i As Integer = Parent.Controls.Count - 1 To index + 1 Step -1
                Dim c As Control = Parent.Controls(i)

                ' Check it's visible and overlaps this control
                If c.Bounds.IntersectsWith(Bounds) AndAlso c.Visible Then
                    ' Load appearance of underlying control and redraw it on this background
                    Dim bmp As New Bitmap(c.Width, c.Height, g)
                    c.DrawToBitmap(bmp, c.ClientRectangle)
                    g.TranslateTransform(c.Left - Left, c.Top - Top)
                    g.DrawImageUnscaled(bmp, Point.Empty)
                    g.TranslateTransform(Left - c.Left, Top - c.Top)
                    bmp.Dispose()
                End If
            Next
        End If
    End Sub

    Public Property Image() As Image
        Get
            Return _image
        End Get
        Set(value As Image)
            _image = value
            MyBase.RecreateHandle()
        End Set
    End Property

    Private Sub refresher_Tick(sender As Object, e As System.EventArgs) Handles refresher.Tick
        MyBase.RecreateHandle()
        refresher.Stop()
    End Sub
End Class

保存类后,清理项目并重新构建。新控件应该出现为新的工具项。找到它,并将其拖到您的表单中。

我曾经遇到过这个控件的问题... 当我尝试加载一个动画的 "Loading" .gif 图像时发生。

图像不会动画,而且当你隐藏控件,然后再次显示它时,还会有显示问题。

解决这些问题,你就会有一个完美的自定义控件类。 :)

编辑:

我不知道以下内容是否适用于 C# 的 IDE,但这是我尝试转换的结果:

using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class TransparentPictureBox
{
    private Timer withEventsField_refresher;
    private Timer refresher {
        get { return withEventsField_refresher; }
        set {
            if (withEventsField_refresher != null) {
                withEventsField_refresher.Tick -= refresher_Tick;
            }
            withEventsField_refresher = value;
            if (withEventsField_refresher != null) {
                withEventsField_refresher.Tick += refresher_Tick;
            }
        }
    }

    private Image _image = null;

    public TransparentPictureBox()
    {
        // This call is required by the designer.
        InitializeComponent();

        // Add any initialization after the InitializeComponent() call.
        refresher = new Timer();
        //refresher.Tick += New EventHandler(AddressOf Me.TimerOnTick)
        refresher.Interval = 50;
        refresher.Start();

    }

    protected override CreateParams CreateParams {
        get {
            CreateParams cp = base.CreateParams;
            cp.ExStyle = cp.ExStyle | 0x20;
            return cp;
        }
    }

    protected override void OnMove(EventArgs e)
    {
        base.OnMove(e);
        base.RecreateHandle();
    }

    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        base.OnPaint(e);

        //Add your custom paint code here
        if (_image != null) {
            e.Graphics.DrawImage(_image, Convert.ToInt32(Width / 2) - Convert.ToInt32(_image.Width / 2), Convert.ToInt32(Height / 2) - Convert.ToInt32(_image.Height / 2));
        }
    }


    protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
    {
        // Paint background with underlying graphics from other controls
        base.OnPaintBackground(e);
        Graphics g = e.Graphics;

        if (Parent != null) {
            // Take each control in turn
            int index = Parent.Controls.GetChildIndex(this);
            for (int i = Parent.Controls.Count - 1; i >= index + 1; i += -1) {
                Control c = Parent.Controls(i);

                // Check it's visible and overlaps this control
                if (c.Bounds.IntersectsWith(Bounds) && c.Visible) {
                    // Load appearance of underlying control and redraw it on this background
                    Bitmap bmp = new Bitmap(c.Width, c.Height, g);
                    c.DrawToBitmap(bmp, c.ClientRectangle);
                    g.TranslateTransform(c.Left - Left, c.Top - Top);
                    g.DrawImageUnscaled(bmp, Point.Empty);
                    g.TranslateTransform(Left - c.Left, Top - c.Top);
                    bmp.Dispose();
                }
            }
        }
    }

    public Image Image {
        get { return _image; }
        set {
            _image = value;
            base.RecreateHandle();
        }
    }

    private void refresher_Tick(object sender, System.EventArgs e)
    {
        base.RecreateHandle();
        refresher.Stop();
    }
}

试一下,自己看看吧 :P

ps:我不是大师,所以预计在C#和VB.NET版本中会出现各种错误。lol


3

如果您在图片框中显示透明的png图片,它会自动考虑透明度,因此您无需设置透明颜色。


我刚刚使用picturebox尝试了你的图像。它可以正常工作,没有任何透明度规定。 - Anton Semenov

1
上述答案似乎解决了你的问题。实际上,你看到的是图片框控件后面的内容——带有白色背景颜色的表单本身。我在这里创建了一个简单的函数,它首先将字节类型(数组)的图像转换为位图,然后将特定颜色(来自位图图片)设置为透明。以下是你可以使用的代码:
 using System;
   using System.Drawing;
   using System.Drawing.Imaging;
   using System.Windows.Forms;
    public void LogoDrawTransparent(PaintEventArgs e)
    {
        // Create a Bitmap object from an image file.
        Image myImg;
        Bitmap myBitmap;

        try
        {
            myImg = cls_convertImagesByte.GetImageFromByte(newImg);
            myBitmap = new Bitmap(myImg); // @"C:\Temp\imgSwacaa.jpg");  

            // Get the color of a background pixel.
            Color backColor = myBitmap.GetPixel(0, 0); // GetPixel(1, 1); 
            Color backColorGray = Color.Gray;
            Color backColorGrayLight = Color.LightGray;
            Color backColorWhiteSmoke = Color.WhiteSmoke;
            Color backColorWhite = Color.White;
            Color backColorWheat = Color.Wheat;

            // Make backColor transparent for myBitmap.
            myBitmap.MakeTransparent(backColor);
                    // OPTIONALLY, you may make any other "suspicious" back color transparent (usually gray, light gray or whitesmoke)
            myBitmap.MakeTransparent(backColorGray);
            myBitmap.MakeTransparent(backColorGrayLight);
            myBitmap.MakeTransparent(backColorWhiteSmoke);

            // Draw myBitmap to the screen.
            e.Graphics.DrawImage(myBitmap, 0, 0, pictureBox1.Width, pictureBox1.Height); //myBitmap.Width, myBitmap.Height);
        }
        catch
        {
            try { pictureBox1.Image = cls_convertImagesByte.GetImageFromByte(newImg); }
            catch { } //must do something
        }
    }

您可以在pictureBox的Paint事件上调用此函数。 这是我引用在上述函数中的类:

    class cls_convertImagesByte
{

    public static Image GetImageFromByte(byte[] byteArrayIn)
    {
        MemoryStream ms = new MemoryStream(byteArrayIn);
        Image returnImage = Image.FromStream(ms);
        return returnImage;
    }

    public static byte[] GetByteArrayFromImage(System.Drawing.Image imageIn)
    {
        MemoryStream ms = new MemoryStream();
        imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
        return ms.ToArray();
    }
}

感谢。Chagbert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接