About Chris Expertise I can answer pretty much any question relating to VB.NET and its use in a Windows environment. I specialize in ASP.NET web development and MSSQL database access.
Experience I have over 5 years of industry experience using VB.NET and other .NET technologies for web and database development.
Education/Credentials I have some college education, but does it really matter in this field of work?
Expert: Chris Date: 8/26/2007 Subject: Filling in pixels
Question I'm trying to find a way to fill in specific pixels on my desktop. I'm using VS2005. Is there a way to, for exmaple, fill in the middle pixels of my desktop all green when the program is running? I tried the PSet function numerous times to no avail, then began searching and read about API functions and got completely lost. I am not a newbie persay, but have never heard of API functions in VB before.
Thanks
Answer Sounds like you're either a convert from VB6 or you found some help online referencing how to do things in VB6. In VB.NET, you should be using the Graphics object to do drawing. If by desktop you mean the background of your form, that's easy. You'll need to write an event handler for the form's Paint event. In VS2005 it's easiest to do that by double-clicking the background of the form to get to the form's Load event handler, then in the dropdown at the top right of the editor, pick the Paint event.
To draw something in the form's background here, you'll need to get ahold of the form's Graphics object, which is provided in the arguments to the event handler. You can then draw things like rectangles, lines, import images of various formats, draw text with any font on the system, and so forth.
An example, that'll draw a 10x10 pixel red square at the top-left of the form...
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
e.Graphics.FillRectangle(Brushes.Red, New Rectangle(0, 0, 10, 10))
End Sub
Now, if what you want to do is draw to the user's desktop, that's going to be a bit harder. You'll have to access a windows API, and yes, you can do this easily now from VB.NET, just as easily in fact as in C# (and unlike VB6, VB.NET compiles to the exact same IL code as C#, and has identical performance).
You've got two options...
First, you can draw on top of every other program, but because they'll all refresh their displays, you'll have to keep redrawing. This is slow, and causes flicker (which can be fixed for the most part by using dobule buffering, but I'm not going to get into that). If this is your plan, you'll need two API calls - GetDC and ReleaseDC - to do the drawing. Make sure to add an Imports System.Runtime.InteropServices to the top of your form's code, then just below the Class declaration, you'll add the two API references, like so:
Public Class MyForm
Private Declare Function GetDC Lib "user32.dll" (ByVal hwnd As IntPtr) As IntPtr
Private Declare Function ReleaseDC Lib "user32.dll" (ByVal hwnd As IntPtr, ByVal hdc As IntPtr) As IntPtr
'...
End Class
Then in some event handler (like a timer's Tick event), you'll use those calls like this:
' Get a handle to the desktop window
Dim hdc As IntPtr = GetDC(IntPtr.Zero)
Try
' Get the graphics object for the desktop window
Dim g As Graphics = Graphics.FromHdc(hdc)
Try
' Fill in a rectangle
g.FillRectangle(Brushes.Red, 10, 10, 100, 100)
Finally
' Dispose the graphics object, failing to do this will cause system instability
g.Dispose()
End Try
Finally
' Release the handle to the desktop window, again, failing to do this is a bad idea.
ReleaseDC(IntPtr.Zero, hdc)
End Try
This probably wasn't exactly what you were thinking of, though. A much better, though more complex solution is to first have your form find the Desktop window, and set itself to be a child window of that. This will allow it to display only when the user shows the desktop, and it will not appear in the taskbar. Then you'll probably want to set its FormBorderStyle property to None, to remove the title bar and so forth. Finally, you'll want to either set it to be transparent to some extent so that you can see it still, but you can also see what's behind it on the desktop. And optionally, you might want to make it a click-through window, so that users can just see it, but not interact with it. This is probably what you were thinking of, otherwise it'd make the things behind it on the desktop unclickable.
So, to put it all together, you'll first need a code module to hold the API calls:
'--[ User32Wrappers.vb ]--
Imports System.Runtime.InteropServices
Module User32Wrappers
Public Enum GWL As Integer
ExStyle = -20
End Enum
Public Enum WS_EX As Integer
Transparent = &H20
Layered = &H80000
End Enum
Public Enum LWA As Integer
ColorKey = &H1
Alpha = &H2
End Enum
' These three calls are used to set a window's opacity, and to enable click-through.
<DllImport("user32.dll", EntryPoint:="GetWindowLong")> _
Public Function GetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As GWL) As Integer
End Function
<DllImport("user32.dll", EntryPoint:="SetWindowLong")> _
Public Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As GWL, ByVal dwNewLong As WS_EX) As Integer
End Function
<DllImport("user32.dll", EntryPoint:="SetLayeredWindowAttributes")> _
Public Function SetLayeredWindowAttributes(ByVal hWnd As IntPtr, ByVal crKey As Integer, ByVal alpha As Byte, ByVal dwFlags As LWA) As Boolean
End Function
' And these two are to find the Desktop window and set a window to be a child of another.
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Function FindWindow(<MarshalAs(UnmanagedType.LPTStr)> ByVal lpClassName As String, <MarshalAs(UnmanagedType.LPTStr)> ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll")> _
Function SetParent(ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As IntPtr
End Function
End Module
'--[ end User32Wrappers.vb ]--
And a Form to test these API calls out:
'--[ DeskPixel2.vb ]--
Public Class DeskPixel2
Private _InitialStyle As Integer
Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None
Me.Opacity = 0.5
Dim hwndf As IntPtr = Me.Handle
Dim hwndParent As IntPtr = FindWindow("ProgMan", Nothing)
SetParent(hwndf, hwndParent)
Me.Location = New Point(100, 100)
Me.Size = New Size(100, 100)
' Grab the Extended Style information for this window and store it.
_InitialStyle = GetWindowLong(Me.Handle, GWL.ExStyle)
' Set this window to Transparent (to the mouse that is!)
SetFormToTransparent()
' Just for giggles, set this window to stay on top of all others so we
' can see what's happening.
Me.TopMost = True
End Sub
Private Sub SetFormToTransparent()
' This creates a new Extended Style for our window, which takes effect
' immediately upon being set, that combines the initial style of our window
' (saved in Form.Load) and adds the ability to be Transparent to the mouse.
' Both Layered and Transparent must be turned on for this to work AND have
' the window render properly!
SetWindowLong(Me.Handle, GWL.ExStyle, _InitialStyle Or WS_EX.Layered Or WS_EX.Transparent)
' Don't forget to set the Alpha for the window or else you won't be able
' to see the window! Possible values are 0 (visibily transparent)
' to 255 (visibly opaque). I'll set it to 70% visible here for show.
' The second parameter is 0, because we're not using a ColorKey!
SetLayeredWindowAttributes(Me.Handle, 0, 255 * 0.7, LWA.Alpha)
End Sub
Private Sub SetFormToOpaque()
' Turn off the Transparent Extended Style.
SetWindowLong(Me.Handle, GWL.ExStyle, _InitialStyle Or WS_EX.Layered)
' Set the Alpha back to 100% opaque.
SetLayeredWindowAttributes(Me.Handle, 0, 255, LWA.Alpha)
End Sub
Private Sub DeskPixel2_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
e.Graphics.FillRectangle(Brushes.Red, New Rectangle(0, 0, 100, 100))
End Sub
End Class
'--[ end DeskPixel2.vb ]--
If you wanted the window on the desktop to have a titlebar so it's movable, you could set its FormBorderStyle to FixedToolWindow or some other setting than None.