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

来源:互联网 发布:数据分析师修炼 编辑:程序博客网 时间:2024/04/29 21:06

由于上位机程序的需要,需要大量的异性控件,开始不以为然,心想随着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)开发

http://download.csdn.net/detail/yangshengchuan/9749122

0 0