using one sub for multiple buttons in excel vba - 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

Related

Run a Macro in Excel 2010 using Enter Key, Whether Worksheet has Changed or Not, when Pressed in Particular Range

The short version of the question: How can I make a macro run in Excel 2010 when the user hits the numeric [Enter] key within a given range of cells, and whether or not they've changed the spreadsheet since the last running of the macro?
LONGER VERSION: I have a macro in Excel 2010, and I currently have a button on the sheet that the user presses to run it. The user enters 4 cells of data (in A2:D2) and runs the macro to populate several tables on several sheets with the data. The user may enter several hundred sets of data in a single sitting, and currently leaves the mouse hover over the button, enters the data, and clicks the mouse without moving it to hit the button. I'd like to simplify it by having a press of the numeric [Enter] key run the macro so they can leave their hand on the numeric key area.
I've found this question, but the answer was to use Worksheet_Change and the user didn't need the Enter Key. I also found this question, which used on the [Enter] key, but also required the worksheet to change to be of use.
My user may use the same data multiple times, so I'd like them to be able to press the [Enter] key multiple times. Currently the macro finishes by selecting A2, the first cell with data, so the user can enter new data or press it again to run it again. I want it to run from anywhere in the range of A2:D2, selecting A2 when complete (as it does now), and if [Enter] is pressed while any other cell is selected, or a cell on any other sheet is selected, I want it to move down a cell as it currently does, without running any macro.
Useful names/numbers/things:
Data range will always be in Sheet 1, cells A2:D2.
Macro to be run is named "InsertIntoTables()" and takes no parameters.
Currently no other application-wide macros or events are being used.
The macro already handles empty or improperly-filed cells in my range.
If there is anything else necessary to provide an answer, I'm happy to supply it.
This is just to get you started. Say clicking the button with the mouse runs InsertIntoTables(). I am assuming that InsertIntoTables() is in a standard module. (this makes it easy to call from another sub)
First run:
Sub NumericEnter()
Application.OnKey "{ENTER}", "InsertIntoTables"
End Sub
After this has been run:
touching the numeric keypad ENTER key will have the same effect as mouse-clickng the button
the normal ENTER key will not be affected
To restore the numeric keyboard ENTER key, run this:
Sub ClearEnter()
Application.OnKey "{ENTER}", ""
End Sub
What you must complete:
The numeric keypad ENTER key will call your sub no matter which worksheet is active. YOU must add the logic to detect which sheet is active and take appropriate actions. You must select which ever cell you want when your sub completes. You must remember to run the restore sub before quitting.
EDIT#1:
I have two worksheets; Sheet1 and Sheet2.Sheet1 is the sheet in which I want to use the "special" ENTER key.
I placed the following in a standard module:
Sub NumericEnter()
Application.OnKey "{ENTER}", "InsertIntoTables"
End Sub
Sub ClearEnter()
Application.OnKey "{ENTER}", ""
End Sub
Sub InsertIntoTables()
MsgBox "Inserting"
End Sub
I placed the following in the Sheet1 code area:
Private Sub Worksheet_Activate()
Call NumericEnter
End Sub
Private Sub Worksheet_Deactivate()
Call ClearEnter
End Sub
I placed the following in the workbook code area:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Call ClearEnter
End Sub
Private Sub Workbook_Open()
Sheets("Sheet2").Activate
Sheets("Sheet1").Activate
End Sub
The purpose of the Workbook_Open() macro is to insure we start with Sheet1 active and the "special" ENTER key active.
EDIT#2:
Use this instead:
Sub ClearEnter()
Application.OnKey "{ENTER}"
End Sub
Reference:
Tims answer

Is it possible set my Excel Userform Combo box to begin narrowing the fields while data is entered?

