Draw over other controls

P

Phil

I would like to draw some text which will be in front of any other controls,
but without obscuring them completely. If I use DrawString in the form's
Paint handler the text is always behind, not in front of the other controls.
In VB6 I would have simply used a label control and set it to transparent,
but this doesn't work in .NET, the background of a label can be set to
'transparent', but this does not show the other controls through, instead it
fills in the background with the form background. I'm sure there must be a
simple way to acheive this, but I can't find it at the moment.
Any ideas appreciated.
Thanks
Phil.
(VB2005Express)
 
L

Linda Liu [MSFT]

Hi Phil,

Yes, WinForm controls support transparent back color. But it doesn't mean
the transparent control will show the controls under it.

To get what you want in VB.NET, I suggest that you create a WinForm and
paint the text you like on this form and show the form over the main form
of your application. We can set the form's TransparencyKey property to its
back color and its FormBorderStyle property to None, so that only the text
drawn in the form will be shown. We also need to set the ShowInTaskbar
property of the form to false in order to make the form invisible in the
taskbar.

One problem is that we need to keep the main form active when we show the
above form to display texts. To solve this problem, we could catch the
WM_NCACTIVATE message in the WndProc method within the main form and if the
value of the wParam parameter is 0, send another WM_NCACTIVATE message with
the wParam of value 1 to the main form.

The following is a sample code. It requires you to add two Buttons on the
form. When you click the Button1, a transparent form is shown displaying
"hello wold" over the Form1. When you click the Button2, the transparent
form is closed.

Public Class Form1

Private WM_NCACTIVATE As Integer = &H86
Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As
IntPtr, ByVal msg As Integer, ByVal wParam As Integer, ByVal lparam As
Integer) As Integer

Dim frm As Form
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
If (frm Is Nothing) Then
frm = New Form()
End If
frm.FormBorderStyle = Windows.Forms.FormBorderStyle.None
frm.TransparencyKey = frm.BackColor
AddHandler frm.Paint, AddressOf frm_Paint
frm.Width = Me.Width
frm.Height = Me.Height
frm.TopMost = True
frm.ShowInTaskbar = False
frm.Show()
frm.Location = Me.PointToScreen(New Point(0, 0))

End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
frm.Close()
frm = Nothing
End Sub

Private Sub frm_Paint(ByVal sender As Object, ByVal e As PaintEventArgs)
e.Graphics.DrawString("hello world", Me.Font, Brushes.YellowGreen,
15, 15)
End Sub

Private Sub Form1_Move(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Me.Move
If Not (frm Is Nothing) Then
frm.Location = Me.PointToScreen(New Point(0, 0))
End If
End Sub

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
If (m.Msg = WM_NCACTIVATE) Then
If (m.WParam.ToInt32() = 0) Then
SendMessage(Me.Handle, WM_NCACTIVATE, 1, 0)
End If
End If
End Sub
End Class

Hope this helps.
If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
P

Phil

Yes, WinForm controls support transparent back color. But it doesn't mean
the transparent control will show the controls under it.

Yes. Microsoft obviously have a different definition of the word transparent
than the rest of us :)
To get what you want in VB.NET, I suggest that you create a WinForm and
paint the text you like on this form and show the form over the main form
of your application. We can set the form's TransparencyKey property to its
back color and its FormBorderStyle property to None, so that only the text
drawn in the form will be shown. We also need to set the ShowInTaskbar
property of the form to false in order to make the form invisible in the
taskbar.

I had thought of that, it seems like a bit of a kludgy workaround to me. I'm
sure this can't be very efficient. There must be a simpler way surely?
One problem is that we need to keep the main form active when we show the
above form to display texts. To solve this problem, we could catch the
WM_NCACTIVATE message in the WndProc method within the main form and if
the
value of the wParam parameter is 0, send another WM_NCACTIVATE message
with
the wParam of value 1 to the main form.

The following is a sample code. It requires you to add two Buttons on the
form. When you click the Button1, a transparent form is shown displaying
"hello wold" over the Form1. When you click the Button2, the transparent
form is closed.

Public Class Form1

Private WM_NCACTIVATE As Integer = &H86
Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As
IntPtr, ByVal msg As Integer, ByVal wParam As Integer, ByVal lparam As
Integer) As Integer

Dim frm As Form
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
If (frm Is Nothing) Then
frm = New Form()
End If
frm.FormBorderStyle = Windows.Forms.FormBorderStyle.None
frm.TransparencyKey = frm.BackColor
AddHandler frm.Paint, AddressOf frm_Paint
frm.Width = Me.Width
frm.Height = Me.Height
frm.TopMost = True
frm.ShowInTaskbar = False
frm.Show()
frm.Location = Me.PointToScreen(New Point(0, 0))

End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
frm.Close()
frm = Nothing
End Sub

