vb.net Executing the same sub two times with different arguments - vb.net

I have this sub that generates an csv file from the result of a query
Private Sub generaReport(classe As String)
Dim query As String = "select bla bla bla"
Dim dt As New System.Data.DataTable()
Using con As New SqlConnection(My.Settings.dbstartConnectionString)
Using cmd As New SqlCommand(query, con)
cmd.Parameters.AddWithValue("#ieri", dataDa)
cmd.Parameters.AddWithValue("#domani", dataA)
cmd.Parameters.AddWithValue("#classe", classe)
Using sda As New SqlDataAdapter(cmd)
sda.Fill(dt)
End Using
End Using
End Using
Dim elencoCsv As String = ToCSV(dt)
If classe = "D" Then
File.Delete(nomeFileCsvD)
Using writer As New StreamWriter(nomeFileCsvD, True)
writer.WriteLine(elencoCsv)
End Using
Else
File.Delete(nomeFileCsvV)
Using writer As New StreamWriter(nomeFileCsvV, True)
writer.WriteLine(elencoCsv)
End Using
End If
InvioEmail(My.Settings.emails)
End
End Sub
I would call it two times like
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
generaReport("D")
generareport("V")
End Sub
but the second time it runs it still use "D" as parameter. How can I solve this? Thanks

As written, you have an End statement right before the End Sub line at the bottom of generaReport(). From the documentation, the End statement...
Terminates execution immediately.
So after executing with the "D" parameter, the entire application closes. It doesn't run "D" twice, it simply never runs "V".
Get rid of the End statement...

Related

Freeze UI with Threads

