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.
Related
I am creating a application to be used with a touch panel device. The touch panel device comes with a standard windows OSK (On screen Keyboard). Whilst testing its been concluded that the standard OSK is to large and too complex for what we need it in. So I have built my own OSK. some of the feilds though only requier numeric inputs so I though of futher simplifying the process by creating a new form which hosts a numeric pad. so far this is all working. the idea is then to have the app which ask for diffrent inputs then to trigger the OSK application, say that the user wants to enter a phonenumber in one textbox I then want to start the OSK app using a parameter that trigers the OSK to start the NumericForm form first... this too I have working but the thing I can't get right is to hide the AlphabetForm I have tried the following method but am a little stumpt on how to get this right
In short its the Me.hide which isnt working as expected?
Private Sub AlphabetForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
#Region "Recive startup parameters (if any)"
Try
Dim OSKParameters As String = Command()
If OSKParameters = "OSKNUM" Then
NumericForm.Show()
Me.Hide()
Else
ShiftSelect = 0
End If
Catch ex As Exception
'Do nothing
End Try
#End Region
End Sub
Three possible setups, as described in comments:
► Using the Application Framework, override OnStartup and set Application.MainForm to a Form object determined by a command-line argument:
To generate ApplicationEvents.vb, where Partial Friend Class MyApplication is found, open the Project properties, Application pane, click the View Application Events Button: it will add the ApplicationEvents.vb file to the Project if it's not already there.
Imports Microsoft.VisualBasic.ApplicationServices
Partial Friend Class MyApplication
'[...]
Protected Overrides Function OnStartup(e As StartupEventArgs) As Boolean
Application.MainForm = If(e.CommandLine.Any(
Function(cmd) cmd.Equals("OSKNUM")), CType(NumericForm, Form), AlphabetForm)
Return MyBase.OnStartup(e)
End Function
'[...]
End Class
► Disabling Application Framework, to start the Application from Sub Main().
In the Project->Properties->Application pane, deselect Enable application framework and select Sub Main() from the Startup form dropdown.
If Sub Main() doesn't exist yet, it can be added to a Module file. Here, the Module is named Program.vb.
Module Program
Public Sub Main(args As String())
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(True)
Dim startForm as Form = If(args.Any(
Function(arg) arg.Equals("OSKNUM")), CType(New NumericForm(), Form), New AlphabetForm())
Application.Run(startForm)
End Sub
End Module
► If the OSK can be moved to UserControls, similar to what jmcilhinney suggested, run the default container Form and select the UserControl to show using the same logic (inspecting the command-line arguments):
Public Class AlphabetForm
Public Sub New()
InitializeComponent()
Dim args = My.Application.CommandLineArgs
Dim uc = If(args.Any(
Function(arg) arg.Equals("OSKNUM")), CType(New NumericUC(), UserControl), New AlphabetUC())
Me.Controls.Add(uc)
uc.BringToFront()
uc.Dock = DockStyle.Fill
End Sub
End Class
VB.NET 2012
My Startup Object is set to (Sub Main). The app needs to collect a few different sets of data before the primary form is loaded
This article http://msdn.microsoft.com/en-us/library/ms235406(v=vs.110).aspx mentions
In Main, you can determine which form is to be loaded first when the program starts
But it never explains how to show the form
If I use ShowDialog the application terminates when mainView’s Visible property is set to False or when mainView is Hidden
Module Module1
Public mainView As New Form1
Public Sub Main()
' initialization code
mainView.ShowDialog() ' this works until I need to hide mainView, ShowDialog returns and the app terminates
End Sub
End Module
If I use Show the application immediately falls out of Sub Main and terminates
Module Module1
Public mainView As New Form1
Public Sub Main()
' initialization code
mainView.Show() ' this doesn't work at all, the app terminates as soon as Main is executed
End Sub
End Module
The primary form needs to exist the entire time the app is running.
I need sections of code to run before the primary form is displayed.
I need to be able to hide the primary at times and show it at others.
What is the best approach to achieve these requirements?
This seems to work perfectly. I had read about the messaging loop but it didn’t seem to work until I tried it like below, thanks LarsTech
Module Module1
Public mainView As Form1
Public Sub Main()
' initialization code
''...
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
mainView = New Form1
Application.Run(mainView) ' I can reference 'mainView' from anywhere in my app, toggle its Visible property etc.
End Sub
End Module
The startup sequence and handling of form instances is quite weird in VB.NET. When you refer to a form as for example Form2.Textbox1.Text = "Foo" then the CLR automatically uses the instance of the form that is created in the background and can be directly acessed through My.Forms.Form2.
I am using a WinForms application that starts up through a custom Sub Main(). Here the application is run by calling Application.Run(frmMain).
Since I have multiple forms that needs initilializing I would like to know exactly at which point the real form instances are created. Are they all created at Application.Run or are they only created when I call Form2.Show()? My startup sequence is as follows right now:
Sub Main()
Sub Main() calls Application.Run(frmMain)
frmMain.Load calls frmNotMain.Show()
I can nowwhere find a line like My.Forms.frmNotMain = New frmNotMain, so it's not apparent where the instance is created.
Thank you in advance.
According to MSDN:
When you access Form through My.Forms.Form1, the factory method checks to see if an instance of Form1 is already open. If it is, that instance is returned. Otherwise, an instance of Form1 is created and returned.
So essentially it is created and Sub New called just before it is shown (not created somewhere and held until needed). The link includes this code showing how it creates those default instances:
'Code ... generated by the compiler
Public m_Form1 As Form1
Public Property Form1() As Form1
Get
m_Form1 = Create__Instance__ (Of Form1)(m_Form1)
Return m_Form1
End Get
Set(ByVal Value As Form1)
If Value Is m_Form1
Return
End If
If Not Value Is Nothing Then
Throw New ArgumentException("Property can only be set to Nothing.")
End If
Dispose__Instance__ (Of Form1)(m_Form1)
End Set
End Property
However, you are talking about the default ("weird") instance method which is ill-advised to begin with. This largely exists to provide compatibility with VB6 type code where you did just do myForm.Show() to instance and show a form (and probably for tinkerers who do not really understand instancing or OOP).
Forms are classes and should be treated as such by explicitly creating instances; so, generally:
Dim frm As New frmMain ' NEW creates the instance
frm.Show
You can set a breakpoint on InitializeComponent in the form's Sub New to see when it is invoked. To create a global reference to it, like you might with any other class:
Friend frmMain As MainForm ' no instance yet
Friend myMain As MainClass
Public Sub Main
' do this before any forms are created
Application.EnableVisualStyles()
myMain = New MainClass()
myMain.DoStuff()
frmMain = New MainForm() ' instanced (NEW)
Application.Run(frmMain)
End Sub
Likewise:
Dim frm2 = New frmNotMain ' declare and instance
' short for:
Dim frm2 As frmNotMain ' declare frm2
frm2 = New frmNotMain ' create instance
frm2.Show
In all cases, Sub New for your form(s) would be called when you use the New operator to create a New form. VB tries to make this clear thru the repeated use of New, but with the default instance all that is actually tucked away in the form factory.
Running into an odd issue with tasks and delegates. Code in question is running under dotNET 4.5.1, VS2013. On the form's code I have a sub that updates a grid, it checks to see if an invoke is required, and if it is it calls a delegate. When a task runs that's called in the same module, it works as expected, no problems. Threaded or not, the grid updates properly.
However, if the same thing is called from another module, the delegate never gets called and the visual component doesn't get updated. Just a watered down bit of pseudocode to clarify..
In the form's module:
Private Delegate Sub DoWhateverDelegate(ByVal _____)
Public Sub DoWhatever(ByVal _____)
If MyComponent.InvokeReqired
Dim Delegated As New DoWhateverDelegate(AddressOf DoWhatever)
Debug.Print("The delegate fired")
Invoke(Delegated, _____)
Else
' .. carry on as usual ..
End If
End Sub
Elsewhere....
Task.Run(Sub()
' .. various things I'd rather not block the UI thread with ..
DoWhatever()
End Sub)
Works fine. I can do Task.Run__ that calls DoWhatever and it's all happy and good. However if I create a task in another module and call DoWhatever, it doesn't fire the delegate and that visual component doesn't update. The code is identical, in the same module it works, in another module it does not.
I'm probably missing something blatantly obvious.. anyone care to point out my mistake? Thanks.
Edit -- just to clarify, that other module is just code, there's only one form in the entire solution. It's created at program startup automatically, there is no other form creation going on.
Should be a thread-specific issue. Check this:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
foo.DoSomething()
End Sub
End Class
The class with the delegate:
Public Class foo
Public Shared Sub DoSomething()
Task.Run(Sub() UpdateText())
End Sub
Public Delegate Sub UpdateTextDelegate()
Public Shared Sub UpdateText()
Dim f = Form1
'Dim f As Form1 = Application.OpenForms("Form1")
If f.InvokeRequired Then
Dim d As UpdateTextDelegate = AddressOf UpdateText
f.Invoke(d)
Else
f.TextBox1.Text = "Hi"
End If
End Sub
End Class
Run the code and the textbox will not be updated. Use the second f=.... (that one that take a reference from OpenForms) and it will be updated.
If you just try to access the default instance and you are outside the UI-thread, a new instance of the form will be created. That means, the content IS updated, but because that form is not shown, you will not see it.
NOTE I do NOT advise to solve your problem, by using OpenForms. I'd advise to correctly instantiate forms!
Add a new module/class to your code:
Module Startup
Public MyForm1 As Form1
Public Sub main()
MyForm1 = New Form1
Application.Run(MyForm1)
End Sub
End Module
Go to project properties -> application. Disable application framework and choose Sub Main as your start object. In the app, access your form via MyForm1 - or whatever you want to name it. Problem should be gone then.
in vb 2008 express this option is available under application properties. does anyone know what is its function? does it make it so that it's impossible to open two instances at the same time?
does it make it so that it's impossible to open two instances at the same time?
Yes.
Why not just use a Mutex? This is what MS suggests and I have used it for many-a-years with no issues.
Public Class Form1
Private objMutex As System.Threading.Mutex
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Check to prevent running twice
objMutex = New System.Threading.Mutex(False, "MyApplicationName")
If objMutex.WaitOne(0, False) = False Then
objMutex.Close()
objMutex = Nothing
MessageBox.Show("Another instance is already running!")
End
End If
'If you get to this point it's frist instance
End Sub
End Class
When the form, in this case, closes, the mutex is released and you can open another. This works even if you app crashes.
Yes, it makes it impossible to open two instances at the same time.
However it's very important to be aware of the bugs. With some firewalls, it's impossible to open even one instance - your application crashes at startup! See this excellent article by Bill McCarthy for more details, and a technique for restricting your application to one instance. His technique for communicating the command-line argument from a second instance back to the first instance uses pipes in .NET 3.5.
Dim _process() As Process
_process = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName)
If _process.Length > 1 Then
MsgBox("El programa ya está ejecutandose.", vbInformation)
End
End If
I found a great article for this topic: Single Instance Application in VB.NET.
Example usage:
Module ModMain
Private m_Handler As New SingleInstanceHandler()
' You should download codes for SingleInstaceHandler() class from:
' http://www.codeproject.com/Articles/3865/Single-Instance-Application-in-VB-NET
Private m_MainForm As Form
Public Sub Main(ByVal args() As String)
AddHandler m_Handler.StartUpEvent, AddressOf StartUp ' Add the StartUp callback
m_Handler.Run(args)
End Sub
Public Sub StartUp(ByVal sender As Object, ByVal event_args As StartUpEventArgs)
If event_args.NewInstance Then ' This is the first instance, create the main form and addd the child forms
m_MainForm = New Form()
Application.Run(m_MainForm)
Else ' This is coming from another instance
' Your codes and actions for next instances...
End If
End Sub
End Module
Yes you're correct in that it will only allow one instance of your application to be open at a time.
There is even a easier method:
Use the following code...
Imports System.IO
On the main form load event do the following:
If File.Exist(Application.StartupPath & "\abc.txt") Then
'You can change the extension of the file to what ever you desire ex: dll, xyz etc.
MsgBox("Only one Instance of the application is allowed!!!")
Environment.Exit(0)
Else
File.Create(Application.StartupPath & "\abc.txt", 10, Fileoptions.DeleteonClose)
Endif
This will take care of single instances as well as thin clients, and the file cannot be deleted while the application is running. and on closing the application or if the application crashes the file will delete itself.