How to display/save multiple (ADF) WIA images after scanned - wpf

I was tasked with creating a document digitizing program since the one the company bough borders on the awful. Its bad. Really bad. After some research I started a VB.NET WPF program. I already made login section and created the folders in a server where the files will be going, with the location of each file being saved in a SQL table. Anyway I basically need three things that are giving me a headache!
1) How to scan multiple pages with WIA? This is my current, and very raw scanning code:
Dim CD As New WIA.CommonDialog
Dim txt As String
Dim picture As Image
Dim F As WIA.ImageFile = CD.ShowAcquireImage(WIA.WiaDeviceType.ScannerDeviceType)
txt = txt1.Text
F.SaveFile("\\serverlocation" + txt + "." + F.FileExtension)
Txt is basically the name being given by the user. The problem is that this code only scans ONE page, how can I make it save multiple ones in the same file/separate files? (whichever works best).
In the same spirit of the previous question, is there a way to convert these files to JPEG/PNG (BMP takes a LOT of space) or even convert them to a PDF file if the user so wishes?
How do I display the scanned picture after its been scanned?
I am using VB.NET and WPF (not Forms). Any replies will be greatly appreciated :).
EDIT: Closest thing I've managed to multiple ADF scanning with http://forums.codeguru.com/showthread.php?439027-Windows-Image-Acquisition-%28WIA%29-Code . I converted C# to VB but the code throws me errors in the compiler.

Private Function scanMe(ByVal myDPI As Integer, ByVal myHeight As Double, ByVal myWidth As Double, ByVal ShowSelectScanner As Boolean, ByVal ShowScanPreview As Boolean) As Byte()
Dim CD As New WIA.CommonDialog
Dim device As WIA.Device = CD.ShowSelectDevice(WIA.WiaDeviceType.ScannerDeviceType, ShowSelectScanner, False)
Dim item As WIA.Item = device.Items(1)
Try
With item
.Properties("Horizontal Resolution").Value = myDPI
.Properties("Vertical Resolution").Value = myDPI
.Properties("Horizontal Extent").Value = myDPI * myWidth
.Properties("Vertical Extent").Value = myDPI * myHeight
End With
Dim F As WIA.ImageFile
If ShowScanPreview Then
F = CD.ShowAcquireImage(WiaDeviceType.ScannerDeviceType, WiaImageIntent.ColorIntent, WiaImageBias.MaximizeQuality, WIA.FormatID.wiaFormatBMP, False, True, False)
Else
F = CD.ShowTransfer(item, WIA.FormatID.wiaFormatPNG, False)
End If
Dim myBuffer As Byte() = F.FileData.BinaryData
Return myBuffer
Catch ex As Exception
MsgBox(ex.Message)
Return Nothing
End Try
End Function

Related

Populating two one-dimensional arrays with text file