I have a timer to make a thread:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
'ListBox2.Items.Add("Hilo")
hiloCertificador1 = New Thread(AddressOf crearObjeto1)
hiloCertificador1.IsBackground = True
hiloCertificador1.Start()
End Sub
Public Sub crearObjeto1()
UpdateList()
End Sub
Private Delegate Sub UpdateListDelegate()
Private Sub UpdateList()
If Me.InvokeRequired Then
Me.BeginInvoke(New UpdateListDelegate(AddressOf UpdateList))
Else
Dim conn As New SqlConnection(parametrosCon)
Dim cmd = New SqlCommand("SELECT TOP 1 * FROM COLA WHERE docentry < 8654 and enviado = 0", conn)
Dim da As New SqlClient.SqlDataAdapter(cmd)
cmd.Connection.Open()
da.SelectCommand = cmd
da.Fill(dataSet, "Query")
For Each fila As DataRow In dataSet.Tables(0).Rows
cmd = New SqlCommand("UPDATE COLA SET enviado = 1 WHERE DOCENTRY = (#docEntry) AND TIPO = (#tipodoc)", conn)
cmd.Parameters.AddWithValue("#docEntry", fila("docentry"))
cmd.Parameters.AddWithValue("#tipodoc", fila("tipo"))
cmd.ExecuteNonQuery()
Dim factura As New FacturaCerificacion(fila("docentry"), fila("tipo"))
Next
cmd.Connection.Close()
dataSet.Tables("Query").Clear()
End If
End Sub
The timer have a 4000 interval, but when a thread start freeze my UI, I think is because the process is so big or the querys but i need make it without freezing.
The comment is correct, and I will describe for you the issues hinted therein
Using a System.Windows.Forms.Timer
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
This will run on the UI thread, and is only appropriate when doing UI things (even then, I am not sure you can really make a case for it over System.Threading.Timer)
Creating a new System.Threading.Thread
hiloCertificador1 = New Thread(AddressOf crearObjeto1)
hiloCertificador1.IsBackground = True
hiloCertificador1.Start()
This now runs off the UI, and is the entire contents of the Timer.Tick. So you've ticked on the UI then created a new thread off the UI. This is very odd
Calling a Sub to call a Sub
Public Sub crearObjeto1()
UpdateList()
End Sub
Private Sub UpdateList()
' do stuff
End Sub
The redundancy should be self-evident
Doing non-UI stuff but following the Control.InvokeRequired/BeginInvoke pattern
Private Delegate Sub UpdateListDelegate()
Private Sub UpdateList()
If Me.InvokeRequired Then
Me.BeginInvoke(New UpdateListDelegate(AddressOf UpdateList))
Else
' looks like a bunch of non-UI stuff
End If
End Sub
This pattern is used for doing things on the UI but there seems to be no UI code in that block.
Not using Using to ensure proper disposal of IDisposable objects
Dim conn As New SqlConnection(parametrosCon)
Dim cmd = New SqlCommand("SELECT TOP 1 * FROM COLA WHERE docentry < 8654 and enviado = 0", conn)
Dim da As New SqlClient.SqlDataAdapter(cmd)
' do stuff
cmd.Connection.Close()
DataSet.Tables("Query").Clear()
Nongermane to your current issue, but also important to know.
Solution
So although it looks like a noble effort, you seem to be going back and forth between UI and not for no reason at all, or more accurately, creating issues where none existed with some over-engineering. The whole thing can be simplified with some minor changes
Use System.Threading.Timer
Dim Timer2 As New System.Threading.Timer(Sub() UpdateList(), Nothing, -1, -1)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' done in Form_Load if your Timer1 is Enabled in designer
' or can be done in a Button.Click, or however you had enabled Timer1
Timer2.Change(2000, 4000) ' this will enable Timer after 2 seconds, then will tick every 4 seconds
'Timer2.Change(-1, -1) ' this is how it's disabled
End Sub
Just call this method, and use Using to properly dispose your database objects. Added a Sub DoUiStuff() which is how the pattern is properly implemented
Private Sub UpdateList()
Timer2.Change(-1, -1)
Using conn As New SqlConnection(parametrosCon)
conn.Open()
Using cmd = New SqlCommand("SELECT TOP 1 * FROM COLA WHERE docentry < 8654 and enviado = 0", conn)
Using da As New SqlClient.SqlDataAdapter(cmd)
da.SelectCommand = cmd
da.Fill(DataSet, "Query")
For Each fila As DataRow In DataSet.Tables(0).Rows
Using cmdInner = New SqlCommand("UPDATE COLA SET enviado = 1 WHERE DOCENTRY = (#docEntry) AND TIPO = (#tipodoc)", conn)
cmd.Connection.Open()
cmd.Parameters.AddWithValue("#docEntry", fila("docentry"))
cmd.Parameters.AddWithValue("#tipodoc", fila("tipo"))
cmd.ExecuteNonQuery()
Dim factura As New FacturaCerificacion(fila("docentry"), fila("tipo"))
End Using
Next
End Using
End Using
DoUiStuff(arguments) ' for example, if you need to update a GridView
DataSet.Tables("Query").Clear()
End Using
End Sub
Private Sub DoUiStuff(arguments As Whatever)
If Me.InvokeRequired() Then
Me.Invoke(New Action(Of Whatever)(AddressOf DoUiStuff), arguments)
Else
' do UI stuff with arguments
End If
Timer2.Change(2000, -1)
End Sub
Finally, so I'm not contradicting myself, I'll add the Dispose method to dispose of the Timer. This Sub will be by default in your Form.Designer.vb file which you can leave there or move it to Form.vb once you add to it.
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()>
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing Then
components?.Dispose()
Timer2?.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub

SELECTing data from database in vb and outputting data into label

