What is a general solution to creating a transparent control? - vb.net

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: https://web.archive.org/web/20141227200000/http://bobpowell.net/transcontrols.aspx
however it 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

Me.Visible = False
OR
Me.Opacity = 0
OR
Me.Hide()

Related

ContextMenuStrip Location Point

How to open the ContextMenuStrip programmatically at the cursor on right mouse button down/clicked over PictureBox?
I need to open ContextMenuStrip only in some areas of an Image (Schedule) in a PictureBox, so I disconnected the PictureBox.ContextMenuStrip property from the ContextMenuStrip1 and I'm firing it manually. That means I need to provide a point, which appears to be surprisingly challanging.
My question is similar to "How to get location of ContextMenuStrip", but I need to account for both vertical and horizontal scrolling, which it doesn't (unless I messed something).
So when I'm trying something like this (in PictureBoxPlan.MouseDown):
Dim rpt As Point = Me.PointToClient(PictureBoxPlan.Parent.PointToScreen(e.Location))
...it has a fundamental flaw, because the e.Location is in context of Image, regardless how it is scrolled within the PictureBox. I tried to Form.MouseDown event, but it's not fired, when [right] clicked on a PictureBox.
Just for confirmation, I still need the point in PictureBox context to analyze whether the ContextMenuStrip should be shown or not and to lookup associated ID.
I am now trying to use some constants (may not work if windows "font size" is set to other than 100%) together with scroll bars values, which is not optimal at all.
OK, I found what is breaking the logic in the ContextMenuStrip.Show(Point) documentation:
Show(Point)
Positions the `ToolStripDropDown` relative to the specified screen location.
So the ContextMenuStrip, to my surprise, takes the screen coordinates, not the Form ones. I then just removed PointToClient and everything works as a charm, regardless of window position on the screen or scrollbars position of the Image container:
Dim rpt As Point = PictureBoxPlan.PointToScreen(New Point(e.Location.X, e.Location.Y))
No need to take into account PanelPlan.VerticalScroll.Value or PanelPlan.HorizontalScroll.Value.

In VB.Net Winforms is there a general solution to prevent a child control from painting outside of the parent control

I have a parent control that contains 1...N child controls. When I horizontally scroll the parent, the child control scroll as well.
I would like to clip the child controls to the bounds of the parent control. When I set a clip region to the bounds of the parent control, the child control paint still renders outside of the parent.
Is there a way to clip a child control to the bounds of the parent control?
I have the clipping working now - but is probably not the best-practice approach. The approach is I used is below.
But I am wondering about the useage of the UserControl.Region property in a Paint event handler vs. the Graphics.SetClip and Graphics.Clip in the same handler. The UserControl.Region property seems to work well - but the Graphics.SetClip and Clip had no effect.
My guess is that the UserControl.Region property uses control coordinates and the Graphics.SetClip and Clip expect a different coordinate system. Is this correct?
Approach:
Compute a clipping rectangle in control coordinates. In my case I want to clip my custom control to a DevExpress XtraChart XYDiagram (so that my control does not spill outside of the plot area).
In my control event handler, set the Region property to the clipping rectangle computed in step 1.
I'm also curious about ways to minimize flicker during paint events. Any comments would be appreciated.

How Can I use Graphics.CopyFromScreen to enable alpha transparent controls?