For my Visual Basic final, my program is required to read data from a text file into two different arrays, each being one-dimensional. The following is my code for doing so:
Option Explicit On
Option Infer Off
Option Strict On
Public Class frmMain
'Constant for filename and a dirty flag variable
Const INVENTORY_FILENAME As String = "inventory.txt"
Dim noFile As Boolean = False
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Populates DVD listbox with text file data upon load
'Variable for reading the file
Dim myFile As IO.StreamReader
'Declaring arrays for DVD names and prices
Dim arrayDVD() As String
Dim arrayPrice() As Double
'Variables for populating arrays with respective data
Dim dvdName As String
Dim dvdPrice As Double
Dim i As Integer = 0
'Checking that file exists then reading data to each array
If IO.File.Exists(INVENTORY_FILENAME) Then
myFile = IO.File.OpenText(INVENTORY_FILENAME)
'Read data to arrays
Do Until myFile.Peek = -1
dvdName = myFile.ReadLine()
dvdPrice = Double.Parse(myFile.ReadLine())
arrayDVD = dvdName
arrayPrice = dvdPrice
'Using arrays to populate multicolumn listbox
lstDVD.Items.Add(arrayDVD(i) & arrayPrice(i))
i += 1
Loop
'Closing the file
myFile.Close()
End If
End Sub
End Class
The text file alternates names and prices of DVDs to be read as individual lines, making the arrays parallel:
Pulp Fiction
9.99
Jumanji
13.99
And so on...
I'm receiving a value type error code stating that I cannot convert 'String' to 'String()' or convert 'Double' to 'Double()' when setting the arrays' values equal to their respective variables. Is there a way to correct this? Thanks in advance!
These lines are wrong:
arrayDVD = dvdName
arrayPrice = dvdPrice
arrayDVD and arrayPrice are arrays. You need to assign to a specific element in each of those arrays:
arrayDVD(i) = dvdName
arrayPrice(i) = dvdPrice
Don't forget to make sure the arrays actually have enough elements for this.
Hint: ReDim Preserve is pretty much the least efficient way possible to make sure an array is big enough. Each use will allocate a brand new array, copy the elements one at a time, assign the new array to the old reference, and then release the old array. It does not preserve in-place. Nevertheless, if this is a 100-level course it might be what you are expected to do at this point.
Finally, you should never use Double when working with money (use Decimal instead).
Separate from the question, here is how I might approach this without the weird array limitation:
Private Iterator Function ReadInventoryFile(filePath As String) As IEnumerable(Of (String, Decimal))
Using rdr As New StreamReader(filePath)
Dim DVDName As String = Nothing
While (DVDName = rdr.ReadLine()) IsNot Nothing
Yield (DVDName, Decimal.Parse(rdr.ReadLine()))
End While
End Using
End Function
Const INVENTORY_FILENAME As String = "inventory.txt"
Private data As List(Of (String, Decimal))
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try 'Replaces the File.Exists() check
data = ReadInventoryFile(INVENTORY_FILENAME).ToList()
For Each item As (String, Decimal) In data
lstDVD.Items.Add($"{item.Item1}{vbTab}{item.Item2:C}")
Next
Catch
' Actually do something here. Empty catch blocks are rarely correct.
' Note I catch at this level, rather than in the ReadFile() method.
End Try
End Sub

How to fix 'Index was outside of bounds of the array'