VBA Newbie here.
I have searched high and low for this answer, and even come across other questions very similar to mine, but cannot get an answer. So I am hoping that this is my lucky day.
I have a Userform in excel that has Four combo boxes. Each combo box has a drop down with several choices. In two of these boxes, there are many business names and a lot of these names are similar. I was wanting to have the feature where are the data was being typed into the box, it would begin to narrow the options. EXAMPLE: if I type "heating and air" it begins to only show items in the list that include that word or phrase.
Is this a properties change in the box, or a code written, or something else?
Please help- I am stumped and no one seems to have the answer.
Very grateful-
Excel Newbie
Yes this is very possible. All you have to do is create a sub that populates that combo box, set it up so that when adding it checks the value of the box for example if it was the basic typing example. basic format. This assumes the possible values are stored in an array. This would add any item that has the string entered in it. (in any position)
For I = 0 to Number of Values
If instr(Value(I), ComboBox.Text) > 0 then
add item
endif
next
I played around a bit and came up with something to get you started. This basically functions as an "auto search". It is not autocomplete because it will search entire terms, not just terms which begin with whatever you've typed in. Basically I assume you have a range of cells (in this example cells A2:A121) that have the date for your drop down in it.
Setup
Add a new generic module (I named mine GlobalVars and add the following code:
Option Explicit
Public IgnoreChange As Boolean
Public RangeOfData As Variant
The Code
Open the code to your UserForm.
Add the following code:
Private Sub UserForm_Initialize()
RangeOfData = Application.WorksheetFunction.Transpose(Sheet1.Range("A2:A121").Value)
IgnoreChange = False
End Sub
Be sure to update A2:A121 and Sheet1 (I am using code name, but Worksheets("Sheet1") would work just as well) to point to the data which contains your combobox choices.
Now, the meat of the job is handled in the Combobox_Change event
Private Sub ComboBox1_Change()
If Me.ComboBox1.Text = vbNullString Then
Me.ComboBox1.Clear
SendKeys ("{Enter}")
End If
If Me.ComboBox1.TextLength > 2 Then
Dim i As Long, j As Long
If IgnoreChange = False Then
Me.ComboBox1.Clear
SendKeys ("{Enter}")
DoEvents 'Bug with NumLock
For i = LBound(RangeOfData) To UBound(RangeOfData)
If UCase(RangeOfData(i)) Like "*" & UCase(Me.ComboBox1.Text) & "*" Then
Me.ComboBox1.AddItem RangeOfData(i)
End If
Next i
Me.ComboBox1.DropDown
End If
End If
IgnoreChange = False
End Sub
Be sure to change ComboBox1 to the name of your combobox control.
Basically, what this does is it handles user input when it reaches two characters or longer. The code will search through your input data range and then return results that match the string as the user is entering it. The results is like a suggestions box.
I should note that with this method, the combobox is NOT pre-populated with data, so users must begin typing something into the combobox.
Additionally, I added the following code to handle the backspace key:
Private Sub ComboBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 8 Then
IgnoreChange = True
End If
End Sub
Again be sure to change ComboBox1 as appropriate.
For my example, I loaded all 120 crayola crayon colors into a spreadsheet (that is what is in Sheet1 from A2:A121).
Here is example output for when I start typing, first I input blu:
As you can see I am getting all values that contain blu, including those that don't start with blu such as Cadet Blue or Midnight blue.
As another example, I will search for flower
So as you can see, instead of being a combobox with 120 static options, it is updated based on what the user types and tied to a list of values in your cells.
I did notice that SendKeys sometimes toggled my NumLock, this is a known issue. The point of that line is to collapse the drop down after the user deletes all of the text or continues texting so as to "refresh" the auto-generated list.

Copy text from cells based on a different cells answer

i am trying to create an order form that copies the customers address details that are in cells C8:C13, I have created a drop down in cell G15 that asks if the customers billing address is the same - yes or no, if the answer is yes I want to the data from c8:c13 to be copied into C16:C21
alternatively I could look to hide the rows C16:C21?
I tried initially to right an IF formula however it just returns true or false.
I think this might fix you problem.
Create a ActiveX ComboBox on whatever cell you want (let's stick with the same cell you wanted, so cell G15);
Open Visual Basic (Developer > Visual Basic);
Insert a module (Insert > Module)
Now lets insert the module code to populate the combobox:
Private Sub Workbook_Open()
With Sheet1.ComboBox1
' Clear values
.Clear
' Populate list
.AddItem "Yes"
.AddItem "No"
End With
End Sub
On your module put this code:
Sub copyThis()
'Defining the variables
Set adressBill = Range("C8:C13")
Set destBill = Range("C16:C21")
'If the combobox is Yes then copy
If Sheet1.ComboBox1.Value = "Yes" Then
adressBill.Select
Selection.Copy
destBill.Select
ActiveWorkbook.Worksheets("Sheet1").Paste
End If
End Sub
If you want to update the values whenever the combobox value changes just click Design Mode on the Developer tab, and select your combobox and click View Code and put this code in it:
Private Sub ComboBox1_Change()
'Calling the function whenever a value changes
Call copyThis
End Sub
Results:
This method is much easier than a formula (and probably safer aswell), since you can't reference a combobox object (directly) in a formula. Instead, you could use the LinkedCell property in the ComboBox.

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.

OnClick in Excel VBA

Is there a way to catch a click on a cell in VBA with Excel? I am not referring to the Worksheet_SelectionChange event, as that will not trigger multiple times if the cell is clicked multiple times. BeforeDoubleClick does not solve my problem either, as I do not want to require the user to double click that frequently.
My current solution does work with the SelectionChange event, but it appears to require the use of global variables and other suboptimal coding practices. It also seems prone to error.
Clearly, there is no perfect answer. However, if you want to allow the user to
select certain cells
allow them to change those cells,
and
trap each click,even repeated clicks
on the same cell,
then the easiest way seems to be to move the focus off the selected cell, so that clicking it will trigger a Select event.
One option is to move the focus as I suggested above, but this prevents cell editing. Another option is to extend the selection by one cell (left/right/up/down),because this permits editing of the original cell, but will trigger a Select event if that cell is clicked again on its own.
If you only wanted to trap selection of a single column of cells, you could insert a hidden column to the right, extend the selection to include the hidden cell to the right when the user clicked,and this gives you an editable cell which can be trapped every time it is clicked. The code is as follows
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'prevent Select event triggering again when we extend the selection below
Application.EnableEvents = False
Target.Resize(1, 2).Select
Application.EnableEvents = True
End Sub
In order to trap repeated clicks on the same cell, you need to move the focus to a different cell, so that each time you click, you are in fact moving the selection.
The code below will select the top left cell visible on the screen, when you click on any cell. Obviously, it has the flaw that it won't trap a click on the top left cell, but that can be managed (eg by selecting the top right cell if the activecell is the top left).
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'put your code here to process the selection, then..
ActiveWindow.VisibleRange.Cells(1, 1).Select
End Sub
SelectionChange is the event built into the Excel Object model for this. It should do exactly as you want, firing any time the user clicks anywhere...
I'm not sure that I understand your objections to global variables here, you would only need 1 if you use the Application.SelectionChange event. However, you wouldn't need any if you utilize the Workbook class code behind (to trap the Workbook.SelectionChange event) or the Worksheet class code behind (to trap the Worksheet.SelectionChange) event. (Unless your issue is the "global variable reset" problem in VBA, for which there is only one solution: error handling everywhere. Do not allow any unhandled errors, instead log them and/or "soft-report" an error as a message box to the user.)
You might also need to trap the Worksheet.Activate() and Worksheet.Deactivate() events (or the equivalent in the Workbook class) and/or the Workbook.Activate and Workbook.Deactivate() events so that you know when the user has switched worksheets and/or workbooks. The Window activate and deactivate events should make this approach complete. They could all call the same exact procedure, however, they all denote the same thing: the user changed the "focus", if you will.
If you don't like VBA, btw, you can do the same using VB.NET or C#.
[Edit: Dbb makes a very good point about the SelectionChange event not picking up a click when the user clicks within the currently selected cell. If you need to pick that up, then you would need to use subclassing.]
I don't think so. But you can create a shape object ( or wordart or something similiar ) hook Click event and place the object to position of the specified cell.
This has worked for me.....
Private Sub Worksheet_Change(ByVal Target As Range)
If Mid(Target.Address, 3, 1) = "$" And Mid(Target.Address, 2, 1) < "E" Then
' The logic in the if condition will filter for a specific cell or block of cells
Application.ScreenUpdating = False
'MsgBox "You just changed " & Target.Address
'all conditions are true .... DO THE FUNCTION NEEDED
Application.ScreenUpdating = True
End If
' if clicked cell is not in the range then do nothing (if condttion is not run)
End Sub
NOTE: this function in actual use recalculated a pivot table if a user added a item in a data range of A4 to D500. The there were protected and unprotected sections in the sheet so the actual check for the click is if the column is less that "E" The logic can get as complex as you want to include or exclude any number of areas
block1 = row > 3 and row < 5 and column column >"b" and < "d"
block2 = row > 7 and row < 12 and column column >"b" and < "d"
block3 = row > 10 and row < 15 and column column >"e" and < "g"
If block1 or block2 or block 3 then
do function .....
end if
I had a similar issue, and I fixed by running the macro "onTime", and by using some global variables to only run once the user has stopped clicking.
Public macroIsOnQueue As Boolean
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
macroIsOnQueue = False
Application.OnTime (Now() + TimeValue("00:00:02")), "addBordersOnRow"
macroIsOnQueue = True
End sub
Sub addBordersOnRow()
If macroIsOnQueue Then
macroIsOnQueue = False
' add code here
End if
End sub
This way, whenever the user changes selection within 2 seconds, the macroIsOnQueue variable is set to false, but the last time selection is changed, macroIsOnQueue is set to true, and the macro will run.
Hope this helps,
Have fun with VBA !!
Just a follow-up to dbb's accepted answer: Rather than adding the immediate cell on the right to the selection, why not select a cell way off the working range (i.e. a dummy cell that you know the user will never need). In the following code cell ZZ1 is the dummy cell
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.EnableEvents = False
Union(Target, Me.Range("ZZ1")).Select
Application.EnableEvents = True
' Respond to click/selection-change here
End Sub