I am developing a Windows Forms application.
I have four forms which is inherited from Baseform in another project.
In all four forms I am using a label to show some transaction count based on network status. I have implemented a thread which gets the active form of application and setting up the text. The code works fine if application screen is active. If I minimize and open any other application, I am getting an null error exception.
How do I get the active form of an application?
Private Sub StartThread()
pollThread =New Thread(AddressOf PollfileStatus)
pollThread.IsBackground =True
running =True
pollThread.Start()
End Sub
Private Sub PollfileStatus()
While (running)
Try
For Each e As Control In Me.ActiveForm.Controls
If (e.Name = "pbStatus") Then
e.Invoke(New SetTextCallback(AddressOf Settext),
New Object() {e, 10})
End If
Next
Catch ex As Exception
Throw New ApplicationException(ex.Message)
End Try
Thread.Sleep(6000)
End While
End Sub
Understandably Me.ActiveForm is empty, since minimizing your form makes it inactive. You have two options:
Test if Me.ActiveForm is empty, and if so, do not update the label. This will effectively 'pause' label updates, until the user restores the window again.
Create a property on your class that contains your thread, to pass a reference of the form you have to update. This way the label on the given form will update even if it is not the active form. This might be a better option, it will update even if the form is inactive in the background, for example.
You should look into the Application.OpenForms shared property. It contains a collection of all forms in your application.
EDIT: Since you're working with .net 1.1 and don't have access to Application.OpenForms, here are some suggestions:
Implement your own shared class (module) that contains an ArrayList of forms. You could then create a base class inheriting from Form, handle the Load event to add the current form to the list of forms and the Closed event to remove it. Once you have that class, have all your real forms derive from it. BTW, it's (almost) how it is done in .Net 2.0.
Turn the problem around and have the working thread raise events when the values change that you can handle in each form to update it.
An old question, but I'd thought I'd offer a suggestion as well: you could implement a Public Shared CurrentForm As Form that you set in the Form.Activated event of a base form class all your forms inherit from. From the thread you could then use CurrentForm to access the desired form.
This is very easy like this in vb.net
Dim MYFormName As String = Me.Name.ToString
You have many solutions for this situation:
first of all you should consider checking if the current form is null before proceeding in any action.
second: if that condition passes, a good approach to store the last active form's reference.
This might work but I cannot test it right now.
For Each frm As Form In Application.OpenForms
If frm.ContainsFocus Then
Return frm
End If
Next
Related
I understand what the InitializeComponent() does in the background - it creates the form and all the controls that were added in the designer. However, what I have not found is WHEN you would add a Public Sub New() constructor to a form and what you would add to that versus the Load() sub.
I did find that this is a good place to put my BackColor and BackGroundColor settings since they are user-preference and stored in Settings. What else should I put in there? I have always used the Load() sub to do any work with controls. Examples: Adding handlers, loading comboboxes, setting DGV columns, loading DataTables) Should I be doing that in the New constructor? Does it make any difference?
The Load event is fired just before the form is displayed for the first time.
... but that might not be right away. It's possible — common, even, in certain environments — to create a form and have it available to code long before the form is ever shown on screen. The form might even never be shown on screen.
Take, for example, a settings form, where properties are defined in the class that map to user preference fields on the form. Someone might decide to build an application that looks directly at a known form object instance to read preferences, but if the user never goes to change anything that form might never display to the screen, and the Load event would never fire. That's just one example, and whether or not it's a good idea is another story; it's enough to know I've seen it happen in real code.
However, what I have not found is WHEN you would add a Public Sub
New() constructor to a form and what you would add to that versus the
Load() sub.
Here is an example to answer (the core?) question of yours. Suppose you have a Form to edit a product. Let's call it ProductEditForm. Your use case for this Form is to edit the values of an existing Product in your system. How would you tell this form what product to edit? By requiring the passing of a Product object when you instantiate the form. For example, in the code page of ProductEditForm:
Private _product As Product = Nothing
Public Sub New(ByVal p As Product)
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_product = p
' Now, you can reference the values of passed-in product "p" via variable _product, anywhere in this Form.
End Sub
And you would instantiate ProductEditForm like this:
Dim p As New Product
p.ID = 1234
p.Name = "Widget"
p.Cost = "10.00"
p.SalePrice = "30.00"
Dim f As New ProductEditForm(p)
In this example, you are making good use of code in Sub New(). You could also, perhaps, assign the values to _product to TextBox controls, check values in _product to set colors or fonts, etc.
(Side note: this method of requiring a target object in the constructor completely avoids the way-too-common bad habit of some WinForms programmers to pass data between Forms via public form properties or global variables.)
If you're talking about Windows Forms, I've found that it's best to do your UI setup in Load rather than in the constructor (New), because sometimes if you do it in New then certain things won't be initialized yet and you'll run into null reference exceptions.
I have been working on a Visual Basic project in Visual Studio and have encountered a problem.
I understand that the Startup form property in the Application page of the Project Designer can be changed to a default form, however what I require is a way to do this through code in ApplicationEvents.vb depending on the value of a variable within application settings.
The goal is that if a user completes a form then a value is assigned to a variable, e.g. variable username = "xxx". If this value is true, then the default startup is a login form (as the user has already registered), and if it is false then the user is taken to a register form.
I appreciate that I could use another form to determine this, however this seems like I would be squandering the capabilities of ApplicationEvents and not using it correctly (I also want to avoid the inevitable flicker of a blank form as it decides).
I know that the default form is stored in Application.myapp, however with the final publication of the .exe this file will (presumably) not be exported with it, so I want to avoid writing directly to it. I have also read into the windowsformsapplicationbase.mainform property, however cannot figure out how to use it?
Here is a example piece of code from ApplicationEvents.vb to demonstrate my question.
If String.IsNullOrEmpty(My.Settings.username) Then
MsgBox("You have not registered")
'set register as default form
Else
MsgBox("You have registered")
'set login as default form
End If
Usually, if you need that much control over what happens at start-up, you just want to disable the a application framework. To do so, just un-check the Enable application framework check-box in the Application tab of the My Project settings designer window. Once you un-check that, you will be able to change the Startup object to Sub Main. Then you can add a new module with a Main method, like this:
Module Module1
Public Sub Main()
Application.EnableVisualStyles()
If String.IsNullOrEmpty(My.Settings.username) Then
Application.Run(New RegisterForm())
Else
Application.Run(New LoginForm())
End If
End Sub
End Module
Be aware, however--by disabling the application framework, you will loose the other automatic functionality that it provides, such as ApplicationEvents. If you want to use the application framework, you can accomplish the same thing by simply setting the MyApplication.MainForm property in the MyApplication.Startup event:
Partial Friend Class MyApplication
Private Sub MyApplication_Startup(sender As Object, e As ApplicationServices.StartupEventArgs) Handles Me.Startup
If String.IsNullOrEmpty(My.Settings.username) Then
Me.MainForm = New RegisterForm()
Else
Me.MainForm = New LoginForm()
End If
End Sub
End Class
Alternatively, you could always show the same form, but then have the form contain nothing but a single UserControl. Then you can simply switch which UserControl is displayed depending upon the settings. The user-controls would need to include all of the controls that would have otherwise been placed on the two different forms.
I am new to multi-threading in VB.NET and have come across a problem whereby I am wanting to append text to a text box on a form from a service thread running in the background.
The application I am developing is a client/server listener, I have been able to get the client and server PC's to talk with each other (confirmed through MsgBox), however I am now struggling to get the service thread on the server to append the text to the textbox, nothing vissible occurs.
I have a form named testDebug which calls a class (RemoteSupport), this class does all the handshake tasks and updates the textbox with the connection data.
Can anyone identify where I am going wrong and point me in the right direction?
The following is the code I have:
The form has a textbox named txtOutput, the following is from the remoteSupport class
Dim outMessage As String = (encoder.GetString(message, 0, bytesRead))
MsgBox(outMessage, MsgBoxStyle.Information, "MEssage Received")
If outMessage IsNot Nothing Then
If testDebug.InvokeRequired Then
' have the UI thread call this method for us
testDebug.Invoke(New UpdateUIDelegate(AddressOf HandleClientComm), New Object() {outMessage}) '
Else
testDebug.txtOutput.AppendText(outMessage)
End If
'RaiseEvent MessageReceived(outMessage) // a previous attempt to use custom events
End If
I am not sure if the invoke method is the ideal solution or if custom events are, I have spent some time on trying to get custom events to work, but these didnt work either.
// In the RemoteSupport class
Public Delegate Sub MessageReceivedHandler(ByVal message As String)
Public Shared Event MessageReceived As MessageReceivedHandler
// Located throughout the RemoteSupport class where debug information is required.
RaiseEvent MessageReceived(outMessage)
// Located in the code-behind of the form
Private Sub Message_Received(ByVal message As String)
testDebugOutput(message) // this is a function I have created
// to append the text to the text box
End Sub
The code supplied has been cut down so if there is anything else that you want to see or any questions please let me know.
Thanks for your assistance.
EDIT: I have uploaded the two VB files (form and class) to my site, I would appreciate it if someone could have a look at it to help me with identifying the problem with the UI not updating.
I have tried a few other things but nothing seems to be updating the UI once the worker thread has started.
Form: mulholland.it/testDebug.vb.txt
Class: mulholland.it/remoteSupport.vb.txt
Thanks for your assistance.
Matt
I have a form named testDebug...
If testDebug.InvokeRequired Then
This is a classic trap in VB.NET programming. Set a breakpoint on the If statement. Notice how it returns False, even though you know that the code is running on another thread?
InvokeRequired is an instance property of a Form. But testDebug is a class name, not a reference to an instance of a form of type testDebug. That this is possible in VB.NET has gotten a lot of VB.NET programmers in deep trouble. It is an anachronism carried over from VB6. It completely falls apart and blows up in your face when you do this in a thread. You'll get a new instance of the form, instead of the one that the user is looking at. One that isn't visible because its Show() was never called. And otherwise dead as a doornail since the thread isn't running a message loop.
I answered this question several times already, with the recommended fix. I'll just refer you to them rather than rehashing it here:
Form is not updating, after custom class event is fired
Accessing controls between forms
The Delegate method is likely the way you want to go, but I don't see the declaration of the UpdateUIDelegate anywhere
I believe your code should look something like this (assuming you have a reference to the testdebug form local to your remotesupport class
Dim outMessage As String = (encoder.GetString(message, 0, bytesRead))
MsgBox(outMessage, MsgBoxStyle.Information, "MEssage Received")
If outMessage IsNot Nothing Then
If testDebug.InvokeRequired Then
' have the UI thread call this method for us
testDebug.Invoke(New MessageReceivedHandler(AddressOf Testdebug.Message_Received), New Object() {outMessage})
Else
testDebug.txtOutput.AppendText(outMessage)
End If
end if
I'm developing a WinForms app in VB.NET, that handles sets of style data, and when the user clicks on another set's label, it prompts through a dialog "You are leaving this style preset to edit another one. keep changes on this one? [Yes] [No]"
But, I'm facing the problem that, when the user clicks either option, and the dialog closes, everything has to be refreshed, and loading the form again seems a good option.
I've tried putting a public sub on a module, that does this:
Public Sub CloseOpenStyleDlg()
KeepOrDiscardPrompt.Close()
StylesDlg.Close()
StylesDlg.ShowDialog()
End Sub
But as soon as that sub is called from the prompt, it crashes the application. (doesn't show an error in debug, simply crashes) How should I, from a given dialog, close the dialog, it's parent, and re-open it's parent? (which triggers all the Dialog_Load() code of the parent)
Thanks in advance! :)
You need to instantiate the dialog again. If I take your code for example:
Public Sub CloseOpenStyleDlg()
KeepOrDiscardPrompt.Close()
StylesDlg.Close()
StylesDlg = new StylesDlg()
StylesDlg.ShowDialog()
End Sub
When a form is closed, all resources created within the object are closed and the form is disposed.
If you want to reuse the Window instance use StylesDialog.Hide() function instead.
I have a windows form application that uses a Shared class to house all of the common objects for the application. The settings class has a collection of objects that do things periodically, and then there's something of interest, they need to alert the main form and have it update.
I'm currently doing this through Events on the objects, and when each object is created, I add an EventHandler to maps the event back to the form. However, I'm running into some trouble that suggests that these requests aren't always ending up on the main copy of my form. For example, my form has a notification tray icon, but when the form captures and event and attempts to display a bubble, no bubble appears. However, if I modify that code to make the icon visible (though it already is), and then display the bubble, a second icon appears and displays the bubble properly.
Has anybody run into this before? Is there a way that I can force all of my events to be captured by the single instance of the form, or is there a completely different way to handle this? I can post code samples if necessary, but I'm thinking it's a common threading problem.
MORE INFORMATION: I'm currently using Me.InvokeRequired in the event handler on my form, and it always returns FALSE in this case. Also, the second tray icon created when I make it visible from this form doesn't have a context menu on it, whereas the "real" icon does - does that clue anybody in?
I'm going to pull my hair out! This can't be that hard!
SOLUTION: Thanks to nobugz for the clue, and it lead me to the code I'm now using (which works beautifully, though I can't help thinking there's a better way to do this). I added a private boolean variable to the form called "IsPrimary", and added the following code to the form constructor:
Public Sub New()
If My.Application.OpenForms(0).Equals(Me) Then
Me.IsFirstForm = True
End If
End Sub
Once this variable is set and the constructor finishes, it heads right to the event handler, and I deal with it this way (CAVEAT: Since the form I'm looking for is the primary form for the application, My.Application.OpenForms(0) gets what I need. If I was looking for the first instance of a non-startup form, I'd have to iterate through until I found it):
Public Sub EventHandler()
If Not IsFirstForm Then
Dim f As Form1 = My.Application.OpenForms(0)
f.EventHandler()
Me.Close()
ElseIf InvokeRequired Then
Me.Invoke(New HandlerDelegate(AddressOf EventHandler))
Else
' Do your event handling code '
End If
End Sub
First, it checks to see if it's running on the correct form - if it's not, then call the right form. Then it checks to see if the thread is correct, and calls the UI thread if it's not. Then it runs the event code. I don't like that it's potentially three calls, but I can't think of another way to do it. It seems to work well, though it's a little cumbersome. If anybody has a better way to do it, I'd love to hear it!
Again, thanks for all the help - this was going to drive me nuts!
I think it is a threading problem too. Are you using Control.Invoke() in your event handler? .NET usually catches violations when you debug the app but there are cases it can't. NotifyIcon is one of them, there is no window handle to check thread affinity.
Edit after OP changed question:
A classic VB.NET trap is to reference a Form instance by its type name. Like Form1.NotifyIcon1.Something. That doesn't work as expected when you use threading. It will create a new instance of the Form1 class, not use the existing instance. That instance isn't visible (Show() was never called) and is otherwise dead as a doornail since it is running on thread that doesn't pump a message loop. Seeing a second icon appear is a dead give-away. So is getting InvokeRequired = False when you know you are using it from a thread.
You must use a reference to the existing form instance. If that is hard to come by (you usually pass "Me" as an argument to the class constructor), you can use Application.OpenForms:
Dim main As Form1 = CType(Application.OpenForms(0), Form1)
if (main.InvokeRequired)
' etc...
Use Control.InvokeRequired to determine if you're on the proper thread, then use Control.Invoke if you're not.
You should look at the documentation for the Invoke method on the Form. It will allow you to make the code that updates the form run on the thread that owns the form, (which it must do, Windows forms are not thread safe).
Something like
Private Delegate Sub UpdateStatusDelegate(ByVal newStatus as String)
Public sub UpdateStatus(ByVal newStatus as String)
If Me.InvokeRequired Then
Dim d As New UpdateStatusDelegate(AddressOf UpdateStatus)
Me.Invoke(d,new Object() {newStatus})
Else
'Update the form status
End If
If you provide some sample code I would be happy to provide a more tailored example.
Edit after OP said they are using InvokeRequired.
Before calling InvokeRequired, check that the form handle has been created, there is a HandleCreated property I belive. InvokeRequired always returns false if the control doesn't currently have a handle, this would then mean the code is not thread safe even though you have done the right thing to make it so. Update your question when you find out. Some sample code would be helpful too.
in c# it looks like this:
private EventHandler StatusHandler = new EventHandler(eventHandlerCode)
void eventHandlerCode(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(StatusHandler, sender, e);
}
else
{
//do work
}
}