C# 实现真正的透明控件(Windows桌面程序)

由于上位机程序的需要,需要大量的异性控件,开始不以为然,心想随着GDI到GDI+的升级,微软应该会给NET打造了一套高效绚丽的绘图方案,使用后才知道完全不是我以为的那么回事.于是开始各种寻找资源,发现网上方法无非使用Web.Transparent作为背景,要么就完全使用控件背后作为控件本身背景,然而这些无非都是掩耳盗铃,真正意义上的透明完全没有做到。

1.其中使用Web.Transparent的透明方案:

Label使用的的透明方案

如图,这种透明系统参考的是控件所属控件背景色,并不是透明,注意左边的两个红色框,其实是两个实现真正透明的控件

 

2.而通过背景截图来作为自己背景图的方案:

由于需要编写太多代码,就不举例了,看起来挺不错,但也经不起推敲,毕竟很多时候,透明者需要盖住一些与用户交互的控件,可想而知,这种设计将是致命的。

 

3.因为Form有TransparencyKey属性,很容易实现真正的透明,就想干脆继承Form来做控件,使用的时候,直接SetParent岂不美好,先不说资源耗费,用户体验.单单位置控制都够呛,其消息队列性质的不同,带来的问题,完全不可取,而且在窗体在被指定到窗体后,其透明属性竟然失效了,大写的囧

难道就没办法了吗??由于透明控件的实现尚未有好的方法,该想法被搁置了半年.

某个偶尔的机会,接触到了Region对象,关于该对象详细信息请参阅微软的官方文档,但是官方文档也只是机械的介绍了该对象成员而已,看不出什么蹊跷的。

我是这么理解这个对象的,该对象告诉了系统这个控件需要占用的界面UI信息,而且这个信息是可以随意编辑的,,,,好知道这么多就够了,如果我把这个对象编辑成我要的形状呢?是不是其他不需要的就消失了?怀着这样疑问写下代码:

 

Region rion = new Region(new Rectangle(0, 0, 20, 20));
Region = rion;

乖乖,控件竟然无论我怎么绘制,在窗口上都只有 20×20 大了,尽管我拖得了很大

 

内心一阵狂喜,,似乎找到了希望

而情况也恰是如此,通过控制Region的信息,完全可以控制控件需要现实和不现实(透明)的部分,

那么问题来了,一些规则的透明还好办,但是如果需要按某种特定无规则来异形呢..难道要一点点去算吗?那也太不科学了,想到这里,自然想到通过 Image(Bitemap)转换成Region

首先看Region的构造函数

使用一个GraphicsPath 对象来构造,通过搜索图像的每个像素,来将需要显示的区域添加的路径画布里,是否可行呢!

由于主题关系,这里延伸对GraphicsPath对象讲解,不熟悉的朋友参考相关资料,谢谢。

根据这一想法,编写转换代码

 

/// <summary>
        /// 根据图片计算GraphicsPath路径(低效率)
        /// </summary>
        /// <param name="img">图像资源</param>
        /// <param name="TranColor">欲透明掉的颜色</param>
        /// <returns>路径画布,已过滤掉了透明颜色</returns>
        public static GraphicsPath ImageToGraphicsPath(Image imgx,Color TranColor)
        {
            if (imgx == null) return null;
            GraphicsPath g = new GraphicsPath(FillMode.Alternate);
            Bitmap bitmap = null;
            if (typeof(Bitmap) == imgx.GetType())
                bitmap = (Bitmap)imgx;
            else
                bitmap = new Bitmap(imgx);

            int ImWidth = bitmap.Width;
            int ImHeight = bitmap.Height;
            Color curColor;
            Rectangle curRect = new Rectangle();
            curRect.Height = 1;
            bool isTransRgn;

            for (int y = 0; y < ImHeight; y++)
            {
                isTransRgn = true;
                for (int x = 0; x < ImWidth; x++)
                {
                    curColor = bitmap.GetPixel(x, y);
                    if (curColor == TranColor || x == ImWidth - 1)//如果遇到透明色或行尾
                    {
                        if (isTransRgn == false)//退出有效区
                        {
                            curRect.Width = x - curRect.X;
                            g.AddRectangle(curRect);
                        }
                    }
                    else//非透明色
                    {
                        if (isTransRgn == true)//进入有效区
                        {
                            curRect.X = x;
                            curRect.Y = y;
                        }
                    }//if curColor
                    isTransRgn = curColor == TranColor;     
                }
            }
            return g;
        }

结果如图:

 

达到目的...似乎任务完成了,,但是回头想想不对呀!为什么要通过GraphicsPath来中间转换呢?为什么不直接把坐标填充到Region 里呢,

因为Region 有一个方法是:

通过GraphicsPath来转换,可能带来其他资源类问题,直接使用这方法估计是不错的选择,于是增加函数:

 

