I'd like to use a running instance of my application (a single instance application) to run a new commandline...
I've heard about mutexes and IPC mechanisms but I don't know how to use it.
Explanation :
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
MsgBox(Environment.CommandLine)
End Sub
End Class
Example :
I launch the app with a file as argument, it shows the MsgBox and I let it run. If I launch once again the app with a file as argument, it won't show the MsgBox...
How can I show it with the new commandline ?
Regards, Drarig29.
In VB.NET you can make your application single instance from the project properties page. Check the "Make single instance application" option, then click the "View Application Events" button:
In the ApplicationEvents.vb class, add a handler for StartupNextInstance - this will be called when the application is already running and you start it again. You can call a method on your main form:
Namespace My
Partial Friend Class MyApplication
Private Sub MyApplication_StartupNextInstance(sender As Object, e As ApplicationServices.StartupNextInstanceEventArgs) Handles Me.StartupNextInstance
' Handle arguments when app is already running
If e.CommandLine.Count > 0 Then
' Pass the argument to the main form
Dim form = TryCast(My.Application.MainForm, Form1)
form.LoadFile(e.CommandLine(0))
End If
End Sub
End Class
End Namespace
In your main form, you can pass the initial command line arguments, and handle the subsequent ones, with a common method:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
' Handle arguments from the initial launch
Dim args = Environment.GetCommandLineArgs()
If args.Length > 1 Then
LoadFile(args(1))
End If
End Sub
Public Sub LoadFile(filename As String)
MessageBox.Show(filename)
End Sub
End Class
Related
Currently, I'm using the 'make app single instance'(MyApplication_StartupNextInstance event) in VB (.net framework win forms) to pass command line arguments from multiple instances to the main form. I'm adding this to a list of string and then passing this list to the next function/ sub. The list captures all the arguments if I add a message box just before calling the next function but then when there's no msgbox, not all the arguments are captured.
I've tired using timers/delays which is a hit and a miss. Tried using timed msgbox that disapper after couple secs, which is the same.
How can I make it wait till all the instances have run and then proceed to the next line of code?
'ApplicationEvents.vb
Private Sub MyApplication_StartupNextInstance(sender As Object, e As ApplicationServices.StartupNextInstanceEventArgs) Handles Me.StartupNextInstance
Dim f = Application.MainForm
If f.GetType Is GetType(my_app_name) Then
CType(f, my_app_name).NewArgumentsReceived(e.CommandLine(0))
End If
End Sub
'my app has the below codes
Public Sub NewArgumentsReceived(args As String)
mylist.Add(args)
End Sub
Private Sub SomeForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
mylist.Add(arg) 'arg is for main form 'args' is for instances
'this is where I want to wait until all the other instances have completed
Anotherfunction(mylist)
End Sub
As I mentioned in my comments, the StartupNextInstance event can be raised any time so you should design your app to react to it at any time. The initial instance has no idea how many subsequent instances there will be or when they will start so it should simply react to them one at a time, whenever they occur. Here's an example of a single instance application where the main form is an MDI parent and the commandline arguments are text file paths that each get opened in a child window.
Child form:
Imports System.IO
Public Class ChildForm
Public Property FilePath As String
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBox1.Text = File.ReadAllText(FilePath)
End Sub
End Class
Parent form:
Public Class ParentForm
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim args = Environment.GetCommandLineArgs()
If args.Length > 1 Then
LoadChildForm(args(1))
End If
End Sub
Public Sub LoadChildForm(filePath As String)
Dim child As New ChildForm With {.MdiParent = Me,
.FilePath = filePath}
child.Show()
End Sub
End Class
Application events:
Imports Microsoft.VisualBasic.ApplicationServices
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_StartupNextInstance(sender As Object, e As StartupNextInstanceEventArgs) Handles Me.StartupNextInstance
Dim args = e.CommandLine
If args.Count > 0 Then
DirectCast(MainForm, ParentForm).LoadChildForm(args(0))
End If
End Sub
End Class
End Namespace
In this case, the initial instance doesn't wait for anything. It just goes ahead and does what it does with its own commandline argument. It has no idea if there will be any subsequent instances or, if there are, how many there will be and when they will start, so it would have no idea what it was waiting for. Any time another instance is started, the initial instance reacts then, using the commandline argument provided.
I have a problem with Drag and Drop file Code, I try many methods but I failed this is my Code.
Module Module1
Sub Main(ByVal args() As String)
Dim pathstring As String
If args.Length > 0 Then
Dim path = args(0)
pathstring = path
End If
End Sub
End Module
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBox1.Text = pathstring
End Sub
End Class
Above Code working fine with Console Application, but not in WindowsApplication
I want to get filename into Textbox1 Control before loading Form.
You need to learn how WinForms apps work. That Main method isn't even being executed because it's not the entry point for the app. Unless you disable the Application Framework, you need to handle the app's Startup event if you want to do something before creating the startup form. That said, you can get the commandline arguments anywhere, any time by calling Environment.GetCommandLineArgs.
This is my situation, there are 2 Classes and my main form Form1:
Class1: has a method doSomethingAndCall(callback) which creates a new thread
Class2: has dynamic created controls with a button that fires Class1.doSomethingAndCall(newCallback)
in code it looks like this (it starts at Class2.Button_Click):
Class Class1
public shared sub doSomethingAndCallAsync(state as object)
Console.WriteLine(Form1.InvokeRequired) 'output: false
Console.WriteLine(Form1.IsHandleCreated) 'output: false
Form1.Invoke(state.callback) 'throws System.InvalidOperationException
end sub
public shared sub doSomethingAndCall(callback as object)
System.Threading.ThreadPool.QueueUserWorkItem(AddressOf doSomethingAndCallAsync, New With {.callback = callback})
end sub
End Class
Class Class2
Public Delegate Sub doSomethingDelegate()
Public Sub doSomething()
Console.WriteLine("success!")
End Sub
Public Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Class1.doSomethingAndCall(New doSomethingDelegate(AddressOf doSomething))
End Sub
End Class
The exact exception I get is:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
and as I can see the console.WriteLine in line 4 shows me that the form is realy not created. So I added this handlers, and now it get's really confusing:
Private Sub Form1_HandleCreated(sender As Object, e As System.EventArgs) Handles Me.HandleCreated
Console.WriteLine("Handle created") 'Output: Handle created, when running program
End Sub
Private Sub Form1_HandleDestroyed(sender As Object, e As System.EventArgs) Handles Me.HandleDestroyed
Console.WriteLine("Handle destroyed") 'Will never Output!
End Sub
So it's created and never destroyed but if i click the button it's nevertheless not avaible? -Can anyone explain me what is going on and how to call a callback correct, thanks!
The instance of My.Forms.Form1 aka. Form1 will be different in each thread. You need a handle to the correct instance. Drop a button onto your Form1 and add the following code:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Wrong())
Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Correct(Me))
End Sub
End Class
Public Class Class1
Public Shared Sub Wrong()
Debug.WriteLine(String.Format("(Other thread, wrong) InvokeRequired={0}, IsHandleCreated={1}", Form1.InvokeRequired, Form1.IsHandleCreated))
End Sub
Public Shared Sub Correct(instance As Form1)
Debug.WriteLine(String.Format("(Other thread, correct) InvokeRequired={0}, IsHandleCreated={1}", instance.InvokeRequired, instance.IsHandleCreated))
End Sub
End Class
Output
(Other thread, correct) InvokeRequired=True, IsHandleCreated=True
(Other thread, wrong) InvokeRequired=False, IsHandleCreated=False
I am experiencing a strange behaviour when using RightToLayout layout:
My form automatically closes.
I have created a simple project to reproduce the problem:
Create a new VB.NET project in VS2012 and add forms "Form1" and "Form2" to it.
Set "Form1" to be the start form.
Then add the following code to "Form1":
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Hide()
Dim f As New Form2
f.ShowDialog()
MessageBox.Show("After dialog closed.")
modControls.setFormRTL(Me) 'This line causes the Form2 to automatically close. Why??? This line should only be processed AFTER the dialog has been shown and closed
End Sub
End Class
Add the following code to "Form2":
Public Class Form2
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles Me.Load
modControls.setFormRTL(Me)
End Sub
End Class
Add a module with the name "modControls" to the project.
Add the following code to it:
Module modControls
Public Sub setFormRTL(ByVal uForm As Form)
uForm.RightToLeft = RightToLeft.Yes
End Sub
End Module
Without the setFormRTL, my project works perfectly fine, but with it, "Form2" automatically closes down. You can see this because the messagebox is shown.
When I remove the line
modControls.setFormRTL(Me)
from Form1 load, it works fine again.
Yes, I really mean from "Form1", not from "Form2"!!!
Now this is really strange because it should not matter at all because this line is not processed before the dialog is closed.
I hope somebody understands what I mean.
Can anybody shed some light on what might be happening here?
Yes, you'll get unexpected behavior if you set the RightToLeft property anywhere other than a control's constructor (in VB.NET parlance, that's the New method).
In fact, the constructor is where you should set all of the properties of a form or control object. If you come from VB 6, it might seem logical to do it in the Load event handler, but that's not idiomatic .NET and, as you've discovered, can cause problems when initializing certain properties.
The technical reason for this is that certain properties (like RightToLeft) can actually only be set on the native window (which is how the Form objects used in the .NET world are implemented behind the scenes) at the time that it is created. When you attempt to change the property, the framework code actually has to destroy and then re-create the native window with the new property values.
Change the code to look like this instead:
Public Class Form1
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
modControls.SetFormRtl(Me)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Hide()
Dim f As New Form2
f.ShowDialog()
MessageBox.Show("After dialog closed.")
End Sub
End Class
Public Class Form2
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
modControls.SetFormRtl(Me)
End Sub
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles Me.Load
End Sub
End Class
Speaking of non-idiomatic .NET code:
Methods should all be Pascal cased by convention. That means your setFormRTL method should be named SetFormRtl.
A helper function that sets the properties of an object just seems wrong to me. If anything, that's just bad OO design. If you want this method to be available for all of your Form objects, derive a custom form class and add this method (or even do the desired initialization in the constructor). Either way, all forms that you derive from this custom form object will inherit the functionality. Example:
Public Class MyCustomForm : Inherits System.Windows.Forms.Form
Public Sub New()
MyBase.New()
Me.SetRtl()
End Sub
Public Sub SetRtl()
Me.RightToLeft = RightToLeft.Yes
End Sub
End Class
Public Class Form1 : Inherits MyCustomForm
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Hide()
Dim f As New Form2
f.ShowDialog()
MessageBox.Show("After dialog closed.")
End Sub
End Class
Public Class Form2 : Inherits MyCustomForm
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles Me.Load
End Sub
End Class
can you try this? all i am doing here is setting the RTL before the form is displayed.
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Hide()
Dim f As New Form2
modControls.setFormRTL(f)
f.ShowDialog()
MessageBox.Show("After dialog closed.")
modControls.setFormRTL(Me)
End Sub
End Class
and remove code form load event of form 2
I have two classes.
Public Class MainForm
Private Project As clsProject
Private Sub btnDo_Click
...
Backgroundworker.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
and two methods inside MainForm
Public Shared Sub setLabelTxt(ByVal text As String, ByVal lbl As Label)
If lbl.InvokeRequired Then
lbl.Invoke(New setLabelTxtInvoker(AddressOf setLabelTxt), text, lbl)
Else
lbl.Text = text
End If
End Sub
Public Delegate Sub setLabelTxtInvoker(ByVal text As String, ByVal lbl As Label)
end class
I want to update the labels of MainForm from the clsProject constructor.
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..", MainForm.lblProgress)
but it does not update them.
What am I doing wrong?
The problem is that you are using the global MainForm instance to access the label in a background thread here:
Public Class clsProject
Public Sub New()
' When accessing MainForm.Label1 on the next line, it causes an exception
MainForm.setLabelTxt("HERE!", MainForm.Label1)
End Sub
End Class
It's OK to call MainForm.setLabelTxt, since that is a shared method, so it's not going through the global instance to call it. But, when you access the Label1 property, that's utilizing VB.NET's trickery to access the global instance of the form. Using the form through that auto-global-instance variable (which always shares the same name as the type) is apparently not allowed in non-UI threads. When you do so, it throws an InvalidOperationException, with the following error message:
An error occurred creating the form. See Exception.InnerException for details. The error is: ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.
I'm guessing that the reason you are not seeing the error is because you are catching the exception somewhere and you are simply ignoring it. If you stop using that global instance variable, the error goes away and it works. For instance, if you change the constructor to this:
Public Class clsProject
Public Sub New(f As MainForm)
' The next line works because it doesn't use the global MainForm instance variable
MainForm.setLabelTxt("HERE!", f.Label1)
End Sub
End Class
Then, in your MainForm, you would have to call it like this:
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject(Me) ' Must pass Me
End Sub
Using the global instance from the background thread is not allowed, but when we use the same label from the background thread, without going through that global variable it works.
So it's clear that you cannot use the global MainForm variable from a background thread, but what may not be clear is that it's a bad idea to use it ever. First, it's confusing because it shares the same name as the MainForm type. More importantly, though, it is a global variable, and global state of any kind is almost always bad practice, if it can be avoided.
While the above example does solve the problem, it's still a pretty poor way of doing it. A better option would be to pass the setLabelTxt method to the clsProject object or even better have the clsProject simply raise an event when the label needs to be changed. Then, the MainForm can simply listen for those events and handle them when they happen. Ultimately, that clsProject class is probably some sort of business class which shouldn't be doing any kind of UI work anyway.
You cannot execute any action on GUI-elements from the BackgroundWorker directly. One way to "overcome" that is by forcing the given actions to be performed from the main thread via Me.Invoke; but this is not the ideal proceeding. Additionally, your code mixes up main form and external class (+ shared/non-shared objects) what makes the whole structure not too solid.
A for-sure working solution is relying on the specific BGW methods for dealing with GUI elements; for example: ProgressChanged Event. Sample code:
Public Class MainForm
Private Project As clsProject
Public Shared bgw As System.ComponentModel.BackgroundWorker
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
bgw = BackgroundWorker1 'Required as far as you want to called it from a Shared method
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
Public Shared Sub setLabelTxt(ByVal text As String)
bgw.ReportProgress(0, text) 'You can write any int as first argument as far as will not be used anyway
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.Label1.Text = e.UserState 'You can access the given GUI-element directly
Me.Label1.Update()
End Sub
End Class
Public Class clsProject
Public Sub New()
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..")
End Sub
End Class
Try:
Me.Invoke(...)
instead of lbl.Invoke(.... I had to do this. This is my implementation:
Delegate Sub SetTextDelegate(ByVal args As String)
Private Sub SetTextBoxInfo(ByVal txt As String)
If txtInfo.InvokeRequired Then
Dim md As New SetTextDelegate(AddressOf SetTextBoxInfo)
Me.Invoke(md, txt)
Else
txtInfo.Text = txt
End If
End Sub
And this worked for me.