Using visual basic. Trying to load a series of reports onto a listview, listview consists of 3 columns (location, date and severity level) everytime it loads it crashes due to 'index being outside the bounds of the array'.Specifically around DOI = reportdetails(1) in my code. It is loading off of a textfile. I have the data within the textfile so I am unsure of why it is saying I am asking for information that doesnt exist. The program also encypts the textfile.
Dim locate, DOI, SeverityLevel, ReportTitles, EReportTitles, ReportDetails(2) As String
Dim Index As Integer 'Define Variables
Dim FileNum As Integer = FreeFile()
Dim IncidentReport As ListViewItem
lstReports.Items.Clear()
If Dir("ReportTitles.txt") <> "" Then 'If the directory of the file exits then continue
FileOpen(FileNum, "ReportTitles.txt", OpenMode.Input) 'open file
Do Until EOF(FileNum) 'Repeat until the end of the file is reached
EReportTitles = "" 'Clear variables, to safeguard against crashes or errors
ReportTitles = ""
EReportTitles = LineInput(FileNum) 'EReportTitles is equal to the current file line
Dim FileName As String = "ReportTitles.txt" 'Define variables
Dim I, C As Integer
Dim Last As Integer = EReportTitles.Length - 1
Dim ThisChar As Char
For I = 0 To Last 'Begin for loop
ThisChar = EReportTitles.Chars(I) 'Decryption of file
C = Asc(ThisChar) Xor 22
ThisChar = Chr(C)
ReportTitles += ThisChar
Next
If ReportTitles <> "" Then
ReportDetails = Split(ReportTitles, ",") 'Split the lines when a "," is encountered
locate = ReportDetails(0) 'Assosciate to relevant value in array
DOI = ReportDetails(1)
SeverityLevel = ReportDetails(2)
IncidentReport = New ListViewItem
IncidentReport.Text = locate 'Add relevant values to IncidentReport ListViewItem variable
IncidentReport.SubItems.Add(DOI)
IncidentReport.SubItems.Add(SeverityLevel)
lstReports.Items.Add(IncidentReport) 'Transfer IncidentReport to listview
Else
End If
Loop
FileClose(FileNum) 'close file
End If
Expected result is to load all of the report location, dates and severity levels onto the listview.
Also sorry about the formatting of this question, i'm new to stack overflow.
There's no point declaring ReportDetails like this:
ReportDetails(2) As String
because that creates an array that you never use. Here:
ReportDetails = Split(ReportTitles, ",")
you are creating a new array anyway and the length of that array will be determined by the number of delimiters in ReportTitles. If you're being told that 1 is an invalid index for that array then that array must only contain 1 element, which means that ReportTitles didn't contain any delimiters.
This is not something that we should have to explain to you because you can easily see it for yourself by debugging and you should ALWAYS debug BEFORE posting here. Set a breakpoint at the top of the code, step through it line by line and examine the state at each step. You can easily see the contents of ReportTitles and ReportDetails and anything else to see whether they are what you expect them to be.
If the point here is to read a CSV file then you really ought to be using the TextFieldParser class. The documentation for that class includes a code example.
This requires .Net Standard 2.1, and so I'm not sure if VB.Net can use the required SpanAction for the String.Create() method, but if it is supported it should greatly outperform the original.
lstReports.Items.Clear()
'Read and "Decrypt" (and I use that term loosely) the file with only a single heap allocation
Dim file As String
Using fs As FileStream = File.OpenRead("ReportTitles.txt")
file = String.Create(fs.Length, fs,
Sub(chars, stream)
For i As Integer = 0 To stream.Length - 1
'THIS IS NOT ENCRYPTION! At best, it's obfuscation.
chars(i) = Chr(fs.ReadByte() Xor 22)
Next
End Sub)
End Using
'Use an actual CSV parser
Using reader As New StringReader(file), _
parser As New TextFieldParser(reader)
parser.TextFieldType = FileIO.FieldType.Delimited
parser.Delimiters = New String() {","}
Dim row As String()
While Not parser.EndOfData
row = parser.ReadFields()
If row.Length >= 3 Then
Dim IncidentReport As New ListViewItem()
IncidentReport.Text = row(0) '
IncidentReport.SubItems.Add(row(1))
IncidentReport.SubItems.Add(row(2))
lstReports.Items.Add(IncidentReport)
End If
End While
End Using
If you are not able to use that version, this is not quite as good, but still a better approach than the original:
lstReports.Items.Clear()
'Load and "Decrypt" the file
Dim file As String
Using fs As FileStream = File.OpenRead("ReportTitles.txt")
Dim builder As New StringBuilder(fs.Length)
For i As Integer = 0 To fs.Length - 1
'THIS IS NOT ENCRYPTION! At best, it's obfuscation.
builder.Append(Chr(fs.ReadByte() Xor 22))
Next
file = builder.ToString()
End Using
'Use an actual CSV parser
Using reader As New StringReader(file), _
parser As New TextFieldParser(reader)
parser.TextFieldType = FileIO.FieldType.Delimited
parser.Delimiters = New String() {","}
Dim row As String()
While Not parser.EndOfData
row = parser.ReadFields()
If row.Length >= 3 Then
Dim IncidentReport As New ListViewItem()
IncidentReport.Text = row(0) '
IncidentReport.SubItems.Add(row(1))
IncidentReport.SubItems.Add(row(2))
lstReports.Items.Add(IncidentReport)
End If
End While
End Using
In both cases, use Try/Catch rather than Dir() to check whether the location exists. Just try to open the file. Dir() costs an extra disk seek, and there are precious few things in programming slower than disk I/O.

Sort Fileinfo list in asc order