/// <summary>
        /// 根据图片计算Region路径(低效率)
        /// </summary>
        /// <param name="img">图像资源</param>
        /// <param name="TranColor">欲透明掉的颜色</param>
        /// <returns>一个离散的路径信息</returns>
        public static Region ImageToRegion(Image imgx, Color TranColor)
        {
            if (imgx == null) return null;
            Region rRegion = new Region();
            rRegion.MakeEmpty();

            Bitmap bitmap = null;
            if (typeof(Bitmap) == imgx.GetType())
                bitmap = (Bitmap)imgx;
            else
                bitmap = new Bitmap(imgx);

            int ImWidth = bitmap.Width;
            int ImHeight = bitmap.Height;
            Color curColor;
            Rectangle curRect = new Rectangle();
            curRect.Height = 1;
            bool isTransRgn;

            for (int y = 0; y < ImHeight; y++)
            {
                isTransRgn = true;
                for (int x = 0; x < ImWidth; x++)
                {
                    curColor = bitmap.GetPixel(x, y);
                    if (curColor == TranColor || x == ImWidth - 1)//如果遇到透明色或行尾
                    {
                        if (isTransRgn == false)//退出有效区
                        {
                            curRect.Width = x - curRect.X;
                            rRegion.Union(curRect);
                        }
                    }
                    else//非透明色
                    {
                        if (isTransRgn == true)//进入有效区
                        {
                            curRect.X = x;
                            curRect.Y = y;
                        }
                    }//if curColor
                    isTransRgn = curColor == TranColor;
                }
            }
            return rRegion;
        }

结果同样成功,就不在上图..

 

然而似乎没什么问题了,但是细心的朋友可能感觉到了,这种方法读取图像资源是一种极度效率低下的方法,这种直接

GetPixel怎么都不像用在如此大量图像处理上的,,,如果用来展示动画类,岂不够呛!

这里不对C#指针,不安全代码,图像处理等知识进行扩展。尽管我们在接下来的函数中使用到相关手段,如果有兴趣的请自行查阅相关文档

修改相关函数提升效率,减少资源耗费

/*
         * 为了鼓励学习研究精神,该函数,仅能用于本示例
         * 如果使用到其他项目,可能会存在错误
         * 如果您确实需要正确代码,请学习位图相关信息
        */
        /// <summary>
        /// 取得一个图片中非透明色部分的区域。
        /// </summary>
        /// <param name="Picture">取其区域的图片。</param>
        /// <param name="TransparentColor">透明色。</param>
        /// <returns>图片中非透明色部分的区域</returns>
        public unsafe static Region ImageToRegionPx(Image Picture, Color TransparentColor)
        {
            if (Picture == null) return null;
            Region rgn = new Region();
            rgn.MakeEmpty();

            Bitmap bitmap = null;
            if (Picture.GetType() != typeof(Bitmap))
                bitmap = new Bitmap(Picture);
            else
                bitmap = (Bitmap)Picture;

            int width = bitmap.Width;
            int height = bitmap.Height;
            BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            byte* p = (byte*)bmData.Scan0;
            int offset = bmData.Stride - width * 3;

            int p0, p1, p2;         // 记录透明色
            p0 = TransparentColor.R;
            p1 = TransparentColor.G;
            p2 = TransparentColor.B;

            Rectangle curRect = new Rectangle();
            curRect.Height = 1;

            int start = -1;
            // 行座标 ( Y col ) 
            for (int Y = 0; Y < height; Y++)
            {
                // 列座标 ( X row ) 
                for (int X = 0; X < width; X++)
                {
                    if (start == -1 && (p[0] != p0 || p[1] != p1 || p[2] != p2))     //如果 之前的点没有不透明 且 不透明 
                    {
                        start = X;                            //记录这个点
                        curRect.X = X;
                        curRect.Y = Y;
                    }
                    else if (start > -1 && (p[0] == p0 && p[1] == p1 && p[2] == p2))      //如果 之前的点是不透明 且 透明
                    {
                        curRect.Width = X - curRect.X;
                        rgn.Union(curRect);
                        start = -1;
                    }

                    if (X == width - 1 && start > -1)        //如果 之前的点是不透明 且 是最后一个点
                    {
                        curRect.Width = X - curRect.X;
                        rgn.Union(curRect);
                        start = -1;
                    }
                    p += 3;//下一个内存地址
                }
                p += offset;
            }
            bitmap.UnlockBits(bmData);
            bitmap.Dispose();
            return rgn;
        }


到此,真正的C#桌面程序透明控件设计完成...

关于其他双缓存,半透明,请参考其他相关知识

相关源代码(VS2008)开发

https://download.csdn.net/download/yangshengchuan/12880592

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页