Bitmap array resetting to Nothing after function (VB 2012) - vb.net

Okay, basically, I'm assigning an array values inside a function of the class. But after the class executes, the array resets to nothing. Here's my code:
Public Class MoisacDialog
Public imgArray(,) As Bitmap
Private Sub cmdCancel_Click(sender As Object, e As EventArgs) Handles cmdCancel.Click
DialogResult = DialogResult.Cancel
End Sub
Private Sub cmdOK_Click(sender As Object, e As EventArgs) Handles cmdOK.Click
Try
Dim rows As Integer = Convert.ToInt32(txtRows.Text)
Dim cols As Integer = Convert.ToInt32(txtCols.Text)
If rows > 0 And cols > 0 Then
ReDim imgArray(rows - 1, cols - 1)
For i As Integer = 0 To cols - 1
For j As Integer = 0 To rows - 1
Using fileImage As New OpenFileDialog
If fileImage.ShowDialog() = DialogResult.OK Then
imgArray(i, j) = CType(Bitmap.FromFile(fileImage.FileName), Bitmap)
End If
End Using
Next
Next
DialogResult = DialogResult.OK
Else
MessageBox.Show("Rows/columns entered are out of range.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End If
Catch ex As FormatException
MessageBox.Show("Invalid rows/columns entered.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
End Class
After cmdOK_Click executes, the whole array imgArray resets to nothing. If I use it like this in a calling form:
Using sizeDialog As New MoisacDialog
If MoisacDialog.ShowDialog() = DialogResult.OK Then
Dim ImageArray(,) As Bitmap = sizeDialog.imgArray
_img = ImProc.PixelEffects.Moisac(ImageArray)
picImage.Image = CType(_img, Image)
End If
End Using
and use debugging view, ImageArray is set to Nothing after the third line, while it's still there by the end of cmdOK_Click.
UPDATE: I changed line 4 of my second snippet to _img = ImageArray(0,0). The problem persists, a NullReferenceException is thrown and handled in the code enclosing my second snippet.

It’s not reset, it was never set in the first place.
Using sizeDialog As New MoisacDialog
If MoisacDialog.ShowDialog() = DialogResult.OK Then
Dim ImageArray(,) As Bitmap = sizeDialog.imgArray
Note that you’re using sizeDialog and MoisacDialog indiscriminately. MoisacDialog, when used as an object, is a default instance of the class of the same name. It’s unfortunate that VB allows this instead of catching the obvious error here at compile time.
To correct the error, simply use sizeDialog.ShowDialog().

Related

vb.NET You are not allowed to perform an operation across different threads

I am running code to read text from pdf files and than create a WordCloud. To inform the user the process is on going I add a BackgroundWorker to my form that shows an image saying Loading. I get an error for operating across different threads.
Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
bTextEmpty = False
ListView1.Items.Clear()
ResultPictureBox.Image = Nothing
If ListBox1.SelectedIndex > -1 Then
For Each Item As Object In ListBox1.SelectedItems
Dim ItemSelected = CType(Item("Path"), String)
Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
If myTempFile = False Then
'when we load the form we first call the code to count words in all files in a directory
'lets check if the folder exists
If (Not System.IO.Directory.Exists(ItemSelected)) Then
Dim unused = MsgBox("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
Exit Sub
Else
Call CreateWordList(ItemSelected)
End If
End If
'if the words file is empty we cant create a cloud so exit the sub
If bTextEmpty = True Then Exit Sub
'then we fill the wordcloud and Listview from the created textfile
Call CreateMyCloud(ItemSelected + "\Words.txt")
Next
Else
Dim unused = MsgBox("You have to choose an Archive to create the Word Cloud",, Title)
End If
Size = New Drawing.Size(1400, 800)
End Sub
I put above code in a Private Sub and called it from my BackgroundWorker. The error occured at this line: If ListBox1.SelectedIndex > -1 Then
After trying the suggested code I get the same error again:
Public Sub CreateMyCloud(ByVal sourcePDF As String)
Dim WordsFreqList As New List(Of WordsFrequencies)
For Each line As String In File.ReadLines(sourcePDF)
Dim splitText As String() = line.Split(","c)
If splitText IsNot Nothing AndAlso splitText.Length = 2 Then
Dim wordFrq As New WordsFrequencies
Dim freq As Integer
wordFrq.Word = splitText(0)
wordFrq.Frequency = If(Integer.TryParse(splitText(1), freq), freq, 0)
WordsFreqList.Add(wordFrq)
End If
Next
If WordsFreqList.Count > 0 Then
' Order the list based on the Frequency
WordsFreqList = WordsFreqList.OrderByDescending(Function(w) w.Frequency).ToList
' Add the sorted items to the listview
WordsFreqList.ForEach(Sub(wf)
error -> ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
End Sub)
End If
Dim wc As WordCloudGen = New WordCloudGen(600, 400)
Dim i As Image = wc.Draw(WordsFreqList.Select(Function(wf) wf.Word).ToList, WordsFreqList.Select(Function(wf) wf.Frequency).ToList)
ResultPictureBox.Image = i
End Sub
Should look something more like:
Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
If ListBox1.SelectedItems.Count > 0 Then
Dim data As New List(Of Object)
For Each Item As Object In ListBox1.SelectedItems
data.Add(Item)
Next
bTextEmpty = False
ListView1.Items.Clear()
ResultPictureBox.Image = Nothing
BackgroundWorker1.RunWorkerAsync(data)
Else
MessageBox.Show("You have to choose an Archive to create the Word Cloud",, Title)
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim data As List(Of Object) = DirectCast(e.Argument, List(Of Object))
For Each Item As Object In data
Dim ItemSelected = CType(Item("Path"), String)
Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
If myTempFile = False Then
'when we load the form we first call the code to count words in all files in a directory
'lets check if the folder exists
If (Not System.IO.Directory.Exists(ItemSelected)) Then
MessageBox.Show("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
Exit Sub
Else
Call CreateWordList(ItemSelected)
End If
End If
'if the words file is empty we cant create a cloud so exit the sub
If bTextEmpty = True Then Exit Sub
'then we fill the wordcloud and Listview from the created textfile
Call CreateMyCloud(ItemSelected + "\Words.txt")
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Size = New Drawing.Size(1400, 800)
End Sub
To fix the error in your second, edited post:
WordsFreqList.ForEach(Sub(wf)
ListView1.Invoke(Sub()
ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
End Sub)
End Sub)
You cannot simply access controls outside of the current thread. You first need to call the Invoke method on the target control and then pass a delegate containing the instructions intended to modify the control outside of the current thread. See this article on MSDN on how to do this: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8

How to load background image from setting?

Before I get into the actual problem, I've done some research and nothing helps. Also, this isn't a duplicate of this question because s/he was using ComboBox, and this question is using an Open file dialog which does not help me.
Here is my problem. I am doing a Virtual System Overlay (some call it vb.net os) and for the "desktop" I need an background image loaded from an application setting. The problem is that I am getting this error:
An unhandled expection of type 'System.IO.FileNotFounfExpection' occurred in System.Drawing.dll
Additional information: My.Resources._2
And my code is:
Image.FromFile(My.Settings.bgdesk.ToString)
The setting looks like:
Name: bgdesk | Type: string | Scope: user | Value: My.Resources._2
Also, have a look at my resources:
How do I load the background image from the resource?
If I properly understood your question, you want to dynamically load and save a background image setting, based on a user choice; the bitmaps to be used are already included in a resource file:
1) Create a Resource file named Resource1.resx
2) Insert bitmaps you want to use (e.g. named "_1", "_2"), in this file
3) In bgdesk, let the code save only the resource name (i.e. "_1", "_2")
4) Add a ComboBox cbImgList
5) Add a Button, which sets the new BackGround image when hit
6) Use the following code (adapt it to your needs)
Private Function GetBackGroundImgFromRes(ByRef sError As String) As Boolean
Try
Dim sImgName As String = My.Settings.bgdesk
If sImgName.Trim <> "" Then
Dim RM As Resources.ResourceManager = My.Resources.Resource1.ResourceManager
Dim objBitmap As Object = RM.GetObject(sImgName)
Me.BackgroundImage = DirectCast(objBitmap, Bitmap)
End If
Return True
Catch ex As Exception
sError = ex.ToString
Return False
End Try
End Function
Private Function SetBackGroundImg(ByRef sError As String) As Boolean
Try
Dim sImgName As String = ""
Select Case cbImgList.SelectedIndex
Case -1
Return True
Case 0
sImgName = "_1"
Case 1
sImgName = "_2"
End Select
Dim RM As Resources.ResourceManager = My.Resources.Resource1.ResourceManager
Dim objBitmap As Object = RM.GetObject(sImgName)
Me.BackgroundImage = DirectCast(objBitmap, Bitmap)
My.Settings.bgdesk = sImgName
Return True
Catch ex As Exception
sError = ex.ToString
Return False
End Try
End Function
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim sError As String = ""
SetBackGroundImg(sError)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
cbImgList.Items.Add("_1")
cbImgList.Items.Add("_2")
Dim sError As String = ""
GetBackGroundImgFromRes(sError)
End Sub

