What Is the Correct Way to Close and Reopen a Form Declared as Public? - vb.net

My problem pertains to a COM add-in for Microsoft Excel. The use case is as follows: 1. User clicks the add-in's button on the ribbon. 2. A form window appears. 3. User interacts with the form window and clicks an OK button. 4. Various reports are generated, while a progress bar on the form window shows progress. 5. At the end of the process, the form window closes.
The process works as designed on the first run, but after the form window has been closed there is no way to start a new "session." From the user's perspective the add-in button becomes non-responsive. When run in debug mode from Visual Studio, clicking the add-in button a second time generates an error message: "Cannot access a disposed object."
Clearly something is wrong with the way I have hooked everything up, but I haven't been able to find a simple description of how to do it correctly. Here is what I have:
In a public class a number of public (or "global" variables) are declared; the form is also declared and instantiated here:
Public Class GlobalVariables
Public Shared FormInstance As New MyFormDesign
End Class
The reason for declaring the form as a public object is to be able to be able to send progress values from various different subs and functions. The GlobalVariables class is imported by all modules that require it.
Behind the ribbon button is a single line of code:
FormInstance.Show()
Clicking the button instantiates and shows the form as intended. To keep things simple we can ignore the bulk of the code; simply clicking the "Cancel" button will trigger the problem. The code behind the "Cancel" button is straightforward:
Me.Close()
GC.Collect()
After closing the form it is no longer possible to create a new instance, per the error message cited above.
I don't really understand what is going on here, but it looks to me like the GlobalVariables class, once created, persists until the end of the Excel session. If that is correct the problem could presumably be cured by instantiating the form in a standard module. Instead of attempting to revive a form that has been disposed, the add-in would just create a new instance each time the user clicks the button. But if I go that route I can't figure out how to send progress values from other subs back to the form. It seems like a Catch-22.
There has got to be a way to both (a) create the form as a public object, and (b) create and destroy a new instance each time the add-in is run. Right? What am I doing wrong?

It has been a long journey, but I finally found out how to build the functionality described in my question. I will post the answer here, as it may help others in the future.
The challenge was to declare a form as Public in order to make it accessible throughout the project, so that subs and functions can send progress updates back to the form.
The problem was that the form, when declared and instantiated as described in my question, can only be created once per Excel session.
The solution is to declare the form as Public without instantiating it, then access it via a Public ReadOnly Property.
First, declare FormInstance as a public variable without instantiating it:
Public FormInstance As MyFormDesign
Second, define a Public ReadOnly Property. This establishes a way to call the form:
Public ReadOnly Property CallMyForm() As MyFormDesign
Get
Return FormInstance
End Get
End Property
Third, the ribbon button's Click event instantiates and shows the form:
Private Sub Button1_Click(sender As Object, e As RibbonControlEventArgs) Handles Button1.Click
FormInstance = New MyFormDesign
FormInstance.Show()
End Sub
Now a new form instance will be created each time the ribbon button is clicked, but the form can still be accessed via the CallMyForm property.
Instead of ...
FormInstance.BackgroundWorker1.ReportProgress(i)
... we will use:
CallMyForm.BackgroundWorker1.ReportProgress(i)
This solves the conundrum laid out in the question.

Related

Save Separate Settings for Copies of the same Form

I have a form, when I right click it, you can select properties, which opens a settings Form that interacts directly with the previous form, ie, it can change the color, size and other properties of the the original form. I want Several Copies of the original form running.
As it Stands, all the new forms take their settings from the same My.settings.
1st How Can I Save all the settings from each copy of the original form, Separately?
right now im using Form1.show(), and dim newform as form1 = new form1.
2nd, how can I make sure the settings form is interacting only with their respective original form?
right now, all the new settings Forms are only interacting with the same original form.
I have a jumble of data types saved, about 10 settings, and 2 of them are Specialized.string to save matrixes
Firstly, I recommend not using My.Settings in this case, since it only serves the purpose of storing global settings.
You'll need to store the settings in the Form1 class for each instance seperately, these could then be saved to a file or in the registry.
To your second question, in the settings form class, add a parameter to the Form.Show method, which is used to pass the instance of a main form object to the settings form.
This will tie a newly opened settings form to a particular main form. This could look like this:
Class SettingsForm
Shadows Sub Show(parentForm As Form1)
'The parentForm paramter will be the Form1 instance which will be controlled by this settings window
End Sub
End Class
You then just call the SettingsForm.Show method and pass the instance of the Form1 object you want to control to the method.

Opening a second-form more than once error