I have the following code which SELECTs data from a database and outputs a value to a label on the form:
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim strConn As String = System.Configuration.ConfigurationManager.ConnectionStrings("yourConnectionString").ToString()
Dim sql As String = "SELECT aid FROM tbl_RAPA WHERE username=#username"
Dim conn As New Data.SqlClient.SqlConnection(strConn)
Dim objDR As Data.SqlClient.SqlDataReader
Dim Cmd As New Data.SqlClient.SqlCommand(sql, conn)
Cmd.Parameters.AddWithValue("#username", User.Identity.Name)
conn.Open()
objDR = Cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection)
While objDR.Read()
Label1.Text = objDR("aid")
End While
End Sub
However, if the value in the database is empty, the program runs into an error. Is there a way for me to do this so the program just returns an empty value rather than crashing?
The error message i am given is System.InvalidCastException: 'Unable to cast object of type 'System.DBNull' to type 'System.Windows.Forms.Label'.' on the line Label1.Text = objDR("aid")
Database objects generally need to be closed and disposed. Using...End Using blocks will do this for you even if there is an error.
Since you are only expecting one piece of data you can use .ExecuteScalar which provides the first column of the first row of the result set. This method returns an object.
Try to always use the the .Add method with Parameters. See http://www.dbdelta.com/addwithvalue-is-evil/
and
https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
and another one:
https://dba.stackexchange.com/questions/195937/addwithvalue-performance-and-plan-cache-implications
Here is another
https://andrevdm.blogspot.com/2010/12/parameterised-queriesdont-use.html
I had to guess at the database type so, check your database for the real value.
Don't update the User Interface until after the connection is closed and diposed. (End Using). I declared aid before the Using block so, it could be used after the block. Check if the object, aid, is not Nothing before adding it to the label's Text.
Imports MySql.Data.MySqlClient
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim aid As Object
Using conn As New MySqlConnection(ConfigurationManager.ConnectionStrings("yourConnectionString").ToString),
cmd As New MySqlCommand("SELECT aid FROM tbl_RAPA WHERE username=#username", conn)
cmd.Parameters.Add("#username", MySqlDbType.VarChar).Value = User.Identity.Name
aid = cmd.ExecuteScalar
conn.Open()
End Using
If Not IsNothing(aid) Then
Label1.Text = aid.ToString
End If
End Sub
I would add this before While objDR.Read() as a precaution in case your query returns no rows:
if objDR.HasRows
...
Then, to handle the null values (this is probably what you mean by empty):
If Not String.IsNullOrEmpty(objDR.Item("aid")) Then
Label1.Text = objDR("aid")
Else
Label1.Text = "Null !"
End if
You could also use ExecuteScalar() if you are only expecting one record. But you would need to handle the situation where no matching record is found.

Form Loads slowly in vb.net

