Trouble with disposing a control within function - vb.net

I am attempting to write a simple plugin for MusicBee (a music player app)
I am building on the example provided with the API.
Some background: this plugin is launched when the main .exe (musicbee) launches.
The Configure() function is run when the user goes to the plugin config screen in the options menu.
The
Dim configPanel As Panel = DirectCast(Panel.FromHandle(panelHandle), Panel)
Dim btn1 As New Button
and configPanel.Controls.AddRange(New Control() {btn1})
lines add a button to this config screen which I am using to enable/disable the only function of my plugin.
To provide user feedback I need to change the text of this button (i.e. "ON", "OFF")
The problem: If I declare the button inside the Configure() function I cannot access it from the other click event function. (btn1_Click())
If I declare it globally then when the user closes the config screen (not the whole plugin) btn1 gets disposed (actually configPanel gets disposed by the musicbee API I think). When the user tries to open the config screen again I get an exception because it tries to access btn1 when it was already disposed and is not re-declared.
MusicBee v3.0.6132.15853 (Win6.1), 7 Mar 2017 20:53:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Button'.
There must be a way to declare/setup btn1 so that this works but I don't know how.
Thanks, Timothy.
Imports System.Runtime.InteropServices
Imports System.Drawing
Imports System.Windows.Forms
Public Class Plugin
Private mbApiInterface As New MusicBeeApiInterface
Private about As New PluginInfo
Dim playMode As Boolean
Public Function Initialise(ByVal apiInterfacePtr As IntPtr) As PluginInfo
CopyMemory(mbApiInterface, apiInterfacePtr, 4)
If mbApiInterface.MusicBeeVersion = MusicBeeVersion.v2_0 Then
' MusicBee version 2.0 - Api methods > revision 25 are not available
CopyMemory(mbApiInterface, apiInterfacePtr, 456)
ElseIf mbApiInterface.MusicBeeVersion = MusicBeeVersion.v2_1 Then
CopyMemory(mbApiInterface, apiInterfacePtr, 516)
ElseIf mbApiInterface.MusicBeeVersion = MusicBeeVersion.v2_2 Then
CopyMemory(mbApiInterface, apiInterfacePtr, 584)
ElseIf mbApiInterface.MusicBeeVersion = MusicBeeVersion.v2_3 Then
CopyMemory(mbApiInterface, apiInterfacePtr, 596)
ElseIf mbApiInterface.MusicBeeVersion = MusicBeeVersion.v2_4 Then
CopyMemory(mbApiInterface, apiInterfacePtr, 604)
ElseIf mbApiInterface.MusicBeeVersion = MusicBeeVersion.v2_5 Then
CopyMemory(mbApiInterface, apiInterfacePtr, 648)
Else
CopyMemory(mbApiInterface, apiInterfacePtr, Marshal.SizeOf(mbApiInterface))
End If
about.PluginInfoVersion = PluginInfoVersion
about.Name = "Single Play Mode"
about.Description = "Adds a single play mode i.e play one track and stop."
about.Author = "Timothy Hobbs"
about.TargetApplication = "" ' current only applies to artwork, lyrics or instant messenger name that appears in the provider drop down selector or target Instant Messenger
about.Type = PluginType.General
about.VersionMajor = 1 ' your plugin version
about.VersionMinor = 0
about.Revision = 1
about.MinInterfaceVersion = MinInterfaceVersion
about.MinApiRevision = MinApiRevision
about.ReceiveNotifications = ReceiveNotificationFlags.PlayerEvents
about.ConfigurationPanelHeight = 40 ' height in pixels that musicbee should reserve in a panel for config settings. When set, a handle to an empty panel will be passed to the Configure function
Return about
End Function
Public Function Configure(ByVal panelHandle As IntPtr) As Boolean
' save any persistent settings in a sub-folder of this path
Dim dataPath As String = mbApiInterface.Setting_GetPersistentStoragePath()
' panelHandle will only be set if you set about.ConfigurationPanelHeight to a non-zero value
' keep in mind the panel width is scaled according to the font the user has selected
' if about.ConfigurationPanelHeight is set to 0, you can display your own popup window
If panelHandle <> IntPtr.Zero Then
Dim configPanel As Panel = DirectCast(Panel.FromHandle(panelHandle), Panel)
Dim btn1 As New Button
btn1.Location = New Point(0, 0)
btn1.Size = New Size(150, 25)
btn1.Text = "Single Play Mode: OFF"
playMode = False
configPanel.Controls.AddRange(New Control() {btn1})
AddHandler btn1.Click, AddressOf btn1_Click
End If
Return True
End Function
Public Sub btn1_Click(sender As Object, e As EventArgs) 'toggle play mode
If playMode Then 'We are in single play mode so turn it OFF
playMode = False
btn1.Text = "Single Play Mode: OFF" '*******ERROR HERE*******
If mbApiInterface.Player_GetStopAfterCurrentEnabled() Then
mbApiInterface.Player_StopAfterCurrent() 'disable it
End If
'also disable stopaftercurrent once more for the current song.
Else 'We are in normal play mode so turn it ON
playMode = True
btn1.Text = "Single Play Mode: ON" '*******AND HERE*******
'enable it once
If Not mbApiInterface.Player_GetStopAfterCurrentEnabled() Then
mbApiInterface.Player_StopAfterCurrent() 'enable it
End If
End If
End Sub
' called by MusicBee when the user clicks Apply or Save in the MusicBee Preferences screen.
' its up to you to figure out whether anything has changed and needs updating
Public Sub SaveSettings()
' save any persistent settings in a sub-folder of this path
Dim dataPath As String = mbApiInterface.Setting_GetPersistentStoragePath()
End Sub
' MusicBee is closing the plugin (plugin is being disabled by user or MusicBee is shutting down)
Public Sub Close(ByVal reason As PluginCloseReason)
End Sub
' uninstall this plugin - clean up any persisted files
Public Sub Uninstall()
End Sub
' receive event notifications from MusicBee
' you need to set about.ReceiveNotificationFlags = PlayerEvents to receive all notifications, and not just the startup event
Public Sub ReceiveNotification(ByVal sourceFileUrl As String, ByVal type As NotificationType)
' perform some action depending on the notification type
Select Case type
Case NotificationType.PluginStartup
' perform startup initialisation
Select Case mbApiInterface.Player_GetPlayState()
Case PlayState.Playing, PlayState.Paused
' ...
End Select
Case NotificationType.TrackChanged
If playMode Then 'if single play mode is enabled
If Not mbApiInterface.Player_GetStopAfterCurrentEnabled() Then
mbApiInterface.Player_StopAfterCurrent() 'enable it
End If
End If
End Select
End Sub
' return an array of lyric or artwork provider names this plugin supports
' the providers will be iterated through one by one and passed to the RetrieveLyrics/ RetrieveArtwork function in order set by the user in the MusicBee Tags(2) preferences screen until a match is found
Public Function GetProviders() As String()
Return New String() {}
End Function
' return lyrics for the requested artist/title from the requested provider
' only required if PluginType = LyricsRetrieval
' return Nothing if no lyrics are found
Public Function RetrieveLyrics(ByVal sourceFileUrl As String, ByVal artist As String, ByVal trackTitle As String, ByVal album As String, ByVal synchronisedPreferred As Boolean, ByVal provider As String) As String
Return Nothing
End Function
' return Base64 string representation of the artwork binary data from the requested provider
' only required if PluginType = ArtworkRetrieval
' return Nothing if no artwork is found
Public Function RetrieveArtwork(ByVal sourceFileUrl As String, ByVal albumArtist As String, ByVal album As String, ByVal provider As String) As String
'Return Convert.ToBase64String(artworkBinaryData)
Return Nothing
End Function
End Class

