How to Know if an Application's Window is Visible to the Eye on the Screen (Not like IsWindowVisible API Call)

  • Thread starter Thread starter Larry G. Robertson
  • Start date Start date
L

Larry G. Robertson

Guest
I have been trying to solve this problem for years.

Let's say that you launch an application, for example Notepad or Word using "New Process". Now in your code you are executing "Process.Start()". You wait a few seconds and the application finally appears on the screen. The amount of time you must wait depends on several factors how many apps are running; how much memory is available the speed of your processor and so on.

But what if for whatever reason you have some code that you want to execute only after the window is truly visible, not 3 seconds before it becomes visible (e.g. IsWindowVisible API).

The most popular solution that people give is one of a handful of API's that either by themselves or combined "should work" as you expect especially the Win32 API function "IsWindowVisible" which returns true if the window has a visible style. The problem with this solution is when you put the call in a loop it returns true several seconds before the window actually appears.

I have found the code that I'm giving you below to be very successful when I used in an application I wrote for the Visually Impaired that informs them with a sound effect that lets them know that a window is visible.

There is however an assumption:

  1. The user does not move or open any windows between the execution of Process.Start and the appearance of the window.

The program basically does the following:

  1. Draws a single red pixel in the center of the screen.
  2. Launches Notepad with Process.Start and centers the window on the screen.
  3. Goes into a Do Loop which continuously gets the color of that center red pixel until it is no longer red then breaks out of the loop and sounds a Beep.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' This application does the following:
' 1. Launches Notepad, if Notepad is already running it will be
' brought to the front and centered on the screen even if it
' was minimized. It works for any application.
' 2. It sounds a Beep when it is actually visible on the screen
' to the human eye, unlike the IsWindowVisible API which
' can return true several seconds before the window appears.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Imports System.Runtime.InteropServices

Public Class Form1
Public Structure RECT
Public left As Int32
Public top As Int32
Public right As Int32
Public bottom As Int32
End Structure
Public Const SM_CXSCREEN = &H0
Public Const SM_CYSCREEN = &H1
Public Const SWP_NOZORDER = &H4
Public Const SWP_NOSIZE = &H1
Public Const WM_SYSCOMMAND As Integer = &H112
Public Const SC_RESTORE = &HF120
Public Const RDW_INVALIDATE As Int32 = 1
Public Const RDW_ALLCHILDREN As Int32 = 128
Public Const RDW_FRAME As Int32 = 1024
Private mprocessNotepad As Process = Nothing
Private mintptrNotepadHandle As IntPtr = IntPtr.Zero

<DllImport("user32.dll", EntryPoint:="GetWindowRect")>
Private Shared Function GetWindowRect(ByVal hWnd As IntPtr, ByRef lpRect As RECT) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

<DllImport("user32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)>
Public Shared Function GetSystemMetrics(nIndex As Integer) As Integer
End Function

<DllImport("User32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Shared Function SetWindowPos(hWnd As IntPtr, hWndInsertAfter As IntPtr, X As Int16, Y As Int16, cx As Int16, cy As Int16, uFlags As UInt16) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

<DllImport("user32", EntryPoint:="SendMessageA", CharSet:=CharSet.Ansi, SetLastError:=True, ExactSpelling:=True)> Public Shared Function SendMessage(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByRef lParam As Integer) As Integer
End Function

<DllImport("user32.dll")>
Private Shared Function SetForegroundWindow(ByVal hWnd As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

<DllImport("user32.dll", SetLastError:=True)>
Private Shared Function SetActiveWindow(ByVal hWnd As IntPtr) As Integer
End Function

<DllImport("user32.dll", SetLastError:=True)>
Private Shared Function RedrawWindow(ByVal hWnd As IntPtr, lprcUpdate As Integer, hrgnUpdate As Integer, uFlags As UInt32) As Boolean
End Function


Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SetPixelCenter()
Dim c1 As Color = GetPixelCenter()
Console.WriteLine(c1)
If Not IsProcessRunning("notepad") Then
mprocessNotepad = New Process
mprocessNotepad.StartInfo.FileName = "c:\windows\notepad.exe"
mprocessNotepad.StartInfo.WindowStyle = ProcessWindowStyle.Normal
mprocessNotepad.Start()
mprocessNotepad.WaitForInputIdle()
Do While mprocessNotepad.MainWindowHandle = IntPtr.Zero
Loop
mintptrNotepadHandle = mprocessNotepad.MainWindowHandle
Else
Dim procs = Process.GetProcessesByName("notepad")
mprocessNotepad = procs(0)
mintptrNotepadHandle = mprocessNotepad.MainWindowHandle
End If
BringWindowToFront(mintptrNotepadHandle)
Dim c2 As Color = Nothing
'Wait for window to become visible
Do
c2 = GetPixelCenter()
Application.DoEvents()
Loop While c2 = Color.Red
' Notify visibility
Beep()
Console.WriteLine(c2)
End Sub

Private Sub CenterWindowOnScreen(pHwnd As IntPtr)
Dim rc As New RECT
GetWindowRect(pHwnd, rc)
Dim xPos As Integer = (GetSystemMetrics(SM_CXSCREEN) - rc.right) / 2
Dim yPos As Integer = (GetSystemMetrics(SM_CYSCREEN) - rc.bottom) / 2
SetWindowPos(pHwnd, 0, xPos, yPos, 0, 0, SWP_NOZORDER Or SWP_NOSIZE)
End Sub

Private Function GetPixelCenter() As Color
Dim xPos As Integer = GetSystemMetrics(SM_CXSCREEN) / 2
Dim yPos As Integer = GetSystemMetrics(SM_CYSCREEN) / 2
Dim pos As Point = New System.Drawing.Point(xPos, yPos)
Dim bitmap = New Bitmap(1, 1)
Dim objGraphics = Graphics.FromImage(bitmap)
objGraphics.CopyFromScreen(pos, New Point(0, 0), New Size(1, 1))
Return bitmap.GetPixel(0, 0)
End Function

Private Sub SetPixelCenter()
Dim xPos As Integer = (GetSystemMetrics(SM_CXSCREEN) - 1) / 2
Dim yPos As Integer = (GetSystemMetrics(SM_CYSCREEN) - 1) / 2
Dim objGraphics = Graphics.FromHwnd(New IntPtr(0))
Dim objPen As Pen = New Pen(Drawing.Color.Red, 1)
Dim objBrush = New SolidBrush(Color.Red)
objGraphics.DrawRectangle(objPen, xPos, yPos, 1, 1)
objGraphics.FillRectangle(objBrush, xPos, yPos, 1, 1)
End Sub

Private Sub BringWindowToFront(hwnd As IntPtr)
SendMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0) ' restore the minimize window
SetForegroundWindow(hwnd)
SetActiveWindow(hwnd)
CenterWindowOnScreen(hwnd)
RedrawWindow(hwnd, IntPtr.Zero, 0, RDW_FRAME Or RDW_INVALIDATE Or RDW_ALLCHILDREN)
End Sub

Private Function IsProcessRunning(pstrName As String) As Boolean
' Get the current process.
Dim currentProcess As Process = Process.GetCurrentProcess()
Dim arrayInstances As Process() = Process.GetProcessesByName(pstrName)
If arrayInstances.Count > 0 Then
Return True
End If
Return False
End Function

End Class

Continue reading...
 
Back
Top