I have a particular problem in database connection disposal and despite there being a lot of back and forth over disposal of connections, datasets, and datatables I can't quite wrap my head around it as a new VB coder. My project has a form with a simple "Import Database" button which opens a connection. Lets assume I have a database with several Datasets and datatables that take up a bunch of memory so I have to be careful how I open and close things. I have a button to close the database when done with it. The code briefly looks like this and does not throw me errors (i.e. I can connect, get the right data and then close the database without an error being thrown)
Public ConnectedDB as DataBaseOps
Class Form1
Private Sub ImportDatabaseButton(sender as object, e and eventargs) handles ImportDatabasebutton.click
'Open filedialog and return the path, etc.
strPATH = OpenFileDialogMaster(...my filters here...)
ConnectedDB.OpenDatabase(strPath)
End Sub
Private Sub populateDSDT(....)
'...populate my datasets which then populate my datagrids
ConnectedDB.PopulateMyDataSet()
'...populate my datagrids
End sub
Private Sub CloseDatabaseButton(sender as object, e and eventargs) handles CloseDatabaseButton.click
ConnectedDB.CloseDatabase()
'Can I put something here to clean up the DatabaseOPs class?
End Sub
End Class
Now in a separate class
Private Class DataBaseOps
Public OpenCon As OleDb.OleDbConnection
Public Sub OpenDataBase(strPath as String)
'My.Settings.ConnectionString is a variable in App.Config
OpenCon = New OleDb.OleDbConnection(My.Settings.ConnectionString & strPath)
OpenCon.Open()
End Sub
Public Sub PopulateMyDataSet()
'...Populate my datasets and tables based on data from the database
'...Note that for the adapters I include "Using" to help keep those clean when I no longer need them
End sub
Public Sub CloseDatabase()
Try
OpenCon.Close()
Catch ex As Exception
MessageBox.Show("No Database file loaded.")
End Try
End Sub
Overrides Sub Finalize()
'...Datasets get .Dispose() which is a topic of its own elsewhere.
OpenCon.Dispose()
End Sub
End Class
Keep in mind there are other ways of doing this but the key factor here is to open the connection, keep it open to populate datagrids, do some other work in the database, and then close it releasing the connection and datasets memory.
My problem is I've tried various means to limit the scope of "ConnectedDB" and its datasets including putting a Finalize() sub into the process in an attempt to remove it from memory. I also toyed with scoping but neither of these ideas work perfectly and it seems to retain items in memory.
Using my general code what can I do to clean up the memory resource here? As a relatively new VB.net coder, am I missing something obvious (like I could maybe inherit one class to the other instead of calling them?)
Keep your connections local to where they are used. Then you can use Using...End Using blocks to ensure that they are closed and disposed.
Thanks to Duncan Edwards Jones for this quote.
"A database connection is like a refrigerator door - You only open it
when you need something, you only take out what you need, and you
close it as soon as you have taken out what you need. "
A DataSet can hold more than one DataTable. I am not sure why you would need more than one DataSet.
Class Form1
Private ConnectedDB As DataBaseOps
Private ds As DataSet
Private Sub ImportDatabaseButton(sender As Object, e And eventargs) Handles ImportDatabaseButton.click
Dim OpenDial As New OpenFileDialog
'Open filedialog and return the path, etc.
Dim strPATH = OpenDial.FileName
ConnectedDB = New DataBaseOps(strPATH)
End Sub
Private Sub populateDSDT()
'...populate my datasets which then populate my datagrids
ds = ConnectedDB.PopulateMyDataSet()
'...populate my datagrids
End Sub
End Class
Public Class DataBaseOps
Private DBFile As String
Public Sub New(file As String)
DBFile = file
End Sub
Public Function PopulateMyDataSet() As DataSet
Dim ds As New DataSet
Using OpenCon As New OleDb.OleDbConnection(My.Settings.ConnectionString & DBFile)
OpenCon.Open() 'But don't do this until directly before the .Execute...
'...Populate my datasets and tables based on data from the database
'...Note that for the adapters I include "Using" to help keep those clean when I no longer need them
End Using
Return ds
End Function
End Class
Add this two lines in your Finalize method after OpenCon.Dispose()
GC.Collect()
GC.WaitForPendingFinalizers()
Related
i'm writing for ask this community the best way for able my software not block during long operation over file multiselection.
My software analyse 1 to n PDF and for every file read all pages. for every page read all lines and for all line analyse data following string algorithm.
In case user load many PDF file the process could be long and could freeze application.
I ask to you the best way for deny freeze in my application.
On this site i read about ASYNC method or Thread sleep function and much other, but i don't understand which is best to use.
Initially i simply thought about a wait timer between file analyse, but this extend time of operation and maybe not deny freeze, which depend on CPU load and file complexity.
***** UPDATE CODE *****
Here a little schema code of my test using Async method...
Private Async Sub cmdParsePDF_Click(sender As Object, e As EventArgs) Handles cmdParsePDF.Click
Await Task.Run(Sub()
ParsePDF(textboxType.text)
End Sub)
End Sub
Public sub ParsePDF(byval intType as integer)
If textboxSource.text <>"" then
io.file.copy(textboxSource.text,textboxDestination.text,1)
'.... others operations....
End If
End Sub
...when code encounter textboxType.text or textboxSource.text and textboxDestination.text go on error because it say this object is from another thread...
How could i solve this problem ? My sub ParsePDF() have many reference to object in the form for extraction of values and count a big number of lines code... is there a way for bypass this issue ?
you can use threads :
simple example to execute sub in separate thread :
Imports System.Threading
{......}
Private Sub Button1_Click(sender As Object, e As EventArgs)
Dim BackProcess = New Thread(Sub() Me.analysePdf("d:\pdfFolder", 1))
BackProcess.Priority = ThreadPriority.Normal
BackProcess.Start()
end Sub
sub analysePdf(pdfPath as string, analyseType as integer)
'your stuff.............
'.......................
'send data to UI with invoke :
Me.BeginInvoke(Sub() textbox1.Text = "running...")
'etc...
end sub
I am working on a project where the user sets a reminder with information, date and time for when a notification should pop up and the notification should be clickable and open another form with the reminder information on it. So far I can set the reminder but it pops up as soon as the remind button in clicked, my notification looks a standard windows 10 notification, I just want the notification to be scheduled for a certain date and time. The information including date and time is saved into an Access Database. I am using VB.Net
Kind Regards
Form:
This is my reminder form as of now
This is how my notification looks like
Imports System.Data.OleDb
Public Class frmReminder
Private CurrentReminderID As Integer = -1
Private Sub frmReminder_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BtnClear.PerformClick()
Timer1.Enabled = True
End Sub
Private Sub BtnClear_Click(sender As Object, e As EventArgs) Handles BtnClear.Click
Label6.Text = ""
TxtCustName.Text = ""
TxtDeviceInfo.Text = ""
TxtPrice.Text = ""
TxtDateDue.ResetText()
End Sub
Private Sub BtnSetReminder_Click(sender As Object, e As EventArgs) Handles BtnSetReminder.Click
If DbConnect() Then
Dim SQLCmd As New OleDbCommand
If CurrentReminderID = -1 Then
With SQLCmd
.Connection = cn
.CommandText = "Insert into TblReminder (CustomerName, DeviceInfo, RepairPrice, ReminderDate)"
.CommandText &= "Values (#CustomerName, #DeviceInfo, #RepairPrice, #ReminderDate)"
.Parameters.AddWithValue("#CustomerName", TxtCustName.Text)
.Parameters.AddWithValue("#DeviceInfo", TxtDeviceInfo.Text)
.Parameters.AddWithValue("#RepairPrice", TxtPrice.Text)
.Parameters.AddWithValue(" #ReminderDate", TxtDateDue.Text)
.ExecuteNonQuery()
.CommandText = "Select ##Identity"
CurrentReminderID = .ExecuteScalar
Label6.Text = CurrentReminderID
End With
End If
End If
Notification.ShowBalloonTip(1000, "Reminder", "Customer Order Due!", ToolTipIcon.None)
End Sub
End Class
You need to use a Timer and to avoid the constant reading of the database you may use something very simple as a DateTimePicker or a TextBox. In this example I'll use a Texbox.
So, insert a TextBox1 in your form and had the value of the first date/time (read from your database just once). The Textbox1 text should be something like "2021-10-21 15:00" (don't put the seconds and don't forget to have a space between date and time)
TextBox1.Text="2021-10-21 15:00" ' read this data from your database
Then you need to insert a Timer in your form and add this values:
Timer1.Interval = 60000 ' 1 minute
Timer1.Enabled = True
For the last you need to double click the Timer1 and write this code:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
If TextBox1.Text = Now.ToString("yyyy-MM-dd HH:mm") Then
' At this moment you should read the next date/time and put that in TextBox1
MsgBox("Show the popup notification")
End If
End Sub
Since you are using DB connections, ill assume you are familiar with multi threading.
RULE 1. The easiest way to do this is for your application to keep track of (in RAM) the next notification. Any time you add a new one through your app it will both insert it into the DB as well as check to see if it is going to elapse before the current one in RAM. When the one in RAM expires it grabs the next one from the DB. All that to say, you find a way to ensure that the next one to elapse is kept in RAM at any given time.
RULE 2. Then, when a notification is 'saved' in RAM, compare the time when the notification should be displayed - current time, this will give you how long until the notification should de shown and set a timer for that amount of time.
RULE 3. When the timer has elapsed display the notification for the one that is in RAM (Per rule 1 this is the one that just elapsed). And iterate the DB to find the next one to expire. Rinse and repeat. (Note, youll need to handle edge cases for when the first one is intered as there will not be any to compare to, and for when the last one is notified as there will not be any notifications left to store in RAM)
Tada, sorry i dont have code samples. But that is how i have solved this very issue numerous times. It works quite nicely.
EDIT 1: CODE
'main for testing
Dim notificationScheduler As NotificationScheduler = NotificationScheduler.getInstance
notificationScheduler.insertNotification(New MyNotificationEntity("Yay, working", DateTime.Now.AddSeconds(15)))
' expected that the one below will be displayed since insertNotification will only keep track of the newest one in the queue
' As mentioned in your post, you have a DB to persist them long term and as such will only need one in the APP's RAM at a time
notificationScheduler.insertNotification(New MyNotificationEntity("Yay, working-2", DateTime.Now.AddSeconds(3)))
' myNotificationEntity, will be replaced by whatever class you are using to package your reminder's in a single class (dont see on in your code sample)
Public Class MyNotificationEntity
Public data As String
Public timeToShow As DateTime
' this is a dumb bucket class, you will not end up using this one but rather whatever class you use to contain your notifications
' you will need to add _timeToShow as a variable to your class or change the code in the NotificationScheduler to be compatible with your Notification class
Public Sub New(data As String, timeToShow As DateTime)
Me.data = data
Me.timeToShow = timeToShow
End Sub
End Class
'NotificationScheduler class
Public Class NotificationScheduler
Private Shared _instance As NotificationScheduler
Private Shared ReadOnly _lock As Object = New Object()
Dim queuedNotification As MyNotificationEntity
Dim WithEvents timer As System.Windows.Forms.Timer = New Timer
Dim timerElapsedTime As DateTime = DateTime.Now
Private Sub New()
timer.Enabled = False
End Sub
Public Shared Function getInstance()
' enforce singleton design pattern (i.e. there should only be one of these in existence)
' If this does not make sense, that is fine, at some point lookup the 'singleton design pattern'
' Also, SyncLock is a VB (.net really) element that means in a multi-threaded application only one of them at any given time is inside the SyncLock block,
' the rest are queued to alleviate race conditions. Again, if this does not make sense, lookup 'race conditions'
SyncLock _lock
If (_instance Is Nothing) Then
_instance = New NotificationScheduler()
End If
End SyncLock
Return _instance
End Function
' called after you have placed the new notification in your DB, replaces the currently queued notification only if the new one will be shown first
Public Sub insertNotification(notificationEntity As MyNotificationEntity)
If timerElapsedTime.Ticks < notificationEntity.timeToShow.Ticks Then
queuedNotification = notificationEntity
timer.Stop()
Dim span As TimeSpan = notificationEntity.timeToShow - timerElapsedTime
timer.Interval = CInt(span.TotalMilliseconds)
timer.Start()
End If
End Sub
Private Sub timer_elapsed() Handles timer.Tick
' the notification time has arrived
showNotification()
readNextFromDb()
End Sub
Private Sub readNextFromDb()
' read the next (if any) from the and handle edge cases for when there is not next one in the DB
'IMPORTANT: if the one you read from the DB has already happened (i.e. the notification dateTime is after now, notify and do this method again)
'' your code here
End Sub
Private Sub showNotification()
MsgBox(queuedNotification.data) ' do whatever notification you want
End Sub
End Class
You will need to (it appears) create a class NotificationDAO (Data Access Object) or NotificationDTO (Data Transfer Object) or a generic NotificationEntity. That class will have a constructor and public instance variables for the items you need to store about a notification (i.e. every column you have in the 'TblReminder ' table) and everything should work fairly easily.
Note, the above code does not work nor check for pulling notifications out of a DB that have already passed their notification time, read the comments in the code for further information.
I am attempting to read a .txt file that I successfully wrote with a separate program, but I keep getting the program stalling (aka no input/output at all, like it had an infinite loop or something). I get the message "A", but no others.
I've seen a lot of threads on sites like this one that list all sorts of creative ways to read from a file, but every guide I have found wants me to change the code between Msgbox A and Msgbox D. None of them change the result, so I'm beginning to think that the issue is actually with how I'm pointing out the file's location. There was one code (had something to do with Dim objReader As New System.IO.TextReader(FileLoc)), but when I asked for a read of the file I got the file's address instead. That's why I suspect I'm pointing to the .txt wrong. There is one issue...
I have absolutely no idea how to do this, if what I've done is wrong.
I've attached at the end the snippet of code (with every single line of extraneous data ripped out of it).
If it matters, the location of the actual program is in the "G01-Cartography" folder.
Private Sub GameMain_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
LoadMap("Map_Cygnus.txt")
End Sub
Private Sub LoadMap(FileLoc As String)
FileLoc = "C:\Users\Adam\Documents\Visual Studio 2013\Projects\G01-Cartography\Maps\" + FileLoc
MsgBox("A")
Using File As New StreamReader(FileLoc)
MsgBox("B")
Dim WholeMap = File.ReadLine()
MsgBox("C")
End Using
MsgBox("D")
End Sub
What does running this show you in the debugger? Can you open the Map_Cygnus.txt file in Notepad? Set a breakpoint on the first line and run the program to see what is going on.
Private BaseDirectory As String = "C:\Users\Adam\Documents\Visual Studio 2013\Projects\G01-Cartography\Maps\"
Private Sub GameMain_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim WholeMap = File.ReadAllText(Path.Combine(BaseDirectory, "Map_Cygnus.txt"))
Debug.Print("Size Of Map: {0}", WholeMap.Length)
End Sub
It looks like you're using the correct methods/objects according to MSDN.
Your code runs for me in an new VB console app(.net 4.5)
A different approach then MSGBOXs would be to use Debug.WriteLine or Console.WriteLine.
If MSGBOX A shows but not B, then the problem is in constructing the stream reader.
Probably you are watching the application for output but the debugger(visual studio) has stopped the application on that line, with an exception. eg File not found, No Permission, using a http uri...
If MSGBOX C doesn't show then problem is probably that the file has problems being read.
Permissions?
Does it have a Line of Text?
Is the folder 'online'
If MSGBOX D shows, but nothing happens then you are doing nothing with WholeMap
See what is displayed if you rewite MsgBox("C") to Debug.WriteLine("Read " + WholeMap)
I have a few suggestions. Firstly, use Option Strict On, it will help you to avoid headaches down the road.
The code to open the file is correct. In addition to avoiding using MsgBox() to debug and instead setting breakpoints or using Debug.WriteLine(), wrap the subroutine in a Try...Catch exception.
Private Sub GameMain_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
LoadMap("Map_Cygnus.txt")
End Sub
Private Sub LoadMap(FileLoc As String)
Try
FileLoc = "C:\Users\Adam\Documents\Visual Studio 2013\Projects\G01-Cartography\Maps\" + FileLoc
MsgBox("A")
Using File As New StreamReader(FileLoc)
MsgBox("B")
Dim WholeMap = File.ReadLine() 'dimming a variable inside a block like this means the variable only has scope while inside the block
MsgBox("C")
End Using
MsgBox("D")
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
Note that you normally should only catch whatever exceptions you expect, but I generally catch everything while debugging things like this.
I would also like to point out that you are only reading one line out of the file into the variable WholeMap. That variable loses scope as soon as the End Using line is hit, thereby losing the line you just read from the file. I'm assuming that you have the code in this way because it seems to be giving you trouble reading from it, but thought I would point it out anyway.
Public Class GameMain
Private WholeMap As String = ""
Private Sub GameMain_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
LoadMap("Map_Cygnus.txt")
End Sub
Private Sub LoadMap(FileLoc As String)
Try
FileLoc = "C:\Users\Adam\Documents\Visual Studio 2013\Projects\G01-Cartography\Maps\" + FileLoc
Using File As New StreamReader(FileLoc)
WholeMap = File.ReadLine() 'dimming the variable above will give all of your subs inside class Form1 access to the contents of it (note that I've removed the Dim command here)
End Using
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
End Class
I have been banging my head against the wall all day trying to figure this one out.
I am finishing up a program to simply delete files in specific temp folders. I have read that it is sometimes good practice to create separate classes for methods and variables. So I have created a separate class for a couple methods to delete files and folders in a specified directory. I am using a Background Worker in my Form1 class and am calling my deleteFiles() method from my WebFixProcesses class in the DoWork event in the Form1 class. I am using a Background Worker so that I can easily report progress back to a progress bar on my main form.
The files get deleted without an issue but I just can't get the label on my main form to reflect the current file being deleted. the label doesn't change in any way.
I know the formula is correct as I can get this working if the method is in the Form1 class. and I simply use:
Invoke(Sub()
lblStatus.Text = File.ToString
lblStatus.Refresh()
End Sub)
here is my method that I am calling from the WebFixProcesses class:
Public Shared Sub deleteFiles(ByVal fileLocation As String)
For Each file As String In Directory.GetFiles(fileLocation)
Try
fileDisplay.Add(file)
For i = 1 To fileDisplay.Count
file = fileDisplay(i)
Form1.BackgroundWorker1.ReportProgress(CInt(i / fileDisplay.Count) * 100)
Next
IO.File.Delete(file)
Form1.labelText(file)
Form1.labelRefresh()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
Next
End Sub
labelText() and labelRefresh() are methods from my main form which are using delegates to try to pass information to the control:
Public Sub labelText(ByVal file As String)
If lblStatus.InvokeRequired Then
Dim del As New txtBoxDelegate(AddressOf labelText)
Me.Invoke(del, file)
Else
lblStatus.Text = file.ToString()
End If
End Sub
Public Sub labelRefresh()
If lblStatus.InvokeRequired Then
Dim del As New txtBoxRefDelegate(AddressOf labelRefresh)
Me.Invoke(del)
Else
lblStatus.Refresh()
End If
End Sub
If anyone can help me out to inform me what I may be doing wrong it would be immensely appreciated as my head is in a lot of pain from this. And maybe I am going at it all wrong, and just being stubborn keeping my methods in their own class. But any help would be awesome. Thanks guys!
What Hans wrote on the question comment is true: Form1 is a type, not an instance, but to make things easier to newbye programmes (coming from VB6), M$ did a "mix", allowing you to use the form name as the instance of the form in the main thread.
This however works only if you are on that thread.
If you reference Form1 from another thread, a new instance of Form1 is created.
To solve the issue, add this code to the form:
Private Shared _instance As Form1
Public ReadOnly Property Instance As Form1
Get
Return _instance
End Get
End Property
We will use this property to store the current instance of the form. To do so, add this line to the Load event:
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
_instance = Me
'other code here
End Sub
Now, from every class, in any thread, if you use
Form1.Instance
...you get the actual form. Now you can Invoke, even from the same form:
Me.instance.Invoke(Sub()
Me.lblStatus.Text = "Hello World"
End Sub)
in vb 2008 express this option is available under application properties. does anyone know what is its function? does it make it so that it's impossible to open two instances at the same time?
does it make it so that it's impossible to open two instances at the same time?
Yes.
Why not just use a Mutex? This is what MS suggests and I have used it for many-a-years with no issues.
Public Class Form1
Private objMutex As System.Threading.Mutex
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Check to prevent running twice
objMutex = New System.Threading.Mutex(False, "MyApplicationName")
If objMutex.WaitOne(0, False) = False Then
objMutex.Close()
objMutex = Nothing
MessageBox.Show("Another instance is already running!")
End
End If
'If you get to this point it's frist instance
End Sub
End Class
When the form, in this case, closes, the mutex is released and you can open another. This works even if you app crashes.
Yes, it makes it impossible to open two instances at the same time.
However it's very important to be aware of the bugs. With some firewalls, it's impossible to open even one instance - your application crashes at startup! See this excellent article by Bill McCarthy for more details, and a technique for restricting your application to one instance. His technique for communicating the command-line argument from a second instance back to the first instance uses pipes in .NET 3.5.
Dim _process() As Process
_process = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName)
If _process.Length > 1 Then
MsgBox("El programa ya está ejecutandose.", vbInformation)
End
End If
I found a great article for this topic: Single Instance Application in VB.NET.
Example usage:
Module ModMain
Private m_Handler As New SingleInstanceHandler()
' You should download codes for SingleInstaceHandler() class from:
' http://www.codeproject.com/Articles/3865/Single-Instance-Application-in-VB-NET
Private m_MainForm As Form
Public Sub Main(ByVal args() As String)
AddHandler m_Handler.StartUpEvent, AddressOf StartUp ' Add the StartUp callback
m_Handler.Run(args)
End Sub
Public Sub StartUp(ByVal sender As Object, ByVal event_args As StartUpEventArgs)
If event_args.NewInstance Then ' This is the first instance, create the main form and addd the child forms
m_MainForm = New Form()
Application.Run(m_MainForm)
Else ' This is coming from another instance
' Your codes and actions for next instances...
End If
End Sub
End Module
Yes you're correct in that it will only allow one instance of your application to be open at a time.
There is even a easier method:
Use the following code...
Imports System.IO
On the main form load event do the following:
If File.Exist(Application.StartupPath & "\abc.txt") Then
'You can change the extension of the file to what ever you desire ex: dll, xyz etc.
MsgBox("Only one Instance of the application is allowed!!!")
Environment.Exit(0)
Else
File.Create(Application.StartupPath & "\abc.txt", 10, Fileoptions.DeleteonClose)
Endif
This will take care of single instances as well as thin clients, and the file cannot be deleted while the application is running. and on closing the application or if the application crashes the file will delete itself.