Related

Change the visibility of a panel (Windows Forms)

I leave it to you because I can't find a solution to my problem : /
Let me explain, when I press a button I display a panel containing other buttons, at the click of one of the buttons on the panel it should launch a method that will convert the selected files to pdf. As soon as the user has clicked on one of the buttons and confirmed the choice of file, I make my panel invisible and I then launch the conversion method.
The problem is that my panel disappears let's say by half (not entirely) because it launches the conversion method as quickly. I told myself that I was going to go through a secondary thread, however I cannot modify graphic elements on the second thread.
There is my code :
Private Sub PBFolder_Click(sender As Object, e As EventArgs) Handles PBFolder.Click
Try
Insert2Db("Debut de la fonction BTransforme_Click " + Environment.UserName.ToString, 1, 0, "ConvertFiles2PDF")
'Log("Debut de la fonction BTransforme_Click")
Dim OFD As New FolderBrowserDialog
If OFD.ShowDialog = Windows.Forms.DialogResult.OK Then
PanFileFolder.Visible = False
ConvertFileFolder(False, OFD.SelectedPath.ToString)
End If
Catch ex As Exception
'Log("Error " + ex.Message)
Insert2Db("Error " + ex.Message + "User : " + Environment.UserName.ToString, 0, 3, "ConvertFiles2PDF")
Finally
Insert2Db("function BTransforme_Click Terminé " + Environment.UserName.ToString, 1, 0, "ConvertFiles2PDF")
'Log("function BTransforme_Click Terminé")
End Try
LAppOne.Visible = True
GifLoad.Visible = False
Button1.Enabled = True
BLog.Enabled = True
End Sub
As you can see I hide my panel thanks to line: PanFileFolder.Visible = False then I launch my conversion method convertFileFolder (False, OFD.SelectedPath.ToString)
I have put 2 images to illustrate my problem.
the 1st image shows you the panel that appears on click:
the second image shows you the problem that this causes me to choose the folder:
When it has finished converting the files, the panel disappears correctly at this time.
Do you have an idea to solve this problem thank you in advance ;)
I told myself that I was going to go through a secondary thread,
however I cannot modify graphic elements on the second thread.
That is exactly what will fix your problem; ConvertFileFolder() needs to be running in a different thread so that GUI can refresh itself and be responsive to user interact. You can update the GUI from that secondary thread using Invoke() calls.
Here I've added Async to the Button click handler, then we Await the ConvertFileFolder() FUNCTION, which now returns a Task:
Private Async Sub PBFolder_Click(sender As Object, e As EventArgs) Handles PBFolder.Click
' ... other code ...
Using OFD As New FolderBrowserDialog
If OFD.ShowDialog = DialogResult.OK Then
PanFileFolder.Visible = False
Await ConvertFileFolder(False, OFD.SelectedPath.ToString)
End If
End Using
' ... other code ...
End Sub
Public Function ConvertFileFolder(ByVal someFlag As Boolean, ByVal someString As String) As Task
Return Task.Run(Sub()
' ... long running code in here ...
For i As Integer = 1 To 10
System.Threading.Thread.Sleep(1000) ' some "work"
' whenever you need to update the GUI, use Invoke()
Dim value As String = i.ToString
Me.Invoke(Sub()
Label1.Text = value
End Sub)
Next
End Sub)
End Function

