How can I save listbox items to my.settings - vb.net

Intro
I have looked up how to save the items in a listbox to my.settings for a while now and there are so many different answers. I've tried them all (a bit excessively to say), but none have really worked. Its probably because I'm doing something wrong due to a bad explanation or my new-beginner stage at programming.
So I have a form where the user can set a bunch of settings. All of them are going to stay the way they were when he closes the application and re-opens it again. Textboxes, checkboxes and so on works fine, but for some reason the Listbox is harder than I'd expect to be saved.
My listbox
The user adds items to the listbox like this (Writes something like c:\test in a textbox tbpath1, presses a button btnAdd1 and the text will become a item in the listbox lbchannel1)
Private Sub btnAdd1_Click(sender As Object, e As EventArgs) Handles btnAdd1.Click
Dim str As String = tbPath1.Text
If str.Contains("\") Then
lbchannel1.Items.AddRange(tbPath1.Text.Split(vbNewLine))
tbext1_1.Text = (tbext1_1.Text)
My attempt (probably one out of ten attempts)
So this is one of my attempts so far. I wish it was this easy.
My.Settings._lbchannel1.Clear()
For Each item In lbchannel1.Items
My.Settings._lbchannel1.Add(item)
Next
My.Settings.Save()
At the attempt above, I get error 'NullReferenceException was unhandled : Object reference not set to an object instance'
I'm guessing it has something to do with items not being a string and so on, but I'm not sure where to go with this. Can someone wrap it up in a simple explained way?

If you do not add at least one item in the IDE, VS doesnt initialize the collection you create in Settings because it doesnt look like you are using it.
If My.Settings._lbchannel1 Is Nothing Then
My.Settings._lbchannel1 = New System.Collections.Specialized.StringCollection()
End If
My.Settings._lbchannel1.Clear()
For Each item In lbchannel1.Items
My.Settings._lbchannel1.Add(item)
Next
My.Settings.Save()
You can also "trick" it into initializing it for you. Add an item via the Settings Tab, save the project, then remove the item.
You can also create a List(of String) to store the data. Serialize it yourself with 1-2 lines of code and use it as the DataSource for the listbox. It is simpler than shuttling items from one collection to another and keeping them in synch. This answer shows a serializing a List(Of Class) but the principle is the same.

Related

Getting Selected Items from a Listbox

Good Wednesday All.
I am running into a brick wall (easy for a shade tree coder to do) I have a Listbox that i populated with a datatable. I want to get the all LicenseID's from the selected items. In other words, if the user selects 3 out of 8 of the list box, I need to get the LicenseID for each of those 3.
Below is how I populated the listbox
Using cmd As New OleDbCommand(cmdText, conn)
conn.Open()
Dim reader As OleDbDataReader = cmd.ExecuteReader()
dt.Load(reader)
ListBox1License.DataSource = dt
ListBox1License.DisplayMember = "InstitutionTypeAbrev"
ListBox1License.ValueMember = "LicenseID"
End Using
I need to get the selected items from the listbox to use later.
I am thinking of adding the selected Items to an array.
I have searched around STackOverflow for some examples but none seem to work for me.
Any Help Appreciated
I'll show you how to derive the answer for this yourself:
I've set up a form:
Really simple; the listbox is like your listbox. The button is just there to give me an easy way to stop the code and examine what is going on.
I wrote some code to populate some things into my listbox. It's a screenshot because it doesn't matter that you have exactly this code, so you don't need to write this code (hence why I'm making it hard to copy paste):
I've double clicked my button to make a click handler. I haven't written any code, but I have put a breakpoint on the method declaration - see it's red? Click the margin where the dot is, to put breakpoints in your code. When you hit them, the code stops and waits for you to inspect:
I've run my app and clicked my button. The code has stopped and VS has switched to showing me the code, not the app:
I can now point to some variable that is in scope (like ListBox1) and see a tooltip, or I can open the Locals/Autos windows and see variables that are in scope and drill into them:
Expand you ListBox in the Autos/Locals window. It has a lot of properties. Scroll to SelectedItems:
SelectedItems is a collection of things.. We can tell partly because Microsoft is good at naming collections of things with a plural name, and because the inspector says "enumerate the enumerable" .. it means that it is a bunch of things that we can ForEach to look through
Expanding it we see that my selecteditems has only one thing selected (i truly did only have one selected item in my list when I clicked the button)
We can see that an entry in the SelectedItems collection is a DataRowView type of object. We can see that a DataRowView has a Row property that is a DataRow.. This Row is the DataRow in the DataTable to which the list is bound (you set the DataSource to a DataTable; this is a row from that table).
Every time you dig into the tree another level, that's like using either a dot or an indexer in your code. At this level we've gone listbox1.SelectedItems(0).Row..
So from this we can see that we need a code like:
' we will "enumerate the enumerable"
For Each drv as DataRowView in listbox1.SelectedItems
Dim originalRow = drv.Row 'we could do this to get the row...
Dim selectedAnimaId = row("AnimalID") ' ..and then index the row to get the animal ID ..
Dim selectedAnimalId = drv("AnimalID") ' ... or it's actually possible to index a DataRowView directly, so you can skip the row part
Next drv
It can be handy to write code while you're stopped on a breakpoint so you can look at the values of things as you're writing, and check you're going in the right direction. You might need to use F10 (or whatever key is associated with "step over"/"step into") to move the yellow bar along and execute code lines one by one:
You can only move the code execution along if you've written complete, legal code, but it doesn't have to be logically correct. You can back up and execute again by dragging the yellow arrow in the margin (or right clicking and choosing Set Next Statement). Here I've put some dummy statement in to move along to, so i can check that my animalID is correctly set in X like I expect . I point to X to see the value:
The standard ListBox won't help you with that, past getting the DataRowView objects from the SelectedItems collection. As an alternative, here's a custom control that you can use in place of a standard ListBox that will help you:
Public Class ListBoxEx
Inherits ListBox
Public Function GetItemValue(item As Object) As Object
Dim index = Me.Items.IndexOf(item)
If (index <> -1 AndAlso Me.DataManager IsNot Nothing) Then
Return Me.FilterItemOnProperty(Me.DataManager.List(index), Me.ValueMember)
End If
Return Nothing
End Function
End Class
You can then call GetItemValue and pass any item and get the same value back as you would if that was the SelectedItem and you got the SelectedValue. To get all the values in an array:
Dim licenseIDs = myListBoxEx.SelectedItems.
Cast(Of Object)().
Select(Function(o) CInt(myListBoxEx.GetItemValue(o)).
ToArray()
For more information, see here.
In case you're unaware, if you add a class to your project and it is a control or component, once you build, it will appear automatically at the top of the Toolbox window.
If you already have a standard ListBox in place and you don't want to have to delete it and add a new control, you can edit the designer code file by hand to change the existing control. To do that, open the Solution Explorer and select a node within your project, click the Show All Files button, expand the node for your form, double-click the designer code file and then replace ListBox with ListBoxEx (or whatever you call it) in the relevant places. I'd advise creating a backup copy or syncing with source control first, in case you mess it up.

My.Settings "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."

I have two forms with combo boxes. The combo box values are stored in My.Settings.testDevices. (System.Collections.Specialized.String.Collection) with a scope of User.
The second form adds the ability to add items to testDevices, and then upon exit it updates My.Settings.testDevices.
Now, only if I make a change to the settings (adding items only), when I exit back to the main form (which remains loaded throughout the process), my application crashes with the following message:
"Additional information: Destination array was not long enough. Check destIndex and length, and the array's lower bounds."
As I understand it, this might be a concurrency issue, however I'm not sure.
My code:
In my main form Load event: (to load from My.Settings)
testDevicesComboBoxMain.Items.Clear()
My.Settings.testDevices.CopyTo(mainFormTestDevices, 0)
testDevicesComboBoxMain.Items.AddRange(mainFormTestDevices)
Where "testDevicesComboBoxMain" is the combo box on the main form.
On the secondary form Close Event: (to save to My.Settings)
Dim items(testDevicesComboBox.Items.Count - 1) As String
testDevicesComboBox.Items.CopyTo(items, 0)
My.Settings.testDevices.Clear()
My.Settings.testDevices.AddRange(items)
My.Settings.Save()
I have found similar questions on here, but none with answers that I understand :P
As I am a beginner with vb.net, could any answers be provided in an easy to understand form please!
Thanks.
I forgot to add:
Public items(My.Settings.testDevices.Count - 1) As String
Public mainFormTestDevices(My.Settings.testDevices.Count - 1) As String
I tried setting separate declarations just in case there was some kind of conflict. These obviously do the same thing, just with different names.
I fixed it by adding a For loop to read from My.Settings.
For Each i As String In My.Settings.testDevices
testDevicesComboBoxMain.Items.Add(i)
Next
This seems to have cured the problem, and may perhaps be a more "modern" way of doing it?

Filter a listview in vb.net

Currently I have a program that is able to write to a ListView with column named : number, time, description . This listview is not bound to anything data, I'm just basically writing into it using the code.
What I want to do is to have a TextBox, whereby if the user wants to look at particular number i.e. 2, when they type into the textbox, then I want the listview to only show data with number = 2. When there's nothing in the textbox, I want the listview to show all the data.
I have being looking around on the internet and I didn't seem to find a filter method. Does it even exist and if so how would I go about implementing this.
All help is appreciated.
While I recommend using a DataGridView with DataSource but in cases that you need to use ListView, you can use this solution.
You can filter your list view this way:
Define a member field as backup storage of items:
In form Load after adding items to list view, store each item in that member field
Put a TextBox and a Button on form and handle Click event of the Button and in the handler, first clear all items of ListView then each item from that backup storage that matches with criteria.
Member Field for Backup of Items
Private ItemsBackup As New List(Of ListViewItem)
Fill backup after loading items in ListView in the form Load event
For Each item As ListViewItem In ListView1.Items
ItemsBackup.Add(item)
Next
Code of Filter
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.ListView1.BeginUpdate()
Me.ListView1.Items.Clear()
For Each item As ListViewItem In ItemsBackup
If (item.Text = Me.TextBox1.Text Or String.IsNullOrEmpty(Me.TextBox1.Text)) Then
Me.ListView1.Items.Add(item)
End If
Next
Me.ListView1.EndUpdate()
End Sub
You can also use above code for TextChanged event of the TextBox.
Here is a test data:
For i = 1 To 30
Dim item As New ListViewItem(i.ToString())
item.SubItems.Add(DateTime.Now.AddHours(i))
Me.ListView1.Items.Add(item)
Next
A normal .NET ListView can't do this without a considerable amount of work. So, you could use ObjectListView -- an open source wrapper around a standard .NET ListView -- which has already done all the work for you.
It has built-in filtering (and highlighting)

Clearing combobox dropdownlists from application

I am working on an assignment that requires a user to answer 20 questions( multiple choice ). I am using the DropDownList property so the user cannot input anything other than A, B, C, or D.
Basically, I have 20 comboboxes and I have a button that clears them, but the code I should obviously be a loop, but I am not sure how to do that.
As of now, my code looks like this:
cboQuestion1.Items.Clear()
cboQuestion2.Items.Clear()
...
cboQuestion20.Items.Clear()
If anyone could shed some light on this, I will be grateful.
All controls reside in the form's Controls collection, so one way would be to iterate that (assumes these CBOs are the only ones you wish to clear):
For Each cbo As ComboBox In Controls.OfType(Of ComboBox)
cbo.Items.Clear
Next
Another way is to store the names of the target controls in a List(of String). Think of this as a shopping list of the controls you wish to track or treat in some special way:
Private myCBONamesList As List(of String)
'...
myCBONamesList.Add("cboQuestion1")
' etc
' add many/all at once:
myCBONamesList.Addrange(New String(){"cboQuestion1", "cboQuestion2" ...etc})
The New String() creates a temp array containing the literal values listed (in {}) and the whole thing is passed to your List to populate it. To use it:
For Each s As String in myCBONamesList
Controls(s).Items.Clear
Next
This method allows you to target certain CBOs and leave others alone.
It may or may not be the best way, but you could add all of your combo boxes to a List and then iterate over the list to clear them all.
Just iterate over the Form's Controls collection.
Here is an example of iterating over the Forms Controls collection with filtering to make sure you don't accidentally clear a non-question ComboBox:
For Each cbo As ComboBox In Me.Controls.OfType(Of ComboBox)
If cbo.Name Like "cboQuestion*" Then
cbo.Items.Clear()
End If
Next
Edit: Or if you're into one-lining things:
For Each cbo As ComboBox In Me.Controls.OfType(Of ComboBox).Where(Function(x) x.Name Like "cboQuestion*")
cbo.Items.Clear()
Next

How to handle updating a DataGridView when the bound DataSource goes to empty?

I have a DataGridView to which I've set a list of objects to the DataSource. (I'm in VS 2005 using VB.) I created the DataGridView by creating a Data Source of type AssetIdentifier and dragging that Data Source onto my form.
I want to update the DataGridView when the selection in either a combo box or another DataGridView changes. (Below I'm considering a click in another DataGridView.) The following works:
Public Class dlgShowAssets
' class member variable
Private assetIdList As List(Of AssetIdentifier)
' pertinent subs and functions
Private Sub RefreshAssetIdentifierDataGridView()
AssetIdentifierDataGridView.DataSource = assetIdList
End Sub
Private Sub AssetDataGridView_CellClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles AssetDataGridView.CellClick
assetIdList = RepopulateTheList(id)
Me.RefreshAssetIdentifierDataGridView()
End Sub
End Class
In this case, I always knew that assetIdList would have at least one element. I'd update the list, and reset the data source of the DataGridView to that list, and all was well.
When I applied this to another situation, for which I couldn't guarantee that the list would have at least one element, things would work fine as long as I had at least one element in the list, but if the list became empty, the DataGridView threw System.IndexOutOfRangeException a number of times. The rows in the DataGridView would not go away if I went from a non-zero number of elements to zero.
I tried a workaround, which was to remove all of the elements, add one "dummy" element, and then re-bind the list to the control, and it still didn't work.
Also, following all of those exceptions, I'd get other similar exceptions when I hovered over the cells in the DataGridView.
I've been trying to track down this behavior for a few hours. Any insights? Thanks!
Will be happy to add more info if needed.
UPDATE: Some of the members of AssetIdentifier were "Nothing" but I fixed that in the constructor, and the exceptions still occur.
Refactored code and it works ...