Unload/Remove vb Resource from Memory - vb.net

I'm extremely new to programming and have still to master a lot of the basics. But I've managed to make an entire Program with help from here and there. However, I think I've got a Memory Leak with regards to my resources, again I'm new and almost certain this is a clumsy way to go about doing this...
I currently have background music in my application which uses a .wav file stored in My.Resources. I've implemented multiple measures to enable, disable and mute the audio successfully, but I've hit a snag when I introduced multiple audio tracks. The idea is to give the end-user an option between 4 background songs (stored as 4 .wav resources) in the 'settings' area of my program. When I build & test the solution it runs great, everything as expected. Then when I change the audio to a different track in the settings area, it works also. The snag happens when I change the song multiple times, I get a OutOfMemory error.
I think I understand what's happening; the resource is being added to the memory each time the user selects it, But I have no idea on how to remove -say- song1 from the memory if -say- song2 is selected. Here's my code which handles the selection & playing of the audio.
(The program uses radio buttons refereed to as '%chkbox' and requires the user to hit 'savebtn' before this code is ran.) Using Visual Basics 2012 .net 4.5
'Save and start songs.
If DjErhain_Mistychkbox.Checked = True Then
My.Settings.AudioDJErhain_UWBeats_Maniac = 0
My.Settings.Save()
My.Settings.AudioDjErhain_Misty = 1
My.Settings.Save()
My.Settings.AudioMachinimaSound_Exigence = 0
My.Settings.Save()
My.Settings.AudioSimplex_Memories_master = 0
My.Settings.Save()
My.Computer.Audio.Play(My.Resources.DjErhain_Misty, AudioPlayMode.BackgroundLoop)
ElseIf DJErhain_UWBeats_Maniacckbox.Checked = True Then
My.Settings.AudioDjErhain_Misty = 0
My.Settings.Save()
My.Settings.AudioDJErhain_UWBeats_Maniac = 1
My.Settings.Save()
My.Settings.AudioMachinimaSound_Exigence = 0
My.Settings.Save()
My.Settings.AudioSimplex_Memories_master = 0
My.Settings.Save()
My.Computer.Audio.Play(My.Resources.DJErhain_UWBeats_Maniac, AudioPlayMode.BackgroundLoop)
ElseIf MachinimaSound_Exigencechckbox.Checked = True Then
My.Settings.AudioMachinimaSound_Exigence = 1
My.Settings.Save()
My.Settings.AudioDJErhain_UWBeats_Maniac = 0
My.Settings.Save()
My.Settings.AudioDjErhain_Misty = 0
My.Settings.Save()
My.Settings.AudioSimplex_Memories_master = 0
My.Settings.Save()
My.Computer.Audio.Play(My.Resources.MachinimaSound_Exigence, AudioPlayMode.BackgroundLoop)
ElseIf Simplex_Memories_masterchckbox.Checked = True Then
My.Settings.AudioSimplex_Memories_master = 1
My.Settings.Save()
My.Settings.AudioDJErhain_UWBeats_Maniac = 0
My.Settings.Save()
My.Settings.AudioDjErhain_Misty = 0
My.Settings.Save()
My.Settings.AudioMachinimaSound_Exigence = 0
My.Settings.Save()
My.Computer.Audio.Play(My.Resources.Simplex_Memories_master, AudioPlayMode.BackgroundLoop)
Else
End If

Yes, this is likely to go wrong, the .wav format is not very compact. When you put it in a resource then using the resource is going to create an UnmanagedMemoryStream. It should be disposed when you don't use it anymore, the garbage collector won't run often enough to keep you out of trouble.
Add a new Module to your project and paste this code:
Imports System.IO
Module PlayerUtilities
Private CurrentStream As WeakReference(Of Stream)
Public Sub PlayResource(wave As Stream)
My.Computer.Audio.Play(wave, AudioPlayMode.BackgroundLoop)
Dim oldwave As Stream = Nothing
If CurrentStream IsNot Nothing AndAlso CurrentStream.TryGetTarget(oldwave) Then
oldwave.Dispose()
End If
CurrentStream = New WeakReference(Of Stream)(wave)
End Sub
End Module
And replace your calls to My.Computer.Audio.Play() with PlayResource(). The Dispose() call in this method on the previous audio stream will keep you out of trouble.

Related

Using Filewatcher for a progress bar with subdirectories, Wont Update Properly