vb.net Notify Icon - Multiple Icons

In the tray, multiple icons appear during the course of the program running. I'm not sure why. I currently have this code:
Public Sub ShowBalloonTip(ByVal Text As String, ByVal Title As String)
niMain.Visible = False
niMain.BalloonTipText = Text
niMain.BalloonTipTitle = Title
niMain.BalloonTipIcon = ToolTipIcon.Info
niMain.Visible = True
niMain.ShowBalloonTip(1000)
End Sub
And when the form is being closed, I set the object to not being visible.
What can I do to solve this?

Proper clipboard format for Windows 10 Start Menu element

I'm implementing an IDropTarget COM interface which allow any OLE application to drag it's data over my application. However, it fails when I drop a menu item from Windows 10 Start Menu.
The code works fine with folders and files from the desktop or Windows Explorer, but fails when it comes from the Start Menu.
The code fails in iDataObject::QueryGetData using CFSTR_SHELLIDLIST clipboard format.
Someone knows what is the proper clipboard format used by a Start Menu item in Windows 10? Apparently I could use IDataObject::EnumFormatEtc but can not find any example.
Here is the relevant code:
_format = RegisterClipboardFormat(CFSTR_SHELLIDLIST)
Public Function DragDrop1(ByVal pDataObj As System.IntPtr, ByVal grfKeyState As Integer, ByVal pt As ShellCOM._POINT, ByRef pdwEffect As Integer) As Integer Implements ShellCOM.IDropTarget.DragDrop
Dim DataObj As ShellCOM.IDataObject
DataObj = Marshal.GetTypedObjectForIUnknown(pDataObj, GetType(ShellCOM.IDataObject))
If DataObj IsNot Nothing Then
Dim format As New FORMATETC
Dim medium As New STGMEDIUM
format.cfFormat = _format
format.ptd = 0
format.dwAspect = DVASPECT.DVASPECT_CONTENT
format.lindex = 0
format.Tymed = TYMED.TYMED_HGLOBAL
If DataObj.QueryGetData(format) = S_OK Then <----- code fail here, what is the correct format of an element from Windows 10 Start Menu?
' ....
' ....
End If
End If
Return S_OK
End Function
Problem solved. IDataObject::QueryGetData website indicates that currently only -1 is supported for lindex member. So now my app can get the menu item paths.
format.lindex = -1

