|
-
Apr 9th, 2011, 07:06 AM
#1
Thread Starter
Fanatic Member
Drawing on the screen with an overlay Window
Hello all.
I have a bit of an issue here. I made a "Colour picker" dialog in my program with which you can pick the colour at the mouse.
It is a simple topmost form with an OnPaint handler used to draw a coloured border around the cursor:

It uses a transparency key to make the non-drawn parts of the form transparent.
It uses the following code to prevent the mouse from clicking through the screen:
Code:
Window.FromControl(Me).ExStyle(Window.ExStyles.Layered) = True
Window.FromControl(Me).ExStyle(Window.ExStyles.Transparent) = False
It sets Layered to True and Transparent to False. This works in Windows 7, but on a XP machine it fails.
When moving the mouse the event does not go to the forms' event handler and you can just click through.
For a screen region selector I simply made a screenshot, but I want the Colour Picker to remain updated.
(just in case you want to get the colour of an animated control)
How can I get this to work on a Windows XP machine?
Last edited by bergerkiller; Apr 9th, 2011 at 07:13 AM.
-
Apr 10th, 2011, 10:28 AM
#2
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
Anyone?
-
Apr 10th, 2011, 02:44 PM
#3
Re: Drawing on the screen with an overlay Window
You could layer another borderless form on top of or underneath the selector. Set its transparency to a low value, e.g 1%. It will be invisible, but will still catch click events instead of passing them through to the form underneath. Use the Owner/Owned relation to keep them together in the Z-order.
Incidentally, you may like to consider making the whole selector an Owned Form instead of Topmost; that will avoid the irritation of the selector staying on top when another application is activated.
BB
-
Apr 11th, 2011, 06:37 AM
#4
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
The issue is that this 1% transparency window will change the colors on the screen. For example, a white 1% opacity form would add 1% of 255 (3 or so) pixel value to all colors on the screen. The color selector must be 100% precise; so the "Top borderless form" must be 100% transparent yet clickable.
If it was that easy I would have done that already, but unfortunately it is not possible in my case. 
EDIT
BTW the greyish middle cross is a cross cursor I added in paint, it is not visible on the actual screenshot.
I use a DC wrapper to get the pixel values:
1. Get hDC of desktop
2. Use GetPixel API to get the color from the Hdc
3. Release the Hdc
I could monitor the cursor position and keydown state in a timer, but the messages would be redirected to the underlying screen.
For example, if you want to capture the color on a button it would click the button when "activating". (clicking the mouse)
Last edited by bergerkiller; Apr 11th, 2011 at 06:43 AM.
-
Apr 11th, 2011, 08:53 AM
#5
Re: Drawing on the screen with an overlay Window
 Originally Posted by bergerkiller
