How to save the values in a userform in vba? - vba

I have a userform which looks like this
All the textboxes are filled with default values once a user opens this form.
The user then changes the values manually and presses OK which will close the form and will affect the charts linked to the form.
If the user opens the form again, the values in the textboxes revert back to default ones and the charts linked will assume their default positions too.
Is there any way where once the user presses OK, the values in the userform gets saved so when the user opens the form the next time, they are presented with the changed values instead of the default ones??
Thanks!

Have you tried using the Sheet as a place to save the data?
Dim i As Integer
Private Sub UserForm_Initialize()
For i = 1 To 49 'Number of TextBoxes (I counted 49)
Controls("TextBox" & i).Text = Cells(i, 1)
Next i
End Sub
Private Sub CommandButton1_Click() 'Save button
For i = 1 To 49
Cells(i, 1) = Controls("TextBox" & i).Text
Next i
End Sub
This will save the values in the first 49 rows of the first coloumn. If you want to save it to an other Sheet, create a new one and add Worksheets("SheetName").Cells....

Another solution if you don´t have similiar names (but Hungarian Notion like cbTest for a combo box) for the control elements, so you cannot iterate through them with a command like Controls("TextBox" & i)....
If you have a userform (e. g. called ufTest), then you can iterate through all control elements like this:
Dim controlElement as Control
For Each controlElement In ufTest.Controls
MsgBox controlElement.Name
' Check for type of control
If Left(controlElement.Name, 2) = "cb" Then
MsgBox = controlElement.Text
ElseIf Left(controlElement.Name, 3) = "txt" Then
MsgBox = controlElement.Value
End If
Next controlElement
This gets you the name of every control element in your userform, no matter how you´ve called your controls.
P. S.: I´m sure there is a way to get the type of the control without using the Hungarian Notion, but I did not find it in time.

Related

Excel VBA userform type mismatch