How do I kill a process that doesn't have any windows in VB.net?

I am new to VB.net and I am trying to write a forms app that works with Autodesk Inventor.
Unfortunately, every time I close Inventor, the "Inventor.exe" process still stays. I didn't realize this while debugging and I only realized this when I checked the task manager after the whole system started to lag.
Killing every process with the same name is fairly simple, but the issue is that the end user might have separate documents open in another Inventor window. So I need to write a function that only kills the Inventor processes that don't have a window open.
Public Class Form1
Dim _invApp As Inventor.Application
Dim _started As Boolean = False
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Try
_invApp = Marshal.GetActiveObject("Inventor.Application")
Catch ex As Exception
Try
Dim invAppType As Type = _
GetTypeFromProgID("Inventor.Application")
_invApp = CreateInstance(invAppType)
_invApp.Visible = True
'Note: if you shut down the Inventor session that was started
'this(way) there is still an Inventor.exe running. We will use
'this Boolean to test whether or not the Inventor App will
'need to be shut down.
_started = True
Catch ex2 As Exception
MsgBox(ex2.ToString())
MsgBox("Unable to get or start Inventor")
End Try
End Try
End Sub
Another section where I start a process, which is a specific 3D model file.
Public Sub SaveAs(oTemplate As String)
'define the active document
Dim oPartDoc As PartDocument = _invApp.ActiveDocument
'create a file dialog box
Dim oFileDlg As Inventor.FileDialog = Nothing
Dim oInitialPath As String = System.IO.Path.GetFullPath("TemplatesResources\" & oTemplate)
_invApp.CreateFileDialog(oFileDlg)
'check file type and set dialog filter
If oPartDoc.DocumentType = kPartDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Part Files (*.ipt)|*.ipt"
ElseIf oPartDoc.DocumentType = kAssemblyDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Assembly Files (*.iam)|*.iam"
ElseIf oPartDoc.DocumentType = kDrawingDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Drawing Files (*.idw)|*.idw"
End If
If oPartDoc.DocumentType = kAssemblyDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Assembly Files (*.iam)|*.iam"
End If
'set the directory to open the dialog at
oFileDlg.InitialDirectory = "C:\Vault WorkSpace\Draft"
'set the file name string to use in the input box
oFileDlg.FileName = "######-AAAA-AAA-##"
'work with an error created by the user backing out of the save
oFileDlg.CancelError = True
On Error Resume Next
'specify the file dialog as a save dialog (rather than a open dialog)
oFileDlg.ShowSave()
'catch an empty string in the imput
If Err.Number <> 0 Then
MessageBox.Show("Any changes made from here will affect the original template file!", "WARNING", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
ElseIf oFileDlg.FileName <> "" Then
Dim MyFile As String = oFileDlg.FileName
'save the file
oPartDoc.SaveAs(MyFile, False)
'open the drawing document
System.Diagnostics.Process.Start(oInitialPath & ".idw")
Dim oFinalPath As String = oPartDoc.FullFileName
MessageBox.Show(oFinalPath, "", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
MessageBox.Show("Loaded", "", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
Dim oDrawingDoc As DrawingDocument = _invApp.Documents.ItemByName(oInitialPath & ".idw")
'oDrawingDoc.SaveAs()
End If
End Sub
Any help is appreciated.
Thanks
At the end on your form, probably FormClosed, add the following:
Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
If (_invApp Is Nothing) Then Return ' if empty, then no action needed
_invApp.Quit()
Marshal.ReleaseComObject(_invApp)
_invApp = Nothing
System.GC.WaitForPendingFinalizers()
System.GC.Collect()
End Sub
This should make .NET release and properly dispose the COM Object for Inventor.
And consider declare the Inventor variable and assign Nothing/null, it's safer (avoid mistakes)
Dim _invApp As Inventor.Application = Nothing

VB.Net Sockets Invoke

I am using vb.net 2010 and I have created a program that uses sockets to transfer data between our windows server and a unix server. The code was originally from a Microsoft sample project hence my little understanding of it.
Everything was fine until I had the idea of changing the program into a service. The Invoke command is not accessable from a service. I think I understand why but more importantly how do I get around it or fix it?
' need to call Invoke before can update UI elements
Dim args As Object() = {command, data}
Invoke(_processInStream, args)
Someone please help I am desperate to finish this program so I can move on :)
Below is the rest of the class, there is a server socket class too but I didnt want to complicate things?
Public Class srvMain
' start the InStream code to receive data control.Invoke callback, used to process the socket notification event on the GUI's thread
Delegate Sub ProcessSocketCommandHandler(ByVal command As NotifyCommandIn, ByVal data As Object)
Dim _processInStream As ProcessSocketCommandHandler
' network communication
Dim WithEvents _serverPRC As New ServerSocket
Dim _encryptDataIn() As Byte
Dim myConn As SqlConnection
Dim _strsql As String = String.Empty
Protected Overrides Sub OnStart(ByVal args() As String)
' watch for filesystem changes in 'FTP Files' folder
Watch()
' hookup Invoke callback
_processInStream = New ProcessSocketCommandHandler(AddressOf ProcessSocketCommandIn)
' listen for Ultimate sending signatures
_serverPRC.Start(My.Settings.listen_port_prc)
myConn = New SqlConnection(My.Settings.Mill_SQL_Connect)
End Sub
Protected Overrides Sub OnStop()
' Add code here to perform any tear-down necessary to stop your service.
End Sub
' this is where we will break the data down into arrays
Private Sub processDataIn(ByVal data As Object)
Try
If data Is Nothing Then
Throw New Exception("Stream empty!")
End If
Dim encdata As String
' decode to string and perform split(multi chars not supported)
encdata = Encoding.Default.GetString(data)
_strsql = encdata
myConn.Open()
Dim commPrice As New SqlCommand(_strsql, myConn)
Dim resPrice As SqlDataReader = commPrice.ExecuteReader
'********************************THIS MUST BE DYNAMIC FOR MORE THAN ONE NATIONAL
If resPrice.Read = True And resPrice("ats" & "_price") IsNot DBNull.Value Then
'If resPrice("ats" & "_price") Is DBNull.Value Then
' cannot find price so error
'natPrice = ""
'natAllow = 2
'End If
natPrice = resPrice("ats" & "_price")
natAllow = resPrice("ats" & "_allow")
Else
' cannot find price so error
natPrice = ""
natAllow = 2
End If
myConn.Close()
' substring not found therefore must be a pricing query
'MsgBox("string: " & encdata.ToString)
'natPrice = "9.99"
Catch ex As Exception
ErrHandle("4", "Process Error: " + ex.Message + ex.Data.ToString)
Finally
myConn.Close() ' dont forget to close!
End Try
End Sub
'========================
'= ServerSocket methods =
'========================
' received a socket notification for receiving from Ultimate
Private Sub ProcessSocketCommandIn(ByVal command As NotifyCommandIn, ByVal data As Object)
' holds the status message for the command
Dim status As String = ""
Select Case command
Case NotifyCommandIn.Listen
'status = String.Format("Listening for server on {0} ...", CStr(data))
status = "Waiting..."
Case NotifyCommandIn.Connected
'status = "Connected to Ultimate" ' + CStr(data)
status = "Receiving..."
Case NotifyCommandIn.Disconnected
status = "Waiting..." ' disconnected from Ultimate now ready...
Case NotifyCommandIn.ReceivedData
' store the encrypted data then process
processDataIn(data)
End Select
End Sub
' called from socket object when a network event occurs.
Private Sub NotifyCallbackIn(ByVal command As NotifyCommandIn, ByVal data As Object) Handles _serverPRC.Notify
' need to call Invoke before can update UI elements
Dim args As Object() = {command, data}
Invoke(_processInStream, args)
End Sub
End Class
Any help is appreciated
Many thanks
Invoke is a member of System.Windows.Forms.Form, and it is used to make sure that a certain method is invoked on the UI thread. This is a necessity in case the method in question touches UI controls.
In this case it looks like you simply can call the method directly, i.e.
instead of
Dim args As Object() = {command, data}
Invoke(_processInStream, args)
you can simply write
ProcessSocketCommandIn(command, data)
Also, in this case you can get rid of the _processInStream delegate instance.