I have an application which is designed to run full screen constantly. This works fine normally, however, when things run in the background, for example, an antivirus update this can bring that window above my app. Thats fine because I can use things like:
SetForegroundWindow
ShowWindow
SwitchToThisWindow
All of which allow me to bring my application back to the front. However, inside the application is a hidden text box which when the application loads is focussed. When I use one of the pInvoke calls above whilst the application is brought back to front, the focus is still on the existing application.
I am currently struggling with the best way of giving focus back to the control.
I could use Control.FromHandle but seems fairly complicated to get the controls I need and offer focus if a specific tab page is at the front. Is there a better way, any thoughts / ideas welcome.
I was running this on a Windows 10 LTSB unit and as previously mentioned SetForegroundWindow and Show functions were not working along with many other functions I found on pInvoke. I managed to select the correct process and bring it to the forefront if something else takes its place to the top. The issue was that it would never activate no matter what I tried.
In the end, I implemented the following code which checks every 5 seconds, if my app is not the foremost and not maximised then minimise / maximise the window and this reactivates and refocuses the app:
Public Declare Function FindWindow Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Public Declare Function ShowWindowAsync Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As IntPtr
Private Declare Function GetWindowPlacement Lib "user32.dll" (ByVal hWnd As IntPtr, ByRef lpwndpl As WINDOWPLACEMENT) As Boolean
Private Declare Function GetForegroundWindow Lib "user32.dll" () As IntPtr
<Serializable>
Friend Structure WINDOWPLACEMENT
Public length As Integer
Public flags As Integer
Public showCmd As ShowWindowCommands
Public ptMinPosition As System.Drawing.Point
Public ptMaxPosition As System.Drawing.Point
Public rcNormalPosition As System.Drawing.Rectangle
End Structure
Friend Enum ShowWindowCommands
Hide = 0
Normal = 1
Minimized = 2
Maximized = 3
End Enum
Private Async Function CheckCurrentApp() As Task
Try
' Try and locate the core process
Dim coreHandle = FindWindow(Nothing, "Name of window")
If coreHandle = IntPtr.Zero Then
' Can't find the core. Exit here.
Exit Try
End If
' Get information about the Core window
Dim currentWindowInfo As WINDOWPLACEMENT
GetWindowPlacement(coreHandle, currentWindowInfo)
' If the core is not the foreground window or isn't maximised then send a minimise (6) and maximise (3) request.
' Activate functions in user32 don't work - I spent a day trying to make it so. I could get the foreground window as the core but the input would
' remain in a different application.
If coreHandle <> GetForegroundWindow() OrElse currentWindowInfo.showCmd <> ShowWindowCommands.Maximized Then
ShowWindowAsync(coreHandle, 6)
ShowWindowAsync(coreHandle, 3)
End If
Catch ex As Exception
' DO SOMETHING WITH THE EXCEPTION.
End Try
Await Task.Delay(TimeSpan.FromSeconds(5))
Await CheckCurrentApp()
End Function
Related
I created an on click button event, which is supposed to move my popup form to the top left corner of the screen. The function that I used was:
Private Sub Command1_Click()
DoCmd.MoveSize(0 ,0)
End Sub
I noticed that if my popup form is positioned on my second monitor it will send the popup form to the top left corner of my primary monitor.
Is there a way to send the form to the top left corner of whichever monitor the form is being opened in?
I tried a different idea where I will use a function to use the ".Move" property of the form. I came up with this:
Private Sub Command1_Click()
Form.Move(0, 0)
End Sub
This didn't work either, as it seems that the "0, 0" coordinates are relative to wherever the Access Window is positioned on the screen and not the top left corner of the monitor.
Is this a limitation of Access VBA or is it doable using some other technique?
While this seems trivial, unfortunately, it isn't, and we're going to need to use a fair amount of WinAPI. That makes it really hard for beginners.
We need a couple of things:
We need to be able to determine on which monitor the form is
We need to be able to determine where that monitor is in the "virtual screen" (think about positioning monitors relative to eachother)
We need to be able to determine the size of the current window in pixels
We need to be able to position the form on the "virtual screen".
For that, we need a couple of declarations. These are best kept on separate modules, but if you're 100% sure they will only be used on this form, they can go on the form too.
First, the type and value declarations:
'https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
Public Type RECT
left As Long
top As Long
right As Long
bottom As Long
End Type
'https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
Public Type MONITORINFO
cbSize As Long
rcMonitor As RECT
rcWork As RECT
dwFlags As Long
End Type
'Either look this one up by Googling, or create a C++ program that references winuser.h and print it
Public Const MONITOR_DEFAULTTONEAREST = &H2
Then, the function declarations:
'https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfow
Public Declare PtrSafe Function GetMonitorInfoW Lib "User32.dll" (ByVal hMonitor As LongPtr, ByRef lpmi As MONITORINFO) As Boolean
'https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
Public Declare PtrSafe Function MonitorFromWindow Lib "User32.dll" (ByVal hWnd As LongPtr, ByVal dwFlags As Long) As LongPtr
'https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-movewindow
Public Declare PtrSafe Function MoveWindow Lib "User32.dll" (ByVal hWnd As LongPtr, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As Boolean) As Boolean
'https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect
Public Declare PtrSafe Function GetWindowRect Lib "User32.dll" (ByVal hWnd As LongPtr, ByRef lpRect As RECT) As Boolean
And then, on the form, putting it all to work:
Private Sub Command0_Click()
Dim mi As MONITORINFO
Dim monitor As LongPtr
Dim myrect As RECT
'Get the current size and position of the window
GetWindowRect Me.hWnd, myrect
'Determine which monitor it is on
monitor = MonitorFromWindow(Me.hWnd, MONITOR_DEFAULTTONEAREST)
'Make sure WinAPI knows the size of the MONITORINFO struct we're working with
mi.cbSize = LenB(mi)
'Get the monitor info
GetMonitorInfoW monitor, mi
'Move the window to the top right, keep width and height equal to the current values
MoveWindow Me.hWnd, mi.rcMonitor.left, mi.rcMonitor.top, myrect.right - myrect.left, myrect.bottom - myrect.top, True
End Sub
Unfortunately, that's quite a lot more code and more complicated concepts than DoCmd.MoveSize(0 ,0), but I do not know of a simpler approach. VBA doesn't really have any support for multiple monitors, so you'll often have to go to WinAPI to account for them.
We have an application that may under some circumstances launch Outlook. It gets an Outlook.Explorer object (oWindow As Outlook.Explorer) and calls its .Activate() procedure.
Up to that point all is hunky dory, but then I want to bring the Explorer object to the foreground, and we call this procedure:
Public Shared Sub BringToFore(ByVal oWindow As Object)
Dim oFoo As IOleWindow
Dim hWnd As IntPtr
oFoo = TryCast(oWindow, IOleWindow)
If Not (oFoo Is Nothing) Then
Try
oFoo.GetWindow(hWnd)
Catch ex As Exception
End Try
End If
Try
If hWnd.ToInt32 <> IntPtr.Zero.ToInt32 Then
Try
If IsIconic(hWnd) Then
ShowWindow(hWnd, SW_RESTORE)
End If
SetForegroundWindow(hWnd)
Catch ex As System.Exception
End Try
Else
End If
Catch ex As Exception
End Try
End Sub
IOleWindow is defined as follows:
<ComImport(), Guid("00000114-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
Private Interface IOleWindow
''' <summary>
''' Returns the window handle to one of the windows participating in in-place activation
''' (frame, document, parent, or in-place object window).
''' </summary>
''' <param name="phwnd">Pointer to where to return the window handle.</param>
Sub GetWindow(<System.Runtime.InteropServices.Out()> ByRef phwnd As IntPtr)
''' <summary>
''' Determines whether context-sensitive help mode should be entered during an
''' in-place activation session.
''' </summary>
''' <param name="fEnterMode"><c>true</c> if help mode should be entered;
''' <c>false</c> if it should be exited.</param>
Sub ContextSensitiveHelp(<[In](), MarshalAs(UnmanagedType.Bool)> ByVal fEnterMode As Boolean)
End Interface
and the usual declarations
Private Const SW_RESTORE As Integer = 9
Private Declare Auto Function IsIconic Lib "user32" (ByVal hWnd As IntPtr) As Boolean
Private Declare Auto Function SetForegroundWindow Lib "user32" (ByVal hwnd As IntPtr) As Long
Private Declare Auto Function ShowWindow Lib "user32" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As IntPtr
The BringToFore procedure works fine most of the time. And sometimes the calling application - a WPF application - just hangs. The Event Viewer records an AppHangB1 and the application crashes.
Is there anything I can do in the BringToFore procedure to prevent this happening? Any idea which of these are causing the problem? TryCast(oWindow, IOleWindow), oFoo.GetWindow(hWnd), IsIconic(hWnd), ShowWindow(hWnd, SW_RESTORE) or SetForegroundWindow(hWnd)? (I personally suspect it may be SetForegroundWindow, to be honest). If so, is there anything I can do in the code? Some condition to check? If this condition is true then don't try to do that ..... sort of thing? I'd rather not go as far as just abandoning the idea of setting the Explorer to the foreground; in that circumstance Outlook may appear "behind" my application and some users may just not realise that something has happened.... if you catch my drift.
Thanks
Pino
Windows would prevent you from using SetForegroundWindow if the window belongs to a process other than the active one. You'd need to call AttachThreadInput first. Here is the function (Delphi):
function ForceForegroundWindow(hWnd: THandle): BOOL;
var
hCurWnd: THandle;
begin
hCurWnd := GetForegroundWindow;
AttachThreadInput(
GetWindowThreadProcessId(hCurWnd, nil),
GetCurrentThreadId, True);
Result := SetForegroundWindow(hWnd);
AttachThreadInput(
GetWindowThreadProcessId(hCurWnd, nil),
GetCurrentThreadId, False);
end;
I think this issue would require more debugging. I found a similar issue posted here quite a few years back with a suggestion which might be useful to get to the root cause.
I have a few excel projects which utilize userforms. Those userforms have some code which uses Windows API calls to modify their style. An example of this can be found here:
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare PtrSafe Function DwmSetWindowAttribute Lib "dwmapi" (ByVal hwnd As Long, ByVal attr As Integer, ByRef attrValue As Integer, ByVal attrSize As Integer) As Long
Private Declare PtrSafe Function DwmExtendFrameIntoClientArea Lib "dwmapi" (ByVal hwnd As Long, ByRef NEWMARGINS As MARGINS) As Long
Private UFSHADOW As Long
Private Type MARGINS
leftWidth As Long
rightWidth As Long
topHeight As Long
bottomHeight As Long
End Type
Sub all_userForms_AddShadow(frm As Object)
'Sub adds a shadow
Dim MARGINS As MARGINS
UFSHADOW = FindWindow("ThunderDFrame", vbNullString) 'Create a new Window
DwmSetWindowAttribute UFSHADOW, 2, 2, 4 'DWMAPI
'Determine Margins
With MARGINS
.rightWidth = 1
.leftWidth = 1
.topHeight = 1
.bottomHeight = 1
End With
DwmExtendFrameIntoClientArea UFSHADOW, MARGINS 'DWMAPI
'Resize
frm.Width = frm.Width - 1
frm.Height = frm.Height - 1
End Sub
The issue is that on certain clients, this will compile fine, but the result will not be displayed when the userform is initialized. I believe this is because on some clients, the windows setting "Enable Desktop Composition" is disabled by default and unable to be modified. A workaround I plan on using is to test whether or not Desktop Composition is enabled and if it is not, I will not call the sub.
My issue is that I cannot figure out how to test this. In the remarks section of this link https://msdn.microsoft.com/en-us/library/windows/desktop/aa969524(v=vs.85).aspx describes what should be returned if the DwmSetWindowAttribute function fails: DWM_E_COMPOSITIONDISABLED. I have tried setting this function equal to a few variable types, but it will not work.
Examples:
Desktop Composition Disabled
Desktop Composition Enabled
Any Suggestions? Thanks
Edit: In response to Mat's Mug's questions:
No error is thrown, it simply just does not draw the shadow.
You probably did not get the intended result as there are a few other API functions I call in relation to the "Add Shadow" sub which turn of the window caption and another which turns off the border. I can post those as well, but would make this post quite long.
I am a bit new to using windows API functions, I don't quite know your comments on the IF conditionals and VB Signatures, but I am researching it now..
As far as the bitness go, it's very likely that this tool will be accessed on both 32 and 64 bit OS. update.. I have just tested on both versions, my local machine has 64bit OS, the problem version has 32bit
I am developing an application which opens and reads an XML document previously embedded in a PowerPoint presentation, or a Word document. In order to read this object (xmlFile as Object) I have to do:
xmlFile.OLEFormat.DoVerb 1
This opens the package object, and I have another subroutine that gets the open instance of Notepad.exe, and reads its contents in to ADODB stream.
An example of this procedure is available on Google Docs:
XML_Test.pptm.
During this process there is a few seconds window where the Notepad.exe gains focus, and an inadvertent keystroke may cause undesired results or error reading the XML data.
I am looking for one of two things:
Either a method to prevent the user from inadvertently inputting (via keyboard/mouse/etc) while this operation is being performed. Preferably something that does not take control of the user's machine like MouseKeyboardTest subroutine, below. Or,
A better method of extracting the XML data into a string variable.
For #1: this is the function that I found, which I am leery of using. I am wary of taking this sort of control of the users system. ##Are there any other methods that I might use?##
Private Declare Function BlockInput Lib "USER32.dll" (ByVal fBlockIt As Long) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub MouseKeyboardTest() 'both keyboard and mouse blocked
BlockInput True ' Turns off Keyboard and Mouse
' Routine goes here
Sleep 5000 ' Optional coding
BlockInput False ' Turns on Keyboard and Mouse
End Sub
For #2: Some background, but the issue seems to be the inability to extract the embedded object reliably using any method other than DoVerb 1. Since I am dealing with an unsaved document in an application (Notepad) that is immune to my VBA skillz, this seems to be the only way to do this. Full background on that, here:
Extracting an OLEObject (XML Document) from PowerPoint VBA
As you correctly guessed in the comment above that taking the focus away from notepad will solve your problem. The below code does exactly that.
LOGIC:
A. Loop through the shape and get it's name. In your scenario it would be something like Chart Meta XML_fbc9775a-19ea-.txt
B. Use APIs like FindWindow, GetWindowTextLength, GetWindow etc to get the handle of the notepad window using partial caption.
C. Use the ShowWindow API to minimize the window
Code (tested in VBA-Powerpoint)
Paste this code in a module in the above PPTM
Private Declare Function FindWindow Lib "User32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetWindowText Lib "User32" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "User32" Alias _
"GetWindowTextLengthA" (ByVal hWnd As Long) As Long
Private Declare Function GetWindow Lib "User32" (ByVal hWnd As Long, _
ByVal wCmd As Long) As Long
Private Declare Function ShowWindow Lib "User32" (ByVal hWnd As Long, _
ByVal nCmdShow As Long) As Long
Private Const GW_HWNDNEXT = 2
Private Const SW_SHOWMINIMIZED = 2
Sub Sample()
Dim shp As Shape
Dim winName As String
Dim Ret As Long
For Each shp In ActivePresentation.Slides(1).Shapes
If shp.Type = msoEmbeddedOLEObject Then
winName = shp.Name
shp.OLEFormat.Activate
Exit For
End If
Next
If winName <> "" Then
Wait 1
If GetHwndFromCaption(Ret, Replace(winName, ".txt", "")) = True Then
Call ShowWindow(Ret, SW_SHOWMINIMIZED)
Else
MsgBox "Window not found!", vbOKOnly + vbExclamation
End If
End If
End Sub
Private Function GetHwndFromCaption(ByRef lWnd As Long, ByVal sCaption As String) As Boolean
Dim Ret As Long
Dim sStr As String
GetHwndFromCaption = False
Ret = FindWindow(vbNullString, vbNullString)
Do While Ret <> 0
sStr = String(GetWindowTextLength(Ret) + 1, Chr$(0))
GetWindowText Ret, sStr, Len(sStr)
sStr = Left$(sStr, Len(sStr) - 1)
If InStr(1, sStr, sCaption) > 0 Then
GetHwndFromCaption = True
lWnd = Ret
Exit Do
End If
Ret = GetWindow(Ret, GW_HWNDNEXT)
Loop
End Function
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
End Sub
My understanding is that you have control over how XML file gets embedded into PowerPoint presentation in the first place. Here I do not quite understand why you chose to keep the data you need as contents of an embedded object.
To be sure, the task of getting those contents back is not a piece of cake. Actually, as long as there is no (simple or even moderately difficult) way to call QueryInterface and use IPersist* interfaces from VBA, there is just one way to get to contents of embedded object. The way involves following steps:
Activate an embedded object. You used OLEFormat.DoVerb 1 for that. A better way would be to call OLEFormat.Activate, but this is irrelevant for your particular problem.
Use embedded object's programming model to perform useful operations like getting contents, saving or whatever is exposed. Notepad.exe exposes no such programming model, and you resorted to WinAPI which is the best choice available.
Unfortunately, your current approach has at least 2 flaws:
The one you identified in the question (activation of notepad.exe leading to possibility of user's interference).
If a user has default program for opening .txt files other than notepad.exe, your approach is doomed.
If you do have control over how embedded object is created then better approach would be to store your XML data in some property of Shape object. I would use Shape.AlternativeText (very straightforward to use; shouldn't be used if you export your .pptm to HTML or have some different scenario where AlternativeText matters) or Shape.Tags (this one is probably the most semantically correct for the task) for that.
I don't think that blocking the user is the right approach,
If you must use a content of a notepad window, I would suggest using the SendKeys method, in order to send this combination:
SendKeys("^A^C")
Which is the equivalent of "Select All" and "Copy",
And then you could continue working "offline" on the clipboard, without fear of interference by keystrokes.
My approach, per Sid's suggestion, was to find a way to minimize the Notepad.exe. Since I already found way to get that object and close it, I figured this should not be as hard.
I add these:
Public Declare Function _
ShowWindow& Lib "user32" (ByVal hwnd As Long, _
ByVal ncmdshow As Long)
Public Const SW_MINIMIZE = 6
And then, in the FindNotepad function, right before Exit Function (so, after the Notepad has been found) I minimize the window with:
ShowWindow TopWnd, SW_MINIMIZE
My question maybe not be very clear, but I just want to know how this process is called or references needed to create something similar, so I can investigate on my own ( but if you have code is welcome.. LOL...)
Basically I have 2 desktop programs ( A and B). For A, I do not have the source code, for B I do. What I need is to create some service/program that after a screen pops up from program A, automatically runs B. IN other words, capture the moment a specific screen is shown in A and execute B.
My real life scenario is that I have a very basic POS where I can't collect customer demographics ( zip code, etc), so I created a second application to capture that but my cashiers are always forgetting to run the program and I need to find a way to run it after a screen is shown ( let's say the "Change Due" in the POS, so they don't forget to run it.
Any pointings will be appreciated!
Thanks
So here's a very quick example of how you can do this with VB which should be very easy to convert to C# if needed. The code search visible windows by title. If your open window doesn't have a title you'll have to work a little more. You can probably find the title of your main app and just enumerate its child windows.
First, some unmanaged code to talk to Win32 directly:
Option Strict On
Option Explicit On
Imports System.Runtime.InteropServices
Imports System.Text
Public Class Unmanaged
<DllImport("user32.dll")>
Public Shared Function EnumWindows(ByVal lpEnumFunc As CallBack, ByVal lParam As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("user32.dll", SetLastError:=True)>
Public Shared Function GetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Public Shared Function GetWindowText(ByVal hwnd As IntPtr, ByVal lpString As StringBuilder, ByVal cch As Integer) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function IsWindowVisible(ByVal hWnd As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Public Const SW_SHOW = 5
Public Const SW_RESTORE = 9
Public Const GW_OWNER = 4
Public Const GWL_HWNDPARENT = (-8)
Public Const GWL_EXSTYLE = (-20)
Public Const WS_EX_TOOLWINDOW = &H80
Public Const WS_EX_APPWINDOW = &H40000
Public Delegate Function CallBack(ByVal hwnd As IntPtr, ByVal lParam As Integer) As Boolean
End Class
Then a non-GUI module which should be set as the startup object:
Option Explicit On
Option Strict On
Module Module1
''//Code loosely base on http://msdntracker.blogspot.com/2008/03/list-currently-opened-windows-with.html
''//This is the title of the window that we are looking for
Public ReadOnly WatchForTitle As String = "About Mozilla Firefox"
''//This is the form that we will show when we find the above
Private MainForm As Form1
<STAThread()>
Public Sub Main()
''//Create the form but don not show it
MainForm = New Form1()
''//Create an infinite loop that checks to see if the target window is open and sleeps for a bit between checks
Do While True
Unmanaged.EnumWindows(AddressOf fEnumWindowsCallBack, IntPtr.Zero)
''//Sleep for a bit
System.Threading.Thread.Sleep(500)
Loop
End Sub
Private Function fEnumWindowsCallBack(ByVal hwnd As IntPtr, ByVal lParam As Integer) As Boolean
''//Ignore our own handle
If hwnd <> Form1.Handle Then
''//Make sure its visible
If Unmanaged.IsWindowVisible(hwnd) Then
Dim lExStyle = Unmanaged.GetWindowLong(hwnd, Unmanaged.GWL_EXSTYLE)
''//We probably want to ignore tool windows, but remove this if needed
If (((lExStyle And Unmanaged.WS_EX_TOOLWINDOW) = 0)) Then
''//Create a buffer to store the title of the window
Dim sWindowText As New System.Text.StringBuilder(256)
''//Get the title of the window
Dim lReturn = Unmanaged.GetWindowText(hwnd, sWindowText, sWindowText.Length - 1)
''//When you are looking for window title uncomment this line
'Trace.WriteLine(sWindowText)
''//Sanity check, make sure we found a window title
If lReturn <> 0 Then
''//See if it matches what we are looking for
If sWindowText.ToString() = WatchForTitle Then
''//If so, show our form
Form1.ShowDialog()
End If
End If
End If
End If
End If
fEnumWindowsCallBack = True
End Function
End Module
Lastly create a regular Windows Form called Form1 (or whatever you want, you'll just need to change it in the Module above).
If you test this it will pop open the form if you go to Firefox's Help->About menu.
I should point out, this is just a start. You'll want to perform better error checking and handle when the program closes or exists or whatever.
It's very unlikely that there is any 'good' way to interact with program A. There won't be a traditional 'event' that fires or anything like that.
Your best bet is going to be looking at some WIN32 API calls. For example, you can use GetPixel() to return the color at a specific location on the screen. So, if program A displays a particular image or screen that is measurably different from any other screen, you can write code that monitors the screen and checks for a match. When the match is found you can execute any code you want, including launching B.