The issue is that this 1% transparency window will change the colors on the screen. For example, a white 1% opacity form would add 1% of 255 (3 or so) pixel value to all colors on the screen. The color selector must be 100% precise; so the "Top borderless form" must be 100% transparent yet clickable.
If it was that easy I would have done that already, but unfortunately it is not possible in my case.
EDIT
BTW the greyish middle cross is a cross cursor I added in paint, it is not visible on the actual screenshot.
I use a DC wrapper to get the pixel values:
I could monitor the cursor position and keydown state in a timer, but the messages would be redirected to the underlying screen.
For example, if you want to capture the color on a button it would click the button when "activating". (clicking the mouse)
I take it that means you are using ROP = SrcCopy|CaptureBLT to get the colour (if you used SrcCopy only or Graphics.CopyFromScreen the transparent layer would be "invisible"). Never mind, if you are capturing from a mouse click, you can switch the "invisible" form's opacity to 0% immediately before the capture and back to a minimum opacity afterwards. Then you will capture the pure colour, but the click still won't be passed through to the button underneath. The actual minimum to intercept mouse events is 1/255.
BB
-
Apr 11th, 2011, 08:58 AM
#6
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
That would cause flickering + lag, since the color is updated in a timer each 1/5 of a second. It would have to set/reset the transparency each time.
There is no way to make a 100% transparent form top most and selectable?
Here is the full code I use right now:
vb Code:
Imports System.Windows.Forms
Public Class ScreenColorPicker
Private _c As Color = Color.FromArgb(255, 255, 255, 255)
Private _p As New Point(0, 0)
Private _dc As API.DC
Public ReadOnly Property SelectedColor() As Color
Get
Return _c
End Get
End Property
Private Sub ScreenColorPicker_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Window.FromControl(Me).ExStyle(Window.ExStyles.Layered) = True
Window.FromControl(Me).ExStyle(Window.ExStyles.Transparent) = False
Me._dc = Window.GetDesktop().GetDC()
Timer1.Start()
End Sub
Private Sub ScreenColorPicker_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
Window.FromControl(Me).ExStyle = 0
_dc.Dispose()
Timer1.Stop()
End Sub
Private Sub ScreenColorPicker_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
Dim p As New Pen(_c)
p.Width = 20
e.Graphics.DrawRectangle(p, _p.X - (50 - p.Width / 2), _p.Y - (50 - p.Width / 2), 100 - p.Width, 100 - p.Width)
e.Graphics.DrawRectangle(Pens.Black, _p.X - 50 + p.Width, _p.Y - 50 + p.Width, 100 - p.Width * 2, 100 - p.Width * 2)
e.Graphics.DrawRectangle(Pens.Black, _p.X - 50, _p.Y - 50, 100, 100)
End Sub
Private Sub ScreenColorPicker_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
INVA()
_p = e.Location
INVB()
End Sub
Private r As Region
Private Sub INVA()
Dim rect As New Rectangle(_p.X - 50, _p.Y - 50, 100, 100)
rect.Inflate(1, 1)
r = New Region(rect)
End Sub
Private Sub INVB()
Dim rect As New Rectangle(_p.X - 50, _p.Y - 50, 100, 100)
rect.Inflate(1, 1)
r.Union(rect)
Me.Invalidate(r)
End Sub
Private Sub ScreenColorPicker_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left Then
_c = _dc.GetPixel(e.X, e.Y)
Me.DialogResult = Windows.Forms.DialogResult.OK
Else
Me.DialogResult = Windows.Forms.DialogResult.Cancel
End If
Me.Close()
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim nc As Color = _c.GetPixel(Cursor.Position)
If nc <> _c Then
_c = nc
INVA()
INVB()
End If
End Sub
End Class
DC:
vb Code:
Public Class DC
Implements System.IDisposable
Private Declare Function BitBlt Lib "gdi32" (ByVal hdc As IntPtr, ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hdcSrc As IntPtr, ByVal nXSrc As Integer, ByVal nYSrc As Integer, ByVal dwRop As CopyPixelOperation) As Boolean
Private Declare Function GetPixel Lib "gdi32" Alias "GetPixel" (ByVal hdc As IntPtr, ByVal X As Int32, ByVal Y As Int32) As Int32
Private Declare Function SetPixel Lib "gdi32" Alias "SetPixel" (ByVal hdc As IntPtr, ByVal X As Int32, ByVal Y As Int32, ByVal crColor As UInt32) As UInt32
Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Boolean
Private Declare Function GetClipBox Lib "gdi32" (ByVal hdc As IntPtr, ByRef lprc As WRECT) As Integer
Private Declare Function GetClipRgn Lib "gdi32" (ByVal hdc As IntPtr, ByRef hrgn As Region) As Integer
Private Declare Function GetRandomRgn Lib "gdi32" (ByVal hdc As IntPtr, ByRef hrgn As Region, ByVal inum As Integer) As Integer
Private managedtype As DCSource
Private managedobject As Object
Private managedgraphics As Graphics
Private hdc As IntPtr
Private _disposed As Boolean
Public Enum DCSource
None
Handle
Graphics
End Enum
Sub New(ByVal g As Graphics)
Me.managedobject = g
Me.managedtype = DCSource.Graphics
Me.hdc = g.GetHdc
End Sub
Sub New(ByVal hwnd As IntPtr, ByVal hdc As IntPtr)
Me.managedobject = hwnd
Me.managedtype = DCSource.Handle
Me.hdc = hdc
End Sub
Sub New()
Me.managedobject = Nothing
Me.managedtype = DCSource.None
End Sub
Public ReadOnly Property Source() As DCSource
Get
Return Me.managedtype
End Get
End Property
Public Sub SetPixel(ByVal X As Integer, ByVal Y As Integer, ByVal C As Color)
SetPixel(Me.hdc, X, Y, C.ToArgb)
End Sub
Public Function GetPixel(ByVal X As Integer, ByVal Y As Integer) As Color
Return ColorTranslator.FromOle(GetPixel(Me.hdc, X, Y))
End Function
Public Property Pixel(ByVal X As Integer, ByVal Y As Integer) As Color
Get
Return GetPixel(X, Y)
End Get
Set(ByVal value As Color)
SetPixel(X, Y, value)
End Set
End Property
Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
If Me._disposed = False Then
Select Case managedtype
Case DCSource.Handle
ReleaseDC(CType(Me.managedobject, IntPtr), Me.hdc)
Case DCSource.Graphics
CType(Me.managedobject, Graphics).ReleaseHdc(Me.hdc)
End Select
If managedgraphics IsNot Nothing Then managedgraphics.Dispose()
Me.managedgraphics = Nothing
Me.managedobject = Nothing
Me.managedtype = Nothing
Me.hdc = Nothing
Me._disposed = True
End If
End Sub
Public Overloads Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class
Last edited by bergerkiller; Apr 11th, 2011 at 09:04 AM.
-
Apr 11th, 2011, 11:10 AM
#7
Re: Drawing on the screen with an overlay Window
 Originally Posted by bergerkiller