Private Sub frm_Paint(ByVal sender As Object, ByVal e As
PaintEventArgs)
e.Graphics.DrawString("hello world", Me.Font, Brushes.YellowGreen,
15, 15)
End Sub

Private Sub Form1_Move(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Me.Move
If Not (frm Is Nothing) Then
frm.Location = Me.PointToScreen(New Point(0, 0))
End If
End Sub

Protected Overrides Sub WndProc(ByRef m As
System.Windows.Forms.Message)
MyBase.WndProc(m)
If (m.Msg = WM_NCACTIVATE) Then
If (m.WParam.ToInt32() = 0) Then
SendMessage(Me.Handle, WM_NCACTIVATE, 1, 0)
End If
End If
End Sub
End Class

Thanks, I'll give this a go.
Cheers,
Phil
 
P

Phil

Thanks, I'll give this a go.
Cheers,
Phil

Unfortunately because the form is TopMost, this means not only does it
display in front of the controls on my existing form, it also displays in
front of all other windows, including other applications that might be
running.
Also setting ShowInTaskbar = False stops an icon showing in the task bar,
but an icon for the form is still shown when pressing Alt+Tab to switch
windows.
 
L

Linda Liu [MSFT]

Hi Phil,

Thank you for your prompt reply!

I work out another solution to your problem, i.e. draw the text to the
screen. To do this, we need to get the handle of the screen and then draw a
text to the screen.

I will illustrate my new solution with an example. It requires you to add
two Buttons on the form. When you click the Button1, a text "hello world"
appears on the screen. If the form is covered by another form and this form
is shown again, the text "hello world" previously drawn on the screen will
be erased. If you click the Button2, the text "hello world" is erased.

You can modify the sample code to accomplish what you want, if necessary.

The following is the sample code.

Public Class Form1
Declare Auto Function GetDC Lib "user32.dll" (ByVal hwnd As IntPtr) As
IntPtr
Declare Auto Function ReleaseDC Lib "user32.dll" (ByVal hwnd As IntPtr,
ByVal hdc As IntPtr) As Integer

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim hScreenDC As IntPtr = GetDC(IntPtr.Zero)
Dim g As Graphics = Graphics.FromHdc(hScreenDC)
Using (g)
Dim screenPt As Point = Me.PointToScreen(New Point(30, 50))
g.DrawString("hello world", Me.Font, Brushes.Thistle,
screenPt.X, screenPt.Y)
End Using

ReleaseDC(IntPtr.Zero, hScreenDC)
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
Me.Refresh()
End Sub
End Class

Please try my new solution to see if it meets your requirement and let me
know the result.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
P

Phil

Please try my new solution to see if it meets your requirement and let me
know the result.

Thanks, I tried it. It looks like this approach may be promising, but there
are still a couple of problems.
If I switch away from the form and back again, the text dissappears,
although I can get around this by placing the code in the Form Paint event.
Also any control that redraws itself (e.g. a button when you hover over with
the mouse) will draw itself over the text. Another problem is if there is
another window partly obscuring the form, the text can still get written
over the top of this other window. I wonder if it is possible to clip the
output of drawstring to the currently visible area of the form?

Thanks again for your help. It is very much appreciated.
Phil.
 
L

Linda Liu [MSFT]

Hi Phil,

Thank you for your reply!
Also any control that redraws itself (e.g. a button when you hover over
with the mouse) will draw itself over the text.

We can solve this problem by handling the child control's Invalidated event
and draw the text on the screen again.
Another problem is if there is another window partly obscuring the form,
the text can still get written over the top of this other window.

I think we could redraw the text in the Activated event handler of the
form.

The following is the modified sample code.

Public Class Form1
Declare Auto Function GetDC Lib "user32.dll" (ByVal hwnd As IntPtr) As
IntPtr
Declare Auto Function ReleaseDC Lib "user32.dll" (ByVal hwnd As IntPtr,
ByVal hdc As IntPtr) As Integer

Private showFnt As Font
Private showText As String
Private showPt As Point
Private invalidateChildControls As New List(Of Control)
Private textRect As New Rectangle

Private Sub Form1_Activated(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Me.Activated
If (showText <> "") Then
DrawText(showText, showFnt, showPt)
End If
End Sub

Private Function GetChildControls(ByVal rect As Rectangle) As List(Of
Control)
Dim ctrls As New List(Of Control)
For Each ctrl As Control In Me.Controls
Dim result As Rectangle = Rectangle.Intersect(ctrl.Bounds, rect)
If (Not result.IsEmpty) Then
ctrls.Add(ctrl)
End If
Next
Return ctrls
End Function

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
DrawText("hello world", Me.Font, New Point(30, 50))
End Sub

Private Sub DrawText(ByVal text As String, ByVal fnt As Font, ByVal pt
As Point)
Me.Refresh()
If (showText = "") Then
Dim textSize As Size = TextRenderer.MeasureText(text, fnt)
textRect.X = pt.X
textRect.Y = pt.Y
textRect.Width = textSize.Width
textRect.Height = textSize.Height
showText = text
showFnt = fnt
showPt = pt
invalidateChildControls = GetChildControls(textRect)
For Each ctrl As Control In invalidateChildControls
AddHandler ctrl.Invalidated, AddressOf
ChildControlInvalidated
Next
End If

Dim hScreenDC As IntPtr = GetDC(IntPtr.Zero)
Dim g As Graphics = Graphics.FromHdc(hScreenDC)
Using (g)
Dim screenPt As Point = Me.PointToScreen(pt)
g.DrawString(text, Me.Font, Brushes.Red, screenPt.X, screenPt.Y)
End Using

ReleaseDC(IntPtr.Zero, hScreenDC)
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
showText = ""
For Each ctrl As Control In invalidateChildControls
RemoveHandler ctrl.Invalidated, AddressOf
ChildControlInvalidated
Next
invalidateChildControls.Clear()
Me.Refresh()
End Sub

Private Sub ChildControlInvalidated(ByVal sender As Object, ByVal e As
System.Windows.Forms.InvalidateEventArgs)
If (showText <> "") Then
DrawText(showText, showFnt, showPt)
End If
End Sub
End Class

Hope it is what you want.


Sincerely,
Linda Liu
Microsoft Online Community Support
 
P

Phil

Also any control that redraws itself (e.g. a button when you hover over
with the mouse) will draw itself over the text.

We can solve this problem by handling the child control's Invalidated
event
and draw the text on the screen again.

I had thought of that, but didn't want to have to add a separate handler for
every child control on the form. I see your code identifies all the child
controls at runtime, so that gets around this problem.
the text can still get written over the top of this other window.

I think we could redraw the text in the Activated event handler of the
form.

I had thought of that, but a form can become visible without becoming
activated. e.g. if another window is moved across it. This is why I put the
code into the Paint event, but then, it can still draw over the other
window, if the form is still partially covered.

I'll try your new code, and see how it works.

Thanks,
Phil.
 
L

Linda Liu [MSFT]

Hi Phil,

How about the problem now?

If there's any question, please feel free to let me know.

Thank you for using our MSDN Managed Newsgroup Support Service!

Sincerely,
Linda Liu
Microsoft Online Community Support
 
P

Phil

Hi Phil,
How about the problem now?

Hello,

Sorry for not getting back to you, been busy with other stuff.
I did try your updated version, and it is better, but as I suspected it does
still suffer from the problem with the text not showing when the form is
visible but inactive. I've got a couple of different uses for this, it'll
probably be OK for one of them, and there may be another way around it for
the other application.
I did think I might have a go at building this into a custom
TransparentLabel control which I could use for both but that may have to
wait until I have a bit more time.
I did have one idea: As you are now identifying all the controls that will
be overwritten, I wonder if it might be better to get a graphics object for
each of these and write to that rather than writing to the screen. This
could be done in the paint event, without the danger of overwriting other
windows. I haven't had chance to try this out yet though.

Cheers,
Phil.
 
L

Linda Liu [MSFT]

Hi Phil,

Thank you for your reply!
I wonder if it might be better to get a graphics object for each of these
and write to that rather than writing to the screen.

If the text is going to be painted on one child control fully, you can get
the graphics object for this child control and draw the text on this
control.

However, if the text couldn't be painted on one control fully, e.g. the
text is long and would be painted in an area that covers several child
controrls, it's difficult to draw the text using the graphics object of
each related child control, because each graphics object should only paint
a part of the text and the part is difficult to determine.


Sincerely,
Linda Liu
Microsoft Online Community Support
 
P

Phil

I wonder if it might be better to get a graphics object for each of these
and write to that rather than writing to the screen.

If the text is going to be painted on one child control fully, you can get
the graphics object for this child control and draw the text on this
control.

Yes, that will be the case for one particular application I have in mind,
but in another case, my text will likely cover a number of other controls,
However, if the text couldn't be painted on one control fully, e.g. the
text is long and would be painted in an area that covers several child
controrls, it's difficult to draw the text using the graphics object of
each related child control, because each graphics object should only paint
a part of the text and the part is difficult to determine.

I haven't tried this yet, but presumably I could just paint the text to all
of them (and to the form). Presumably anything I try to paint outside of the
control's visible rectangle, would not be shown anyway, so this could work.
 
L

Linda Liu [MSFT]

Hi Phil,

I am reviewing this post and would like to know the status of this issue.

If you have any question, please feel free to let me know.

Thank you for using our MSDN Managed Newsgroup Support Service!

Sincerely,
Linda Liu
Microsoft Online Community Support
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top