I'm trying to copy Files from a local Computer to a Network device. I'm trying to get a Progress Bar working for the File copy, and got it working for a Single Directory with no Subdirectory:
Private Sub CopyPictures()
Try
If Not Directory.Exists(DestinationPath) Then
My.Computer.FileSystem.CreateDirectory(DestinationPath)
End If
Dim counterLocalFiles = My.Computer.FileSystem.GetFiles(SourcePath)
UpdateProgressBarMaximum1(CInt(counterLocalFiles.Count))
UpdateLabelText2(CStr(counterLocalFiles.Count)) 'is a label which shows copied X files of Label2 Files
fsw1 = New IO.FileSystemWatcher(DestinationPath)
fsw1.EnableRaisingEvents = True
My.Computer.FileSystem.CopyDirectory(SourcePath, DestinationPath)
GetSettingsFromFile()
Catch Exec As System.IO.IOException
Dim dr As DialogResult = MessageBox.Show("Some Random Error Code", "Exception Title", MessageBoxButtons.OKCancel)
If (Not DialogResult.OK = dr) Then
Exit Sub
Return
End If
End Try
End Sub
Private Sub fsw1_Created(sender As Object, e As FileSystemEventArgs) Handles fsw1.Created
Dim counterRemoteFiles = My.Computer.FileSystem.GetFiles(DestinationPath)
UpdateProgressBar1(CInt(counterRemoteFiles.Count))
UpdateLabelText1(CStr(counterRemoteFiles.Count))
End Sub
The Update ObjectX Subs are just invoke Functions since the CopyPictures is raised by a backgroundworker as well looking all like this one for example
Private Sub UpdateProgressBar1(Value As Int32)
If ProgressBar1.InvokeRequired Then
ProgressBar1.Invoke(New Action(Of Integer)(AddressOf UpdateProgressBar1), Value)
Else
'We are on the UI thread so update the control.
ProgressBar1.Value = Value
End If
End Sub
This code works perfectly fine for me, but I have to deal with SubDirectories which contain the Images, and the names of the subs are random so i cant predetermine them so I came up with slight changes:
The Counter is looking now like this:
Dim counterLocalFiles = System.IO.Directory.GetFiles(SourcePath, "*.jpg*", SearchOption.AllDirectories).Length
UpdateProgressBarMaximum1(CInt(counterLocalFiles))
UpdateLabelText2(CStr(counterLocalFiles))
And this:
Dim counterRemoteFiles = IO.Directory.GetFiles(DestinationPath, "*.jpg", SearchOption.AllDirectories).Length
UpdateProgressBar1(CInt(counterRemoteFiles))
UpdateLabelText1(CStr(counterRemoteFiles))
And I added:
fsw1.IncludeSubdirectories = True
Now the weired Problems started: It would properly count the file in the source Directory setting label2 to the correct amount of files in all subdirectories and then start copying. It would NOT update the Progressbar though in real time. It just updated it once when it was done with the first directory and just adding the amount of files to it which it contained. After that it completly stoppedd nored the second directory and didn't add that at all to the progressbar. What am I doing wrong here? I hope my english is fine, If you have any question or If I was not clear enough, please let me know. Thank you
You don't have an event consumer that triggers your progressbar update routine - you call it once when your filesystemwatcher is instantiated.
You need to declare an event that handles the copy event and fires off your progress update code. Because Filesystemwatcher cannot monitor network drives, you may want to declare an event that fires off your progress update method when the counterRemoteFiles count increments.
Turns out I just made a mistake with correctly putting the
fsw1.IncludeSubdirectories = True
I was setting it to true in the Form Editor instead of doing it in the code. Once i actually put that in the code after initialising the fsw, it would work just fine

Why won't my code Loop?

