Tying ComboBox/NumericUpDown selections to an array? - arrays

I'm working on a VB project that has a lot of comboboxes and numericupdown items.
Lets say we have ComboBox1, 2, 3, 4, and 5; and we have NumericUpDown1, 2, 3, 4, 5.
When the user clicks the "Save" button, I want to save all of their selected combobox items and numericupdown numbers to a CSV file. Is there an elegant/automatic way to tie all of the .SelectedIndex and .Value for these items to an array so I can easily write the array out to a CSV?
The only way I know to do this so far is to manually associate each one with an array position:
Arr(0) = ComboBox1.SelectedIndex
Arr(1) = ComboBox2.SelectedIndex
...
Arr(5) = NumericUpDown1.Value
Arr(6) = NumericUpDown2.Value
...
etc.
This wouldnt be too bad, except I have a LOT of these items, and writing a line for each one seems silly. I'm new to VB, so this might be an obvious solution to some. Any ideas?
Having them bound to an array would be really handy because I also allow the user to Load a CSV file, which I would like to automatically populate the ComboBoxes and NumericUpDowns from the CSV values. The only way I know to do this is to manually move each array item to the respective combobox/numeric item when they click the Load file button:
ComboBox1.SelectedIndex = Arr(0)
ComboBox2.SelectedIndex = Arr(1)
...
NumericUpDown1.Value = Arr(5)
NumericUpDown2.Value = Arr(6)
...
etc.
Edit: Here is some application info as requested...
The CSV file that can be saved/loaded looks like this:
#"Device Info","123456","asdfgh","0000","1.0x","1"
000F,0000,0032,0000,00C8,0001,0078,0101,0000,0001,0000,0001
010F,0078,0000,0103,0001,0000,000A,0005,0007,0006,0000
0001,000A,000A,000A,000A,0005,0005,0005,0002
...etc
The header line just has serial number, version, and other misc info; it is automatically generated by the target device. All of the other lines are configuration setpoints that the target device reads in and automatically configures itself. I'm writing this PC program to be able to edit (and create from scratch) these configuration CSV files with a nice GUI interface. Each item is tied to a specific setpoint, such as 000F = Language, 0032 = System Frequency, 00C8 = System Voltage, etc. The easiest way I saw to make this configuration program was to use numeric entry and drop-down comboboxes that the user can select what they want. Each NUD and CBOX equates to one of the CSV file data fields.

You can use Controls.Find() to get a reference to the desired control based on an index value. Here's a quick example to demonstrate what I mean:
For i As Integer = 1 To 30
Dim matches() As Control = Me.Controls.Find("NumericUpDown" & i, True)
If matches.Length > 0 AndAlso TypeOf matches(0) Is NumericUpDown Then
DirectCast(matches(0), NumericUpDown).Value = i
End If
Next
You can incorporate code like that into the load/save routines.

I would use binary serialization. This eliminates the need to format strings or xml when saving control properties. Similar to Plutonix's solution, it only works on certain types of control. However, it can be modified to work on any type of control - but only supports a single property to be loaded for each control. It will work on all controls of type X instead of controls named xName. You can add further restrictions by grouping the controls to be serialized in a panel or other means.
Make a new Form called Form1. Add some NumericUpDowns, TextBoxes, and ComboBoxes. Put some values in the ComboBoxes at design time, otherwise calling loadState() in Form_Load will be meaningless. However, loadState() can be called whenever (i.e. after the comboboxes have been populated).
You will need to import these two namespaces:
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
In your class:
Private Shared stateFileName As String = "SavedState.bin"
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
saveState(Me)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
loadState(Me)
End Sub
And these are the methods you will use to save and load states. The save state method:
Private Shared Sub saveState(instance As Form)
Dim controlProperties As Dictionary(Of String, Object) =
instance.Controls.OfType(Of Control).ToDictionary(Of String, Object)(
Function(c) c.Name,
Function(c)
' You can support different types of controls here too
If TypeOf c Is NumericUpDown Then
Return CType(c, NumericUpDown).Value
ElseIf TypeOf c Is ComboBox Then
Return CType(c, ComboBox).SelectedIndex
Else
' All other controls get their text property saved
' .Text is a property of Control
Return c.Text
End If
End Function)
Using myFileStream As Stream = File.Create(stateFileName)
Dim serializer As New BinaryFormatter
serializer.Serialize(myFileStream, controlProperties)
End Using
End Sub
The load state method:
Private Shared Sub loadState(instance As Form)
If File.Exists(stateFileName) Then
Using myFileStream As Stream = File.OpenRead(stateFileName)
Dim deserializer As New BinaryFormatter()
Dim controlProperties = CType(deserializer.Deserialize(myFileStream), Dictionary(Of String, Object))
For Each c As Control In instance.Controls
If controlProperties.ContainsKey(c.Name) Then
' You can support different types of controls here too
If TypeOf c Is NumericUpDown Then
CType(c, NumericUpDown).Value = CDec(controlProperties(c.Name))
ElseIf TypeOf c Is ComboBox Then
CType(c, ComboBox).SelectedIndex = CInt(controlProperties(c.Name))
Else
c.Text = controlProperties(c.Name).ToString()
End If
End If
Next
End Using
End If
End Sub
You should add exception handling, and note that a binary file is not meant to be edited by a human without the assistance of a machine.

