When Microsoft Access is shutting down, how can I catch the current properties of a CustomTaskPane before the controls are disposed..? - vsto

I've created a VSTO addin for Microsoft Access by following the directions by Microsoft guru Andrew Whitechapel here, and it's working nicely. But the addin has a CustomTaskPane, and I'm having an issue with it when Access is closing.
If the CustomTaskPane is open when Access closes, the addin should save the properties of the CustomTaskPane controls. If code for this is placed in ThisAddIn_Shutdown(), I receive the following error:
System.ObjectDisposedException: Cannot access a disposed object.
at Microsoft.Office.Tools.CustomTaskPane.get_Control()
at MyAddin.ThisAddIn.ThisAddIn_Shutdown(Object sender, EventArgs e) in C:\...\ThisAddIn.vb:line nn
I'm not sure if this is the normal operation of CustomTaskPanes or Windows Forms controls, or if it's because VSTO isn't designed for Access. I'm wondering if it happens because Access doesn't have application-level events such as Access.Application."OnClose", as do the other VSTO-approved apps such as Excel & Word.
After some experimentation I found a workaround by using the HandleDestroyed event for the controls, which occurs before Dispose(), and thus the control properties are still available. This works:
Private Sub TextBox1_HandleDestroyed(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles TextBox1.HandleDestroyed
MsgBox(TextBox1.Text)
End Sub
Is there a better way..? Workarounds make me nervous.

In following the trail of events, I realized the answer to my own question. The focal point is the Dispose method in usercontrol.designer.vb. However, it is widely known designer-generated code shouldn't be directly modified, as it can and will be refreshed and over-written after any subsequent changes to the usercontrol in the designer.
Except...that rule doesn't completely apply to certain methods such as Dispose. See here. If the programmer subsequently moves such methods from usercontrol.designer.vb to usercontrol.vb, the designer will defer to them in usercontrol.vb and will not regenerate them in usercontrol.designer.vb.
And so, we've arrived at the answer: move the Dispose method to usercontrol.vb, remove the System.Diagnostics.DebuggerNonUserCode attribute, and then add the necessary code to save the control properties, as shown below:
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
SaveUserControlProperties() <--- additional code added here
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub

Related

programmatically set win forms user controls to nothing on dispose

I have vb.net winforms application. It has memory leaks which is caused by a third party control. I am left with the option of reducing instead of eliminating the memory leaks.
The form has several user controls declared as Friend WithEvents. They are not GCed when the form is closed, because the form is in memory and it holds a reference to the user control. However, when I set the user control references to nothing on dispose, the memory leak is reduced.
Now I want to expand this to the whole application instead of just one form. How would I use reflection to scan through all the user controls and set them to nothing on dispose ? Interesting to note is that the user controls are declared using "Friend WithEvents" signature. Is it possible to somehow use this while scanning ?
As #Plutonix states, if you dispose of the control, it will invalidate and dispose of its child controls. If you believe they are using dynamic controls, you could just recursively dispose of the controls?
Sub DisposeControls(parentControl as Control)
For each control as Control in parentControl.Controls
If control.HasChildren() Then Call Me.DisposeControls(control)
control.Dispose()
Next
End Sub
Edit: should mention, I am unsure if code above will compile - treat as pseudocode.
I ended up doing this. Its not optimal but it works really well. I have all the forms inheriting a base form. In the disposed method of that form, I use this reflection code.
Private Sub BaseForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed
NullifyReferencesOnDispose()
RemoveHandler Me.Disposed, AddressOf BaseForm_Disposed
End Sub
Protected Overridable Sub NullifyReferencesOnDispose()
Dim typ = CType(Me.GetType, Reflection.TypeInfo)
For Each fld In typ.DeclaredFields
fld.SetValue(Me, Nothing)
Next
End Sub

Handling custom messages in windows compact framework 3.5 using windows mobile handheld 6.5 on a POCKETPC

I'm updating an existing application that scans barcodes and is written in VB.net running on windows compact framework 3.5. The scanner is a POCKETPC running windows mobile handheld 6.5. I have added code that uses Asynchronous TCP sockets in a class module. The sockets code is reading and sending data to and from a buffer pool. I now need to “inform” the GUI form that data has been received from the TCP socket and is ready for processing. Because the two processes are running on different threads I realise I cannot access the GUI controls directly. I therefore create a windows message (WM_CUSTOMMSG = &H400) and then use “SENDMESSAGE”. There is an existing WndProc sub (Protected Overrides Sub WndProc(ByRef msg As Microsoft.WindowsCE.Forms.Message)) that handles the WM_DECODEDATA for the scanner message. I added in code to now also process the WM_CUSTOMMSG message I am creating. The WM_CUSTOMMSG is arriving at the WndProc and I am able to display a MessageBox and write a log file, but any changes made to the GUI controls just disappear. I tried to start a forms timer but this also has no effect. Code for the WM_DECODEDATA message updates the GUI controls perfectly. What am I missing / done wrong?
Public Class frmHome
Public SockReceiveMsg As Microsoft.WindowsCE.Forms.Message
Public Sub New()
Private yy As Integer = 0
Private xx As Integer = 0
InitializeComponent()
Me.MsgWin = New MsgWindow(Me)
' Add any initialization after the InitializeComponent() call.
SockReceiveMsg = Microsoft.WindowsCE.Forms.Message.Create(MsgWin.Hwnd, MsgWindow.WM_CUSTOMMSG, New IntPtr(xx), New IntPtr(yy))
end class
Private Sub ReceiveCallback(ByVal ar As IAsyncResult)
'This is the async call back sub
MessageWindow.SendMessage(frmHome.SockReceiveMsg)
end sub
Protected Overrides Sub WndProc(ByRef msg As Microsoft.WindowsCE.Forms.Message)
Dim rc As Integer
Dim ar() As String
If msg.Msg = WM_CUSTOMMSG Then
Try
MsgBox("restart timer") 'this displays
Reader.ReaderEngineAPI.Beeper(8, "") 'a quick ok beep. this works
frmHome.timer1.Enabled = False
frmHome.timer1.Interval = 100
frmHome.timer1.Enabled = True
Catch ex As Exception
MsgBox("wndproc Error1: " & ex.Message)
End Try
End If
'pass all messages onto the base processing. Here the windows ones get processed and our ones get cleared and the storage released
MyBase.WndProc(msg)
End Sub
I don't know if/what you're doing wrong, but you can probably do things alot easier. All Controls (including Forms) have an Invoke() method that you can call to let the framework deal with the windows messages.
This article describes it in a bit more detail, including the InvokeRequired property, which you can probably ignore if you know the data is sent from another thread.
If you do choose to handle these messages manually (since you already have the WndProc routine), be sure to catch all exceptions in the method that updates the GUI, and perhaps inspect the InvokeRequired to see if the control agrees that you can update it from that thread.
I would go with a delegate and an eventhandler on the thread code and use InvokeRequired with a custom delegate to update the GUI thread.
Handling custom message is not recommended. Or why does MS hide WndProc in WindowsCE.Forms? OTOH you may need to Refresh the controls that have been changed to let them know that they need to update. Do you use PostMessage or SendMessage. If later, that would block the thread code until the message is processed, which may block the GUI to update itself, if the WndProc itself uses SendMessage inside the code to handle the custom message.
Private Sub UpdateTextBox(ByVal sender As Object, ByVal e As EventArgs)
'---delegate to update the textbox control
txtMessagesArchive.Text += str
End Sub
That would be called from a background thread in the same class via:
Me.Invoke(New EventHandler(AddressOf UpdateTextBox))
Different ways to update GUI from background thread, but in C#
Thank you to all who provided answers. I had previously tried a delegate with invoke but this caused the system to crash. I spent over a week trying to find out what was wrong with the delegate code – I mean it is so simple, but was not successful. I then tried the WndProc route. Josef’s example in his answer showed me that the delegate must be in the same class as the backgound thread. I had placed the delegate in the form class. Once I put the delegate in the correct class it works perfectly. I have 50 years of experience in IT and programming and you can still learn.
I am not going to try to get to the bottom of the WndProc problem. This is now academic as the delegate route is a better solution and is now working. Thank you

How to solve Dispatcher.BeginInvoke error

I have this procedure (converted from C#):
Private Sub _biometrics_IdentifyFailed(ByVal sender As Object, ByVal e As AuthenticationFailedEventArgs)
' See comment above...
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, New Action(Function()
StatusTextBox.Text = "Failed"
UsernameTextBox.Text = [String].Empty
_session.Close()
_session = _biometrics.OpenSession()
End Function))
End Sub
I am having an error in the 'Dispatcher.BegingInvoke' saying 'Reference to a non-shared member requires an object reference'.
I can't seem to figure out what this means or how to solve it.
Can someone understand it and help me solve it?
This is a windows forms application, VS 2010, .NET framework 4.0.
Thanks.
There is no Dispatcher property available in the current scope. Since the Dispatcher is also a type, the compiler defaults to attempting to call a static BeginInvokemethod defined on the Dispatcher type. There is none, there is only an instance method, and that's what the exception is saying.
What you are really doing is you are copypasting WPF code snippets into your Windows Forms application. The Dispatcher is used in WPF applications. This is known as "god tier" application development. You aren't programming within this heightened sphere. Because this can be read by children, I'll refrain from describing what Windows Forms development using VB.NET is.
You're probably trying to update the UI from a background thread. In this case, you will be using Control.BeginInvoke to update the control from a background thread. You're probably in the codebehind for a control, so just invoke the method this way:
Private Sub _biometrics_IdentifyFailed(ByVal sender As Object, ByVal e As AuthenticationFailedEventArgs)
' See comment above...
BeginInvoke(New InvokeDelegate(AddressOf InvokeMethod))
_session.Close()
_session = _biometrics.OpenSession()
end Sub
Public Sub InvokeMethod()
StatusTextBox.Text = "Failed"
UsernameTextBox.Text = [String].Empty
End Sub
Note that _biometrics_IdentifyFailed is executing on the background thread, so only background work should be happening there. InvokeMethod will execute on the UI thread, so only UI updates should happen there. I don't VB, so I might have some syntax errors in here. Good luck.

How to Close VB6 Parent Form From .NET Interop-ed UserControl?

I'm building a "plug-in" of sorts for an already-deployed VB6 executable. I'm using .NET along with COM-Interop. The VB6 creates a blank form and then loads my .NET UserControl into it (however by now the .dll has been compiled into a .ocx ActiveX UserControl that can be seen by VB6).
I've got it working well, but I would like to be able to close the VB6 parent form from inside of my .NET code. I am able to add VB6 code into my VB6-ifyed UserControl, but I cannot seem to find an event that fires when the UserControl is destroyed.
What I've tried so far:
Calling ParentForm.Close from the Disposing event of my .NET control. Receive error Object Reference not set to Instance of an Object.
Trying to close from the VB6 (I am able to get a handle on the parent form from there). Using ControlRemoved, Terminated, and a couple other hackish workarounds that truly make no sense in retrospect don't get triggered.
Calling Application.Exit (truly getting desperate at this point) closes the whole Application (who woulda thunk...)
I looked in the VB6 Interop code that I put in my .NET control and the following does look promising:
#Region "VB6 Events"
'This section shows some examples of exposing a UserControl's events to VB6. Typically, you just
'1) Declare the event as you want it to be shown in VB6
'2) Raise the event in the appropriate UserControl event.
Public Shadows Event Click() 'Event must be marked as Shadows since .NET UserControls have the same name.
Public Event DblClick()
Private Sub InteropUserControl_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Click
RaiseEvent Click()
End Sub
Private Sub InteropUserControl_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.DoubleClick
RaiseEvent DblClick()
End Sub
#End Region
Is it just a matter of adding an event in this section? I'm not terribly familiar with Interop, or VB6 for that matter.
Alright, I figured it out and will post what I did for future generations :P
I was right with the event handlers in the VB6 code, and MarkJ was correct as well.
I created an event in the .NET code,
Public Event KillApp()
and then when I wanted to close everything, raised it:
RaiseEvent KillApp()
In the VB6 UserControl code, I declared the event again,
Public Event KillApp()
and then added a handler for it:
Private Sub MenuCtl_KillApp()
Unload Me.ParentForm
End Sub
where MenuCtl is my instance of the .NET control, and Me.ParentForm is the VB6 container form that houses the control. It now correctly closes the form!
In retrospect it makes a lot of sense, but I was unaware that you could pass events back and forth between managed/unmanaged that easily.

In VB/C# .NET, does a dialog always have to be disposed of manually?

I'm looking into disposing of resources and getting a little mixed up over the different ways to do it.
I've just found out that using Close() on a form shown with ShowDialog() only actually hides it and doesn't completely kill it off, so to speak. While this is useful for what I want at the moment, I'm now worrying about memory leaks elsewhere.
After using ShowDialog(), should I always call Dispose() on the form or use a Using block? Is there a difference? Or will the form be automatically disposed of when exiting the subroutine is was created in? For example, one of my typical simple usages:
Private Sub btnEdit_Click(sender As System.Object, e As System.EventArgs) Handles btnEdit.Click
Dim frm As New frmSomething()
frm.ShowDialog()
'frm is exited by clicking the X or using Close()
'At this point, frm is still in memory. Is it automatically disposed of
'after the End Sub here, or should I do frm.Dispose() ?
End Sub
It won't be automatically disposed, no. It may well not cause a problem, and there may well be a finalizer to do everything that's required, so the cost would just be some extra resources before the finalizer runs (and a longer delay before eventual GC) but it would be better to dispose it explicitly - ideally with a Using statement:
Using frm As New frmSomething()
frm.ShowDialog()
End Using