Sorry for the messy code :S
If CheckBox2.Checked = True Then
For i As Integer = 0 To 1 Step 0
If CheckBox1.Checked = True Then
If TextBox1.Text = lblCLickLImit.Text Then
Timer1.Stop()
TextBox1.Text = "0"
System.Windows.Forms.SendKeys.Send("{F5}")
System.Threading.Thread.Sleep(delaydelaytime)
System.Windows.Forms.SendKeys.Send("{ENTER}")
Else
If CheckBox1.Checked = False Then
If TextBox1.Text = lblCLickLImit.Text Then
Timer1.Stop()
TextBox1.Text = "0"
End If
End If
End If
Else
If CheckBox2.Checked = False Then
If CheckBox1.Checked Then
If TextBox1.Text = lblCLickLImit.Text Then
Timer1.Stop()
TextBox1.Text = "0"
System.Windows.Forms.SendKeys.Send("{F5}")
System.Threading.Thread.Sleep(delaydelaytime)
System.Windows.Forms.SendKeys.Send("{ENTER}")
End If
Else
If CheckBox1.Checked = False Then
If TextBox1.Text = lblCLickLImit.Text Then
Timer1.Stop()
TextBox1.Text = "0"
End If
End If
End If
End If
End If
Next
Basically this code is for an Auto Clicker program,(Hopefully this will help you understand, http://prntscr.com/7tuc3o interface) Ok so when the "Continuous" checkbox is selected the code is in theory supposed to loop for infinity. However when I run the program with everything selected as shown all that happens is the program clicks once and then crashes (not responding). Any help I have tried this loop in other programs and it works, just not with this code.
Your loop is tying up the UI thread. You'll need to look into using either a background worker:
BackgroundWorker handles long-running tasks. It does not freeze the entire program as this task executes.
(dotnetperls.com)
Here is the msdn walkthrough of how to set-up a backgroundworker:
https://msdn.microsoft.com/en-us/library/ywkkz4s1.aspx
Or
If this is a personal project and no one you love will need to maintain this code, you can use Application.DoEvents() to continue to pump messages while the program is looping. Here is the msdn documentation for that https://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents(v=vs.110).aspx
First of all, a Step of 0 doesn't really make any sense in a for loop. It may /work/ but it is going to drive anyone reading it later insane. If you want an infinite loop don't use a for loop, use:
While True
'code here
End While
For loops are used when you know exactly how many iterations of your loop you need. A while loop is designed to iterate as long as some condition is true. In this example the condition is always true so it loops indefinitely.
Your code is also just going to spin the UI thread constantly. It never pauses for input (even your sleep calls do not release the thread for input). As far as the OS knows, your app has locked up because it never processes any of the window messages that get posted to it. It is still happily spinning away though, until windows finally gets tired of it and prompts you to kill it.
Not sure what other "programs" you're working with but I can tell you you don't want to use a For loop for this. You want a do/while loop, like either:
While True
...
End While
or
Do
...
Loop While True

VB 2013 Application Out of Memory

I'm new to VB but recently created my first working app :) Anyway it just compresses files and little bit more. The latest thing that I added was a marquee style progress bar to animate while the operation was in progress and stop when it ends and the user can do the next zip operation. The progress bar wasn't updating, so I used a background worker to do the actual task while the button click just did the animation. Since then I've notcied serious degredation in the app. It struggles to load. I even got an out of memory error. Not sure if the background worker is related, but I thought I'd mention as it was the last update. Has anyone experienced anything similar? If I can provide and specific info, please ask me for it! Many thanks.
UPDATE: So I understand that I'm not using the BGWorker correctly. I will change that. But I found even with that removed, I still had issues. So I created a new form and started adding in bits of my code one by one. Anyway, I fell at the first hurdle with my form load sub. So I added that in slowly. I found that when ever I have any statements that load a variable from settings for persistent settings that the app falls over. Below is my code. Can anyone see what's up?????
UPDATE: I've found that if I load from settings the memory useage shoots up. I tried this too with saving settins on form closed. Below is the error received. The same out of memory occurs when trying to load settings too. I never experienced this on the first form I created. So perhaps I have missed some settings on the second, because the implementation in the code hasn't changed.
System.Configuration.ConfigurationErrorsException: Failed to save settings: An error occurred executing the configuration section handler for userSettings/Backup_Tool.My.MySettings. ---> System.Configuration.ConfigurationErrorsException: An error occurred executing the configuration section handler for userSettings/Backup_Tool.My.MySettings. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown
This is when I added in the code below:
Private Sub Form1_Closed(sender As Object, e As EventArgs) Handles MyBase.FormClosed
' TAB PAGE 1.
' Save controls to settings.
My.Settings.StartPathTextBox1 = StartPathTextBox1.Text
My.Settings.ZipPathTextBox1 = ZipPathTextBox1.Text
My.Settings.CopyPathTextBox1 = CopyPathTextBox1.Text
My.Settings.ZipSelectCheckBox1 = ZipSelectCheckBox1.Checked
My.Settings.CopySelectCheckBox1 = CopySelectCheckBox1.Checked
For Each s As String In StartNameListBox1.Items()
My.Settings.StartNameListBoxItems1.Add(s)
Next
For Each s As String In StartNameListBox1.SelectedItems()
My.Settings.StartNameListBoxSelectedItems1.Add(s)
Next
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' FORM 1.
' Initialise specialised string collections.
If My.Settings.StartNameListBoxItems1 Is Nothing Then
My.Settings.StartNameListBoxItems1 = _
New System.Collections.Specialized.StringCollection
End If
If My.Settings.StartNameListBoxSelectedItems1 Is Nothing Then
My.Settings.StartNameListBoxSelectedItems1 = _
New System.Collections.Specialized.StringCollection
End If
' TAB PAGE 1.
' Restore controls from saved settings.
StartPathTextBox1.Text() = My.Settings.StartPathTextBox1
ZipPathTextBox1.Text() = My.Settings.ZipPathTextBox1
CopyPathTextBox1.Text() = My.Settings.CopyPathTextBox1
ZipSelectCheckBox1.Checked = My.Settings.ZipSelectCheckBox1
CopySelectCheckBox1.Checked = My.Settings.CopySelectCheckBox1
For Each s As String In My.Settings.StartNameListBoxItems1()
StartNameListBox1.Items.Add(s)
Next
For Each s As String In My.Settings.StartNameListBoxSelectedItems1()
StartNameListBox1.SelectedItems.Add(s)
Next
' Decide controls initial states.
If StartNameListBox1.SelectedItems.Count = 0 Then
ZipSelectCheckBox1.Enabled = False
RunButton1.Enabled = False
End If
If ZipSelectCheckBox1.Checked = False Then
ZipPathTextBox1.Enabled = False
ZipBrowseButton1.Enabled = False
End If
If ZipPathTextBox1.Text = String.Empty Then
CopySelectCheckBox1.Enabled = False
End If
If CopySelectCheckBox1.Checked = False Then
CopyPathTextBox1.Enabled = False
CopyBrowseButton1.Enabled = False
End If
End Sub
It appears to be that you are only ever adding the current selections to the Settings collections. You might well clear the ListBox when they make new selections, but you do not do the same thing with the Settings Collections like My.Settings.StartNameListBoxItems1:
For Each s As String In StartNameListBox1.Items()
My.Settings.StartNameListBoxItems1.Add(s)
Next
The collection will have all the items in it from all the other times it has ever run already and you are now going to add more to it. Eventually you will have many, many, many items in it.
My.Settings.StartNameListBoxItems1.Clear ' REMOVE ALL OLD ITEMS
' Save just the current items to the collection
For Each s As String In StartNameListBox1.Items()
My.Settings.StartNameListBoxItems1.Add(s)
Next
use .Clear on both Collections

vb.net printing error: print_printpage continues to loop even though e.hasmorepages = false

So here is my situation: First of all, all of my printing code is stored in a module, which is called when I click the print button. My problem is, my pages print fine the first time I print my document, but if I click print again, the pages start printing on top of one another. When I debug, e.hasmorepages will be set to false, but it loops around and runs the print_page event again a couple times... I'm confused why it loops several times even though hasmorepages is definitely set to false?? The fact that usually prints right the first time but not the second time I click print makes the think something needs to be disposed. I know my page number variable is set to 1 before each print so it isn't that. The code I am using used to work until I moved it to it's own module.
Note: When I click print, i choose if I want a delivery recipt. if no, a store copy and customer copy are printed. If yes then Store, customer, and delivery copy are printed. Usually the Store and customer copy are printed on top of one another but the delivery copy is correct, so 2 pages are printed instead of 3.
Here is the basic outline of my module:
Imports System.Drawing
Imports System.Drawing.Printing
Module Receipt2
Public copy As Integer
Dim row As Integer
Dim ItemsRowCount As Integer = Invoice.dgvInvoiceItems.RowCount
Private Doc As New PrintDocument()
Public Sub printInvoice()
Try
copy = 1
AddHandler Doc.PrintPage, AddressOf Print_PrintPage
Doc.Print()
row = 0
Doc.Dispose()
copy = 1
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub Print_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs)
Try
'RECEIPT ITEMS PRINTED HERE
'print Store/customer copy and sig line
If copy = 1 Then
g.DrawString("Store Copy", New Font("Verdana", 15, FontStyle.Italic), Brushes.DarkRed, 50, 1045)
e.HasMorePages = True
copy = copy + 1
row = 0
Exit Sub
ElseIf copy = 2 Then
g.DrawString("Customer Copy", New Font("Verdana", 15, FontStyle.Italic), Brushes.DarkRed, 50, 1045)
If Invoice.boolDeliveryReceipt = True Then
e.HasMorePages = True
copy = copy + 1
row = 0
Exit Sub
End If
ElseIf copy = 3 Then
g.DrawString("Delivery Copy", New Font("Verdana", 15, FontStyle.Italic), Brushes.DarkRed, 50, 1045)
End If
'e.HasMorePages = False
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
End Module
Thanks so much for taking the time to look this over! I've spent hours trying to track the problem down and just not having any luck.
Private Doc As New PrintDocument()
This is where the problem started.
AddHandler Doc.PrintPage, AddressOf Print_PrintPage
This is where you nailed yourself. The As New syntax is pretty handy, but it will create an object just once. Problem is, only the first call to printInvoice creates the PrintDocument object. But every time you call printInvoice(), you add another PrintPage event handler to the same object. The net effect is that the second time you print, your PrintPage event handler will run twice for each page. The third time it will run three times. Etcetera. The Dispose() method doesn't otherwise do anything, a PrintDocument doesn't use disposable resources. Setting it to Nothing would fix the problem.
You fix this by just creating a new PrintDocument object every time you print. So
Private Doc As PrintDocument
Public Sub printInvoice()
Try
copy = 1
Doc = new PrintDocument()
AddHandler Doc.PrintPage, AddressOf Print_PrintPage
ItemsRowCount = Invoice.dgvInvoiceItems.RowCount
Doc.Print()
'' etc...
Further improve this code by making "Doc" a local variable. Or moving the code into a class.
Note how your ItemsRowCount variable has the same problem. It might be initialized too early, storing the wrong row count. And if you print again, later, with a different invoice then you'll definitely get the wrong number of rows.
Be careful with global variables, they have a knack for causing problems like this.

