I'm testing an addin in ArcMap to open an existing file, the program stops after the dialog opened, it seems the SendKeys already executed before the dialog. Your advice is much appreciated.
Dim pCmdItmOpen As ICommandItem 'file open dialog
Dim pUIDopn As New UID
pUIDopn.Value = "{119591DB-0255-11D2-8D20-080009EE4E51}"
pUIDopn.SubType = 2
pCmdItmOpen = mxApp.Document.CommandBars.Find(pUIDopn)
pCmdItmOpen.Execute()
SendKeys.SendWait("C:\TEST.mxd")
SendKeys.SendWait("{TAB 3}")
SendKeys.SendWait("{ENTER}")
I got this solution:
Detecting whether the dialog has been opened or not, if existing, then execute SendKeys. If not, waiting for a moment, maybe 3 sec, then detecting again.
How to:
Detecting whether the dialog has been opened or not
FindWindow FindWindowEx
Use FindWindow to find the dialog
Waiting for a period then execute the next step
Using timers in vb
Use timer to count the waiting time
Reply:
I don't have 50 reputations, so not allowed to post a comment.
To use the FindWindow, you have to use the correct parameter. You could use Spy++ (Visual Studio, Tools/Spy++) to find the parameter of this dialog window.
You could use code below:
Declare:
<DllImport("user32.dll", CharSet:=CharSet.Auto, EntryPoint:="FindWindow")>
Private Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
End Function
Function catchDialog:
Private Sub catchDialog()
Dim hwnd As IntPtr = FindWindow("Class", "Caption")
If hwnd <> IntPtr.Zero Then
yourSendKeyProcess(hwnd)
Else
' dialog not showing, waiting for 3 Sec. then detecting again.
' Use Thread.Sleep() is simple then timer.
Thread.Sleep(3000)
catchDialog()
End if
End Sub
Function yourSendKeyProcess:
Private Sub yourSendKeyProcess(ByVal window as IntPtr)
SetForegroundWindow(window)
SendKeys.SendWait("C:\TEST.mxd")
SendKeys.SendWait("{TAB 3}")
SendKeys.SendWait("{ENTER}")
End Sub
Use Spy++ to find the value of "Caption", and "Class" of the dialog.
The example above, the value of Caption of the window is "Add to Archive"
and the value of Class is "#32770(Dialog)", and the code would be:
Dim hWnd As IntPtr = FindWindow("#32770", "Add to Archive")
But even you could catch the dialog window, I am not sure that your code SendKey could work, so why you use those codes?
SendKeys.SendWait("C:\TEST.mxd")
SendKeys.SendWait("{TAB 3}")
SendKeys.SendWait("{ENTER}")
You want to input a string ("C:\TEST.mxd") in the textbox on the dialog, then press a button on the dialog?
Edit:
Set the form1 always on top but not influence operation on other window:
Add this sub to make the form1 on top in the beginning:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.TopMost = True
End Sub
and Function catchDialog:
Private Sub catchDialog()
Dim hwnd As IntPtr = FindWindow("Class", "Caption")
If hwnd <> IntPtr.Zero Then
me.TopMost = false
yourSendKeyProcess(hwnd)
Else
' dialog not showing, waiting for 3 Sec. then detecting again.
' Use Thread.Sleep() is simple then timer.
Thread.Sleep(3000)
catchDialog()
End if
End Sub
and Function yourSendKeyProcess:
Private Sub yourSendKeyProcess(ByVal window as IntPtr)
SetForegroundWindow(window)
SendKeys.SendWait("C:\TEST.mxd")
SendKeys.SendWait("{TAB 3}")
SendKeys.SendWait("{ENTER}")
me.TopMost = true
End Sub
Thanks for your advice. I have tried the code below however the hWnd return 0. It seems the window name parameter "Open" in the FindWindow function not working?
It works in ArcMap vba use "SendKeys" and "Doevents" everything just prefect and easy to debug in ArcMap. Now I'm converting the vba to vb.net and I found it is more difficult than I thought!!
The "SendKeys.SendWait' wait for the dialog to close then proceed. If I use the "SendKeys.Send" the error "SendKeys.Send cannot run inside this application...".
Dim pCmdItmOpen As ICommandItem 'file open dialog
Dim pUIDopn As New UID
pUIDopn.Value = "{119591DB-0255-11D2-8D20-080009EE4E51}"
pUIDopn.SubType = 2
pCmdItmOpen = mxApp.Document.CommandBars.Find(pUIDopn)
pCmdItmOpen.Execute()
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim hWnd As IntPtr = FindWindow("Open", Nothing)
MsgBox(hWnd)
If hWnd.Equals(IntPtr.Zero) Then
Return
End If
SetForegroundWindow(hWnd)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
SendKeys.SendWait("C:\TEST.mxd")
SendKeys.SendWait("{TAB 3}")
SendKeys.SendWait("{ENTER}")
Related
I have the below code to check if 'chrome' is running when I click Button1. If not it launches chrome. This works but I dont know the code needed in the If statement to switch to the chrome if its already running. Hopefully this is something very simple i am missing.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If Process.GetProcessesByName("chrome").Count > 0 Then
??**SHOW RUNNING APPLICATION**??
Else
Process.Start("C:\Program Files\Google\Chrome\Application\chrome.exe")
End If
End Sub
As I mentioned in my above comment, Chrome starts many instances of itself. Each tab has its own process, so how are you going to tell it which one to switch to?. This come's down to what tab was selected when you minimize the window or it minimizes itself to the task bar. Below should help you out and it's tried and tested. The only issue is, if you open Chrome and have multiple tabs it's fine, but if you create another instance of Chrome it will not show the second instance, it will only bring forward the first instance... If you close the first instance, the second instance of course will come forward.
Public Class Form1
#Region "DLL Imports"
<System.Runtime.InteropServices.DllImport("User32.dll")> _
Private Shared Function SetForegroundWindow(handle As IntPtr) As Boolean
End Function
<System.Runtime.InteropServices.DllImport("User32.dll")> _
Private Shared Function ShowWindow(handle As IntPtr, nCmdShow As Integer) As Boolean
End Function
<System.Runtime.InteropServices.DllImport("User32.dll")> _
Private Shared Function IsIconic(handle As IntPtr) As Boolean
End Function
#End Region
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
StartOrShowProcess("chrome")
End Sub
Private Sub StartOrShowProcess(ByVal strProcessName As String)
Try
Dim handle As IntPtr
Dim proc As Process() = Process.GetProcessesByName(strProcessName)
If proc.Count > 0 Then
For Each procP As Process In proc
handle = procP.MainWindowHandle
If handle <> 0 AndAlso IsIconic(handle) Then 'Do we have a handle and is it minimized?
ShowWindow(handle, 9)
SetForegroundWindow(handle)
End If
Next
Else 'Not running or started...
Process.Start(strProcessName)
End If
Catch ex As Exception
'Handle your error...
End Try
End Sub
End Class
I've created a GUI in Visual Basic 2010 that launches another program, except the other program is hidden behind the GUI window when it starts. I'm already retrieving the Process ID of that other program in order to later kill it, but I'm not sure how to turn the ID into something I can use to bring the window forward.
The other solution is to move send my GUI to the back, but that doesn't work either. I think that's because of the other program though. There's a splash screen that comes up before the main window launches that requires interaction. My program doesn't get sent to back until after the splash screen is closed, defeating the purpose.
Have you looked into AppActivate. It's supposed to set focus to the application you want as long as it's running.
There are a couple ways you could do this. You can use AppActivate as Timmy has suggested or you can use pinvoke with the SetForegroundWindow function:
Imports System.Runtime.InteropServices
Public Class Form1
Dim oProcess As New Process()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Add buttons to the form
Dim cmd As New Button
cmd.Name = "cmdAppActivate"
cmd.Text = "App Activate"
cmd.Location = New Point(0, 0)
cmd.Size = New Size(90, 25)
AddHandler cmd.Click, AddressOf cmdAppActivate_Click
Me.Controls.Add(cmd)
cmd = New Button
cmd.Name = "cmdSetForegroundWindow"
cmd.Text = "Set Foreground Window"
cmd.Location = New Point(0, 30)
cmd.Size = New Size(130, 25)
AddHandler cmd.Click, AddressOf cmdSetForegroundWindow_Click
Me.Controls.Add(cmd)
' Open notepad
oProcess.StartInfo = New ProcessStartInfo("notepad.exe")
oProcess.Start()
End Sub
Private Sub cmdAppActivate_Click(sender As Object, e As EventArgs)
AppActivate(oProcess.Id) ' use appactivate to bring the notepad window to the front
End Sub
Private Sub cmdSetForegroundWindow_Click(sender As Object, e As EventArgs)
SetForegroundWindow(oProcess.MainWindowHandle) ' Use pinvoke (SetForegroundWindow) to bring the notepad window to the front
End Sub
End Class
Module Module1
<DllImport("user32.dll")> _
Public Function SetForegroundWindow(ByVal hWnd As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
End Module
I am writing a script / program to log into SAP then just grab a few bits of data from a employee then close SAP.
This I can do already but am trying to make a fancy GUI just for that instead of clicking many windows in SAP to get it...mainly because im lazy cant be bothered clicking a million things... Some call this innovation i think :)
I have been researching BackGroundWorker in vb.net but wouldnt this still load the window and just keep the form active and responsive while running the program?
I dont have 'admin' rights (can create and modify user accounts su01,pa30 etc) to the server etc so cant log into the server\database...hence running script to obtain result..
Does anyone know how Ican log SAP and have it hidden while running ?
You can check this post about how to hide an external application window.
http://www.vbforums.com/showthread.php?669210-RESOLVED-Hiding-Window-of-external-process
In this example, guys trying to start calc.exe in hidden mode.
First off all insert at the beggining of your progect
Imports System.Runtime.InteropServices
Then you have to enum flags for ShowWindow (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548%28v=vs.85%29.aspx
Private Enum ShowWindowCommand As Integer
Hide = 0
Show = 5
Minimize = 6
Restore = 9
End Enum
Set a specified window's show state
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function ShowWindow(ByVal hwnd As IntPtr, ByVal nCmdShow As ShowWindowCommand) As Boolean
End Function
Determine whether the specified window handle identifies an existing window
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function IsWindow(ByVal hWnd As IntPtr) As Boolean
End Function
Determines whether a specified window is minimized.
Private Declare Auto Function IsIconic Lib "user32.dll" (ByVal hwnd As IntPtr) As Boolean
variable to save window handle (you can choise your own name, but rename in other part of code)
Private calc_hWnd As IntPtr
launch windows calculator (in your case saplogon.exe) minimized and hidden, while loading form
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim test As New Process
Try
' file to launch
test.StartInfo.FileName = "calc.exe" ' note: full path not needed for windows calc.
' Note: This next line has no effect on WinXP "calc.exe" and some other apps like FireFox.
'test.StartInfo.WindowStyle = ProcessWindowStyle.Minimized
' start the app
test.Start()
' wait until app is in idle state
test.WaitForInputIdle(-1)
' get app main window handle
Dim tmp_hWnd As IntPtr = test.MainWindowHandle
' make sure handle is valid (non zero)
' try up to 10 times within one second
' do a re-try loop that runs for a second or two
For i As Integer = 1 To 10
tmp_hWnd = test.MainWindowHandle
If Not tmp_hWnd.Equals(IntPtr.Zero) Then Exit For ' got handle so exit loop
Threading.Thread.Sleep(100) ' wait 100ms
Next '- try again
If Not tmp_hWnd.Equals(IntPtr.Zero) Then
' use ShowWindow to change app window state (minimize and hide it).
ShowWindow(tmp_hWnd, ShowWindowCommand.Minimize)
ShowWindow(tmp_hWnd, ShowWindowCommand.Hide)
' save handle for later use.
calc_hWnd = tmp_hWnd
Else
' no window handle?
MessageBox.Show("Unable to get a window handle!")
End If
Catch ex As Exception
' error !
MessageBox.Show(ex.Message)
End Try
End Sub
on exit restore/unhide app if found running.
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
' is our variable set to non-zero?
If Not calc_hWnd.Equals(IntPtr.Zero) Then
' is app window found?
If IsWindow(calc_hWnd) = True Then
' if app is minimized then restore it
If IsIconic(calc_hWnd) Then
ShowWindow(calc_hWnd, ShowWindowCommand.Restore)
End If
' make sure window is seen incase it was hidden.
ShowWindow(calc_hWnd, ShowWindowCommand.Show)
End If
End If
End Sub
But you can write another code in and kill saplogon.exe process.
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
For Each p As Process In System.Diagnostics.Process.GetProcessesByName("saplogon.exe")
Try
p.Kill()
' possibly with a timeout
p.WaitForExit()
' process has already exited - might be able to let this one go
Catch ex As Exception
MessageBox.Show(ex.toString)
End Try
Next
End Sub
I am working on a vb.net project and i have a "start" and "Pause" Buttons on the FormPost.exe
I am trying to schedule a batch process to run every day in the morning at 4:00 AM.
How can i run a command prompt to execuite FormPost.exe and them click on "start" button, all via command prompt?
Please let me know. Thanks
What you can do is this override the OnControlCreateMethod() as follows:
Public Class Form1
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
Protected Overrides Sub OnCreateControl()
MyBase.OnCreateControl()
If Environment.CommandLine.Contains("/clickme") Then
ClickMeButton.PerformClick()
// ... need to wait here until click event handler has finished, e.g.
// using synchronization objects
Close()
End If
End Sub
End Class
If you pass "/clickme" on the command line it will execute the click event and then close the form. If the form is the only one in the application it will terminate.
Be aware, though, that you will need to add some logic that waits for the click event handler to finish. Try avoid using polling or sleep. Instead try using synchronization objects.
If this is your application; you can modify the code so that it checks if you are running it from the command line / with appropriate arguments and fire the button click() itself. That'd be the easiest approach (I think John's answer shows this)
If it's not your application; you can still accomplish the same thing, but it's not as pretty. You can write code that will execute the winForm then activate it (to ensure it has focus)
Public Shared Sub ActivateWoW()
Dim myApp As Process = Process.GetProcessesByName("MyApp").First
AppActivate(myApp.Id)
End Sub
Then, you can use SendKeys() to simulate interaction with the form. Let's say the start button takes two 'tab' keys to be selected...
SendKeys.Send("{TAB}{TAB}")
Then a quick pause...
Thread.Sleep(25)
Then hit the enter key (which is almost always just as good as mouse click when the button is selected)
SendKeys.Send("{ENTER}")
If you want to get more involved than that; you need to start using WIN32 API calls. Here is some sample code for a Mouse click...
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Private Const WM_LBUTTONUP As Long = &H202
Private Const WM_RBUTTONUP As Long = &H205
Private Const WM_LBUTTONDOWN As Long = &H201
Private Const WM_RBUTTONDOWN As Long = &H204
Private Shared Function MakeDWord(ByVal LoWord As Integer, ByVal HiWord As Integer) As Long
Return (HiWord * &H10000) Or (LoWord And &HFFFF&)
End Function
Public Shared Sub SendMouseClick()
Dim Wow As Long = FindWindow("GxWindowClass", "MyWindow")
Dim dWord As Long = MakeDWord(LastX - LastRectX, LastY - LastRectY)
SendMessage(Wow, WM_RBUTTONDOWN, 1&, dWord)
Threading.Thread.Sleep(100)
SendMessage(Wow, WM_RBUTTONUP, 1&, dWord)
End Sub
Preface: I know this is an unusual/improper way to do this. I can do this with a "real" ShowDialog(), background worker/thread, and so on. I'm not looking for help doing it that way; I am trying to do specifically what I describe here, even if it is ugly. If this is impossible for X reason, please let me know though.
I have created a fancy progress dialog for some of our long running operations. I need to have this dialog shown on a new thread while having processing continue on the calling (UI in most cases) thread.
This has 3 real requirements:
Prevent user interaction with the calling form (similar to ShowDialog(this))
Keep the progress dialog above the main window (it can fall behind now)
Allow the main thread to continue processing
What I have looks like this (and works just fine so far, as far as running goes, except for those issues above):
Using ... ShowNewProgressDialogOnNewThread() ...
Logic
UpdateProgress() //static
Logic
UpdateProgress() //static, uses Invoke() to call dialog
...
End Using // destroys the form, etc
I have tried a few ways to do this:
ShowDialog() on BackgroundWorker / Thread
Action.BeginInvoke() which calls a function
ProgressForm.BeginInvoke(... method that calls ShowDialog... )
Wrapping main form in a class that implements IWin32Window so it can be called cross-threaded and passed to ShowDialog() - this one failed somewhere later one, but at least causes ShowDialog() to not barf immediately.
Any clues or wisdom on how to make this work?
Solution (For Now)
The call to EnableWindow is what did what I was looking for.
I do not experience any crashes at all
Changed to use ManualResetEvent
I set TopMost, because I couldn't always guarantee the form would end up on top otherwise. Perhaps there is a better way.
My progress form is like a splash screen (no sizing, no toolbar, etc), perhaps that accounts for the lack of crashes (mentioned in answer)
Here is another thread on the EnableWindow topic (didn't reference for this fix, tho)
Getting the progress window consistently displayed on top of the (dead) form is the difficult requirement. This is normally handled by using the Form.Show(owner) overload. It causes trouble in your case, WF isn't going to appreciate the owner form belonging to another thread. That can be worked around by P/Invoking SetWindowLong() to set the owner.
But now a new problem emerges, the progress window goes belly-up as soon as it tries to send a message to its owner. Somewhat surprisingly, this problem kinda disappears when you use Invoke() instead of BeginInvoke() to update progress. Kinda, you can still trip the problem by moving the mouse over the border of the disabled owner. Realistically, you'll have to use TopMost to nail down the Z-order. More realistically, Windows just doesn't support what you are trying to do. You know the real fix, it is at the top of your question.
Here's some code to experiment with. It assumes you progress form is called dlgProgress:
Imports System.Threading
Public Class ShowProgress
Implements IDisposable
Private Delegate Sub UpdateProgressDelegate(ByVal pct As Integer)
Private mOwnerHandle As IntPtr
Private mOwnerRect As Rectangle
Private mProgress As dlgProgress
Private mInterlock As ManualResetEvent
Public Sub New(ByVal owner As Form)
Debug.Assert(owner.Created)
mOwnerHandle = owner.Handle
mOwnerRect = owner.Bounds
mInterlock = New ManualResetEvent(False)
Dim t As Thread = New Thread(AddressOf dlgStart)
t.SetApartmentState(ApartmentState.STA)
t.Start()
mInterlock.WaitOne()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
mProgress.BeginInvoke(New MethodInvoker(AddressOf dlgClose))
End Sub
Public Sub UpdateProgress(ByVal pct As Integer)
mProgress.Invoke(New UpdateProgressDelegate(AddressOf dlgUpdate), pct)
End Sub
Private Sub dlgStart()
mProgress = New dlgProgress
mProgress.StartPosition = FormStartPosition.Manual
mProgress.ShowInTaskbar = False
AddHandler mProgress.Load, AddressOf dlgLoad
AddHandler mProgress.FormClosing, AddressOf dlgClosing
EnableWindow(mOwnerHandle, False)
SetWindowLong(mProgress.Handle, -8, mOwnerHandle)
Application.Run(mProgress)
End Sub
Private Sub dlgLoad(ByVal sender As Object, ByVal e As EventArgs)
mProgress.Location = New Point( _
mOwnerRect.Left + (mOwnerRect.Width - mProgress.Width) \ 2, _
mOwnerRect.Top + (mOwnerRect.Height - mProgress.Height) \ 2)
mInterlock.Set()
End Sub
Private Sub dlgUpdate(ByVal pct As Integer)
mProgress.ProgressBar1.Value = pct
End Sub
Private Sub dlgClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs)
EnableWindow(mOwnerHandle, True)
End Sub
Private Sub dlgClose()
mProgress.Close()
mProgress = Nothing
End Sub
'--- P/Invoke
Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
If IntPtr.Size = 4 Then
Return SetWindowLongPtr32(hWnd, nIndex, dwNewLong)
Else
Return SetWindowLongPtr64(hWnd, nIndex, dwNewLong)
End If
End Function
Private Declare Function EnableWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal enabled As Boolean) As Boolean
Private Declare Function SetWindowLongPtr32 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
Private Declare Function SetWindowLongPtr64 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
End Class
Sample usage:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Using dlg As New ShowProgress(Me)
For ix As Integer = 1 To 100
dlg.UpdateProgress(ix)
System.Threading.Thread.Sleep(50)
Next
End Using
End Sub
I know it's a bit dirty but can't you just do the work in the dialog??
I mean something like
Dialog.MyShowDialog(callback);
and do all the work in callback as well as the UI update.
That way you'll retain the ShowDialog behaivour while allowing different code to be called.
I wrote a blog post on this topic a while ago (dealing with splash forms, but the idea is the same). The code is in C#, but I will try to convert it an post it here (coming...).