I am currently working in ERP project on vb.net. I want to load product data in a textbox on form load. I am using autocomplete method but having a data of around 26000 the form loads slowly for 4 mins. Is there any way to avoid this or is there any way to call this function in background when the application starts?
This is my autocomplete textbox code. It works fine but it hangs alot as of the data is so large.
Private Sub pn()
Try
con = Class1.dbconn
Dim dt As New DataTable
Dim ds As New DataSet
ds.Tables.Add(dt)
Dim da As New SqlDataAdapter("select [Part Name] from
Part_Master_Download$", con)
da.Fill(dt)
Dim r As DataRow
TextBox9.AutoCompleteCustomSource.Clear()
For Each r In dt.Rows
TextBox9.AutoCompleteCustomSource.Add(r.Item(0).ToString)
Next
con.Close()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
the properties of the textbox should be set to true for autocompletetextbox
Don't populate the AutoCompleteCustomSource in a loop. Populate an array first and then load the list in one go with a single call to AddRange:
Dim items = dt.Rows.Cast(Of DataRow)().
Select(Function(row) CStr(row(0)).
ToArray()
TextBox9.AutoCompleteCustomSource.AddRange(items)
You should find that that speeds things up considerably. If there's still a problem with performance, we can look a bit further.
EDIT: To prove my point, I just tested the following code:
Public Class Form1
Private timer As Stopwatch
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
timer = Stopwatch.StartNew()
Dim rng As New Random
Dim a = Convert.ToInt32("a"c)
Dim z = Convert.ToInt32("z"c)
Dim items = Enumerable.Range(1, 26000).Select(Function(n) Convert.ToChar(rng.Next(a, z + 1)).ToString())
For Each item In items
TextBox1.AutoCompleteCustomSource.Add(item)
Next
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
MessageBox.Show(timer.Elapsed.ToString())
End Sub
End Class
and the message displayed "00:03:08.3167858", i.e. just over three minutes to load the list. I then changed the Load event handler to this:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
timer = Stopwatch.StartNew()
Dim rng As New Random
Dim a = Convert.ToInt32("a"c)
Dim z = Convert.ToInt32("z"c)
Dim items = Enumerable.Range(1, 26000).Select(Function(n) Convert.ToChar(rng.Next(a, z + 1)).ToString())
TextBox1.AutoCompleteCustomSource.AddRange(items.ToArray())
End Sub
so a single call to AddRange instead of calling Add in a loop, and the message was "00:00:00.0557427", i.e. just under 56 milliseconds. Is that better?
You can use paging to control the amount of returned data. Check this link for detailed examples.
Another way is to use (Task Async and await) so you won't lock your UI.
Here is a few last suggestions not related directly to the question but might help clean the code a bit:
you can skip the dataset and use the datatable directly, you don't need it for just one table.
Bind your results in the datatable to a datagridview or a combobox instead of iterating through your results and filling a textbox.
cheers

Getting strings from a textfile in a combobox, vb.net

Here is my code :
I'm trying to have some value out from a TXT file to a combobox or label but i feel combobox would be easier.
here's my code :
please note, some config.txt will only have 1 value while other 5-6
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim IDinFile As String
Dim ID As String
If IO.File.Exists("config.txt") Then
Using StreamReader As New IO.StreamReader("config.txt")
Do
IDinFile = StreamReader.ReadLine
If (IDinFile.IndexOf("7656")) <> -1 Then
ID = IDinFile.Substring(2)
ID = ID.Trim().Remove(ID.Length - 1)
ComboBox1.Items.Add(ID)
Exit Do
End If
Loop Until IDinFile Is Nothing
End Using
End If
End Sub
the file here in .png :
https://i.stack.imgur.com/iYaqP.png
Re-written the code for you. Problem was wrongly placed Exit Do. Also, its advisable to check the line before entering the loop rather than at the end of the loop.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim IDinFile As String
Dim ID As String
Const FILENAME As String = "config.txt"
If IO.File.Exists(FILENAME) Then
Using StreamReader As New IO.StreamReader(FILENAME)
Do While StreamReader.Peek() >= 0
IDinFile = StreamReader.ReadLine.Trim()
If (IDinFile.IndexOf("7656")) <> -1 Then
ID = IDinFile.Substring(1, IDinFile.Length - 2)
ComboBox1.Items.Add(ID)
End If
Loop
End Using
End If
End Sub
After you add the first item to the combobox you have an Exit Do statement. It no longer continues checking further lines and adding them to the combobox.
You should remove that statement.
Try this. It'll work if the values are organized line by line in the txt file.
Dim srd as New StreamReader("config.txt")
If io.file.exists("config.txt") then
Dim str() = srd.ReadToEnd.split(environment.newline)
For i = 0 to str.count-1
Combobox.item.add(str(i))
Next
srd.close

Getting the error "Expression does not produce a value

I am relatively new to using visual basic and I am having a problem populating a list box from a database. The error that comes up is Expression does not produce a value.
Here is the code from My form:
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Populate Blu Ray and DVD listboxes
Dim objMovies As New clsMovies
objMovies.Select_BR_List()
objMovies.Select_Dvd_List()
For Each strBluRay As String In objMovies.Select_BR_List
lstBluRay.Items.Add(strBluRay)
Next
For Each strDVD As String In objMovies.Select_Dvd_List
lstDvd.Items.Add(strDVD)
Next
End Sub
And here's the code from the class:
Public Sub Select_Dvd_List()
Dim objConnection As New SqlCeConnection(mstrCN)
'Create SQL statement
mstrSQL = "Select * from Dvd"
'Instantiate command
Dim objCommand As New SqlCeCommand(mstrSQL, objConnection)
'open Database
objCommand.Connection.Open()
'Instantiate Data Reader
Dim objDataReader As SqlCeDataReader
'Execute SQL
objDataReader = objCommand.ExecuteReader()
'read Sql results
Do While (objDataReader.Read)
mlstDvd.Add(objDataReader.Item("dvdTitle").ToString)
Loop
'Close
objCommand.Dispose()
objDataReader.Close()
objDataReader.Dispose()
objConnection.Close()
objConnection.Dispose()
End Sub
The issue is that you're trying to enumerate a Sub. In VB.NET, methods can either a Sub, which doesn't return a value, or a Function, which does return a value. Your Select_Dvd_List method is a Sub, so it doesn't return a value. You have this code though:
For Each strDVD As String In objMovies.Select_Dvd_List
That is trying to loop through the result of Select_Dvd_List but, as we've already established, Select_Dvd_List has no result. In that method, you are adding items to mlstDvd so surely that loop should be:
For Each strDVD As String In objMovies.mlstDvd