First time asking a question here, my apologies if my question has already been answered (If it was, I didn't understand it because I am an utter novice). My Excel userform that I use to update quantities of stock supplies used on a particular job is generating a
type mismatch error
It is supposed to add the quantity from the useform to the entry in the appropriate cell on the sheet. I assume that this has something to do with a variable not being declared correctly.
Private Sub SubmitFormButton_Click()
Dim Data_Start As Range
Dim i As Integer
Set Data_Start = ActiveSheet.Cells(6, 6)
For i = 1 To 31
Data_Start.Offset(i, 0) = Data_Start.Offset(i, 0) + AddToform.Controls("TextBox" & i).Value
Next i
Unload AddToform
End Sub
AddToform.Controls("TextBox" & i).Value is making a number of assumptions:
AddToForm was shown with AddToForm.Show. If you're doing this:
With New AddToForm
.Show
'...
End With
...then the code is not referring to the instance that's being displayed, and the .Value of the textbox is very likely not what you expect it to be, since the textbox you're reading from isn't the textbox that the user entered a value in.
There's a control named "TextBox" & i on the form. This means if you ever rename your textboxes to more meaningful names, e.g. FirstNameBox, LastNameBox (or whatever makes sense), then the code breaks. Using control names to hold metadata (e.g. some worksheet row number offset) can work, but it's probably better to iterate the controls on the form (whatever their names are), test if the current control is a TextBox (e.g. If TypeOf ctrl Is MSForms.TextBox Then), and then pull the metadata from the control's Tag property. That way your controls can have meaningful names and renaming them won't break any of the logic.
User input is valid. That's always a bad assumption to make, regardless of the language or technology being used: always protect your code from invalid input. If the user enters "ABC", that loop breaks. One way to do this, is to introduce a local variable, to separate getting the user input from consuming the user input - and validate it on the way:
If IsNumeric(Controls("TextBox" & i).Value) Then
Dim validInput As Double
validInput = CDbl(Controls("TextBox" & i).Value)
Data_Start.Offset(i, 0) = Data_Start.Offset(i, 0) + validInput
End If
And that should fix your bug.

Export data from one sheet to another via drop down list - VBA Excel Macro

I have an Excel workbook with two worksheets on.
Sheet 1 is a form which gets filled in and it has a submit button (created with VBA) which takes the data and adds it to the next empty line on sheet 2.
So sheet 2 is filled with previously submitted form information and sheet 1 can be cleared (again via a button created with VBA) ready for the next lot of information.
Each entry has a unique number for reference purposes, but what I would like to do is on sheet 1(the form) to have a drop down list of all the unique numbers which I can select one and for it to bring all the relevant information back in to the form so any edits can be made and a button to be able to save/overwrite the data instead of saving it as a new line.
So would like to be able to bring the data back to sheet 1 to edit/amend/save/overwrite.
My VBA knowledge is limited as this is the first project I have dealt with it on so I'm still learning the basics as I go.
Any advice or suggestions would be gratefully appreciated.
Many Thanks
Rachael.
I've put together a quick example demoing what you requested, it can be downloaded here.
I did the following:
Added Form (with a few data editing fields) and Data (with example data) worksheets.
Added a validation dropdown to the datasheet showing the ID + Name data from your Data worksheet.
When the selected option in the validation dropdown is changed it triggers the Worksheet_Change event, running the following code:
Private Sub Worksheet_Change(ByVal Target As Range)
'check that the Target cell is our dropdown
If Target.Address = "$C$2" Then
'get the value of the dropdown using the range method
Dim dropDownValue As String
dropDownValue = CStr(wsForm.Range(Target.Address).Value)
'if that dropdown value exists (has a length of more than zero)
If Len(dropDownValue) > 0 Then
'get the corresponding record from the data sheet
Dim index As Integer
index = Left(dropDownValue, 1)
wsForm.Range("C3").Value = index
wsForm.Range("C4").Value = Application.WorksheetFunction.VLookup(index, wsData.Range("A:E"), 2, False)
wsForm.Range("C5").Value = Application.WorksheetFunction.VLookup(index, wsData.Range("A:E"), 3, False)
wsForm.Range("C6").Value = Application.WorksheetFunction.VLookup(index, wsData.Range("A:E"), 4, False)
End If
End If
End Sub
Which uses vlookups to retrieve the information from the datasheet to populate the editing form.
When the save button is clicked, the following code is run:
Sub Button4_Click()
Dim index As Integer
index = wsForm.Range("C3")
If index > 0 Then
Dim foundIndexRange As Range
Set foundIndexRange = wsData.Range("A:A").Find(index)
If (Len(foundIndexRange.Value) > 0) Then
foundIndexRange.Offset(0, 1).Value = wsForm.Range("C4").Value
foundIndexRange.Offset(0, 2).Value = wsForm.Range("C5").Value
foundIndexRange.Offset(0, 3).Value = wsForm.Range("C6").Value
End If
MsgBox "Record saved"
Else
MsgBox "Please choose from the dropdown"
End If
End Sub
Which uses the range.Find method to locate the range where our index is on the data sheet, then offset to overwrite our new values.
I hope that makes sense, please ask if you have any questions.
Instead of bringing the data back to sheet 1, in sheet 2, you can turn on "Filters" and in the unique numbers column, filter/search for the number whose data you want to change. It will then only show the entry of data corresponding to that number. Then make the edits on sheet 2.
Hope this is useful.

using one sub for multiple buttons in excel vba

I have a spreadsheet containing client data, one client per row and each row has a button that launches a userform showing the data for that client, you can then update the data for that client and write it back to the row for that client. Before we start using the spreadsheet each case worker will filter so that only their clients are shown.
I wondered whether there is a way of having one command button procedure for the buttons on each row ie when you press the button in row 6 it runs a procedure for CommandButton6 to call the data in that row, when you press the button in row 8 it runs a procedure for CommandButton8 to call the data in row 8. However the procedure for both is the same so can I have a CommandButtoni sub where I is the row number.
It is a very simple procedure but I don't want to have to copy it 350 times!
Private Sub CommandButton1_Click()
UserForm1.TextBox1.Value = Worksheets("Sheet1").Range("C2").Value
GetData
UserForm1.Show
End Sub
You would need a parameterized Click handler, which you can't have in VBA. You could assign 350 form buttons to the same handler procedure, but they would all assign UserForm1.TextBox1.Value whatever is in Worksheets("Sheet1").Range("C2").Value, and actually reading your question you seem to want to have some "row parameter".
Having 350 form/ActiveX buttons on a worksheet is generally not a very good idea: I'd try to think of a different approach instead.
Is the Selection in the row you need it to be when a button is clicked? You could make the current/active row highlight somehow (handle SelectionChanged worksheet event), and have one button somewhere (in the Ribbon?) that works off the Selection:
UserForm1.TextBox1.Value = Sheet1.Range("C" & Selection.Row).Value
Other than that, Rory's suggestion would work... but form buttons weren't intended to be used like this.
After implementing Rory's suggestion, I think it is the simplest and most elegant code solution. Allows copy and paste of the buttons without any further configuration if done uniformly with the code.
Sub AddVote()
' This is a simple vote tally counter. Each time a button is clicked,
' the vote count in the third column is incremented by one.
' Imagine: first column = candidate name; second column holds form button;
' Thid column = vote count
' Create a Form button and assign this macro. Copy and paste button!
Dim rngTally As Range
' Use rngTally to point to the cell in the third column (3, in the formula)
' in the same row as the top left corner of the button.
Set rngTally = Cells(ActiveSheet.Buttons(Application.Caller).TopLeftCell.Row, 3)
' Increment the value of the cell pointed to by rngTally by 1
rngTally = rngTally + 1
End Sub
If you don't want/need to design a custom user form, you could use the Form... command:
Click the Customize Quick Access Toolbar drop-down menu in the Excel
Title Bar.
Select More Commands.
If the document will be distributed to other users, change the "For all documents (default)" option to the document name.
Select to Choose commands from: Commands Not in the Ribbon.
Select the Form... command.
Click Add to add the button to the right side menu.
Click OK.
With any cell in a table selected, click the Form button. Edit and press Enter. Could also use Criteria to filter on a value. Unfortunately no way to edit the form, but useful for narrow cell contents. No coding required.
https://support.office.com/en-us/article/Add-edit-find-and-delete-rows-by-using-a-data-form-9443c80d-66c6-4c17-b844-64aac5ae841d
A neater way for a sub for each button.
Private Sub Delete0_Click(): DeleteRow (0): End Sub
Private Sub Delete1_Click(): DeleteRow (1): End Sub
Private Sub Delete2_Click(): DeleteRow (2): End Sub
Private Sub Delete3_Click(): DeleteRow (3): End Sub
Private Sub Delete4_Click(): DeleteRow (4): End Sub
Sub DeleteRow(row As Integer)
Debug.Print "DeleteRow(" & row & ")"
End Sub

VBA listbox select worksheet by index

I have a form with listbox which dynamically provides a list of the worksheets in the current workbook (code below). I wish to take the selected Sheet and refer to it in a formula later in the process. From hours of playing around I cannot seem to accomplish this. I believe I read somewhere that you cannot take the string back to the sub and use it to refer to to an object. So I thought maybe I can create two listboxes
for sheet name
for sheet index
that I could pass the index number to and maybe use that in my formula to lookup items from the correct sheet.
For the life of my I cannot seem to find a way to connect the two since the items will always be changing; the code will be ran on multiple workbooks by multiple operators so the layout will most likely change between users. I can easily add the second list box with index #'s but I have a block on how to associate the name which will have meaning to the user and the index which I can pass back to the sub. I realize the "On click" procedure for the list box to associate the two but with the dynamic nature of the fields I cannot come up with the logic to put that into code.
For N = 1 To ActiveWorkbook.Sheets.Count
With ListBox1
.AddItem ActiveWorkbook.Sheets(N).Name
End With
Next N
Try this out.
Declare a public variable above the code for the UserForm, making it available throughout your workbook from any module or code.
Public listChoice As String
Using your code to get the sheet names for the ListBox rowsource.
Private Sub UserForm_Activate()
For n = 1 To ActiveWorkbook.Sheets.count
With ListBox1
.AddItem ActiveWorkbook.Sheets(n).name
End With
Next n
End Sub
Including an update event for the ListBox
Private Sub ListBox1_AfterUpdate()
listChoice = ListBox1.Text
End Sub
I included a test just to demonstrate that the result is still retained. You don't need this, it demonstrates the results on the screenshot.
Private Sub cmdTestChoice_Click()
MsgBox ("The choice made on the ListBox was: " & listChoice)
End Sub
edit: To access that sheet later, you can call it using something like this:
Some examples of different ways to access a cell, using .Range, or .Cells, with numbers or letters.
Using lRow & lCol as Long to set row and column numbers.
Sheets(listChoice).Cells(lRow, lCol).Value = TextBox1.Value 'Set cell on sheet from TextBox
TextBox2.Value = Sheets(listChoice).Range("A2").Value 'Set TextBox From Cell on Sheet
'Set a cell on another sheet using the selected sheet as a source.
Sheets("AnotherSheet").Cells(lRow, "D") = Sheets(listChoice).Range("D2")

Excel macro to create a command button and assign macro to the button

I am trying to build a main page for my costing model. On this page I have created a drop down list using a combo box and then I want to assign a macro that creates a list of different buttons/command buttons once an option is selected from the list. Then I want to build another macro that is assigned to those buttons which then take the user to another tab/sheet within the same workbook depending on the option they selected from the drop down.
Can someone please give me an idea as to what code I should be using, first to create a command button that refers to the selected option from the drop down and then assign a simple macro to that button which then takes me to the specified tab/sheet?
So far, I have got the following:
Option Explicit
Sub Select_Change()
With ThisWorkbook.Sheets("Main Page").Shapes("Select").ControlFormat
Select Case .List(.Value)
Case "Vehicle1": All_States1
Case "Vehicle2": All_States2
Case "Vehicle3": All_States3
Case "Vehicle4": All_States4
Case "Vehicle5": All_States5
Case "Vehicle6": All_States6
Case "Vehicle7": All_States7
End Select
End With
End Sub
I then tried to use the name All_States1 to create various buttons but it's not working properly, as all selected options are showing the same button and the button won't go away either. Also, I can't seem to assign a macro to the created button.
This is just an example of :
creating a Button
assigning a macro to it
.
Sub button_maker()
Dim r As Range
Set r = Selection
ActiveSheet.Buttons.Add(94.5, 75.75, 51, 27.75).Select
With Selection
.OnAction = "mooney"
.Characters.Text = "Bump"
End With
r.Select
End Sub
Sub mooney()
Range("A1").Value = Range("A1").Value + 3
End Sub
If I understand the problem correctly, you want to have a dropdown (combo box) on your sheet, and when a button is clicked you want to run a macro based on the selection. The following does that - see if it helps you.
First - create a combobox, and a range for the inputs (names in the combobox) and output (value selected). For example, you could call the input selectionIn and the result selectionOut.
Exact steps:
Wrote values of combobox selections in E1:E4 . Selected the four cells, then typed selectionIn in the name box (to the left of the formula bar). That creates a named range (there are other ways to create named ranges, but this is my preferred method).
Called cell F1 selectionOut
Created a combobox, and referenced these two ranges for its input and output:
Created a button, gave it the label "Go" and linked it to the action runIt.
Finally, I created the following code in a workbook module:
Sub runIt()
Dim whatToDo, makeName As Boolean
' look up the name of the combo based on the value:
whatToDo = Range("selectionIn").Cells([selectionOut].Value, 1)
MsgBox "have to do '" & whatToDo & "'"
makeName = False
Select Case whatToDo
Case "one"
' example of putting the code you need right in the select:
MsgBox "doing the first thing"
Case "two"
' example of calling a specific routine:
Call caseTwo
Case "three"
Application.Run "case" & whatToDo ' making the name of the function on the fly
Case "four"
makeName = True
End Select
If makeName Then
Dim nameToRun
nameToRun = "case" & whatToDo
Application.Run nameToRun
End If
End Sub
Sub caseTwo()
MsgBox "called the code for case two"
End Sub
Sub caseThree()
MsgBox "doing case three here"
End Sub
Sub caseFour()
MsgBox "even four can be done"
End Sub
This shows a few different ways to handle different cases depending on what was selected. Of course you can have a macro run every time the combobox selection is changed - but it sounds from your description like that is not what you want.
Let me know how you get on with this code example - I tried to keep it simple but show some options at the same time.
One alternative (which might be simpler) would be to have an array with the names of the functions that you want to call:
Sub otherMethod()
Dim functionList()
functionList = Array("caseOne", "caseTwo", "caseThree", "caseFour")
Application.Run functionList([selectionOut].Value - 1)
End Sub
That's certainly the most compact way I can think of to do this... you need the offset of -1 because the array index is base 0 (by default anyway) and the combobox returns 1 for the first selection. You could make your code more robust by writing
functionIndex = [selectionOut].Value + LBound(functionList) - 1
Application.Run functionList(functionIndex)
This ensures that if you change the base index of the functionList array to another value, it will all still work correctly.