问题描述
因此,经过一段时间的研究,我可能已经阅读了二十种不同的实现,但是还没有看到一个可靠的,基于控件的,基于框架的通用解决方案来实现真正透明的控件。
这种可能过于简化的方法根本不做任何事情,并创建了一个永远不会使用的例程(InvalidateEx):
这似乎暗示了先前的答案:
但是使用计时器刻度来执行InvalidateEx方法
这似乎是放弃了多层控件,并在单层面板上处理所有绘图,而gl讨论标签对象看起来如何实现其自身的透明度:
这是一个完全基于非框架的方法:
这个答案对我根本不起作用:
我得到的控件是普通的纯色(白色),上面绘制有透明图像。
所以,我从我自己的实现开始。
我' d宁愿不依赖自下而上的GDI绘图,即以最父级的形式执行所有绘图,我宁愿不对透明位图进行预渲染,也不愿继承Forms.Control之外的其他东西执行自己的一些巧妙技巧来简化透明度。我真的很想深入了解如何在具有完全透明性支持的控件表面上进行绘制。显然,例如,标签控件内部具有完整的实现,它是如何工作的?
逻辑上,透明控件不能依赖简单的1位剪辑区域,并且需要保持基本完整。多层控件需要正确处理,即正确的绘图顺序等,而不会导致无限循环。
第一个也是最常见的建议是:
SetStyle(ControlStyles.SupportsTransparentBackColor,True)
Me.BackColor = Color.Transparent
我了解的第一行是
受保护,将ReadOnly属性CreateParams作为System.Windows.Forms.CreateParams
获取
昏暗结果作为System.Windows.Forms.CreateParams = MyBase.CreateParams
result.ExStyle = result.ExStyle或WindowsMessages .WS_EX_TRANSPARENT
返回结果
最终获得
最终财产
根据要继承的类,其行为有很大不同。对于Forms.Control,OnPaintBackground选择使用父控件的背景色清除剪辑矩形。不太聪明。
重写并删除基本的OnPaintBackground调用会使控件保持脏状态,而不会覆盖其下方的内容。使用透明颜色清除(乐观方法)会产生黑色透明像素场,并使控件看起来完全是黑色。
从逻辑上讲,这是有道理的,投标中的基础控件为了提高效率,请不要重绘他们期望通过重绘进行控制的区域。然后,表格的该区域永远不会更新,并且保留表格存在之前的所有内容。在其上绘制透明颜色有些令人困惑,因为它暗示图形缓冲区甚至不包含基础像素,而是实际的东西,而实际的东西被黑色的透明物体代替,从而使控件显得黑色。
第一个挑战似乎是处理控件本身重绘时的问题,使所有基础控件在该区域中重绘自身(而不会自动使控件本身无效以引发堆栈溢出),然后在其上进行绘制基础像素和最新像素。我还没有找到一种方法。我最接近的是通过挖掘.net代码并找到ButtonRenderer.DrawParentBackground方法。这似乎在很大程度上起作用,尽管快速测试显示,重叠组件的z顺序仍未正确处理,而底层控件位于此控件的顶部。
第二个挑战是,当基础控件发生更改时,使控件正确重绘自身。通过断开挑战一中的自动失效,很难通知控件使其自身失效并且不使其父母失效。
如果有人已经完全解决了此问题,请提供代码,如果没有,请提供一些如何处理问题的一个或几个部分的经验。
非常感谢。 / p>
编辑:
根据
研究更新:实际上,ButtonRenderer.DrawParentBackground仅由Control本身使用,仅当使用Application.EnableVisualStyles()且仅强制父级背景绘制时才使用。
InvokePaintBackground(Parent,e.Graphics)
似乎是同义词
将复制父控件的所有背景绘制细节。
我已经创建了必需的常规透明度控件,解决方案是该控件及其父级中所有同级控件的详细分解,并依靠InvokePaint()在OnPaintBackground()方法上准备一个递归绘图例程和InvokePaintBackground()方法。通过依次定义较小的剪辑并与较低的控件相交,该方法可以最大程度地减少绘图开销。
-
完整的控件包括一种检测方法并响应同级控件的Invalidate方法,有效地绘制透明控件的堆栈,并有效地在动画基础控件上允许完整的Alpha通道叠加。
-
在控件中未考虑命中测试。
> -
此控件不会覆盖在绘制事件期间未执行绘制的控件。这是一个很大的限制。
-
要使用控件,只需从基础继承并覆盖OnPaint方法即可执行自定义绘制。只要首先调用基本方法,也可以覆盖OnPaintBackground方法。
最后,如果有人想要完整的代码(包括无效处理),请告诉我。如果您对系统绘制的组件有解决方案,请告诉我。另外,如果您有一个更琐碎的解决方案,这意味着我在此上浪费了很多时间,那么我也不会为收到该消息而感到沮丧!
OnPaintBackground方法的控件摘录为呈现:
'''< summary>
’’递归绘制其相关区域中的所有基础控件,并根据需要堆叠绘图操作。
’’< / summary>
’’< param name = pevent>< / param>
’’<备注>< /备注>
受保护的重写Sub OnPaintBackground(Pevent作为System.Windows.Forms.PaintEventArgs)
'存储剪辑并进行转换以进行还原
Dim initialClip As Region = pevent.Graphics.Clip
Dim initialTransform As Drawing2D.Matrix = pevent.Graphics.Transform
'开发基础控件列表
Dim submarinedControls作为新列表(控件)
每个控件作为m_Siblings $ b中的控件$ b如果Control.Visible AndAlso Above(Control)AndAlso Me.ClientRectangle.IntersectsWith(Control.RelativeClientRectangle(Me))然后submarinedControls.Add(Control)
Next
'为父对象准备剪辑将
Dim parentClip绘制为System.Drawing.Region = New System.Drawing.Region(initialClip.GetRegionData)
每个控件作为submarinedControls中的控件
parentClip.Exclude(Control.RelativeClientRectangle(Me) )
下一个
pevent.Graphics.Clip = parentClip
'评估控制关系ip到父级,暂时调整父级重绘的转换。这种转换必须是相对的,因为传入的图形可能已经应用了有意义的变换。
Dim translation as Point = Parent.RelationTo(Me)
pevent.Graphics.Transform = New Drawing2D.Matrix(1、0、0、1,initialTransform.OffsetX + translation.X,initialTransform.OffsetY + translation.Y)
'完全绘制父级背景
InvokePaintBackground(父母,pevent)
InvokePaint(父母,pevent)
'重置同级转换drawing
pevent.Graphics.Transform = initialTransform
'开发潜艇兄弟姐妹的初始剪辑
Dim siblingClip作为System.Drawing.Region = New System.Drawing.Region(initialClip.GetRegionData )
siblingClip.Exclude(parentClip)
将每个控件作为submarinedControls中的控件
'将已继承的兄弟姐妹相对于自身的相对位置
translation = Control.RelationTo(Me )
'在变换之前*定义并应用剪辑* b $ b昏暗交集将剪辑作为新区域(Control.RelativeClientRectangle(Me))
junctionClip.Intersect(siblingClip)
pevent.Graphics.Clip =相交剪辑
'应用变换
pevent.Graphics.Transform =新Drawing2D.Matrix(1、0、0、1 ,initialTransform.OffsetX + translation.X,initialTransform.OffsetY + translation.Y)
'提高同级控件的绘制事件
InvokePaintBackground(Control,pevent)
InvokePaint(Control,pevent) )
'还原转换并排除区域
pevent.Graphics.Transform = initialTransform
siblingClip.Exclude(intersectionClip)
下一个
'将转换并裁剪到绘图前状态
pevent.Graphics.Transform = initialTransform
pevent.Graphics.Clip = initialClip
End Sub
So having looked this up for a while now, I have read probably twenty different implementations but have not yet seen a solid, control-based, framework based general solution to implementing truly transparent controls that works.
This possibly oversimplified approach doesn't do anything at all and creates a routine (InvalidateEx) that never gets used: http://bytes.com/topic/c-sharp/answers/248836-need-make-user-control-transparent
This seems to be hinting at what the previous answer was getting at: http://www.bobpowell.net/transcontrols.htm
however uses a timer tick to execute the InvalidateEx method.
This appears to be abandoning multi-layered controls and handles all drawing on a single layered panel, while glossing over how the label object appears to be implementing its own transparency: http://www.codeproject.com/Articles/25048/How-to-Use-Transparent-Images-and-Labels-in-Window
This is a completely non-framework based method: http://www.codeproject.com/Articles/30135/General-solution-for-transparent-controls
The accepted solution in this answer simply does not work for me: Transparent Control on Transparent control?
The control I get is a generic solid colour (white) with the transparent image drawn over it.
So, I start on my own implementation.
I'd rather not rely on bottom-up GDI drawing, i.e. perform all drawing in the parent-most form, I'd rather not do pre-rendering to transparent bitmaps, I'd rather not inherit from something other than Forms.Control that is performing some clever tricks of its own to short-cut transparency. I really want to get to the bottom of how to draw on a control surface with complete transparency support. Evidently the label control for instance has a full implementation inside itself, how does it work?
Logically, a transparent control cannot rely on simple 1-bit clip regions and needs to stay mostly complete. Multi-layering of controls needs to be handled correctly, i.e. correct drawing order etc. without causing infinite loops.
The first and most common recommendation is this:
SetStyle(ControlStyles.SupportsTransparentBackColor, True)
Me.BackColor = Color.Transparent
where the first line I understand is synonymous with
Protected Overrides ReadOnly Property CreateParams As System.Windows.Forms.CreateParams
Get
Dim result As System.Windows.Forms.CreateParams = MyBase.CreateParams
result.ExStyle = result.ExStyle Or WindowsMessages.WS_EX_TRANSPARENT
Return result
End Get
End Property
Which has very different behaviours depending on which class is being inherited. In the case of Forms.Control, OnPaintBackground chooses to clear the clip rectangle with the parent control's background colour. Not too clever.
Overriding and removing the base OnPaintBackground call causes the control to stay 'dirty' having never overwritten what came below it. Clearing using a transparent colour (optimistic approach) yields a field of black transparent pixels and causes the control to appear completely black.
Logically this makes sense, underlying controls in a bid for efficiency do not redraw the area they anticipate this control with draw over. That area of the form then never gets updated and stays whatever came before the form existed. The painting of a transparent colour onto this is a little confusing, since it implies that the graphics buffer does not even contain the underlying pixels but actual nothings that are replaced with black transparent somethings to make the control appear black.
The first challenge appears to be handling when the control itself redraws, getting all underlying controls to redraw themselves in that region (without automatically invalidating the control itself to start a stack overflow), then painting itself over the underlying and up-to-date pixels. I haven't found a way to do this yet. The closest I have come is through digging around the .net code and finding the ButtonRenderer.DrawParentBackground method. This largely seems to work, though a quick test showed that z order of overlapping components was still not handled correctly, with underlying controls drawing over the top of this control.
The second challenge then is to have the control properly redraw itself when an underlying control changes. By disconnecting the automatic invalidation in challenge one, it makes it difficult to notify the control to invalidate itself and not invalidate its parents.
If someone has solved this problem completely, please provide the code, if not, please provide some experience in how I might handle one or several of the problem's parts.
Many thanks.
Edit:
It appears the assumed detail about WS_EX_TRANSPARENT as per WS_EX_TRANSPARENT - What does it actually do?
Research Update: ButtonRenderer.DrawParentBackground is actually just used by Control itself, only if Application.EnableVisualStyles() is used and simply forces the background draw of the parent. It appears to be a synonym of
InvokePaintBackground(Parent, e.Graphics)
Which will reproduce all background drawn details of the parent control.
I have created the required general transparency control, the solution was a detailed breakdown of the control and all of its siblings in the parent, and preparing a recursive drawing routine on the OnPaintBackground() method relying on the InvokePaint() and InvokePaintBackground() methods. By defining successively smaller clips and intersecting with lower controls, this method minimises drawing overhead.
The complete control includes a method of detecting and responding to sibling controls' Invalidate methods, efficiently drawing the stack of transparent controls and effectively allowing full alpha channel overlays on animated underlying controls.
The control can be interleaved with all permutations of child and parent controls while giving visually accurate transparency.
Hit testing has not been considered in the control.
This control will not overdraw controls whose drawing is not performed during paint events. This is a big limitation.
To use the control one simply inherits from the base and overrides the OnPaint method to perform custom drawing. It is also possible to override the OnPaintBackground method so long as a call to the base method is called first.
Finally, if anyone would like the full code including invalidation handling, let me know. If you have a solution to system drawn components let me know that too. Also if you have a more trivial solution that implies I wasted a bunch of time on this, I wouldn't be upset about receiving that either!
An excerpt of the control on the OnPaintBackground method is presented:
''' <summary>
''' Recursively paint all underlying controls in their relevant regions, stacking drawing operations as necessary.
''' </summary>
''' <param name="pevent"></param>
''' <remarks></remarks>
Protected Overrides Sub OnPaintBackground(pevent As System.Windows.Forms.PaintEventArgs)
'Store clip and transform for reversion
Dim initialClip As Region = pevent.Graphics.Clip
Dim initialTransform As Drawing2D.Matrix = pevent.Graphics.Transform
'Develop list of underlying controls
Dim submarinedControls As New List(Of Control)
For Each Control As Control In m_Siblings
If Control.Visible AndAlso Above(Control) AndAlso Me.ClientRectangle.IntersectsWith(Control.RelativeClientRectangle(Me)) Then submarinedControls.Add(Control)
Next
'Prepare clip for parent draw
Dim parentClip As System.Drawing.Region = New System.Drawing.Region(initialClip.GetRegionData)
For Each Control As Control In submarinedControls
parentClip.Exclude(Control.RelativeClientRectangle(Me))
Next
pevent.Graphics.Clip = parentClip
'Evaluate control relationship to parent, temporarily adjusting transformation for parent redraw. This translation must be relative since the incoming graphics may already have a meaningful transform applied.
Dim translation As Point = Parent.RelationTo(Me)
pevent.Graphics.Transform = New Drawing2D.Matrix(1, 0, 0, 1, initialTransform.OffsetX + translation.X, initialTransform.OffsetY + translation.Y)
'Fully draw parent background
InvokePaintBackground(Parent, pevent)
InvokePaint(Parent, pevent)
'Reset transform for sibling drawing
pevent.Graphics.Transform = initialTransform
'Develop initial clip of submarined siblings
Dim siblingClip As System.Drawing.Region = New System.Drawing.Region(initialClip.GetRegionData)
siblingClip.Exclude(parentClip)
For Each Control As Control In submarinedControls
'Define relative position of submarined sibling to self
translation = Control.RelationTo(Me)
'Define and apply clip *before* transformation
Dim intersectionClip As New Region(Control.RelativeClientRectangle(Me))
intersectionClip.Intersect(siblingClip)
pevent.Graphics.Clip = intersectionClip
'Apply transformation
pevent.Graphics.Transform = New Drawing2D.Matrix(1, 0, 0, 1, initialTransform.OffsetX + translation.X, initialTransform.OffsetY + translation.Y)
'Raise sibling control's paint events
InvokePaintBackground(Control, pevent)
InvokePaint(Control, pevent)
'Revert transformation and exclude region
pevent.Graphics.Transform = initialTransform
siblingClip.Exclude(intersectionClip)
Next
'Revert transform and clip to pre-drawing state
pevent.Graphics.Transform = initialTransform
pevent.Graphics.Clip = initialClip
End Sub
这篇关于创建透明控件的一般解决方案是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!