I have a list of files as follows
10_2017
123_2018
500_2017
20_2019
100_2017
25_2017
32_2018
Which i want to be sorted like
10_2017
25_2017
100_2017
500_2017
32_2018
123_2018
20_2019
I can sort the array by Array.sort(f1,new FileInfoSort) if I read the years separate but I need it in one sorted array or list
I have tried several threads on here including this method. used f1.Sort(Function(x, y) x.Name.CompareTo(y.Name)) and Array.Sort(f1.toArray, New FileInfoSort) without any success
I have even tried to separate the files into folders by year and read them separately into lists and combining them which didnt seem to work either
Using the code
Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) = d1.GetFiles("*.LBK", SearchOption.TopDirectoryOnly).ToList
'f1.Sort(Function(x, y) x.Name.CompareTo(y.Name))
f1.SortNatural(Function(x) x.Name)
Separating the files into folders by year and then reading it and combining
Dim d1 As New DirectoryInfo(AppFolder.User.data)
Dim d2 As List(Of DirectoryInfo) = New List(Of DirectoryInfo)
'data folders have names like "d_2019"
For Each d As DirectoryInfo In d1.GetDirectories
If d.Name.ToString.Substring(0, 1) = "d" Then d2.Add(d)
Next
Dim f1 As List(Of FileInfo) = New List(Of FileInfo)
For Each Dir As DirectoryInfo In d2
f1.AddRange(Dir.GetFiles("*.LBK", SearchOption.TopDirectoryOnly))
'trying to sort
'f1.Sort(Function(x, y) x.Name.CompareTo(y.Name))
f1.SortNatural(Function(x) x.Name)
Next
Module ListExt
<DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
Private Function StrCmpLogicalW(ByVal lhs As String, ByVal rhs As String) As Integer
End Function
<Extension()>
Sub SortNatural(Of T)(ByVal self As List(Of T), ByVal stringSelector As Func(Of T, String))
self.Sort(Function(lhs, rhs) StrCmpLogicalW(stringSelector(lhs), stringSelector(rhs)))
End Sub
<Extension()>
Sub SortNatural(ByVal self As List(Of String))
self.Sort(AddressOf StrCmpLogicalW)
End Sub
End Module
Any method Ive used so far outputs
10_2017
20_2019
25_2017
32_2018
100_2017
123_2018
500_2017
I have no idea how to approach this even. If such a list/array is not possible ideas of how i might structure my files so that i can read in the files in the order i prefer above would be welcome too!
You'll have to write code to parse the separate sections of the name and treat them as numbers. Any built-in comparer you use will treat strings as strings, where anything that starts with a 1 comes before anything that starts with a 2, even when the values are '100' and '2'. Even the so-called "natural" sorts are only good enough to check the first section.
You also need to strip the LBK extension from the name.
Dim splitChars() As Char = {"_"c}
Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) =
d1.EnumerateFiles("*.LBK", SearchOption.TopDirectoryOnly).
OrderBy(Function(fi)
Dim parts = fi.Name.Replace(".LBK", "").Split(splitChars)
Return (Integer.Parse(parts(1)) * 1000) + Integer.Parse(parts(0))
End Function).
ToList()
For fun, here's a Regex version:
Dim exp As New Regex("(\d{1,3})_(\d{4})");
Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) =
d1.EnumerateFiles("*.LBK", SearchOption.TopDirectoryOnly).
OrderBy(Function(fi)
Dim parts = exp.Matches(fi.Name)(0).Groups
Return (Integer.Parse(parts(2).Value) * 1000) + Integer.Parse(parts(1).Value)
End Function).
ToList()
See it work here:
https://dotnetfiddle.net/UAae8P
I also wanted to comment on this line from one of the samples in the question:
If d.Name.ToString.Substring(0, 1) = "d" Then d2.Add(d)
It stood out as needing some attention. There's a ton of extra work going on here that isn't needed:
d.Name is already a string, no need to call ToString()
Since you just want one character you can access it via subscript, no need to call Substring()
You're comparing one character, no need to do string compares.
You really want this:
If d.Name(0) = "d"c Then d2.Add(d)
That's a lot less code, and it will perform so much better, and it will be worth your time to study this and understand why.

Getting Multi Rows in Database and transferring it in a multiline textbox in VB.net WinForms