Importing a CSV into a combobox, then changing other things in VB

What I want to do is import a CSV file (called fwlist.txt), that looks like this:
modelname,power type,pic,part number,firmware, option1, option 1, etc
End result, i would like a combobox that shows the modelname, and when the model name is selected from the pulldown, it updates various labels and text boxes on the form with other information.
Here's what I have so far:
Dim filename As String = "fwlist.txt"
Dim pwrtype As String
Dim pic As String
Dim partnum As String
Dim lineread As String
Dim FirmwareName As String
Private Sub ReadFirmwaresLoad(sender As Object, e As EventArgs) Handles Me.Load
' Load the items into the NameComboBox list.
Dim ResponseDialogResult As DialogResult
Try
Dim FirmwareStreamReader As StreamReader = New StreamReader(filename)
' Read all the elements into the list.
Do Until FirmwareStreamReader.Peek = -1
lineread = FirmwareStreamReader.ReadLine()
Dim fields As String() = lineread.Split(",")
FirmwareName = fields(0) 'Take First Field
cbFW.Items.Add(FirmwareName)
'Set Text labels based on position in line.
pwrtype = fields(1)
pic = fields(2)
partnum = fields(3)
(...etc through options)
Loop
' Close the file.
FirmwareStreamReader.Close()
Catch ex As Exception
' File missing.
ResponseDialogResult = MessageBox.Show("File not Found!", "File Not Found",
MessageBoxButtons.OK, MessageBoxIcon.Question)
If ResponseDialogResult = DialogResult.OK Then
' Exit the program.
Me.Close()
End If
End Try
End Sub
Private Sub cbFW_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cbFW.SelectedIndexChanged
lblPwrType.Text = pwrtype
lblPic.Text = pic
lblPartNum.Text = parnum
....etc thruogh options
End Sub
This code works, but only sort of. If i select anything from the combo box, it only gives me the information from the very last line of the CSV file - even if its the first entry in the box. I'm pretty sure it's something simple that I'm messing up.. anyone help?
First we read the lines into Firmware objects then we set this List(Of Firmware) as the DataSource of the ComboBox.
Then we handle the SelectedIndexChanged event of the ComboBox which will get the currently selected firmware and loads its data to the TextBox controls.
This is a tested, working example below:
Public Class Firmware
Public Property ModelName As String
Public Property PowerType As String
Public Property Pic As String
Public Property PartNumber As String
Public Property Firmware As String
Public Property Option1 As String
End Class
Public Class MainForm
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
Dim lines As String() = Nothing
Try
' Read the file in one step
lines = File.ReadAllLines("fwlist.txt")
Catch ex As Exception
Dim dialogResult As DialogResult = MessageBox.Show(Me, "File not found! Would you like to exit program?", "Error reading file", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2)
If dialogResult = DialogResult.Yes Then
Me.Close()
End If
Exit Sub
End Try
Dim firmwares As List(Of Firmware) = New List(Of Firmware)
For Each line As String In lines
Dim rawData As String() = line.Split({","}, StringSplitOptions.None)
' Create Firmware object from each line
Dim firmware As Firmware = New Firmware() With
{
.ModelName = rawData(0),
.PowerType = rawData(1),
.Pic = rawData(2),
.PartNumber = rawData(3),
.Firmware = rawData(4),
.Option1 = rawData(5)
}
' We store the read firmwares into a list
firmwares.Add(firmware)
Next
' Set the list as the data source of the combobox
' DisplayMember indicates which property will be shown in the combobox
With cboModelNames
.DataSource = firmwares
.DisplayMember = "ModelName"
End With
End Sub
Private Sub cboModelNames_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboModelNames.SelectedIndexChanged
RefreshForm()
End Sub
Private Sub RefreshForm()
' Get the selected item as Firmware object
Dim currentFirmware As Firmware = DirectCast(cboModelNames.SelectedItem, Firmware)
' Refresh all the textboxes with the information
With currentFirmware
txtPowerType.Text = .PowerType
txtPic.Text = .Pic
txtPartNumber.Text = .PartNumber
txtFirmware.Text = .Firmware
txtOption1.Text = .Option1
End With
End Sub
End Class
Every time you set the value of a variable like pwrtype, its old value is thrown away and there is no way to get it back. And even if you did save multiple values for each variable, you would need a way to figure out which value goes with the currently selected item in the combo box. To resolve these two issues you need:
a way to group values from the same line together
a way to save all values read from the file, not just the most recent ones
Let's start with the first issue, grouping values together. In the example code below, I created a Structure type called FirmwareInfo to serve that purpose. Each FirmwareInfo has its own pwrtype, pic, etc.
The other piece of the puzzle that you are missing is a way to save more than one Firmware. The easiest way to do this is to add each FirmwareInfo to the combo box's item list, instead of adding just the display string. The combo box will convert each item to a string in the UI; the ToString() method tells it how you want this conversion to be done.
I haven't run this example code so I can't guarantee there aren't mistakes in there, but hopefully it's enough to show the general idea.
Dim filename As String = "fwlist.txt"
Structure FirmwareInfo
Public pwrtype As String
Public pic As String
Public partnum As String
Public FirmwareName As String
Public Overrides Function ToString() As String
Return FirmwareName
End Function
End Structure
Private Sub ReadFirmwaresLoad(sender As Object, e As EventArgs) Handles Me.Load
' Load the items into the NameComboBox list.
Dim ResponseDialogResult As DialogResult
Try
Dim FirmwareStreamReader As StreamReader = New StreamReader(filename)
' Read all the elements into the list.
Do Until FirmwareStreamReader.Peek = -1
Dim lineread = FirmwareStreamReader.ReadLine()
Dim fields As String() = lineread.Split(",")
Dim info As New FirmwareInfo With {
.FirmwareName = fields(0),
.pwrtype = fields(1),
.pic = fields(2),
.partnum = fields(3)
}
cbFW.Items.Add(info)
Loop
' Close the file.
FirmwareStreamReader.Close()
Catch ex As Exception
' File missing.
ResponseDialogResult = MessageBox.Show("File not Found!", "File Not Found",
MessageBoxButtons.OK, MessageBoxIcon.Question)
If ResponseDialogResult = DialogResult.OK Then
' Exit the program.
Me.Close()
End If
End Try
End Sub
Private Sub cbFW_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cbFW.SelectedIndexChanged
Dim currentInfo = DirectCast(cbFW.SelectedItem, FirmwareInfo)
lblPwrType.Text = currentInfo.pwrtype
lblPic.Text = currentInfo.pic
lblPartNum.Text = currentInfo.parnum
' ....etc through options
End Sub