Related

How can I store an array in my.settings in vb.net

I am trying to store a user input array in a my.settings variable in vb.net. I would like for the user to enter an array in the form {1,2,3}, store that as a string in the setting and then be able to use the setting value later to create a new array. The code will be something like:
Dim inputarray()
Dim outputarray()
inputarray=textbox1.text
my.settings.inputstoredarray.add(inputarray)
outputarray=my.settings.inputstoredarray
textbox2.text=outputarray(0)
'If the user types "{1,2,3}' in textbox1, textbox2 should show "1"
I have tried multiple versions of this but there seem to always be type conversion errors. I don't understand why it works if I hardcode:
inputarray={1,2,3}
and yet the below code does not work:
inputarray=my.settings.inputstoredarray
How can I store a user provided array in my.settings and retrieve it for use later?
does not work even if I go into settings and set the string value for the setting to {1,2,3}
Set up your setting in Project Properties, Settings tab as follows.
Then save the setting as follows.
Private Sub SaveStringToSettings()
My.Settings.StringOfInts = TextBox1.Text 'User types in {1, 2, 3}
End Sub
To retrieve the setting and turn it into an array
Private Sub CreateArrayFromSettings()
Dim SettingValue = My.Settings.StringOfInts
Debug.Print(SettingValue) 'View this in the Immediate window
'Get rid of the braces
Dim TrimmedString = SettingValue.Trim({"{"c, "}"c})
'Split the string by the commas into an array
Dim Splits = TrimmedString.Split(","c)
'Get rid of the spaces
For i = 0 To Splits.Length - 1
Splits(i) = Splits(i).Trim
Next
TextBox1.Text = Splits(0) 'Displays 1
End Sub
You can browse for additional setting types in your Project properties/Settings tab but you'll find it fairly limited with the most common message being that you can't use that type. Array is one you can't use, I'm afraid.
But you can use the fully-supported StringCollection:
Dim saveTest As New StringCollection() From {"1", "2", "3", "4"}
My.Settings.MySetting = saveTest
My.Settings.Save()
Dim loadTest As StringCollection = My.Settings.MySetting
Another option would be to use, say, and XML or JSON string that you de-serialize but that's getting a bit involved.
You can't store an array in application settings. What you can do is create a setting of type StringCollection. You can then use it as a StringCollection in code or you can transfer the data back and forth between the StringCollection and an array if you really need an array.
Start by opening the Settings page of the project properties and creating a new setting of type System.Collections.Specialized.StringCollection. For this example, I'll name it MyStringCollection but you should name it appropriately for your app. When you do this, notice that the Value field is empty by default. This means that the setting is Nothing by default. That's OK but it means that you need to actually create the collection object in code before using it for the first time, e.g.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
If My.Settings.MyStringCollection Is Nothing Then
My.Settings.MyStringCollection = New StringCollection
End If
'Use My.Settings.MyStringCollection here.
End Sub
Alternatively, you can force the settings UI to create the object for you. To do that, select the Value field for that setting and click the browse (...) button. Add any arbitrary character and click the OK button. Notice that the Value field is populated with an XML snippet that contains the text you entered. Click the browse (...) button again, delete the text and click OK again. Notice that the XML remains, even though the text you had entered is removed. That XML will now create the StringCollection object automatically, so you don't need code to do so.
If you're happy wiith a collection of String values then you can use the setting directly in code wherever you like. It works, for the most part, like a List(Of String), allowing you to add and remove items at will. If you specifically need an array or you need a type other than String then you will have to do some translation, e.g.
'Load the collection items into a String array.
Dim myStringArray = My.Settings.MyStringCollection.Cast(Of String)().ToArray()
'Load the collection items into an Integer array.
Dim myIntegerArray = My.Settings.MyStringCollection.Cast(Of String)().Select(Function(s) CInt(s)).ToArray()
'Repopulate the collection from a String array.
My.Settings.MyStringCollection.Clear()
My.Settings.MyStringCollection.AddRange(myStringArray)
'Repopulate the collection from an Integer array.
My.Settings.MyStringCollection.Clear()
My.Settings.MyStringCollection.AddRange(myIntegerArray.Select(Function(n) n.ToString()).ToArray())
If you want to display the contents of your collection in a TextBox than you might do something like this:
TextBox1.Text = String.Join(",", My.Settings.MyStringCollection.Cast(Of String)())
That will create a single, comma-delimited String containing all the items. To repopulate the collection from a TextBox containing such text, do this:
My.Settings.MyStringCollection.Clear()
My.Settings.MyStringCollection.AddRange(TextBox1.Text.Split(","c))

