Is there a way to have a desktop vb application take control of an already open browser window? For example, have it mouse click certain coordinates in the window or check if the window contains certain elements.
I've looked at using Microsoft Internet Controls(shdocvw) and MSHTML(IHTMLDocument2) but I am struggling on how to access elements of the browser window (e.g. body.innnerHTML).
You can use AppActivate in VBScript which you may be able to use in VB.NET
set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "iexplore"
WScript.Sleep 100
WshShell.AppActivate "Windows Internet Explorer"
WshShell.SendKeys "~"
WScript.Sleep 500
WshShell.SendKeys "www.google.com"
~ is to TAB, and you can use sendkeys to emulate keystrokes.
Add References to Microsoft HTML Object Library and Microsoft Internet Controls
Option Explicit On
Public Class Form1
'Functions used to set cursor location and mouse clicks
Public Declare Auto Function SetCursorPos Lib "User32.dll" (ByVal X As Integer, ByVal Y As Integer) As Long
Public Declare Sub mouse_event Lib "user32" Alias "mouse_event" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Public Declare Auto Function ShowWindow Lib "user32" (ByVal _
hwnd As Long, ByVal nCmdShow As Long) As Long
'Constants used for cursor and mouse functions, and to maximize window
Public Const SW_MAXIMIZE = 3
Public Const MOUSEEVENTF_LEFTDOWN = &H2 ' left button down
Public Const MOUSEEVENTF_LEFTUP = &H4 ' left button up
Public Const MOUSEEVENTF_RIGHTDOWN = &H8 ' right button down
Public Const MOUSEEVENTF_RIGHTUP = &H10 ' right button up
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'retrieve all the open instances of IE
Dim shellWindows = New SHDocVw.ShellWindowsClass()
Dim htmlInput As mshtml.HTMLInputElement
'for each instance of IE
For Each ie As SHDocVw.InternetExplorer In shellWindows
'If the title of the IE window matches the designated title
If ie.Document.title = "Page Title" Then
'retrieve the control with the designated field id
htmlInput = ie.Document.getElementById("fieldID")
'if the control's inner html matches the designated text
If htmlInput.innerHTML = "innerHTML" Then
'show the IE window maximized and with focus
ShowWindow(ie.HWND, SW_MAXIMIZE)
'move the cursor to the designated x,y coordinates
SetCursorPos(xCoord, yCoord)
'left mouse click down and up
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
'send the designated keyboard command
My.Computer.Keyboard.SendKeys("keyboardCommand")
End If
End If
Next
End Sub
End Class
Related
I'm running a PowerPoint Macro-Enabled Slide Show. When a user opens this file the presentation starts immediately. The presentation contains various shapes that, when pressed (use of links), will open a new Powerpoint Slide Show in front of the main Slide Show.
In the background I'm using VBA (code is located in the main Macro enabled slide show) to measure the time a user spends on all of the slides. I want the user to be able to stop this timer with a userform and a button. However, when a new Powerpoint Slide Show is opened, it appears in front of the main slide show. The userform will then disappears behind the new slide show. Using a second screen i have been able to view the userform. But when clicked on the userform it brings the main slide show in front of the other slide show.
So in short: I would like a userform that is in front of all slide show windows.
I tried using vbmodeless but this does not help. I've also tried out various bits of code:
http://www.vbaexpress.com/forum/showthread.php?58189-Make-userform-stay-on-top-of-all-windows-when-macro-is-fired
https://www.mrexcel.com/board/threads/userform-always-on-top.386643/
https://www.mrexcel.com/board/threads/keeping-userform-on-top-when-changing-active-workbook.1165439/
Unfortunately, none of these seem to be working. Some of these are for excel and I've not been able to rewrite these bits of code.
P.S. If this isn't possible, maybe I could hide the main slide show?
Simple version
Create a class module, MyClass, and put this code there:
Public WithEvents App As Application
Private Sub App_SlideShowBegin(ByVal Wn As SlideShowWindow)
UserForm1.Show False
End Sub
Create a module and put this code in it:
Dim MyThing As New MyClass
Sub InitializeApp()
Set MyThing.App = Application
End Sub
Run the InitializeApp method first. Now, when you start your presentation, your UserForm1 will show up. The False flag makes it non-modal, which is what I think you are looking for.
Slightly more advanced version
As above, but change the module to this:
Option Explicit
Public Const SWP_NOMOVE = &H2
Public Const SWP_NOSIZE = &H1
Public Const HWND_TOP = 0
Public Const HWND_BOTTOM = 1
Public Const HWND_TOPMOST = -1
Public Const HWND_NOTOPMOST = -2
Public Declare Function SetWindowPos Lib "user32" _
(ByVal hWnd As Long, _
ByVal hWndInsertAfter As Long, _
ByVal X As Long, _
ByVal Y As Long, _
ByVal cx As Long, _
ByVal cy As Long, _
ByVal uFlags As Long) As Long
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Dim MyThing As New MyClass
Sub InitializeApp()
Set MyThing.App = Application
End Sub
And add this to your form code:
Option Explicit
Option Explicit
Private Sub UserForm_Initialize()
Dim formHWnd As Long
formHWnd = FindWindow("ThunderDFrame", Me.Caption)
SetWindowPos formHWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE
End Sub
I have an excel vba application that runs a separate process. During the external process a progress indicator with abort button is initialized. It is crucial for the end users that the abort button is available. It is also usefull to see the external process running.
I would like to have the progress indicator/abort button placed on top of the external process. but I do NOT wont to force the userform on top of everything.
I have tried to use findwindow / setwindowpos, resulting in the following problems:
If I initialize HWND_TOPMOST before running the process, then the userform is always on top, regardless of what the user wants. I find this very annoying, especially if some sort of errors occur where the debuging window might be blocked by the inactive vba userform. However the workbook remains in the background which is desired.
If I use HWND_TOP (after the external process is up and running) then the entire workbook is activated (not just the userform), which then hides to progress of the external application. Not very benificial compared to activating the workbook.
Are there any suggestions on how to put the userform in front of the external applicaiton, while still allowing the user to deactivate it?
code snippets:
Option Explicit
' Code stolen with pride from various sources.
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function BringWindowToTop Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function SetWindowPos Lib "user32" ( _
ByVal hwnd As Long, _
ByVal hWndInsertAfter As Long, _
ByVal X As Long, _
ByVal Y As Long, _
ByVal cx As Long, _
ByVal cy As Long, _
ByVal wFlags As Long) As Long
Public Const SWP_NOMOVE = &H2
Public Const SWP_NOSIZE = &H1
Public Const HWND_TOP = 0
Public Const HWND_BOTTOM = 1
Public Const HWND_TOPMOST = -1
Public Const HWND_NOTOPMOST = -2
Public id As Integer
Public ProgressForm As fProgress
' Main routine, launch application and progress indicator.
Sub LoadForm()
Set ProgressForm = New fProgress
ProgressForm.Show
' Force userform to front, makes it on top but does not allow reorder of windows.
ForceToFront ProgressForm
' Run external process, notepad used for example.
id = Shell("notepad", vbNormalFocus)
End Sub
' Routine to bring userform to front after the external program is up and running.
Sub TestBringToFront()
BringToFront ProgressForm
End Sub
Sub BringToFront(fm As fProgress)
Dim hwnd As Long, ret As Variant
hwnd = FindWindow("ThunderDFrame", fm.Caption)
ret = SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE)
End Sub
Sub ForceToFront(fm As fProgress)
Dim hwnd As Long, ret As Variant
hwnd = FindWindow("ThunderDFrame", fm.Caption)
ret = SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE)
End Sub
Unfortunately this is the designated behaviour of windows and applications. However, I can suggest some workarounds:
Use TOPMOST option and make the userform clickable, e.g. capture the ProgressForm_Click and toggle between TOPMOST and NOTOPMOST state. ALternatively use can employ an "Always On Top" checkbox on ProgressForm like it is in the Task Manager.
Use TOP option and resize the workbook window behind to as small as possible, or even move aside (out of the screen), and restore size and position after the external app finished.
It may be worthy making a try with ProgressForm.Show(vbModeless) and TOP
I want that my excel xml always display in full screen view.
For this I code the next:
Private Sub Workbook_Open()
Application.WindowState = xlMaximized
ActiveWindow.WindowState = xlMaximized
Application.DisplayFullScreen = True
End Sub
It is working fine until I minimize excel, once I maximize again It shows in normal view mode, how to proceed? Any suggestion? The main idea is to remove the tool bars as I don't want user to interact with them.
Paste this into the workbook module. It will maximize the windows whenever it gets resized:
Private Sub Workbook_WindowResize(ByVal Wn As Window)
ActiveWindow.WindowState = xlMaximized
End Sub
There is an event that you can trap I'd try adding this to your ThisWorkbook module
Option Explicit
Private mbToggle As Boolean
Private mlPriorState(-1 To 0) As XlWindowState
Private Sub Workbook_WindowResize(ByVal Wn As Window)
mlPriorState(mbToggle) = Wn.WindowState
mbToggle = Not mbToggle
If Wn.WindowState = xlNormal And mlPriorState(mbToggle) <> xlMaximized Then
ActiveWindow.WindowState = xlMaximized
End If
End Sub
Though this may only work on windows that represent the worksheet/workbook. I'd try this first; other solutions involving Windows API are way more complicated.
Folded in some feedback. This code works for me.
Workbook_Activate will bring full screen mode while other will bring back normal mode.
Private Sub Workbook_Activate()
On Error Resume Next
With Application
.DisplayFullScreen = True
.CommandBars("Worksheet Menu Bar").Enabled = False
End With
End Sub
Private Sub Workbook_Deactivate()
On Error Resume Next
With Application
.DisplayFullScreen = False
.CommandBars("Worksheet Menu Bar").Enabled = True
End With
End Sub
EDIT
you shouldn't 'modify' the way Windows works at a system level. However, if you really, really must; add the following to a new module and call the SetStyle procedure.
That code is offered UNTESTED'as is' - the API is a way to modify Windows at a system level and can be dangerous (sudden crashes, data file corruption...) if you do not know what you are doing.
VB:
Option Explicit
'Related Windows API functions
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetSystemMenu Lib "user32" (ByVal hWnd As Long, ByVal bRevert As Long) As Long
Private Declare Function DeleteMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long
Private Declare Function DrawMenuBar Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function SetFocus Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function LockWindowUpdate Lib "user32" (ByVal hWndLock As Long) As Long
'Window style constants
Private Const GWL_STYLE As Long = (-16) '// The offset of a window's style
Private Const GWL_EXSTYLE As Long = (-20) '// The offset of a window's extended style
Private Const WS_CAPTION As Long = &HC00000 '// Title bar bit
Private Const WS_SYSMENU As Long = &H80000 '// System menu bit
Private Const WS_THICKFRAME As Long = &H40000 '// Sizable frame bit
Private Const WS_MINIMIZEBOX As Long = &H20000 '// Minimize box bit
Private Const WS_MAXIMIZEBOX As Long = &H10000 '// Maximize box bit
Private Const WS_EX_TOOLWINDOW As Long = &H80 '// Tool Window: small titlebar bit
'Constant to identify the Close menu item
Private Const SC_CLOSE As Long = &HF060
Public Sub SetStyle()
Dim lStyle As Long, hMenu As Long
'Get the basic window style
lStyle = GetWindowLong(Application.hWnd, GWL_STYLE)
If lStyle = 0 Then
MsgBox "Unable to determine application window handle...", vbExclamation, "Error"
Exit Sub
End If
'// Build up the basic window style flags for the form
'// Uncomment the features you want...
'// Set it True to enable, FALSE to disable
'// The first 2 are obvious, ThickFrame controls if the Window is sizable or not.
'// SetBit lStyle, WS_CAPTION, True
'// SetBit lStyle, WS_SYSMENU, False
'// SetBit lStyle, WS_THICKFRAME, False
SetBit lStyle, WS_MINIMIZEBOX, False
SetBit lStyle, WS_MAXIMIZEBOX, False
'Set the basic window styles
SetWindowLong Application.hWnd, GWL_STYLE, lStyle
'Get the extended window style
lStyle = GetWindowLong(Application.hWnd, GWL_EXSTYLE)
'// Handle the close button differently
'// If Close button is wanted
'// hMenu = GetSystemMenu(Application.hWnd, 1)
'// Not wanted - delete it from the control menu
hMenu = GetSystemMenu(Application.hWnd, 0)
DeleteMenu hMenu, SC_CLOSE, 0&
'Update the window with the changes
DrawMenuBar Application.hWnd
SetFocus Application.hWnd
End Sub
'// Set or clear a bit from a style flag
Private Sub SetBit(ByRef lStyle As Long, ByVal lBit As Long, ByVal bOn As Boolean)
If bOn Then
lStyle = lStyle Or lBit
Else
lStyle = lStyle And Not lBit
End If
End Sub
I have VBA code that runs every time a new email message is received. It has several processing steps including creating excel spreadsheets, so can take a minute or two to execute.
I would like to display a modeless dialog box that shows updated status messages as processing of the email progresses. I created a UserForm1, but can't figure out how to instantiate it from the VBA code.
Like so:
Dim uf As UserForm1
Set uf = New UserForm1
uf.Show False
However, this is not a good practice as a notification should be modal. Maybe you want SystemModal (in front of ALL windows) instead of ApplicationModal (in front of Application)? The VBA MsgBox can be quite customized actually so check out this post here to learn more on how to customize a MsgBox.
To make a Window TopMost
If you want your Form window to be TopMost try this:
https://support.microsoft.com/en-us/kb/184297
Option Explicit
Public Const SWP_NOMOVE = 2
Public Const SWP_NOSIZE = 1
Public Const FLAGS = SWP_NOMOVE Or SWP_NOSIZE
Public Const HWND_TOPMOST = -1
Public Const HWND_NOTOPMOST = -2
Declare Function SetWindowPos Lib "user32" Alias "SetWindowPos" _
(ByVal hwnd As Long, _
ByVal hWndInsertAfter As Long, _
ByVal x As Long, _
ByVal y As Long, _
ByVal cx As Long, _
ByVal cy As Long, _
ByVal wFlags As Long ) As Long
Public Function SetTopMostWindow(hwnd As Long, Topmost As Boolean) _
As Long
If Topmost = True Then 'Make the window topmost
SetTopMostWindow = SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, _
0, FLAGS)
Else
SetTopMostWindow = SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, _
0, 0,FLAGS)
SetTopMostWindow = False
End If
End Function
To use this on your form:
res = SetTopMostWindow(uf.hwnd, True)
Final version of the test code after receiving info from #AnalystCave.com follows. Still working on how to force the userform to stay on top of the other windows.
Public Sub TestForm()
Dim uf As UserForm1
Set uf = New UserForm1
Load uf
uf.Show vbModeless
uf.msgStatus.Text = "11111111111111111"
uf.msgStatus.Text = "22222222222222222"
uf.msgStatus.Text = "33333333333333333"
uf.Hide
Unload uf
End Sub
In my particular application, what appears on the desktop is predictable. In this case, one way to solve the problem is:
''' execute something that you know will show on top of the userform then
uf.Show vbModeless ' this will put the userform back on top
I can manually shorten or lengthen the Name Box (which is just to the left of the Formula Bar) by dragging the "dot" to the right or left. (This also shortens or lengthens the Formula Bar.)
How can I do the adjustment with VBA??
PHEW!!!!
Things that you throw my way!!! :P
When I realized that there are is no native way to achieve what you want, I resorted to the API way but then I was again disappointed because the "Name Box" only exposed WS_CHILDWINDOW, WS_VISIBLE, CBS_DROPDOWN, CBSAUTOHSCROLL and CBS_HASSTRINGS. The "Dot" doesn't even have a handle.
Out of frustration, I started thinking along the lines of what Mark proposed in his answer. The Registry way. It took me some 20 odd mins to find the Registry key. But Alas, that joy also didn't last long when I realized that changing the registry key didn't have any effect till I restarted Excel.
After this there was only one way left Simulation of the mouse. I would have smashed my laptop on the ground if that didn't work!.
I tried with some hardcoded values in the beginning and was happy with the results. So here is the final version...
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long
Private Declare Function SetCursorPos Lib "user32" _
(ByVal X As Integer, ByVal Y As Integer) As Long
Public Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function GetWindowRect Lib "user32" _
(ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Sub mouse_event Lib "user32.dll" (ByVal dwFlags As Long, _
ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Private Const MOUSEEVENTF_MOVE = &H1 ' mouse move
Private Const MOUSEEVENTF_LEFTDOWN = &H2 ' left button down
Private Const MOUSEEVENTF_LEFTUP = &H4 ' left button up
Private Const MOUSEEVENTF_ABSOLUTE = &H8000 ' absolute move
Private Type POINTAPI
X As Long
Y As Long
End Type
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Dim pos As RECT
Sub Sample()
Dim hwndExcel As Long
Dim hwndPanel As Long
Dim hwndCombo As Long
Dim dest_x As Long
Dim dest_y As Long
Dim cur_x As Long
Dim cur_y As Long
Dim Position As POINTAPI
'~~> Get the handle of the Excel Window
hwndExcel = FindWindow("XLMAIN", Application.Caption)
If hwndExcel = 0 Then Exit Sub
'MsgBox "Excel Window Found"
'~~> Get the handle of the Panel where the Name Box is
hwndPanel = FindWindowEx(hwndExcel, ByVal 0&, "EXCEL;", vbNullString)
If hwndPanel = 0 Then Exit Sub
'MsgBox "Excel Panel Found"
hwndCombo = FindWindowEx(hwndPanel, ByVal 0&, "Combobox", vbNullString)
If hwndCombo = 0 Then Exit Sub
'MsgBox "Excel Name Box Found"
'~~> Retrieve the dimensions of the bounding rectangle of the
'~~> specified window. The dimensions are given in screen
'~~> coordinates that are relative to the upper-left corner of the screen.
GetWindowRect hwndCombo, pos
'~~> Get the approx location of the DOT. It is where the Combobox ends
cur_x = pos.Right
cur_y = pos.Top + 10
'~~> New Destination
dest_x = cur_x + 500 '<~~ Change width here
dest_y = cur_y
'~~> Move the cursor to the specified screen coordinates of the DOT.
SetCursorPos cur_x, cur_y
Wait 1 '<~~ Wait 1 second
'~~> Press the left mouse button on the DOT
mouse_event MOUSEEVENTF_LEFTDOWN, cur_x, cur_y, 0, 0
'~> Set the new destination. Take cursor there
SetCursorPos dest_x, dest_y
'~~> Press the left mouse button again to release it
mouse_event MOUSEEVENTF_LEFTUP, dest_x, dest_y, 0, 0
Wait 1
MsgBox "done"
End Sub
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
End Sub
Instructions
Paste this code in a module and then from the sheet press ALT+F8 and then select Sample and press ALT+R
Tested in Excel 2010
Before
After
As there isn't a NameBox object within VBA Excel.Application I don't think it's possible in native VBA.
You'd have to delve into REGISTRY. The registry key is
Note: Even if you set the value, for it to take effect, you will have to close and open Excel.