Blank value obtained when retrieving the text property for a control

I'm currently working through a rather odd issue that I can't seem to figure out. I'm a novice programmer who is currently using VB.net to automate some lab equipment via the GPIB interface.
I'm using Excel to collect and tabulate my data. So the core of my problem is that I'm using a Backgroundworker to perform the "saving" of my data that I obtain from various lab equipment. Often times, I will have to record the assembly data of the specimens I'm testing. Here, I created another form with relevant fields to fill out all the data I'd ever desire to remember.
The backgroundworker thread will go into the secondary form, I've named it DAq6.vb, and pull the data. However, I've been noticing that it returns "" rather than the actual string that is present.
Public Function GetControlValue(ByVal ctl As Object) As String
Dim text As String
Try
If ctl.InvokeRequired Then
text = ctl.Invoke(New GetControlValueInvoker(AddressOf GetControlValue), ctl)
Else
If TypeName(ctl) = "ComboBox" Then
text = ctl.Text
ElseIf TypeName(ctl) = "NumericUpDown" Then
text = ctl.value
ElseIf TypeName(ctl) = "TextBox" Then
text = ctl.Text
ElseIf TypeName(ctl) = "RadioButton" Then
text = ctl.Checked
ElseIf TypeName(ctl) = "Decimal" Then
text = ctl.Value
ElseIf TypeName(ctl) = "Label" Then
text = ctl.Text
Else
text = "Type name unknown"
End If
End If
Return text
Catch ex As Exception
MsgBox("Error: " & Err.Description)
End Try
End Function
Above is the function that I use to safely retrieve a "control value" from a different thread.
And below is representative of the method I use to obtain values from the form I'm currently in (DAq1.vb). This code works, and it successfully retrieves the strings.
objRange = objSheet1.Range("B1")
objRange.Value = GetControlValue(txtDate)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objRange)
Here is code that doesn't work, curiously. I'm baffled as to why it wouldn't work.
objRange = objSheet6.Range("G2")
objRange.Value = GetControlValue(DAq6.txtLotNum1)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objRange)
Why would this be the case? Again, I'm a novice programmer that is primarily self-taught in VB.net syntax, so I apologize if this causes any cringing. But I have dug around the internet looking for an issue for a few days now, and I'm stumped.
Thank you so much for even just reading all of this!
Edit: I've adjusted the function according to Bjørn-Roger's suggestions. After inserting a breakpoint right before the error producing lines of code, I see that it correctly goes to the TypeName(ctl) = "TextBox" portion of the if statement. Yet, it still returns a "" rather than the correct value.
Ensure you return code on all paths and that you reference the correct instance of DAq6.
Here's a simple example:
Public Class DAq1
'Inherits Form
Public Sub New()
Me.InitializeComponent()
End Sub
Public Function GetControlValue(ByVal ctl As Control) As String
If (ctl Is Nothing) Then
Throw New ArgumentNullException("ctl")
End If
Try
If (ctl.InvokeRequired) Then
Return CStr(ctl.Invoke(New GetControlValueInvoker(AddressOf GetControlValue), ctl))
Else
If (TypeOf ctl Is NumericUpDown) Then
Return DirectCast(ctl, NumericUpDown).Value.ToString()
ElseIf (TypeOf ctl Is RadioButton) Then
Return DirectCast(ctl, RadioButton).Checked.ToString()
Else
'Fallback to Control.Text
Return ctl.Text
End If
End If
Catch ex As Exception
MessageBox.Show("Error: " & Err.Description)
End Try
Return Nothing
End Function
Private Sub _DoWork(s As Object, e As DoWorkEventArgs) Handles worker.DoWork
Dim f As DAq6 = DirectCast(e.Argument, DAq6)
e.Result = Me.GetControlValue(f.txtLotNum1)
End Sub
Private Sub _Completed(s As Object, e As RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
If (e.Error Is Nothing) Then
MessageBox.Show(CStr(e.Result), Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information)
Else
MessageBox.Show(e.Error.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
End If
End Sub
Private Sub _Shown(s As Object, e As EventArgs) Handles Me.Shown
Dim f As New DAq6()
f.txtLotNum1.Text = "YES!"
Me.worker.RunWorkerAsync(f)
End Sub
Private WithEvents worker As New BackgroundWorker
Public Delegate Function GetControlValueInvoker(ctl As Control) As String
Public Class DAq6
Inherits Form
Public ReadOnly txtLotNum1 As New TextBox
End Class
End Class

Why is gridview.selectedColumns nothing?

I want to run a code like this, but it always jumps over the loop, so I see no line in the console.
That means that selectedColumns is empty. My assumption was that I (or the user) select a cell from a Column and then, selectedColummns are +1. But as it looks, it doesnt work. Then I tried to set proberties of selectionMode to select full columns, but then an exception is thrown:
"System.InvalidOperationException" Additional Information: the SortMode cannot be Automatic, if full Column selection is selected.
I don't know what SortMode is.
For Each col As DataGridViewColumn In datagridview2.SelectedColumns
Console.Write(datagridview2.SelectedColumns.Count)
Console.Write("1")
Next
Any Ideas how to get that the columns into selectedColumns?
Here my code that solved my problem, but I guess it is not the smartest one:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim body As String = ""
Dim myWriter As New StreamWriter("H:\downloads\test.csv", True)
Dim list As List(Of Integer) = New List(Of Integer)
For Each cell As DataGridViewCell In datagridview2.SelectedCells
If list.Contains(cell.ColumnIndex) = False Then
list.Add(cell.ColumnIndex)
End If
Next
For i = 0 To datagridview2.Rows.Count - 1
For ix = 0 To datagridview2.Columns.Count - 1
If list.Contains(ix) Then
If datagridview2.Rows(i).Cells(ix).Value IsNot Nothing Then
body = body + datagridview2.Rows(i).Cells(ix).Value.ToString + ";"
Else
body = body + ";"
End If
End If
Next
myWriter.WriteLine(body)
body = ""
Next
myWriter.Close()
End Sub
You need to do this after the selected changed event has fired.
For example:
Private Sub Mydg_ColSelected(sender As Object, e As SelectionChangedEventArgs) Handles datagridview2.SelectionChanged
For Each col As DataGridViewColumn In datagridview2.SelectedColumns
Console.Write(datagridview2.SelectedColumns.Count)
Console.Write("1")
Next
End Sub
Or if this isn't what you are after, then try handling the ColumnHeaderMouseClick event instead. I'm not sure what technology you are using, e.g Winforms, WPF, silverlight