That would cause flickering + lag, since the color is updated in a timer each 1/5 of a second. It would have to set/reset the transparency each time.
I measured opacity switching between 1/255 and 0 for a form measuring 150 x 170 pixels and averaged 1.46 milliseconds per switch. Form Opacity is hardware accelerated (I have read) so I find this a bit disappointing, but it looks like it should be feasible. I see no visible flickering for opacity changes in the subvisible range. Ideally of course this should be checked on a range of different systems.
This is the test code I used:
Code:
Dim sw As Stopwatch = Stopwatch.StartNew
For i As Integer = 0 To 1000
Form2.Opacity = 1/ 255
Form2.Opacity = 0
Next
MessageBox.Show((sw.ElapsedTicks / Stopwatch.Frequency).ToString("N5") & "milliseconds per switch")
The test seems to give consistent results. Switching between 0.99 and 0 also took about 1.5 ms but switching between 1 and 0 took about 10 ms.
BB
edit: the measured times are for two-way switching.
Last edited by boops boops; Apr 11th, 2011 at 11:37 AM.
-
Apr 11th, 2011, 11:52 AM
#8
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
Well with lag I mean redrawing the screen. You can set the opacity to 0, get the pixel and set the opacity back to 1, but I doubt the screen has redrawn (invalidated and updated) the screen before the GetPixel command comes into view. I have seen a program (on screen drawing program) that does not change the color of the screen at all when ran. How did they do it?
-
Apr 11th, 2011, 01:49 PM
#9
Re: Drawing on the screen with an overlay Window
 Originally Posted by bergerkiller
