My goal is to run a third-party application though the cmd shell. My VB program will start multiple instances and I like to set the cmd title to keep track of those multiple windows. I'm running into the following issue: when I change the title using VB, the change is not consistent. The new title is changed back to the default title, as soon as you use a copy/paste function in this window or click anywhere in the cmd window. Here is the VB code I use:
Imports System.Threading
Public Class Form1
Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Integer, ByVal lpString As String) As Integer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim h_wnd As Integer
Dim proc As New Process
proc = Process.Start("cmd.exe")
Thread.Sleep(2000)
h_wnd = proc.MainWindowHandle
SetWindowText(h_wnd, "Test Text")
End Sub
End Class
When I do the same thing through PowerShell, the rename is consistent. Here's the PS code I use
Add-Type -Type #"
using System;
using System.Runtime.InteropServices;
namespace WT {
public class Temp {
[DllImport("user32.dll")]
public static extern bool SetWindowText(IntPtr hWnd, string lpString);
}
}
"#
$titletext = "Test Text"
# Start a thread job to change the window title to $titletext
$null = Start-ThreadJob { param( $rawUI, $windowTitle )
Start-Sleep -s 2
if ( $rawUI.WindowTitle -ne $windowTitle ) {
$rawUI.WindowTitle = $windowTitle
}
}-ArgumentList $host.ui.RawUI, $titletext
echo $rawUI
& 'C:\Windows\System32\cmd.exe'
The problem is that I won't be able to use PowerShell, because part of the parameters parsed to the script is a password and PowerShell logs all entries in the Windows Powershell log, including the password.
I can't explain why the title change is persistent in PS and why it isn't in VB. Does anybody has an idea?
Thanks for any help in advance!
Kind regards,
Eric
Extra code added, to give an example of my rename issue in VB:
Imports System.Threading
Public Class Form1
Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Integer, ByVal lpString As String) As Integer
Public Shared proc As New Process
Public Shared h_wnd As Integer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
proc = Process.Start("cmd.exe", "/k title My new title & powershell.exe")
Thread.Sleep(2000)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
h_wnd = proc.MainWindowHandle
SetWindowText(h_wnd, "My new title")
End Sub
End Class
To set cmd.exe's window title once, on startup:
You can use cmd.exe's CLI and its internal title command to persistently set the new console window's title:
proc = Process.Start("cmd.exe", "/k title Test Text")
/k creates an interactive cmd.exe session that stays open, and accepts a startup command (by contrast, /c executes the given startup command and then exits - see cmd /?)
title sets the window title; do not enclose the argument in "...", because the " will then be retained in the title. If your title contains cmd.exe metacharacters (other than spaces), such as &, escape them individually with ^.
A title set this way stays in effect unless overwritten by a console application called from the session, while that application is running.
If a console application does not modify the title, cmd.exe appends the invocation command line to its title, for the duration of the execution (e.g., Test Text - some.exe foo bar), but only in an interactive session, i.e. one launched with cmd /k (plus a startup command) or without any arguments; by contrast, this does not happen with cmd /c.
Once the application exits, the original title is restored, in either case.
If you need to undo the effects of a title modification performed by a console application while it is running, then an asynchronous approach seems like the only option, which cannot be achieved with cmd.exe alone, because it doesn't support background jobs.
Both setting the original window title with title and
your asynchronous VB.NET approach of calling SetWindowText() later actions are needed, because when you use SetWindowText() to change the console window title later, cmd.exe doesn't realize that that happened and reverts to its title on interacting with the window, such as when selecting text, which is what you saw.
You can make your SetWindowText() approach more deterministic by waiting in a loop until the title actually changes from its startup value, and then make the call, analogous to what your PowerShell code does, only from outside the process.
Because as noted above, using /k makes cmd.exe append the invocation command line to its window title while a program launched from it is running, so that on interacting with the window, you won't just see Test Text, but something like Test Text - some.exe foo bar.
Therefore, call with cmd /c, and make a nested cmd /k call after executing your console application in order to then enter an interactive session; e.g.: (some.exe is a sample executable name, and foo and bar are sample arguments - adapt as needed):
Process.Start("cmd.exe", "/c title Test Text & some.exe foo bar & cmd /k")
Alternatively, if you simply want to keep the window open until the user presses a key (i.e. if you don't need an interactive cmd.exe session after execution ends), replace cmd / k with pause; or simply omit a final command if the window doesn't need to stay open after termination at all.
To put it all together (again, change the sample some.exe call as needed):
Imports System.Threading
Public Class Form1
Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Integer, ByVal lpString As String) As Integer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim proc As New Process
' Note: special chars. such as "&", ">", "|", ... would require "^"-escaping
Dim customTitle As String = "Test Text"
proc = Process.Start("cmd.exe", "/c title " & customTitle & " & some.exe foo bar & cmd /k")
' Wait until the console application has changed the window title.
Do
' Note: It is assumed that 300 msecs. are enough for the intitial `title` command to take effect.
Thread.Sleep(300)
Loop Until Not proc.MainWindowTitle.StartsWith(customTitle)
' The console application has changed the window title,
' change it back.
SetWindowText(proc.MainWindowHandle, customTitle)
End Sub
End Class
Note:
You may want to implement a timeout for the loop that waits for the window title to change, so that you don't get stuck in an endless loop if your console application fails to launch, for instance.
The assumption is that your console application only changes its window title once, on startup.
Update: You report that this is apparently not true, so you'd need a loop that keeps monitoring for title changes throughout the life time of the console application, which in turn requires creating a dedicated thread that performs this monitoring, so as not to block your GUI.
It is unclear why, but you state that with your PowerShell approach a one-time restoration of the original title is sufficient (in that case, you do properly set the console window title, due to running inside the console); therefore, it's simplest to stick with your PowerShell approach, whose password-logging problem you can avoid by providing the password via an environment variable that you must set in your VB.NET application first, as discussed in this answer to your follow-up question.
Related
I am new at programming. I have been asked to create a code which will locally monitor and logs the name of the application to a text file whenever user starts or executes an application on their system. I don't have much idea about processes, can u help me please?
User stars any application, Log is saved in a text file with time and name of application.
You have to use WMI. Then you can monitor Win32_ProcessStartTrace to be notified when a process starts. Additionally you even can use Win32_ProcessStopTrace to be notified when a process stops.
Your code would look like that:
Public Shared Sub Main()
Dim startWatcher As ManagementEventWatcher = New ManagementEventWatcher(New WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace"))
startWatcher.EventArrived += New EventArrivedEventHandler(startWatcher_EventArrived)
startWatcher.Start()
End Sub
Private Shared Sub startWatcher_EventArrived(ByVal sender As Object, ByVal e As EventArrivedEventArgs)
Dim logString As String ="{0}: Process started: {1}".Format( Now.ToString(), e.NewEvent.Properties("ProcessName").Value)
Using sw As StreamWriter = File.AppendText(YourLogFile)
sw.WriteLine(logString)
End Using
End Sub
I made a file search program in visual studio on windows 10 using .net lang,
My problem starts from form1 with a "dim frm2 as form2 = new form2" call,
after the new form being shown i start a while loop on form1 that feeds data into a listbox in form 2:
1)form1 call form2 and show it.
2)form1 start a while loop.
3)inside the while loop data being fed to listbox1 in frm2
Now everything works on windows 10, the while loop can run as much as it needs without any trouble, the window can loose focus and regain focus without showing any "Not Responding.." msgs or white\black screens..
But, when i take the software to my friend computer which is running windows 7, install all required frameworks and visual studio itself, run it from the .sln in debug mode, and do the same search on the same folder the results are:
1) the while loop runs smoothly as long as form 2 dont loose focus
(something that doesnt happen on windows 10)
2) when i click anywhere on the screen the software loose focus what
causes 1) to happen (black screen\white screen\not responding etc..)
3) if i wait the time needed for the loop and dont click anywhere else
it keeps running smoohtly, updating a label like it should with the
amount of files found.. and even finish the loop with 100% success
(again unless i click somewhere)
Code Example:
Sub ScanButtonInForm1()
Dim frm2 As Form2 = New Form2
frm2.Show()
Dim AlreadyScanned As HashSet(Of String) = New HashSet(Of String)
Dim stack As New Stack(Of String)
stack.Push("...Directoy To Start The Search From...")
Do While (stack.Count > 0)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Dim ScanDir As String = stack.Pop
If AlreadyScanned.Add(ScanDir) Then
Try
Try
Try
Dim directoryName As String
For Each directoryName In System.IO.Directory.GetDirectories(ScanDir)
stack.Push(directoryName)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Next
frm2.ListBox1.Items.AddRange(System.IO.Directory.GetFiles(ScanDir, "*.*", System.IO.SearchOption.AllDirectories))
Catch ex5 As UnauthorizedAccessException
End Try
Catch ex2 As System.IO.PathTooLongException
End Try
Catch ex4 As System.IO.DirectoryNotFoundException
End Try
End If
Loop
End Sub
My conclusions was simple!
1) windows 7 dont support live ui (label) update from a while loop
called from a button...
2) windows 7 could possibly support a new
thread running the same loop
i think mabye if i run all the code in a thread mabye the ui will remain responsive
(by the way the UI is not responsive in windows 10 but i still see
the label refresh and nothing crashes when form loose focus..)
so i know how to do that but i also know that if i do that a thread will not be able to update a listbox or a label in a form and refresh it..
so the thread will need to update an external file with the data and the form2 will need to read that data live from the file but will it make the same problems? i have no idea what to do.. can use some help and tips. THANK YOU!
I must menttion the fact that the loop is working on windows 10 without a responsive UI means i cant click on any button but i can
still see the label refresh BUT on windows 7 everything works the same
UNLESS i click somewhere, no matter where i click on windows the loop
crashes
im using framework 4.6.2 developer
While I'm glad you found a solution, I advise against using Application.DoEvents() because it is bad practice.
Please see this blog post: Keeping your UI Responsive and the Dangers of Application.DoEvents.
Simply put, Application.DoEvents() is a dirty workaround that makes your UI seem responsive because it forces the UI thread to handle all currently available window messages. WM_PAINT is one of those messages which is why your window redraws.
However this has some backsides to it... For instance:
If you were to close the form during this "background" process it would most likely throw an error.
Another backside is that if the ScanButtonInForm1() method is called by the click of a button you'd be able to click that button again (unless you set Enabled = False) and starting the process once more, which brings us to yet another backside:
The more Application.DoEvents()-loops you start the more you occupy the UI thread, which will cause your CPU usage to rise rather quickly. Since every loop is run in the same thread your processor cannot schedule the work over different cores nor threads, so your code will always run on one core, eating as much CPU as possible.
The replacement is, of course, proper multithreading (or the Task Parallel Library, whichever you prefer). Regular multithreading actually isn't that hard to implement.
The basics
In order to create a new thread you only need to declare an instance of the Thread class and pass a delegate to the method you want the thread to run:
Dim myThread As New Thread(AddressOf <your method here>)
...then you should set its IsBackground property to True if you want it to close automatically when the program closes (otherwise it keeps the program open until the thread finishes).
Then you just call Start() and you have a running background thread!
Dim myThread As New Thread(AddressOf myThreadMethod)
myThread.IsBackground = True
myThread.Start()
Accessing the UI thread
The tricky part about multithreading is to marshal calls to the UI thread. A background thread generally cannot access elements (controls) on the UI thread because that might cause concurrency issues (two threads accessing the same control at the same time). Therefore you must marshal your calls to the UI by scheduling them for execution on the UI thread itself. That way you will no longer have the risk of concurrency because all UI related code is run on the UI thread.
To marhsal calls to the UI thread you use either of the Control.Invoke() or Control.BeginInvoke() methods. BeginInvoke() is the asynchronous version, which means it doesn't wait for the UI call to complete before it lets the background thread continue with its work.
One should also make sure to check the Control.InvokeRequired property, which tells you if you already are on the UI thread (in which case invoking is extremely unnecessary) or not.
The basic InvokeRequired/Invoke pattern looks like this (mostly for reference, keep reading below for shorter ways):
'This delegate will be used to tell Control.Invoke() which method we want to invoke on the UI thread.
Private Delegate Sub UpdateTextBoxDelegate(ByVal TargetTextBox As TextBox, ByVal Text As String)
Private Sub myThreadMethod() 'The method that our thread runs.
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), TextBox1, "Status update!") 'We are in a background thread, therefore we must invoke.
Else
UpdateTextBox(TextBox1, "Status update!") 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
'This is the method that Control.Invoke() will execute.
Private Sub UpdateTextBox(ByVal TargetTextBox As TextBox, ByVal Text As String)
TargetTextBox.Text = Text
End Sub
New UpdateTextBoxDelegate(AddressOf UpdateTextBox) creates a new instance of the UpdateTextBoxDelegate that points to our UpdateTextBox method (the method to invoke on the UI).
However as of Visual Basic 2010 (10.0) and above you can use Lambda expressions which makes invoking much easier:
Private Sub myThreadMethod()
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(Sub() TextBox1.Text = "Status update!") 'We are in a background thread, therefore we must invoke.
Else
TextBox1.Text = "Status update!" 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
Now all you have to do is type Sub() and then continue typing code like if you were in a regular method:
If Me.InvokeRequired = True Then
Me.Invoke(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End Sub)
Else
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End If
And that's how you marshal calls to the UI thread!
Making it simpler
To make it even more simple to invoke to the UI you can create an Extension method that does the invoking and InvokeRequired check for you.
Place this in a separate code file:
Imports System.Runtime.CompilerServices
Public Module Extensions
''' <summary>
''' Invokes the specified method on the calling control's thread (if necessary, otherwise on the current thread).
''' </summary>
''' <param name="Control">The control which's thread to invoke the method at.</param>
''' <param name="Method">The method to invoke.</param>
''' <param name="Parameters">The parameters to pass to the method (optional).</param>
''' <remarks></remarks>
<Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object()) As Object
If Parameters IsNot Nothing AndAlso _
Parameters.Length = 0 Then Parameters = Nothing
If Control.InvokeRequired = True Then
Return Control.Invoke(Method, Parameters)
Else
Return Method.DynamicInvoke(Parameters)
End If
End Function
End Module
Now you only need to call this single method when you want to access the UI, no additional If-Then-Else required:
Private Sub myThreadMethod()
'Do some background stuff...
Me.InvokeIfRequired(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
End Sub)
'Do some more background stuff...
End Sub
Returning objects/data from the UI with InvokeIfRequired()
With my InvokeIfRequired() extension method you can also return objects or data from the UI thread in a simple manner. For instance if you want the width of a label:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function() Label1.Width)
Example
The following code will increment a counter that tells you for how long the thread has run:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim CounterThread As New Thread(AddressOf CounterThreadMethod)
CounterThread.IsBackground = True
CounterThread.Start()
Button1.Enabled = False 'Make the button unclickable (so that we cannot start yet another thread).
End Sub
Private Sub CounterThreadMethod()
Dim Time As Integer = 0
While True
Thread.Sleep(1000) 'Wait for approximately 1000 ms (1 second).
Time += 1
Me.InvokeIfRequired(Sub() Label1.Text = "Thread has been running for: " & Time & " seconds.")
End While
End Sub
Hope this helps!
The reason your application is freezing is that you are doing all the work on the UI thread. Check out Async and Await. It uses threading in the background but makes it way easier to manage. An example here:
https://stephenhaunts.com/2014/10/14/using-async-and-await-to-update-the-ui-thread/
I have already implemented context menu to appear when a user right-clicks a file in windows explorer using Registry. The file address will be passed to the application as command lines. Parsing it is no problem.
How can I implement that is similar to "Add To Windows Media Player Playlist"? It does not open another instance of the app but works on the same open window and adds it to a list?
There are 2 ways to do this depending on how your app starts.
Method 1: Using VB App Framework and a MainForm
This is the easiest because you mainly just need to add some code for an Application event. First, add a method to your main form to receive new arguments from subsequent instances of your app:
Public Class MyMainForm ' note the class name of the form
...
Public Sub NewArgumentsReceived(args As String())
' e.g. add them to a list box
If args.Length > 0 Then
lbArgs.Items.AddRange(args)
End If
End Sub
Next:
Open Project Properties
Check the "Make Single Instance" option
At the bottom, click View Application Events
This will open a new code window like any other; Select MyApplication Events in the left drop down; and StartupNextInstance in the right one.
Here, we find the main form and send the command line arguments to the method we created:
Private Sub MyApplication_StartupNextInstance(sender As Object,
e As ApplicationServices.StartupNextInstanceEventArgs) _
Handles Me.StartupNextInstance
Dim f = Application.MainForm
' use YOUR actual form class name:
If f.GetType Is GetType(MyMainForm) Then
CType(f, MyMainForm).NewArgumentsReceived(e.CommandLine.ToArray)
End If
End Sub
Note: Do not try to fish the main form out of Application.OpenForms. A few times I've had it fail to find an open form in the collection, so I have quit relying on it. Application.MainForm is also simpler.
That's it - when a new instance runs, its command line args should be passed to the form and displayed in the listbox (or processed however your method sees fit).
Method 2: Starting From Sub Main
This is more complicated because starting your app from a Sub Main means that the VB Application Framework is not used, which provides the StartupNextInstance event. The solution is to subclass WindowsFormsApplicationBase to provide the functionality needed.
First, give your main form a meaningful name and add something like the NewArgumentsReceived(args As String()) as above.
For those not aware, here is how to start your app from Sub Main():
Add a module named 'Program' to your app
Add a Public Sub Main() to it.
Go to Project -> Properties -> Application
Uncheck Enable Application Framework
Select your new "Sub Main" as the Startup Object
The module can actually be named anything, Program is the convention VS uses for C# apps. The code for Sub Main will be later after we create the class. Much of the following originated from an old MSDN article or blog or something.
Imports Microsoft.VisualBasic.ApplicationServices
Imports System.Collections.ObjectModel
Public Class SingleInstanceApp
' this is My.Application
Inherits WindowsFormsApplicationBase
Public Sub New(mode As AuthenticationMode)
MyBase.New(mode)
InitializeApp()
End Sub
Public Sub New()
InitializeApp()
End Sub
' standard startup procedures we want to implement
Protected Overridable Sub InitializeApp()
Me.IsSingleInstance = True
Me.EnableVisualStyles = True
End Sub
' ie Application.Run(frm):
Public Overloads Sub Run(frm As Form)
' set mainform to be used as message pump
Me.MainForm = frm
' pass the commandline
Me.Run(Me.CommandLineArgs)
End Sub
Private Overloads Sub Run(args As ReadOnlyCollection(Of String))
' convert RO collection to simple array
' these will be handled by Sub Main for the First instance
' and in the StartupNextInstance handler for the others
Me.Run(myArgs.ToArray)
End Sub
' optional: save settings on exit
Protected Overrides Sub OnShutdown()
If My.Settings.Properties.Count > 0 Then
My.Settings.Save()
End If
MyBase.OnShutdown()
End Sub
End Class
Note that the three main things the App Framework can do for us ("Enable XP Styles", "Make Single Instance" and "Save Settings on Exit") are all accounted for. Now, some modifications to Sub Main:
Imports Microsoft.VisualBasic.ApplicationServices
Imports System.Collections.ObjectModel
Module Program
' this app's main form
Friend myForm As MyMainForm
Public Sub Main(args As String())
' create app object hardwired to SingleInstance
Dim app As New SingleInstanceApp()
' add a handler for when a second instance tries to start
' (magic happens there)
AddHandler app.StartupNextInstance, AddressOf StartupNextInstance
myForm = New MyMainForm
' process command line args here for the first instance
' calling the method you added to the form:
myForm.NewArgumentsReceived(args)
' start app
app.Run(myForm)
End Sub
' This is invoked when subsequent instances try to start.
' grab and process their command line
Private Sub StartupNextInstance(sender As Object,
e As StartupNextInstanceEventArgs)
' ToDo: Process the command line provided in e.CommandLine.
myForm.NewArgumentsReceived(e.CommandLine.ToArray)
End Sub
End Module
The SingleInstanceApp class can be reused with any Sub Main style app, and the code in that method is mainly a copy-paste boilerplate affair except for perhaps the form reference and actual name of the NewArgumentsReceived method.
Testing
Compile the app, then using a command window, send some commandline arguments to the app. I used:
C:\Temp>singleinstance "First Inst" apple bats cats
This starts the app as normal, with the arguments shown. Then:
C:\Temp>singleinstance "Next Inst" ziggy zoey zacky
C:\Temp>singleinstance "Last Inst" 111 222 3333
It doesnt matter which approach you use - they both work the same. The Result:
Note that depending on security settings, your firewall may request permission for apps using either method to connect to other computers. This is a result of how an instance sends or listens for the arguments from others. At least with mine, I can deny permission to connect and everything still works fine.
#Plutonix solution is quite efficient and elegant.
However if you program goes through multiple Forms i.e. if the Main Form can change during program execution, for example if you have a login form and then a main form, or a sequence of non-modal forms, Application.MainForm won't always be the same form and may not be known beforehand (hard coded).
Plutonix code assumes it is known and hard codes it.
In this case, you may want to be able to receive the NewArguments at all times, in whichever form is active at the time in your application.
There are 2 solutions to extend Plutonix solution:
1) Repeatedly force Application.MainForm to a specific form in code (I haven't tested this but Application.MainForm is Read/Write so it could work).
2) The most elegant is to implement an Interface on all forms that can possibly become the MainForm:
Create the Basic interface:
Public Interface INewArgumentsReceived
Sub NewArgumentsReceived(args As String())
End Interface
Modify #Plutonix code for MyApplication_StartupNextInstance to:
Private Sub MyApplication_StartupNextInstance(sender As Object, e As ApplicationServices.StartupNextInstanceEventArgs) Handles Me.StartupNextInstance
Dim f = Application.MainForm
If f.GetType.GetInterfaces.Contains(GetType(INewArgumentsReceived)) Then
CType(f, INewArgumentsReceived).NewArgumentsReceived(e.CommandLine.ToArray)
Else
MsgBox("The current program state can't receive new requests.",, vbExclamation)
End If
Now on all possible forms that can become the Main Form, implement the INewArgumentsReceived Interface:
Public Class FormA: Implements INewArgumentsReceived
Public Sub NewArgumentsReceived(args As String()) Implements INewArgumentsReceived.NewArgumentsReceived
MsgBox("Got new arguments")
End Sub
The other advantage of using the Interfaces is that we can check if the current Application.MainForm implements it and is able to receive it.
If the current Application.MainForm does not implement the Interface it fails gracefully with an informational message.
I have written an application with the following sub main:
Public Sub Main()
Dim Value As String() = Environment.GetCommandLineArgs
Dim F As Form
Select Case Value.Last.ToLower
Case "-character"
F = New frmCharacterSheet
Case "-viewer"
F = New frmClient
Case Else
F = New frmCombat
End Select
Application.Run(F)
End Sub
This is because I want to be able to install my app with three different startup modes based on the command line. I did have a form that did this, but this has made error trapping very hard because the main form just reports the error.
This console seems to work well but I don't want the user to see the black console screen at startup.
I have searched for the answer but most solutions are 'switch back to a windows forms application'. I don't want to do this though for the above reason. (I cannot use application.run(f) in a winforms start situation because I get a threading error.
I need to know either how to hide the console window, or alternatively how to code a main menu that will launch one of the other three forms (but making them the startup form).
Any help would be appreciated....
Try:
Private Declare Auto Function ShowWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As Boolean
Private Declare Auto Function GetConsoleWindow Lib "kernel32.dll" () As IntPtr
Private Const SW_HIDE As Integer = 0
Sub Main()
Dim hWndConsole As IntPtr
hWndConsole = GetConsoleWindow()
ShowWindow(hWndConsole, SW_HIDE)
'continue your code
End Sub
It has a side effect that the window will be shown and then immediately hidden
valter
"or alternatively how to code a main menu that will launch one of the other three forms (but making them the startup form)."
Start with a standard WinForms Project and use the Application.Startup() event. From there you can check your startup parameters and then dynamically change the Startup form by assigning your desired instance to "My.Application.MainForm". This will cause that form to load as if it was the one originally assigned to the "Startup Form" entry.
Click on Project --> Properties --> Application Tab --> "View Application Events" Button (bottom right; scroll down).
Change the Left dropdown from "(General)" to "(MyApplication Events)".
Change the Right dropdown from "Declarations" to "Startup".
Simplified code:
Namespace My
' The following events are available for MyApplication:
'
' Startup: Raised when the application starts, before the startup form is created.
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
' UnhandledException: Raised if the application encounters an unhandled exception.
' StartupNextInstance: Raised when launching a single-instance application and the application is already active.
' NetworkAvailabilityChanged: Raised when the network connection is connected or disconnected.
Partial Friend Class MyApplication
Private Sub MyApplication_Startup(sender As Object, e As ApplicationServices.StartupEventArgs) Handles Me.Startup
If True Then
My.Application.MainForm = New Form1 ' <-- pass your desired instance to MainForm
End If
End Sub
End Class
End Namespace
Just go to Project Properties> Application> Application Type> and select Windows Forms Application
At this point your ConsoleApplication turns totally invisible, with no User-Interface.
I just want to add another solution although Idle_Mind has already provided an excellent one. This demonstrates that you can use Application.Run(Form) inside a WinForms app.
Public Class Form1
Private Shared applicationThread As New Threading.Thread(AddressOf Main)
Private Shared Sub Main()
Dim myForm As Form
Dim config = 2 ' if 3, will run Form3
Select Case config
Case 2
myForm = New Form2
Case 3
myForm = New Form3
Case Else
MessageBox.Show("Bad config!")
Exit Sub
End Select
Application.Run(myForm)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
applicationThread.Start()
' immediately dispose Form1 so it's not even shown
Dispose()
End Sub
End Class
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