I have a button in form1 that opens form2. I do this using frm2.show(). It works totally fine for the first time that I open frm2, but when I close frm2 and click on the button in frm1 to open frm2 again, I get this error:
Can someone tell me how to solve it?
Edit: I have a module where I have my database connection and my declaration for the forms:
Public frmGame As New Game
Public frmPlay As New Play
Public frmFinish As New GameFinish
Public frmLogin As New Login
Public frmManage As New Manage
Public frmInsert As New Toevoegen
Where I open the form is just when I click on a button in form1.
Sounds to me like you are Close()ing frm2. Closing a form should dispose it and release its resources, so you can't simply Show() it again. Instead, you need to create a new instance of the object, like this:
frm2=new Form2()
frm2.Show()
If that doesn't work (perhaps because you don't want to re-initialize the form's data members), you could use Hide(), rather than Close() to temporarily hide the form during your program's execution.
If you need to prevent the form from being closed with the X button, you can do this with a few different methods:
The best way to go may be to hide or disable the close button. Read up on This post to get a better idea of how to do that.
You can use the FormClosing event, either from inside frm2 or from the main window. Set the Cancel property on the FormClosingEventArgs object that gets passed in. The problem here is that you will need to provide an additional code path to close the form when you actually want it to close. The CloseReason property of the FormClosingEventArgs object should give you a way to handle that properly. Needless to say, this is probably the riskiest way to do things, since you need this window to close when the application shuts down, but you're also intentionally block that from happening.
You could catch the FormClosed event in the the form that spawns frm2, then create a new instance of the window. Again, you'll need to provide a code path to allow the window to close when the application shuts down.
In all, method 1 is probably the safest. You can close the window with .Close() when the application shuts down, but the user can't close it with the X button.

Access event in a user control created in code behind?