Manipulating Array After Pulling From Text File

I've been thoroughly combing StackOverflow and other sources for the answers to these problems, and have not been able to find a solution that would work cohesively with the steps I need to accomplish.
Things I need to do:
Create an array from a text file and display in a listbox (this is done and works)
Have user fill in a text box, click a button, and the array is searched for anything matching the text box's value
Have the results of the search displayed in a separate listbox
Here's what I've got so far, and it's fairly hacked together, so if there's anything that can be improved, naturally, I'd be all for that.
`
Public Class Form1
Dim lblName As Object
Public colleges As String
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim colleges() As String = IO.File.ReadAllLines("Colleges.txt")
ListBoxCollege.Items.AddRange(colleges)
End Sub
Private Sub btnSearchGo_Click(sender As Object, e As EventArgs) Handles btnSearchGo.Click
Dim n As Integer, college As String
college = txtCollegeSearchUserInput.Text
n = Array.IndexOf(colleges(), college)
If n <> 1 Then
[[Needs to output into new listbox, preferably here]]
End If
End Sub
If there's anything else needed from VB, I can provide if necessary!
In your case you can do something like this
For i As Integer = 0 To ListBoxCollege.Items.Count -1
If ListBoxCollege.Items(i).ToString().IndexOf(college, StringComparison.OrdinalIgnoreCase) > -1 Then
findList.Items.Add(ListBoxCollege.Items(i))
End If
Next
The difference here - you calling IndexOf on array and I call it for each item in list. Therefore I return all matches, while you only the first one
This is little bit limited in search criteria. You could use regex as well for wild cards etc. Or you store your data (colleges) in System.Data.DataTable, and you would be able to run Sql Select queries on it almost like in database.

How to have a global Dictionary in VB.NET/WPF application to save data from different windows?

I am new to VB.NET and WPF.
I am building a "Questionnaire" app. Users will be presented sequentially with different questions/tasks (windows). After they respond on each question/task and press a "submit" button a new window will open with a new question/task, and previous window will close. After each question, when the button is pressed, I need to store data to some global object. After all questions are answered the data of this object should be written out to the output file.
I figured out that Dictionary will be the best to store the results after each window.
I am not sure how, where to create this global Dictionary and how to access it. Should I use View Model? If yes, can you give an example? Or, should it be just a simple class with shared property? (something like this)
EDIT 2: I tried many different ways recommended online
GlobalModule:
Module GlobalModule
Public Foo As String
End Module
GlobalVariables:
Public Class GlobalVariables
Public Shared UserName As String = "Tim Johnson"
Public Shared UserAge As Integer = 39
End Class
Global properties:
Public Class Globals
Public Shared Property One As String
Get
Return TryCast(Application.Current.Properties("One"), String)
End Get
Set(ByVal value As String)
Application.Current.Properties("One") = value
End Set
End Property
Public Shared Property Two As Integer
Get
Return Convert.ToInt32(Application.Current.Properties("Two"))
End Get
Set(ByVal value As Integer)
Application.Current.Properties("Two") = value
End Set
End Property
End Class
Here is where I save the data to global variables/properties in the first window. I need to store data in this subroutine before closing an old window and opening a new window. I use MessageBox just for testing.
Private Sub btnEnter_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnEnter.Click
Dim instructionWindow As InstructionsWindow
instructionWindow = New InstructionsWindow()
Application.Current.Properties("number") = textBoxValue.Text
Globals.One = "2"
Globals.Two = 3
MessageBox.Show("GlobalVariables: UserName=" & GlobalVariables.UserName & " UserAge=" & GlobalVariables.UserAge)
GlobalVariables.UserName = "Viktor"
GlobalVariables.UserAge = 34
GlobalModule.Foo = "Test Foo"
'testing if it saved tha value
'MessageBox.Show(Application.Current.Properties("number"))
Application.Current.MainWindow.Close()
instructionWindow.ShowDialog()
End Sub
Next subroutine is where I am trying to retrieve the value from global Properties/variables in the second window, but message boxes come out empty. There might also the case that I am assigning values in a wrong way, or not reading them in a right way (casting?) :
Private Sub FlowDocReader_Initialized(ByVal sender As Object, ByVal e As System.EventArgs) Handles FlowDocReader.Initialized
' Get a reference to the Application base class instance.
Dim currentApplication As Application = Application.Current
MessageBox.Show(currentApplication.Properties("number"))
MessageBox.Show("One = " & Globals.One & " Two = " & Globals.Two)
MessageBox.Show("GlobalVariables: UserName=" & GlobalVariables.UserName & " UserAge=" & GlobalVariables.UserAge)
MessageBox.Show("GlobalModule.Foo = " & GlobalModule.Foo)
Dim filename As String = My.Computer.FileSystem.CurrentDirectory & "\instructions.txt"
Dim paragraph As Paragraph = New Paragraph()
paragraph.Inlines.Add(System.IO.File.ReadAllText(filename))
Dim document As FlowDocument = New FlowDocument(paragraph)
FlowDocReader.Document = document
End Sub
Thanks.
You can make public Dictionary property for form and put your dictionry to this property or make constructor with Dictionary argument.
You already have this dictionary Application.Properties
Look here, please.
First, you can define a dictionary (list of lists) as follows at the beginning of a form or in a module
Dim dic As New Dictionary(Of String, List(Of String))
As the user completes questions on a form, write the partucular form number and query results to a single record in the dic before going to the next form (place this code into the "Next" button):
'Assume q1response=3, q2response=4,..., qpresponse="text", etc.
Dim myValues As New List(Of String)
myValues.Add(formname)
myValues.Add(q1response)
myValues.Add(q2response)
.
.
myValues.Add(qpresponse)
dic.Add(username, myValues)
When a user is done, there will be multiple records in the dictionary, each of which starts with their name and is followed by question responses. You can loop through multiple dictionary records, where each record is for a user using the following:
For Each DictionaryEntry In dic 'this loops through dic entries
Dim str As List(Of String) = DictionaryEntry.Value
'here you can do whatever you want with results while you read through dic records
'username will be = str(0)
'formname will be str(1)
'q1 response on "formname" will be str(2)
'q2 response on "formname" will be str(3)
'q3 response on "formname" will be str(4)
...
Next
The trick is that there will be multiple dictionary records with results for one user, where record one can have results like "John Doe,page1,q1,q2,q3" and record 2 will be "John Doe,page2,q4,q5,q6." Specifically, the "str" in the above loop will be an array of string data containing all the items within each dictionary record, that is, in str(0), str(1), str(2),... This is the information you need to work with or move, save, analyze, etc.
You can always put all the code I provided in a class (which will be independent of any form) and dimension the sic is a Sub New in this class, with the updating .Add values lines in their own sub in this same class). Then just Dim Updater As New MyNewClassName. Call the Updater in each continue button using Call Updater.SubNameWithAddValues(q1,q2,...qp). It won't matter where you are in your program since you using a specific class. The one thing I noticed with my code is that you can only use the line that adds the "key" or the username once, so use it after the last query -so put it in a Sub Finished in your new class and call as Call Updater.Finished(username,q30,q31,last)

Excel VBA: Dynamic range for ComboBox.Rowsource values not displayed when userForm called from commandbutton

The title should give a fair overview of the problem but I'm running a dynamic named range for use in a combo box in a userform. When I run the form, the values appear as intended. When I call a module sub-routine via a command button, the values don't appear and I've no idea why.
I'll paste all code and highlight the offending snippet(s) below:
Private Sub btnGetGAToken_Click()
'--------------------------------
'Obtain API Token from Google Analytics (GA), indicate to user that token has been obtained and populate Account combobox
'with a unique list of accounts, which will in turn populate the Profile combobox with the profiles associated with the chosen
'account
'--------------------------------
Dim txtEmailField As String
Dim txtPasswordField As String
'Values written to sheet for use in UDFToken and UDFGetGAAcctData array formulas
Range("FieldEmail").Value = Me.txtEmailField.Text
Range("FieldPassword").Value = Me.txtPasswordField.Text
Range("GAToken").Calculate
With Me.lblGATokenResponseField
.Caption = Range("GAToken").Value
.ForeColor = RGB(2, 80, 0)
End With
Call FindUniqueAccountNames
cboAccountNamesComboBox.RowSource = Sheet1.Range("ListUniqueAccountNames").Address
End Sub
Private Sub cboAccountNamesComboBox_Change()
'Value written to sheet for use in the 'ListProfileNames' dynamic, named range
Range("ChosenAccount").Value = Me.cboAccountNamesComboBox.Value
With Me.cboProfileNamesComboBox
.Value = ""
.RowSource = Sheets("CodeMetaData").Range("ListProfileNames").Address
End With
End Sub
The dynamic range was created using the name manager and is below:
Named Range: "ListUniqueAccountNames" =OFFSET(CodeMetaData!$J$5,0,0,COUNTA(CodeMetaData!$J$5:$J$5000))
and for ease of reference, the code I'm using to run it is below:
cboAccountNamesComboBox.RowSource = Sheets("CodeMetaData").Range("ListUniqueAccountNames").Address
The sub-routine calling the userform is here:
Public Sub ShowReportSpecsForm()
Load frmReportSpecs
frmReportSpecs.Show
End Sub
Forgive me for posting so much of the code, but I'm not sure exactly what it is that's causing the problem - I'm still very much a rookie with forms.
Any help will be greatly appreciated. Thanks.
If you are using the rowsource property and named ranges then I would suggest setting the rowsource property of the combobox's at design time. Then to debug where required use:
Debug.Print Range("ListUniqueAccountNames").Address
This will return the named range address to the immediate window where you can check it is correct.
Remember that the property Address from a named dynamic range returns a normal static address.
For example, Range("ListUniqueAccountNames").Address can returns $J$5:$J$20.
You do not need use a Excel address in RowSource property. You can use a Excel name.
Besides, when you show a Userform it is necessary refresh the RowSource property from a ComboBox or ListBox control in order to update its values. (Excel control does not watch if the range or data change)
That refresh can be made inside Activate event (it runs immediately before form Show and it is shown below) and any situation where data or range changes.
Private Sub UserForm_Activate()
Me.cboAccountNamesComboBox.RowSource = ""
Me.cboAccountNamesComboBox.RowSource = "ListUniqueAccountNames"
End Sub

Infragistics UltraWinGrid EmptyDataText Equivalent?

We're using Infragistics UltraWinGrid as a base class for customized controls. One of the projects that will use this control to display search results has a requirement to display a user friendly message when no matches are located.
We'd like to encapsulate that functionality into the derived control - so no customization beyond setting the message to display is required by the programmer who uses the control. This would have to be done in generic fashion - one size fits all datasets.
Is there allowance in the UltraWinGrid for this type of usage already? If so, where would I find it hidden. :-)
If this functionality needs to be coded, I can think of an algorithm which would add a blank record to whatever recordset was set and place that into the grid. In your opinion, is this the best way to handle the solution?
I don't know if this will help, but here's to finishing up the thread. I didn't find a built in way, so I solved this problem as follows: In my class which inherits UltraGrid
Public Class MyGridPlain
Inherits Infragistics.Win.UltraWinGrid.UltraGrid
I added two properties, one to specify what the developer wants to say in the empty data case, and another to enable the developer to place their message where they want it
Private mEmptyDataText As String = String.Empty
Private mEmptyDataTextLocation As Point = New Point(30, 30)Public Shadows Property EmptyDataTextLocation() As Point
Get
Return mEmptyDataTextLocation
End Get
Set(ByVal value As Point)
mEmptyDataTextLocation = value
setEmptyMessageIfRequired()
End Set
End Property
Public Shadows Property EmptyDataText() As String
Get
Return mEmptyDataText
End Get
Set(ByVal value As String)
mEmptyDataText = value
setEmptyMessageIfRequired()
End Set
End Property
I added a method which will check for empty data and set the message if so. And another method which will remove the existing empty message.
Private Sub setEmptyMessageIfRequired()
removeExistingEmptyData()
'if there are no rows, and if there is an EmptyDataText message, display it now.
If EmptyDataText.Length > 0 AndAlso Rows.Count = 0 Then
Dim lbl As Label = New Label(EmptyDataText)
lbl.Name = "EmptyDataLabel"
lbl.Size = New Size(Width, 25)
lbl.Location = EmptyDataTextLocation
ControlUIElement.Control.Controls.Add(lbl)
End If
End SubPrivate Sub removeExistingEmptyData()
'any previous empty data messages?
Dim lblempty() As Control = Controls.Find("EmptyDataLabel", True)
If lblempty.Length > 0 Then
Controls.Remove(lblempty(0))
End If
End Sub
Last - I added a check for empty data to the grid's InitializeLayout event.
Private Sub grid_InitializeLayout(ByVal sender As Object, _
ByVal e As Infragistics.Win.UltraWinGrid.InitializeLayoutEventArgs) _
Handles MyBase.InitializeLayout
setEmptyMessageIfRequired()
End Sub

Resources