Well with lag I mean redrawing the screen. You can set the opacity to 0, get the pixel and set the opacity back to 1, but I doubt the screen has redrawn (invalidated and updated) the screen before the GetPixel command comes into view. I have seen a program (on screen drawing program) that does not change the color of the screen at all when ran. How did they do it? 
I don't see why you can't use the method I suggested with an "invisible" form. Switching between opacity 1 and opacity 0 does indeed take more time as I mentioned in my previous post, and I guess that is because the pixels have to be updated. But switching between anything less than 1 and 0 is far quicker. As I understand it, a partly transparent form behaves like a layered window. It occupies a separate hardware layer in the graphics subsystem and the pixels underneath it do not have to be updated because they are in a different layer.
I'm sure I could learn a lot by studying your DC class, but if your purpose is to pick a colour from the screen I think I would do it in a much simpler way:
Code:
Public Function GetColorAtCursor() As Color
Dim bmp As New Bitmap(1, 1)
Using g As Graphics = Graphics.FromImage(bmp)
g.CopyFromScreen(MousePosition, Point.Empty, bmp.Size)
End Using
Return bmp.GetPixel(0, 0)
End Function
Since only 1 pixel is involved, both CopyFromScreen and GetPixel will be practically instantaneous. The drawback of Graphics.CopyFromScreen is that is doesn't do CaptureBlt so it can't see pixels with Alpha less than 255. If you don't need to capture from non-opaque windows then you can use the above code and you won't even need to switch the opacity of the invisible form. Otherwise, you could try my own version of CopyFromScreen:
Code:
Imports System
Imports System.Runtime.InteropServices
Public Class GrabPixels
Public Shared Function CopyFromScreen(ByVal upperLeftSource As Point, ByVal upperLeftDestination As Point, ByVal blockRegionSize As Size) As Bitmap
Dim hDesk As IntPtr = W32.GetDesktopWindow()
Dim hSrce As IntPtr = W32.GetWindowDC(hDesk)
Dim hDest As IntPtr = W32.CreateCompatibleDC(hSrce)
Dim hBmp As IntPtr = W32.CreateCompatibleBitmap(hSrce, blockRegionSize.Width, blockRegionSize.Height)
Dim hOldBmp As IntPtr = W32.SelectObject(hDest, hBmp)
W32.BitBlt(hDest, upperLeftDestination.X, upperLeftDestination.Y, blockRegionSize.Width, blockRegionSize.Height, hSrce, _
upperLeftSource.X, upperLeftSource.Y, CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
Dim bmp As Bitmap = Bitmap.FromHbitmap(hBmp)
W32.SelectObject(hDest, hOldBmp)
W32.DeleteObject(hBmp)
W32.DeleteDC(hDest)
W32.ReleaseDC(hDesk, hSrce)
Return bmp
End Function
End Class
Win32 is a module with Interop methods but I assume you can use your own. Using this method is even simpler than the Graphics version above:
Code:
Public Function GetColorAtCursor2() As Color
Dim bmp = New Bitmap(GrabPixels.CopyFromScreen(MousePosition, Point.Empty, New Size(1, 1)))
Return bmp.GetPixel(0, 0)
End Function
BB
-
Apr 11th, 2011, 02:24 PM
#10
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
CopyFromScreen uses BitBlt in the background, they are the same. CopyFromScreen performs a BitBlt operation between the desktop Hdc and the current Hdc (Graphics). I do not want to store it all in a new Bitmap and use GetPixel on that, since it would be the exact same as GetPixel with the source Hdc. Only difference is that the Hdc is then stored onto a new (temprary) surface. 
Full code of the DC class:
vb Code:
Public Class DC
Implements System.IDisposable
Private Declare Function BitBlt Lib "gdi32" (ByVal hdc As IntPtr, ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hdcSrc As IntPtr, ByVal nXSrc As Integer, ByVal nYSrc As Integer, ByVal dwRop As CopyPixelOperation) As Boolean
Private Declare Function GetPixel Lib "gdi32" Alias "GetPixel" (ByVal hdc As IntPtr, ByVal X As Int32, ByVal Y As Int32) As Int32
Private Declare Function SetPixel Lib "gdi32" Alias "SetPixel" (ByVal hdc As IntPtr, ByVal X As Int32, ByVal Y As Int32, ByVal crColor As UInt32) As UInt32
Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Boolean
Private Declare Function GetClipBox Lib "gdi32" (ByVal hdc As IntPtr, ByRef lprc As WRECT) As Integer
Private Declare Function GetClipRgn Lib "gdi32" (ByVal hdc As IntPtr, ByRef hrgn As Region) As Integer
Private Declare Function GetRandomRgn Lib "gdi32" (ByVal hdc As IntPtr, ByRef hrgn As Region, ByVal inum As Integer) As Integer
Private managedtype As DCSource
Private managedobject As Object
Private managedgraphics As Graphics
Private hdc As IntPtr
Private _disposed As Boolean
Public Enum DCSource
None
Handle
Graphics
End Enum
Sub New(ByVal g As Graphics)
Me.managedobject = g
Me.managedtype = DCSource.Graphics
Me.hdc = g.GetHdc
End Sub
Sub New(ByVal hwnd As IntPtr, ByVal hdc As IntPtr)
Me.managedobject = hwnd
Me.managedtype = DCSource.Handle
Me.hdc = hdc
End Sub
Sub New()
Me.managedobject = Nothing
Me.managedtype = DCSource.None
End Sub
Public ReadOnly Property Source() As DCSource
Get
Return Me.managedtype
End Get
End Property
Public Sub SetPixel(ByVal X As Integer, ByVal Y As Integer, ByVal C As Color)
SetPixel(Me.hdc, X, Y, C.ToArgb)
End Sub
Public Function GetPixel(ByVal X As Integer, ByVal Y As Integer) As Color
Return ColorTranslator.FromOle(GetPixel(Me.hdc, X, Y))
End Function
Public Property Pixel(ByVal X As Integer, ByVal Y As Integer) As Color
Get
Return GetPixel(X, Y)
End Get
Set(ByVal value As Color)
SetPixel(X, Y, value)
End Set
End Property
Public ReadOnly Property ClipBox() As Rectangle
Get
Dim w As WRECT
GetClipBox(Me.hdc, w)
Return w.Value
End Get
End Property
Public ReadOnly Property ClipSize() As Size
Get
Return Me.ClipBox.Size
End Get
End Property
Public Property Handle() As IntPtr
Get
Return Me.hdc
End Get
Set(ByVal value As IntPtr)
Me.hdc = value
End Set
End Property
Public ReadOnly Property Disposed() As Boolean
Get
Return Me._disposed
End Get
End Property
Public Sub Draw(ByVal source As Graphics, ByVal destrect As Rectangle, ByVal sourceoffset As Point, ByVal operation As CopyPixelOperation)
Dim hdc As IntPtr = source.GetHdc
Draw(hdc, destrect, sourceoffset, operation)
source.ReleaseHdc(hdc)
End Sub
Public Sub Draw(ByVal source As DC, ByVal destrect As Rectangle, ByVal sourceoffset As Point, ByVal operation As CopyPixelOperation)
Draw(source.hdc, destrect, sourceoffset, operation)
End Sub
Public Sub Draw(ByVal sourcehdc As IntPtr, ByVal destrect As Rectangle, ByVal sourceoffset As Point, ByVal operation As CopyPixelOperation)
BitBlt(Me.hdc, destrect.X, destrect.Y, destrect.Width, destrect.Height, sourcehdc, sourceoffset.X, sourceoffset.Y, operation)
End Sub
Public Sub CopyTo(ByVal destination As Graphics, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
CopyTo(destination, Me.ClipBox, operation)
End Sub
Public Sub CopyTo(ByVal destination As Graphics, ByVal destrect As Rectangle, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
CopyTo(destination, destrect, New Point(0, 0), operation)
End Sub
Public Sub CopyTo(ByVal destination As Graphics, ByVal destrect As Rectangle, ByVal sourceoffset As Point, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
Dim hdc As IntPtr = destination.GetHdc
CopyTo(hdc, destrect, sourceoffset, operation)
destination.ReleaseHdc(hdc)
End Sub
Public Sub CopyTo(ByVal destination As DC, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
CopyTo(destination.hdc, Me.ClipBox, New Point(0, 0), operation)
End Sub
Public Sub CopyTo(ByVal destination As DC, ByVal destrect As Rectangle, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
CopyTo(destination.hdc, destrect, New Point(0, 0), operation)
End Sub
Public Sub CopyTo(ByVal destination As DC, ByVal destrect As Rectangle, ByVal sourceoffset As Point, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
CopyTo(destination.hdc, destrect, sourceoffset, operation)
End Sub
Public Sub CopyTo(ByVal desthdc As IntPtr, ByVal destrect As Rectangle, ByVal sourceoffset As Point, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
BitBlt(desthdc, destrect.X, destrect.Y, destrect.Width, destrect.Height, Me.hdc, sourceoffset.X, sourceoffset.Y, operation)
End Sub
Public Function ToBitmap(Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt) As Bitmap
Return ToBitmap(Me.ClipSize, operation)
End Function
Public Function ToBitmap(ByVal Resolution As Size, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt) As Bitmap
ToBitmap = New Bitmap(Resolution.Width, Resolution.Height)
Dim g As Graphics = Graphics.FromImage(ToBitmap)
Dim ghdc As IntPtr = g.GetHdc
BitBlt(ghdc, 0, 0, Resolution.Width, Resolution.Height, Me.hdc, 0, 0, operation)
g.ReleaseHdc(ghdc)
g.Dispose()
End Function
Public ReadOnly Property Graphics() As Graphics
Get
If Me.managedgraphics Is Nothing Then Me.managedgraphics = Graphics.FromHdc(Me.hdc)
Return Me.managedgraphics
End Get
End Property
Public Shared Function FromHandle(ByVal handle As IntPtr) As DC
FromHandle = New DC()
FromHandle.hdc = handle
End Function
Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
If Me._disposed = False Then
Select Case managedtype
Case DCSource.Handle
ReleaseDC(CType(Me.managedobject, IntPtr), Me.hdc)
Case DCSource.Graphics
CType(Me.managedobject, Graphics).ReleaseHdc(Me.hdc)
End Select
If managedgraphics IsNot Nothing Then managedgraphics.Dispose()
Me.managedgraphics = Nothing
Me.managedobject = Nothing
Me.managedtype = Nothing
Me.hdc = Nothing
Me._disposed = True
End If
End Sub
Public Overloads Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class
The class is simply an extension to handle operations directly on Graphics or Hdc objects, since the Graphics class does not have GetPixel or BitBlt functions.
-
Apr 15th, 2011, 04:22 PM
#11
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
So there is no possible way to make a 100% transparent canvas on top of the screen?
-
Apr 16th, 2011, 05:58 AM
#12
Re: Drawing on the screen with an overlay Window
 Originally Posted by bergerkiller
So there is no possible way to make a 100% transparent canvas on top of the screen? 
Not if you want the transparent parts to raise mouse events.
By the way, there is another easy way to solve the problem with your color selector without using an "invisible" form. Place the selector form off-centre relative to the mouse position so that the click hotspot is over an opaque part. When the user clicks it, pick the color located at the form's actual (transparent) centre.
BB
-
Apr 16th, 2011, 06:42 AM
#13
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
I tried that before, but when you move the mouse fast enough it will leave the forms' opaque center and the form then just sits there. Just wondering...is it possible to "drag" the form around? Like, when the form starts, you make the form go into "drag mode", since then mouse events go to the main form any ways, as if you are moving a scrollbar. I could send a mouse down event on the opaque part, but I am not sure of how to catch the "after dragging". Plus I think sending a mouse down event is a bit "open for bugs".
I'll try and see if I can make the form move by dragging it around.
EDIT
Aw yes! That works great. Only problem is I have to click and drag the form before it works. Anyone knows how to activate this dragging without sending a mouse down event?
Code:
Public Class ScreenColorSelect
Private _c As Color = Color.White
Private _dc As API.DC
Private Sub ScreenColorSelect_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
Dim p As New Pen(_c)
p.Width = 20
e.Graphics.DrawRectangle(p, p.Width / 2, p.Width / 2, 100 - p.Width, 100 - p.Width)
e.Graphics.DrawRectangle(Pens.Black, p.Width, p.Width, 100 - p.Width * 2, 100 - p.Width * 2)
e.Graphics.DrawRectangle(Pens.Black, 0, 0, 99, 99)
End Sub
Private Sub ScreenColorSelect_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
If e.Button = Windows.Forms.MouseButtons.Left Then
Me.Location = Point.Subtract(Cursor.Position, New Point(50, 50))
End If
End Sub
Public ReadOnly Property SelectedColor() As Color
Get
Return _c
End Get
End Property
Private Sub ScreenColorSelect_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
_dc = Window.GetDesktop.GetDC
Timer1.Start()
End Sub
Private Sub ScreenColorSelect_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
_dc.Dispose()
Timer1.Stop()
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
_c = _dc.GetPixel(Cursor.Position)
Me.Invalidate()
End Sub
Private Sub ScreenColorSelect_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseUp
If e.Button = Windows.Forms.MouseButtons.Left Then
Me.DialogResult = Windows.Forms.DialogResult.OK
_c = _dc.GetPixel(Cursor.Position)
Else
Me.DialogResult = Windows.Forms.DialogResult.Cancel
End If
Me.Close()
End Sub
End Class
Last edited by bergerkiller; Apr 16th, 2011 at 07:05 AM.
-
Apr 16th, 2011, 07:10 AM
#14
Re: Drawing on the screen with an overlay Window
The jerky dragging is caused by updating the selector form's location from its own MouseMove event. Instead, use a timer to set the form's location relative to MousePosition. Of course you can drag instead; just check MouseButtons = MouseButtons.Left. You don't "send" a mouse down event; Windows sends it and you handle it. BB
EDIT: now I see your own last edit. I'm always so darned slow with these thing I see you have sorted out the dragging using Cursor.Position instead of e.Location; very nice. If you don't want to drag (and have the form behave like a cursor) just don't test for the mouse button. Then the selector will behave like a custom cursor.
BB
Last edited by boops boops; Apr 16th, 2011 at 07:17 AM.
-
Apr 16th, 2011, 07:13 AM
#15
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
No I fixed that lag by setting DoubleBuffered to True; now I got one problem, though. I have to activate this dragging when the form is Shown. Right now you have to "Mouse down" on the opaque part and the form then follows the cursor nicely. The MouseUp handles the closing. The mouse could be up while the form is displayed, if the mouse state does not change it is no issue; you will just have to click (one time down and then up to close it).
Instead of having to click in the control, I want this dragging to occur as soon you enter the form, as if the mouse clicked it. I used this (sending a mouse event) to do it, but it cause the cursor to change to a waitcursor and nothing happens:
Code:
Private Sub ScreenColorSelect_Shown(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Shown
Window.FromControl(Me).SendMouse(New Point(0, 0), Windows.Forms.MouseButtons.Left, Window.MouseEvent.Down)
End Sub
Uses:
Code:
Public Structure MouseCommand
Sub New(ByVal X As UShort, ByVal Y As UShort, Optional ByVal Button As MouseButtons = MouseButtons.None, Optional ByVal EventType As MouseEvent = MouseEvent.Move)
Me.EventType = EventType
Me.Button = Button
Me.Modifier = MK.NONE
Me.x = X
Me.y = Y
End Sub
Sub New(ByVal Location As Point, Optional ByVal Button As MouseButtons = MouseButtons.None, Optional ByVal EventType As MouseEvent = MouseEvent.Move)
Me.EventType = EventType
Me.Button = Button
Me.Modifier = MK.NONE
Me.x = Location.X
Me.y = Location.Y
End Sub
Public EventType As MouseEvent
Public Button As MouseButtons
Public x As UShort
Public y As UShort
Public Modifier As MK
Public Function Post(ByVal hwnd As IntPtr) As Boolean
Dim msg1 As msg = 0
Dim msg2 As msg = 0
Dim add As Boolean = EventType >= 0 And EventType <= 2
Select Case Button
Case MouseButtons.Left
msg1 = msg.LBUTTONDOWN
Case MouseButtons.Middle
msg1 = msg.MBUTTONDOWN
Case MouseButtons.Right
msg1 = msg.RBUTTONDOWN
Case Else
add = False
msg1 = msg.MOUSEMOVE
End Select
If EventType = MouseEvent.DoubleClick Then
msg2 = msg1 + MouseEvent.Up
msg1 = msg1 + MouseEvent.DoubleClick
ElseIf EventType = MouseEvent.Click Then
msg2 = msg1 + MouseEvent.Up
msg1 = msg1 + MouseEvent.Down
ElseIf add Then
msg1 += EventType
End If
Dim success As Boolean = True
If msg1 <> 0 AndAlso Not API.PostMessage(hwnd, msg1, Modifier, New API.DWORD(Me.x, Me.y)) Then success = False
If msg2 <> 0 AndAlso Not API.PostMessage(hwnd, msg2, Modifier, New API.DWORD(Me.x, Me.y)) Then success = False
Return success
End Function
Public Enum MK As Integer
'No keys are down
NONE = &H0
'The CTRL key is down.
CONTROL = &H8
'The left mouse button is down.
LBUTTON = &H1
'The middle mouse button is down.
MBUTTON = &H10
'The right mouse button is down.
RBUTTON = &H2
'The SHIFT key is down.
SHIFT = &H4
'The first X button is down.
XBUTTON1 = &H20
'The second X button is down.
XBUTTON2 = &H40
End Enum
Private Enum msg As UInteger
MOUSEACTIVATE = &H21
MOUSEMOVE = &H200
LBUTTONDOWN = 513
LBUTTONUP = 514
LBUTTONDBLCLK = 515
RBUTTONDOWN = 516
RBUTTONUP = 517
RBUTTONDBLCLK = 518
MBUTTONDOWN = 519
MBUTTONUP = 520
MBUTTONDBLCLK = 521
End Enum
End Structure
Public Enum MouseEvent
Down = 0
Up = 1
DoubleClick = 2
Click = 3
Move = 4
End Enum
EDIT
SendInput did the job (GlobalInput is a class I made around SendInput and others to handle global key/mouse events):
Code:
Private Sub ScreenColorSelect_Shown(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Shown
GlobalInput.KeyPressed(Keys.LButton) = False
Me.Location = Cursor.Position
GlobalInput.KeyPressed(Keys.LButton) = True
Me.Location = Point.Subtract(Cursor.Position, New Point(50, 50))
End Sub
I guess this is fixed, although I still think sending a mouse command is a bit of a hack. Any way to send some command to the control that does this dragging as well?
And yes, it is "supposed" to act like a colored cursor.
Last edited by bergerkiller; Apr 16th, 2011 at 07:50 AM.
-
Apr 16th, 2011, 07:47 AM
#16
Re: Drawing on the screen with an overlay Window
See the edit to my previous post. It would be so much simpler to position the selector form off-centre relative to the actual mouse position and pick the pixel at its centre. BB
-
Apr 16th, 2011, 07:51 AM
#17
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
I already do that, right? The form has a size of 100x100 and it's position is set to be in the middle of the cursor, like the screenshot of my first post.
If not, please explain what you mean with "off centre"
-
Apr 16th, 2011, 08:17 AM
#18
Re: Drawing on the screen with an overlay Window
Since your selector appears to have a non-transparent border, you could just do Me.Position = Cursor.Position in the MouseMove event handler. Then pick the pixel color at point (50, 50) when the user clicks. Wait, I see the cross in the first image is an actual cursor or custom cursor. So just hide the real cursor with Cursor.Hide and draw the cross yourself.
BB
EDIT: when drawing your own cross in the middle of the form, make the middle pixel transparent with the transparency key, of course.
Last edited by boops boops; Apr 16th, 2011 at 08:37 AM.
-
Apr 16th, 2011, 08:41 AM
#19
Re: Drawing on the screen with an overlay Window
[sorry, accidental double posting]
-
Apr 16th, 2011, 08:46 AM
#20
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
Ah you mean making the entire form like that. Well the problem with that is again getting the pixel. If I were to use something like that it would be impossible to get the pixel, since the drawn cross is in front of it. That is why I use the cursor as centre marking. There is no real problem now though, it all works nicely and the cursor controls the position of the form. The form is set to be 50/50 offset to the left/top of the cursor, so the cursor is in the middle of the form. Changing the "custom cursor form" to be not in the middle would make it harder than it has to be.
Current code:
Code:
Public Class ScreenColorSelect
Private _c As Color = Color.White
Private _dc As API.DC
Private Sub ScreenColorSelect_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
Dim p As New Pen(_c)
p.Width = 20
e.Graphics.DrawRectangle(p, p.Width / 2, p.Width / 2, 100 - p.Width, 100 - p.Width)
e.Graphics.DrawRectangle(Pens.Black, p.Width, p.Width, 100 - p.Width * 2, 100 - p.Width * 2)
e.Graphics.DrawRectangle(Pens.Black, 0, 0, 99, 99)
End Sub
Private Sub ScreenColorSelect_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
If e.Button = Windows.Forms.MouseButtons.Left Then
Me.Location = Point.Subtract(Cursor.Position, New Point(50, 50))
End If
End Sub
Public ReadOnly Property SelectedColor() As Color
Get
Return _c
End Get
End Property
Private Sub ScreenColorSelect_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
_dc = Window.GetDesktop.GetDC
Timer1.Start()
End Sub
Private Sub ScreenColorSelect_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
_dc.Dispose()
Timer1.Stop()
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
_c = _dc.GetPixel(Cursor.Position)
Me.Invalidate()
End Sub
Private Sub ScreenColorSelect_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseUp
If e.Button = Windows.Forms.MouseButtons.Left Then
Me.DialogResult = Windows.Forms.DialogResult.OK
_c = _dc.GetPixel(Cursor.Position)
Else
Me.DialogResult = Windows.Forms.DialogResult.Cancel
End If
Me.Close()
End Sub
Private Sub ScreenColorSelect_Shown(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Shown
GlobalInput.KeyPressed(Keys.LButton) = False
Me.Location = Cursor.Position
GlobalInput.KeyPressed(Keys.LButton) = True
Me.Location = Point.Subtract(Cursor.Position, New Point(50, 50))
End Sub
End Class
It does not matter if the dragged cursor is outside of the form, the MouseMove event is still passed to the control. Think of the ScrollBar, you can move it when dragging outside of the form with no problems. I only need a replacement for:
Code:
GlobalInput.KeyPressed(Keys.LButton) = False
Me.Location = Cursor.Position
GlobalInput.KeyPressed(Keys.LButton) = True
Me.Location = Point.Subtract(Cursor.Position, New Point(50, 50))
'//*under GlobalInput.KeyPressed()*//
If key = Keys.LButton Then
If value = True Then inputs(0) = New API.Input(0, 0, API.Input.MouseEvent.LeftDown)
If value = False Then inputs(0) = New API.Input(0, 0, API.Input.MouseEvent.LeftUp)
'//*Input structure*//
<StructLayout(LayoutKind.Explicit)> Public Structure Input
Sub New(ByVal dx As Integer, ByVal dy As Integer, ByVal flags As MouseEvent, Optional ByVal data As MouseData = 0)
If (flags And MouseEvent.MoveScreen) = MouseEvent.MoveScreen Then
'convert to screen absolute
dx = dx / My.Computer.Screen.Bounds.Width * UShort.MaxValue
dy = dy / My.Computer.Screen.Bounds.Height * UShort.MaxValue
flags -= MouseEvent.MoveScreen
If Not (flags And MouseEvent.MoveAbsolute) = MouseEvent.MoveAbsolute Then
flags += MouseEvent.MoveAbsolute
End If
End If
Me.type = dwType.MOUSE
With Me.mi
.dx = dx
.dy = dy
.time = 0
.dwExtraInfo = 0
.mouseData = data
.dwFlags = flags
End With
End Sub
<FieldOffset(0)> Private type As dwType
<FieldOffset(4)> Private mi As MOUSEINPUT
<FieldOffset(4)> Private ki As KEYBDINPUT
<FieldOffset(4)> Private hi As HARDWAREINPUT
Private Structure MOUSEINPUT
Public dx As Integer
Public dy As Integer
Public mouseData As Integer
Public dwFlags As MOUSEEVENTF
Public time As Integer
Public dwExtraInfo As IntPtr
Public Enum MOUSEEVENTF As Integer
MOVE = &H1
LEFTDOWN = &H2
LEFTUP = &H4
RIGHTDOWN = &H8
RIGHTUP = &H10
MIDDLEDOWN = &H20
MIDDLEUP = &H40
XDOWN = &H80
XUP = &H100
WHEEL = &H800
VIRTUALDESK = &H4000
ABSOLUTE = &H8000
End Enum
End Structure
Private Structure KEYBDINPUT
Public wVk As Short
Public wScan As Short
Public dwFlags As KEYEVENTF
Public time As Integer
Public dwExtraInfo As IntPtr
Public Enum KEYEVENTF As Integer
KEYDOWN = 0
EXTENDEDKEY = 1
KEYUP = 2
UNICODE = 4
SCANCODE = 8
End Enum
End Structure
Private Structure HARDWAREINPUT
Public uMsg As Integer
Public wParamL As Short
Public wParamH As Short
End Structure
Private Enum dwType As Integer
MOUSE = 0
KEYBOARD = 1
HARDWARE = 2
End Enum
Public Enum MouseEvent
''' <summary>
''' Moves the cursor with the offset dx and dy
''' </summary>
Move = &H1
''' <summary>
''' Places the cursor at the screen coordinates dx and dy
''' </summary>
MoveScreen = &H1000
''' <summary>
''' Places the cursor at the screen using dx and dy ranging from 0 (left/top) to 65535 (right/bottom)
''' </summary>
MoveAbsolute = Move Or &H8000
''' <summary>
''' Places the cursor at the desktop using dx and dy ranging from 0 (left/top) to 65535 (right/bottom)
''' </summary>
MoveVirtualDesktop = MoveAbsolute Or &H4000
''' <summary>
''' Press the left mouse button
''' </summary>
LeftDown = &H2
''' <summary>
''' Release the left mouse button
''' </summary>
LeftUp = &H4
''' <summary>
''' Press the right mouse button
''' </summary>
RightDown = &H8
''' <summary>
''' Release the right mouse button
''' </summary>
RightUp = &H10
''' <summary>
''' Press the middle mouse button
''' </summary>
MiddleDown = &H30
''' <summary>
''' Release the middle mouse button
''' </summary>
MiddleUp = &H40
''' <summary>
''' Press the XButton specified in the data member
''' </summary>
XDown = &H80
''' <summary>
''' Release the XButton specified in the data member
''' </summary>
XUp = &H100
''' <summary>
''' Scroll the vertical mousewheel with the delta count of the data member
''' </summary>
MouseVWheel = &H800
''' <summary>
''' Scroll the horizontal mousewheel with the delta count of the data member
''' </summary>
MouseHWheel = &H20E
End Enum
Public Enum MouseData As Integer
XButton1 = 1
XButton2 = 2
Wheel_Delta_Backward = -120
Wheel_Delta_Forward = 120
Wheel_Delta_Left = -120
Wheel_Delta_Right = 120
End Enum
Public Enum KeyEvent
Down = 0
Up = 2
End Enum
Private Declare Function SendInput Lib "user32.dll" (ByVal cInputs As Integer, ByVal pInputs() As Input, ByVal cbSize As Integer) As Integer
Public Shared Function Send(ByVal ParamArray inputs() As Input) As Boolean
Return SendInput(inputs.Count, inputs, Marshal.SizeOf(GetType(Input)))
End Function
Public Function Send() As Boolean
Return Send(Me)
End Function
End Structure
Which makes the cursor start dragging the form. I believe sending input is not the best method around. Let's say I try to move the bar on a scrollbar around.
Last edited by bergerkiller; Apr 16th, 2011 at 08:56 AM.
-
Apr 16th, 2011, 09:28 AM
#21
Re: Drawing on the screen with an overlay Window
 Originally Posted by bergerkiller
Ah you mean making the entire form like that. Well the problem with that is again getting the pixel. If I were to use something like that it would be impossible to get the pixel, since the drawn cross is in front of it. That is why I use the cursor as centre marking.
Just leave the middle pixel of the cross transparent and draw the arms of the cross. It will look fine like that.
On testing, it's better to make the cursor position a small distance (e.g. 30 pixels) inside the form. This prevents the real cursor from flickering during fast dragging.
BB
-
Apr 16th, 2011, 09:40 AM
#22
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
Yeah, as a total replacement cursor I would have to do that yes, thanks. 
But, since the actual position of the form does not necessarily have to match the cursor position, I don't mind a minor delay when moving the cursor. Even then, the delay is rather small.
-
May 17th, 2011, 04:55 PM
#23
Thread Starter
Fanatic Member
Re: Drawing on the screen with an overlay Window
Time for a bump...
I decided to go a different road, something unrelated to this, but the same idea.
I have this external program and I want to make another topmost form above it.
Any pressed keys or mouse events that this topmost screen obtains are redirected to the underlying program, thereby keeping track of what is going on.
For this I need a 100% transparent form, 99% would either make the program underneath lighter or darker, or worse, coloured. On my OS (Windows 7 32 bit) I have no problems with this; I can simply click on the transparent window. But I fear that on other machines the OS will click through the form (and click on the program below). A colorkey could be used as well, but the same clickable rule must count there.
I see I have to trick the OS into believing a form is present at the location, whilst drawing a completely transparent form. Anyone has any idea of how to accomplish this? I tried some regular commands like setting layered to true, they do not work out.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|