Here in my code, i have a database which has table of my applicants. As you will see in the code below, i want to get the number of rows from my command text and transfer it to the string "abc"
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
myr.Close()
mycom.Connection = cn
mycom.CommandText = "SELECT Count(Cellphone) FROM tbl_applicant where Gender='Female';"
myr = mycom.ExecuteReader
Dim abc As String
If myr.Read Then
abc = myr(0)
End If
myr.Close()
On the code Below i used the abc as the number of data i must acquire. Then i used the new query to get the values i wanted to and transfer them to a String Array, as you can see I Redim the universal variable Numb to abc to have its array boundery.
mycom.CommandText = "SELECT Cellphone FROM tbl_applicant where Gender='Female';"
myr = mycom.ExecuteReader
ReDim Numb(abc)
If myr.Read Then
For i As Integer = 1 To abc.ToString - 1
LOT = myr(0).ToString
LOT = LOT + (myr(i).ToString + ",") <- this is where i get the error it says that index is our of range.
Numb = LOT.Split(",")
Next
End If
In this code below, i want the values of Variable Numb() to be transferred to a multiline textbox
Dim sbText As New System.Text.StringBuilder(500)
For i As Integer = 0 To Numb.Length - 2
' This will convert the number to a string, add it to the stringbuilder
' and then append a newline to the text buffer
sbText.AppendLine(Numb(i))
Next i
' Now move the buffer into the control
TextBox1.Text = sbText.ToString()
End Sub
The end value i must see in the textbox should be like
11111111111
11111111112
11111111113
11111111114
and so forth, please try to understand the numbers i am referring it to real phone numbers. Any help with the problem or solution maybe.. Thanks
I don't think you need to first query the db to get the count of records before then going back to the db to get the phonenumbers, you could just do this:
mycom.CommandText = "SELECT Cellphone FROM tbl_applicant where Gender='Female';"
myr = mycom.ExecuteReader
While myr.Read()
TextBox1.Text = TextBox1.Text & myr(0) & Environment.NewLine
End While
No need for array's or List's
While this is just a rough guide and an attempt at understanding your issue, try the code and see if it works for you.

Need help getting array to work with string builder

So I am building a stock info retrieval program, and I have a good bit of it done so far. Right now I have it return the price from the Yahoo Finance API by singling out the price in the returned API data and then shove it in a listbox with the stock symbol and price. That part works great, but I want to take it a step further now and be able to do what I want with other parts of that returned data. The normal format for the data is:
"<symbol>", <price>, "<date>", "<time>", etc.
If you take a look at my code, right now I have a Getstockinfo function that retrieves the full API output and converts each object(symbol, price, etc) into a new stringbuilder line, and then separates the lines by the commas using the ModifyLine function.
I want to now get the GetStockInfo to return an array, which would allow me to use that array as I pleased outside of that function(in eventhandlers, etc.)
ANY help would be much appreciated!
Public Function GetStockInfo(ByVal pstrSymbol As String) As String
Dim strURL As String
Dim strApiOutput As String
'Yahoo API
strURL = "http://quote.yahoo.com/d/quotes.csv?" & _
"s=" & pstrSymbol & _
"&d=t" & _
"&f=sl1d1t1c1ohgvj1pp2wern"
strApiOutput = RequestWebData(strURL)
'Create stringbuilder(easier to append, replace and insert data)
Dim strReturn As New System.Text.StringBuilder
'Seperate API output into different lines by using LineFeed
For Each strLine As String In strApiOutput.Split(ControlChars.Lf)
'makes sure line actually exists, if so, seperate and add to array using ModifyLine function
If strLine.Length > 0 Then
strReturn.Append(ModifyLIne(strLine) & Environment.NewLine)
End If
Next
Return strReturn.ToString
End Function
Private Function ModifyLIne(ByVal strLine As String) As String
Dim arrLine() As String
'Splits lines by the commas(the normal api output is as follows: <Obj1>, <Obj2>, <Obj3>, etc
'with Intermediary quotes here and there for certain objects such as Company Name
arrLine = strLine.Split(","c)
decStockPrice = CDec(arrLine(1))
Return decStockPrice.ToString
End Function
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
Dim strSymbol As String = txtSymbol.Text
lstStocks.Items.Add(strSymbol.ToUpper() & " - " & GetStockInfo(strSymbol))
End Sub
I want to take it a step further now and be able to do what I want with other parts of that returned data This will do what you want, just not how you want (arrays are old-school)
Class StockQuote
Public Symbol As String
Public Price As Decimal
Public QuoteDate As DateTime
' etc
End Class
Friend myQuotes As New List(of StockQuote)
Then after you get a quote and determine you want to save it (are you saving all of them?):
Dim SQ as New StockQuote
With SQ
.Symbol = arrLine(0) ' so you can pass an array or list of ticker syms
.Price = Convert.ToDecimal(arrLine(1))
' not much sense saving Time and Date as sep items
' if you are getting multiple quotes for a day (vs ending)
.QuoteDate = combine arrline(2) and arrLine(3) ?
etc
End SQ
myQuotes.Add(SQ)
Now, myQuotes will have a list of all the quotes for all the symbols. Get them like you populated it: txtSym.Text = myQuotes(N).Symbol

Resources