Button color change does not work on other computers

I have a strange problem that I can't seem to figure out. I have a timer that runs every 3 seconds and changes a button color to yellow, black, or green depending on if there are schedules pending, no schedules, or schedule currently running, respectively.
It works fine on my computer when I go into debug mode and add a schedule or have one run, it changes color like it should. I put this program onto a virtual machine and made the same schedule with the same data parameters, but the button doesn't change colors. I have .net 4.0 installed on both machines.
Public Sub createTimer()
buttonTimer = New Timer()
buttonTimer.Start()
buttonTimer.Interval = 3000
AddHandler buttonTimer.Tick, AddressOf buttonTimer_Tick
createTimer() is called from a runonce function when the page is loaded.
Public Sub buttonTimer_Tick(sender As Object, e As EventArgs)
If Scheduler.AutomationRunning = True Then
btnAutoStartMenu.ForeColor = Color.Green
ElseIf Scheduler.AutomationRunning = False And Automation.ScheduleList.Count > 0 Then
btnAutoStartMenu.ForeColor = Color.Yellow
ElseIf Scheduler.AutomationRunning = False And Automation.ScheduleList.Count = 0 Then
btnAutoStartMenu.ForeColor = Color.Black
End If
I am wondering could it possibly be a dll I'm not including in the installer? But color change seems like something easily built into .net framework, so having 4.0 on both should take care of that correct?
EDIT:
Also, tried it on another virtual machine instance and the same problem remains.
#RoadBump is correct. What happens if Scheduler is Nothing?
You should get some more clue by adding some error handling:
Try
If Scheduler.AutomationRunning = True Then
btnAutoStartMenu.ForeColor = Color.Green
ElseIf Scheduler.AutomationRunning = False And Automation.ScheduleList.Count > 0 Then
btnAutoStartMenu.ForeColor = Color.Yellow
ElseIf Scheduler.AutomationRunning = False And Automation.ScheduleList.Count = 0 Then
btnAutoStartMenu.ForeColor = Color.Black
End If
Catch ex as Exception
btnAutoStartMenu.ForeColor = Color.Red
End Try
So if your button goes red then you need to start some further investigation