I am trying to use (and this may be my problem already) e.Graphics.CopyFromScreen() in the OnPaint() method of my control to prepare a background onto which I can draw the content of the control. The goal is a tool-tip with rounded corners and other specific features to replace the default Windows tool tip.
Ideally the tool tip could be antialiased and blended into the backdrop, however at a pinch I could tolerate an aliased boundary. This may also be necessary as the control will often be covering a control with an OpenGL overlay and the whole blending procedure may end up a nightmare.
I have created a custom user control, a persistent instance of which belonds to my applications's main form, overriding the OnPaint and OnPaintBackground methods. The application of transparency however is challenging. As I understand, the graphics object passed to the paint method starts with a copy of the current VisibleClipBounds, where generally the first move is to clear the control with a solid colour and start drawing the main features. My first thought was to bypass the fill and start drawing on the captured background. For the first redraw this works fine, but subsequent redraws keep the previous content instead of re-capturing the backdrop. I hoped that CopyFromScreen would specifically eliminate the currently drawing control from its capture so as to start from a clean base but this is not the case, it captures the rectangle of the new location including the image of the previous location if it is in the bounds.
Other solutions and permutations thereof such as
SetStyle(ControlStyles.SupportsTransparentBackColor, True)
Me.BackColor = Color.Transparent
e.Graphics.Clear(Color.FromArgb(0,0,0,0))
have to-date yielded no appropriate results.
I started with a transparent form using the transparent key colour. This worked with aliasing but using a form as a tool tip seemed excessive and caused annoying artifacts (some of which could be overcome) such as the temporary presence of the taskbar button, focus loss of underlying form with subsequent visual feedback in the host form's title bar colour. The control is much lighter weight but I am struggling to make it work even as well as the form.
At this point I simply ask how to achieve the desired result and whether CopyFromScreen is a part of that solution.
That's a lot of text to read - but addressing transparency to achieve a custom-shaped form/control... Control.Region can do the same thing:
[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern IntPtr CreateRoundRectRgn
(
int nLeftRect, // x-coordinate of upper-left corner
int nTopRect, // y-coordinate of upper-left corner
int nRightRect, // x-coordinate of lower-right corner
int nBottomRect, // y-coordinate of lower-right corner
int nWidthEllipse, // height of ellipse
int nHeightEllipse // width of ellipse
);
private void CreateRegion()
{
this.Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height, 40, 40));
}
If your control is set as a top-level window and you're running it on Windows 7 or 8, check into DwmEnableBlurBehindWindow. In the parameter structure, I set enable to true and I create a region 1x1 pixels for the blur. With BackColor and TransparencyKey set to Black, this gives me a entirely transparent window (complete with mouse pass-through) that I can paint 32-bit images to with full alpha transparency (using Graphics.FromHwnd). A bonus to this method is that if the window moves or the background changes, Windows DWM does all the work--I don't have to update a thing.

GDI+ Tearing! VB.NET

I have problem with the Graphic object. I have a loop which goes through some array and it gets the images from them and It draws them on a picture box. Every thing is fine but When I try to resize or draw another thing which is a little bit more heavy, Every thing start flashing Like when they're painting. I know it's too heavy to draw all that damn things ! but is there any way to avoid tearing?
Thanks.
Edit:
my code:
graphic.Clear(frmmain.Workspace.BackColor)
For i = 0 To mObjectsList.Count - 1
graphic.DrawImage(mObjectsList(i).oGraphic, mObjectsList(i).oX, mObjectsList(i).oY, mObjectsList(i).oWidth, mObjectsList(i).oHeight)
Next
graphic is a variable which I created it from my picturebox Graphic object
A picturebox doesn't have a Graphic object. Do not use its CreateGraphics() method. Whatever you draw through that stays on the screen for only a fraction of a second, barely making a blip. Use e.Graphics in the Paint event handler instead. That draws into the double-buffered bitmap. PictureBox always has its DoubleBuffered property set to true. That bitmap gets drawn when the Paint event completes. Which is why your objects flicker, they get overdrawn again by that bitmap.

"Z-Index" in WinForms

In CSS, we have a Property called z-index, what is the same in WinForms set for a Panel control to the "Z-Index?
WinForms has a z-order, but you can't access it as a number. Instead, every control has a BringToFront method and a SendToBack method, which move the control to the top of the z-order or to the bottom, respectively.
Not sure exactly why it was exposed this way, although you rarely encounter situations where either BringToFront or SendToBack don't provide what you need.
Update: I'm wrong, you can access the z-order directly via a method on the control's container's Controls collection. Here's a simple method that wraps it:
public void SetControlZOrder(Control ctrl, int z)
{
ctrl.Parent.Controls.SetChildIndex(ctrl, z);
}
I'm guessing they encapsulated this in BringToFront and SendToBack just to keep everything simple and easy to use. I applaud.
Update 2: I interpreted your comments to a different answer here to mean that you want to be able to take a control that is inside a panel and larger than the panel (so that part of it is hidden) and make it so that the control is in front of the panel and larger than it (so that you see the whole control).
You can do this by removing the control from the panel, shifting its position by the original panel's position, and adding it to the form's controls:
panel1.Controls.Remove(button1);
button1.Left += panel1.Left;
button1.Top += panel1.Top;
this.Controls.Add(button1);
The Left and Top shifts are necessary because the button's position was originally relative to the panel, and will now be relative to the form. The shifts keep it in the original virtual position, so it appears to come out of the panel.
You would then have to deal with putting it back in the panel, which is just a reverse of the above code.