I'm trying to create a user control in my code behind, and then respond to events in that control. Presumably because the control doesn't exist at compile time, Visual Studio can't compile the handler subroutine I created to catch my control's event. Importantly, I want to decide the type of control at runtime (which is why I'm not just hard-coding it).
[before going on, the controls work correctly, including events and event handlers when used in the 'normal' way of creating the controls in XAML. I want to create the control instances in code behind so I can avoid duplicating pages that are 99% identical]
This 'works' (but doesn't give me the flexibility I need):
Public WithEvents AnswerPanel As MyControls.ScrollerControl
... (and the initialisation in the New() sub):
AnswerPanel = New MyControls.ScrollerControl
ItemStack3.Children.Add(AnswerPanel)
AddHandler AnswerPanel.GuessMade, AddressOf CheckAnswer
... (this is the handler sub responding to a custom event in the ScrollerControl)
Public Sub CheckAnswer(answer As String) Handles AnswerPanel.GuessMade
With the code above everything works as I expect: the control is created at runtime and its event is handled correctly.
What I want to achieve is to be able to choose a different user control when I initialise my control (e.g. ScrollerControl2, ScrollerControl3, etc.) I can create the controls this way by changing the first line to:
Public WithEvents AnswerPanel As UserControl
But once that change is made I can no longer reference the custom event in my handler as (presumably) the compiler sees it as a generic UserControl, which doesn't include my custom GuessMade event. The compiler errors on the event name and tells me it doesn't exist.
I'm sure I'm doing something wrong here. I think it's a theory/concept issue rather than my code.
Am I on the right track or going about this in the wrong way?
If I am reading this right, you have a user control that fires an event and you want the parent page to catch that even? If so, you need to raise the event, which will cause the event to bubble to the the parent. IE:
Partial Class user_controls_myControl
Inherits System.Web.UI.UserControl
Public Event DataChange As EventHandler
End Class
This creates a control with a public event called DataChange. Now, if you look at the code in the parent page that instantiates the user control, you will see that it has an event called "OnDataChange". Just like an onCLick event, you can assign this a method in the parent page. Now, you just need to raise the event in the user control. This can be added in some event in the control, like a button click or radio button change event:
RaiseEvent DataChange(Me, New EventArgs)
This takes two objects, the sender and event arguments. Typically I pass ME, which is the user control. This is great because you can use reflection to get all the controls public properties. You can also use this to cast objects to your control type. I rarely pass event arguments but you certainly could.
I answered a similar question here: Handling events of usercontrols within listview
If this is not what you had in mind, let me know
EDIT: To add a user control dynamically and attach the event:
First, in the page that will be using the control, you will need to add a place holder:
<asp:PlaceHolder ID="placeholder1" runat="server"></asp:PlaceHolder>
as well as a reference to the user control at the head of the page (depending on how the page is setup, you may not need this. If you get a page directive error, remove it):
<%# Reference="" Control="~/user_controls/myControl.ascx"%>
In the parent page, you can then create a user control and add it to the place holder. You must declare the user control with events like this:
Private WithEvents myNewControl As New user_controls_myControl
then, in some method you can add it to the page like this:
Dim getPh As New PlaceHolder
'create an instance of the user control
newMyControl = CType(LoadControl("~/user_controls/myControl.ascx"), user_controls_myControl)
'get a handle on the place holder
getPh = me.placeHolder1
'add the user control to the place holder
getPh.Controls.Add(newMyControl)
Then, make sure you have event method:
Protected Sub myEvent(ByVal sender As Object, ByVal e As EventArgs) Handles myNewControl.DataChange
End Sub
So, if you added the RaiseEvent to the user control like I suggested earlier, this should work for you.
I have an answer to this now. As I suspected I was sort of thinking about the problem from the wrong angle.
In a nutshell I was trying to raise an event from my user controls, but I needed to be raising the events in the base class and calling that from my user controls.
So my base class (which my user controls inherit from), now contains the following:
Public Event GuessMade(answer As String)
Protected Sub RaiseGuessEvent(answer As String)
RaiseEvent GuessMade(answer)
End Sub
Then, in my user control(s), when I need to raise the event, I simply call the RaiseGuessEvent sub like this:
Me.RaiseGuessEvent(CurrentValue)
And additionally, I had to remove the event from my subclasses/user controls, of course.

VB.NET: How can you activate the childform when only the control inside is clicked?

*edit: OK, so this is my real problem, below scenario happens only when the form is MDIChild.. thanks for anyone that could provide me with the code
I have a form with labels, panels, buttons etc. Where I'm having problem is, while form2 is my active window/form and I clicked on a control inside form1, the form1 does not activate itself. What I would like to happen is for form1 to activate even when it's not the form I clicked, only the control inside it (any control)..
I'm thinking that if I clicked a control on the form, there's an event fired on the form. If I could only know of that certain event, that would help - maybe (coz I could just add Me.activate on that event if it exists). I've tried searching for series of events when a control (ex. label) is clicked but to no avail. I hope that someone could help me with this one.
Thanks in advance.
*edit
i will just try to make my question more understandable..
How can I activate the form when only the control is clicked (say, label or textbox)? My forms does not activate or focused when I click inside it except the form itself..
I can do this on one control..
Private Sub Label1_Click - Handles Label1.Click
Me.Activate()
End Sub
But what if I have 20 controls (labels, buttons, textbox, combobox, etc)? See? =)
EDIT: this answer does not apply to MDI applications.
I think what you really want to know is which one of your forms is currently the foreground window (if any). The first thing you need to understand is that a form instance lives inside a window, but the window's behavior is controlled somewhere higher up. Similar to how a form instance is identified by a variable pointing to the instance, a window can be identified by what's known as a window handle.
Knowing this, the proper way to find out whether a form is the "active" form is to:
find out the window handles of the windows containing our instances of Form1 and Form2
find out the window handle of the foreground window (which can be any window)
compare the value found in step 2 to all of the values found in step 1
Perhaps you'd then like to fire an event if the foreground window changes, but I'll leave the actual implementation up to you. There are probably several ways to perform step 1 and 2, but I can't give any solutions off the top of my head. Hopefuly I've put you back on the right track.
EDIT
Alternatively, you can use the form's Containsfocus property. If its value is True, you can safely assume that your form is the foreground window. I didn't find out about this property until after I wrote my own implementation, which I'll show you anyway:
One module containing only a windows API call
Friend Module NativeMethods
Friend Declare Function GetForegroundWindow Lib "user32.dll" () As IntPtr
End Module
Calling this method will return the window handle of the foreground window (if any).
One module containing the extension method for the Form class
Imports System.Runtime.CompilerServices
Public Module FormExtensions
<Extension>
Public Function IsForeground(f As Form) As Boolean
Return (f.Handle = NativeMethods.GetForegroundWindow)
End Function
End Module
Calling this method returns whether the specified form f has the same window handle as the foreground window.
Usage example
You could use a Timer that periodically checks whether a form is the foreground window.
Public Class Form1
Private WithEvents timer As New Timer With {.Enabled = True}
Private Sub timer_Tick(sender As Object, e As EventArgs) Handles timer.Tick
If Me.IsForeground() Then
Console.WriteLine("this instance of Form1 is the foreground window")
End If
End Sub
End Class
Like I said before, you can use Me.ContainsFocus instead of my extension method and it will work just fine.
In non-MDI forms, the form is automatically activated when you click any control inside it.

VB.NET: How to close and re-open a dialog in this case?

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.