I'm trying to use buttons as the only input into a worksheet database.
Only buttons 1,2 & 3 will add data to my worksheet via the code below
'Name for Button 1 = B_1
Private Sub B_1_Click()
Dim iRow As Long
Dim ws As Worksheet
Set ws = Worksheets("Database")
'find first empty row in database
iRow = ws.Range("A:D").Find(What:="*", SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues).Row + 1
'copy the data to the database
ws.Cells(iRow, 1).Value = Date
ws.Cells(iRow, 2).Value = Time
'This has to be bacon, tomato or cheese
ws.Cells(iRow, 3).Value = ???????
ws.Cells(iRow, 4).Value = 1
MsgBox "Data added", vbOKOnly + vbInformation, "Data Added"
Application.DisplayAlerts = False
ActiveWorkbook.Save
End Sub
IRow, 3 is where I have the problem. I want to store the info for bacon, tomato or cheese first and then when I press 2. The data in the worksheet should look something like this.
Date 12/01/2015
Time 14:20
Food BACON
Quantity 2
I've used a textbox previously as the input.
Can anybody please assist me with this code.
Thanks
If you want to work with only one food at a time, you should use Option Buttons instead of Commandbuttons. Option Buttons are mutually exclusive - if you select Cheese, Bacon is automatically deselected.
If you want to process multiple foods at once, you should use Toggle Buttons. Toggle Buttons are either up or down, but they're not mutually exclusive. They have the added benefit of looking like Command Buttons.
If you want the mutual exclusivity of Option Buttons, but you must have something that looks and works like a Commandbutton, then you have a couple of options, none of them optimal:
You could use a Tab Strip and hide everything except the tabs.
You could set a module-level variable that remembers the last button pushed. You would probably want to add code that would change the color of the button so the user knows which they pressed. And if you did that, you probably don't need the module-level variable, you could just read which button had the color.
You can make Toggle Buttons mutually exclusive through code. I'd personally go with this one so you get the visual effect of the button being pressed.
Here's some code to get you started
Private mbEventsDisabled As Boolean
Public Property Let EventsDisabled(ByVal bEventsDisabled As Boolean): mbEventsDisabled = bEventsDisabled: End Property
Public Property Get EventsDisabled() As Boolean: EventsDisabled = mbEventsDisabled: End Property
Private Sub tgBacon_Click()
If Not Me.EventsDisabled Then ClearToggles Me.tgBacon
End Sub
Private Sub tgCheese_Click()
If Not Me.EventsDisabled Then ClearToggles Me.tgCheese
End Sub
Private Sub tgTomato_Click()
If Not Me.EventsDisabled Then ClearToggles Me.tgTomato
End Sub
Public Sub ClearToggles(tg As ToggleButton)
Me.EventsDisabled = True
Me.tgBacon.Value = Me.tgBacon.Name = tg.Name
Me.tgCheese.Value = Me.tgCheese.Name = tg.Name
Me.tgTomato.Value = Me.tgTomato.Name = tg.Name
Me.EventsDisabled = False
End Sub
If you had more than three toggles, you'd want to refactor the ClearToggles sub to loop instead of calling them out individually.
Public Sub ClearToggles(tg As ToggleButton)
Dim ctl As Control
Me.EventsDisabled = True
For Each ctl In Me.Controls
If TypeName(ctl) = "ToggleButton" And ctl.Name <> tg.Name Then
ctl.Value = False
End If
Next ctl
Me.EventsDisabled = False
End Sub
Related
I have a table with the following values:
Now, I would like to call the Userform in column H based on the value in column G, but I can't work out how to call the Userform based on the cell value. The error occurs in line
form.Name = wsControls.Cells(loop2, 8).Value
Here is my code:
Sub Check_Scenarios()
Dim wsAbsatz As Worksheet
Dim wsControls As Worksheet
Dim wsData As Worksheet
Dim loop1 As Long
Dim loop2 As Long
Dim lngKW As Long
Dim form As UserForm
Set wsAbsatz = ThisWorkbook.Worksheets("Production")
Set wsData = ThisWorkbook.Worksheets("Data")
Set wsControls = ThisWorkbook.Worksheets("Controls")
lngKW = wsControls.Cells(1, 2).Value + 2
If lngKW = 3 Then
Exit Sub
End If
For loop1 = wsControls.Cells(10, 2).Value To wsControls.Cells(19, 2).Value Step 10
If wsData.Cells(loop1 + 3, lngKW).Value <> "" Then
MsgBox (wsData.Cells(loop1 + 3, lngKW).Value)
For loop2 = 2 To 16
If wsData.Cells(loop1 + 3, lngKW).Value = wsControls.Cells(loop2, 7).Value Then
form.Name = wsControls.Cells(loop2, 8).Value 'error occurs here
form.Show
End If
Next loop2
End If
Next loop1
End Sub
Project:
Many thanks for your help!
You are trying to assign a Name to a blueprint. These are two errors.
You have to initialize your blueprint as something. Like this:
Dim form As New UserForm
Then, most probably your UserForm does not have a property called Name. It is called Caption. Thus it is like this:
Sub TestMe()
Dim uf As New UserForm1 'judging from your screenshot
uf.Caption = "Testing"
uf.Show
End Sub
Disclaimer:
There is a better way to work with UserForms, not abusing the blueprint, although almost every VBA book shows this UserForm.Show method (in fact every single one I have read so far).
If you have the time and the OOP knowledge implement the ideas from here - or from my interpretation of the ideas. There was also a documentation article about it in StackOverflow, but it was deleted with the whole documentation idea.
You don't "call" a userform. You instantiate it, and then you Show it.
UserForm is the "base class" from which all userforms are derived. See there is inheritance in VBA, only not with custom classes.
So you have a UserForm2 class, a UserForm3 class, a UserForm4 class, and so on.
These classes need to be instantiated before they can be used.
Dim theForm As UserForm
Set theForm = New UserForm2
theForm.Show
Set theForm = New UserForm3
theForm.Show
'...
So what you need is a way to parameterize this Set theForm = New ????? part.
And you can't. Because whatever you're going to do, the contents of a cell is going to be a string, and there's no way you can get an instance of a UserForm3 out of a String that says "UserForm3".
Make a factory function that does the translation:
Public Function CreateForm(ByVal formName As String) As UserForm
Select Case formName
Case "UserForm1"
Set CreateForm = New UserForm1
Case "UserForm2"
Set CreateForm = New UserForm2
Case "UserForm3"
Set CreateForm = New UserForm3
'...
End Select
End Function
And then call that function to get your form object:
Set form = CreateForm(wsControls.Cells(loop2, 8).Value)
If Not form Is Nothing Then form.Show
I am working on simple excel application for multiple users who will enter the data during different stages of the process. Unfortunately I met the problems with storage the data from multiple userforms in one row of the table.
I will try to explain what is the whole thing about as clear as I can.
For example purposes I called the application "Movie Time Control". Let's imagine that it is a tool for controlling the movies watched with focus on:
when the movie started,
if there were some breaks during displaying (and why)
when the movie has been restarted (how long the break took, and how many breaks there were and what actions were taken to continue),
in case when the movie was aborted, when, and why?
when the movie ended.
The MENU of application segment will look as on the screenshot below:
For each button different userform is assigned. The data entered in each form should be stored in one row in specific sheet.
Functionality of the userforms:
MOVIE START: Creating the entry in the table with movie title, date and time when its started.
MOVIE BREAK: Based on the movie title previously defined, filling out the date and time, reason of break (from the drop-down list or text box if not standard). The function can be used up to three times (three breaks).
MOVIE RESTART: If the break occurred, filling out the information about the date, time when movie was restarted, and what action has been taken in order to deal with the previously defined reason of break. For each break (possible three) function can be used.
MOVIE ABORT When (date and time) movie has been aborted (without intention to continue).
MOVIE FINISHED When (date and time) movie ended.
Where the problems occurred (questions):
When the data from the first row are entered, the entry with the specific title is created in the table separate sheet. Based on this entry, Title Combobox in all other userforms should list the titles which were started but not finished or aborted - just to quickly choose the "open title" and fill out other information related to the title. How to create a macro to list the "open cases" in the combobox?
I couldn't find out how to transfer the rest of the data to the same row of the table but different columns from all the forms after creating the entry with the specific movie title. Important thing is that the data can be added only to row with corresponding title (chosen from combobox from first question). Could you help me with the macro?
Macros I created until now (I am very beginner with VBA, thanks for understanding):
MOVIE START: For creating the entry with movie title.
Private Sub movie_start_save_Click()
If MsgBox("ARE YOU SURE?", vbYesNo, "Please confirm") = vbYes Then
Dim emptyRow As Long
'Make Sheet2 active
Sheet2.Activate
'Determine emptyRow
emptyRow = WorksheetFunction.CountA(Range("A:A")) + 1
'Transfer information
Cells(emptyRow, 1).Value = Movie_Title_Box.Value
Cells(emptyRow, 2).Value = Start_Date_Box.Value
Cells(emptyRow, 3).Value = Start_Time_Box.Value
'Closing the form
Unload Me
'Back to MENU
Sheet1.Select
End If
End Sub
Private Sub movie_start_cancel_Click()
Unload Me
End Sub
MOVIE BREAK: For defining the time and reason (cannot transfer the data):
Private Sub UserForm_Initialize()
'Fill ReasonComboBox
With ReasonComboBox
.AddItem "Tea"
.AddItem "Coffee"
.AddItem "Popcorn"
.AddItem "Dinner"
.AddItem "Not standard"
End With
'Default text in the reason box
ReasonTextBox.ForeColor = &HC0C0C0 '<~~ Grey Color
ReasonTextBox.Text = "In case of 'not standard' reason leave your comment here"
movie_break_cancel.SetFocus '<~~ This is required so that the focus moves from TB
End Sub
'Default text in the reason box - disapearing when you want to edit
Private Sub ReasonTextBox_Enter()
With ReasonTextBox
If .Text = "In case of 'not standard' reason leave your comment here" Then
.ForeColor = &H80000008 '<~ Black Color
.Text = ""
End If
End With
End Sub
'Default text in the reason box - somehow disappearing for good, but ok
Private Sub ReasonTextBox_AfterUpdate()
With ReasonTextBox
If .Text = "" Then
.ForeColor = &H80000008
.Text = ""
End If
End With
End Sub
'Cancel Button
Private Sub movie_break_cancel_Click()
Unload Me
End Sub
The rest is actually similar with a few differences.
Link to download the excel file:
https://drive.google.com/file/d/0BxFSL2h-9qflQjRzNTQ2ZlhJNjA/view?usp=sharing
Hopefully I expressed myself clear enough to understand this.
Greetings!
In my example below, I show how to configure a ComboBox to hold multiple columns of data and to later retrieve the values. This will allow you to store the Row number along with the movie data in the ComboBox.
'Filtering for not finished jobs for combobox
Private Sub UserForm_Initialize()
Dim ws As Worksheet
Dim x As Long
With Me.Movie_Title_ComboBox
.ColumnCount = 4
.ColumnWidths = "0 pt;250 pt;90 pt; 90 pt;"
'.ListWidth = 500
.TextColumn = 2
.BoundColumn = 1
End With
Set ws = Sheet2
With ws
For x = 2 To .Range("A" & .Rows.Count).End(xlUp).Row
If .Cells(x, 4).Value = "" Then
AddItems Me.Movie_Title_ComboBox, x, .Cells(x, 1).Value, Format(.Cells(x, 3).Value, "MM/DD/YYYY"), Format(.Cells(x, 3).Value, "HH:MM")
End If
Next
End With
End Sub
Private Sub Movie_Title_ComboBox_Change()
With Me.Movie_Title_ComboBox
If .ListIndex > -1 Then
Finish_Date_Box.Value = .List(.ListIndex, 2)
End If
End With
End Sub
Private Sub movie_finished_save_Click()
With Sheet2
.Cells(Me.Movie_Title_ComboBox.Value, 4) = Me.Finish_Date_Box.Value
.Cells(Me.Movie_Title_ComboBox.Value, 5) = Me.Start_Time_Box.Value
End With
End Sub
Add this function to a Public Code Module so that it will be available to all your userforms.
Sub AddItems(oComboBox As MSForms.ComboBox, ParamArray Items() As Variant)
Dim x As Long
With oComboBox
.AddItem Items(0)
For x = 1 To UBound(Items)
.List(.ListCount - 1, x) = Items(x)
Next
End With
End Sub
I'm going crazy trying to find a way for code to run when I click on ANY of the checkboxes on my sheet. I've seen multiple articles talking about making a class module, but I can't seem to get it to work.
I have code that will populate column B to match column C. Whatever I manually type into C10 will populate into B10, even if C10 is a formula: =D9. So, I can type TRUE into D10 and the formula in C10 will result in: TRUE and then the code populates B10 to say: TRUE. Awesome... the trick is to have a checkbox linked to D10. When I click the checkbox, D10 says TRUE and the formula in C10 says TRUE, but that is as far as it goes. The VBA code does not recognize the checkbox click. If I then click on the sheet (selection change), then the code will run, so I know I need a different event.
It is easy enough to change the event to "Checkbox1_Click()", but I want it to work for ANY checkbox I click. I'm not having ANY luck after days of searching and trying different things.
here is the code I'm running so far
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
For i = 3 To 11
Range("B" & i).Value = Range("c" & i)
Next i
End Sub
Any help would be appreciated.
this works
' this goes into sheet code
Private Sub Worksheet_Activate()
activateCheckBoxes
End Sub
.
' put all this code in class a module and name the class module "ChkClass"
Option Explicit
Public WithEvents ChkBoxGroup As MSForms.CheckBox
Private Sub ChkBoxGroup_Change()
Debug.Print "ChkBoxGroup_Change"
End Sub
Private Sub ChkBoxGroup_Click()
Debug.Print "ChkBoxGroup_Click"; vbTab;
Debug.Print ChkBoxGroup.Caption; vbTab; ChkBoxGroup.Value
ChkBoxGroup.TopLeftCell.Offset(0, 2) = ChkBoxGroup.Value
End Sub
.
' this code goes into a module
Option Explicit
Dim CheckBoxes() As New ChkClass
Const numChkBoxes = 20
'
Sub doCheckBoxes()
makeCheckBoxes
activateCheckBoxes
End Sub
Sub makeCheckBoxes() ' creates a column of checkBoxes
Dim sht As Worksheet
Set sht = ActiveSheet
Dim i As Integer
For i = 1 To sht.Shapes.Count
' Debug.Print sht.Shapes(1).Properties
sht.Shapes(1).Delete
DoEvents
Next i
Dim xSize As Integer: xSize = 2 ' horizontal size (number of cells)
Dim ySize As Integer: ySize = 1 ' vertical size
Dim t As Range
Set t = sht.Range("b2").Resize(ySize, xSize)
For i = 1 To numChkBoxes
sht.Shapes.AddOLEObject ClassType:="Forms.CheckBox.1", Left:=t.Left, Top:=t.Top, Width:=t.Width - 2, Height:=t.Height
DoEvents
Set t = t.Offset(ySize)
Next i
End Sub
Sub activateCheckBoxes() ' assigns all checkBoxes on worksheet to ChkClass.ChkBoxGroup
Dim sht As Worksheet
Set sht = ActiveSheet
ReDim CheckBoxes(1 To 1)
Dim i As Integer
For i = 1 To sht.Shapes.Count
ReDim Preserve CheckBoxes(1 To i)
Set CheckBoxes(i).ChkBoxGroup = sht.Shapes(i).OLEFormat.Object.Object
Next i
End Sub
All you need is to let EVERY checkbox's _Click() event know that you want to run the Worksheet_SelectionChange event. To do so you need to add the following line into every _Click() sub:
Call Worksheet_SelectionChange(Range("a1"))
Please note that it is irrelevant what range is passed to the SelectionChange sub since you do not use the Target in your code.
I'm using VBA to code an application for an Excel file. Put simply, I need the names of my textboxes to change depending on where a certain variable is in an ArrayList.
I have one textbox to start, when someone pushes a button it should add a textbox after the first one, and do this as many times as one presses the button. So the first box should be named tbx1, the second should be tbx2, the third tbx3, and so on.
Now when they press a different button located next to any of the boxes, it deletes that box and button and all boxes after that one are named one lower to make up for it.
Any ideas how to do this? I'm only assuming ArrayList is the best tactic, please correct me if there is a better way.
Here's an example that you can hopefully modify to your needs. I have a userform named UClassList with one commandbutton, cmdAdd, and one textbox, tbxClass_1.
Private mEventButtons As Collection
Public Property Get ClassMax() As Long
ClassMax = 75
End Property
Private Sub cmdAdd_Click()
Dim i As Long
For i = 2 To Me.ClassMax
'find the first invisible control and make it visible
If Not Me.Controls("tbxClass_" & i).Visible Then
Me.Controls("tbxClass_" & i).Visible = True
Me.Controls("cmdClass_" & i).Visible = True
Exit For 'stop after one
End If
Next i
End Sub
Private Sub UserForm_Initialize()
Dim i As Long
Dim tbx As MSForms.TextBox
Dim cmd As MSForms.CommandButton
Dim clsEventClass As CEventClass
Set mEventButtons = New Collection
'Add as many textboxes and commandbuttons as you need
'or you can do this part at design time
For i = 2 To Me.ClassMax
Set tbx = Me.Controls.Add("Forms.TextBox.1", "tbxClass_" & i, False)
tbx.Top = Me.tbxClass_1.Top + ((i - 1) * 25) 'use the first textbox as the anchor
tbx.Left = Me.tbxClass_1.Left
tbx.Width = Me.tbxClass_1.Width
tbx.Height = Me.tbxClass_1.Height
'Create a delete commandbutton
Set cmd = Me.Controls.Add("Forms.CommandButton.1", "cmdClass_" & i, False)
cmd.Top = tbx.Top
cmd.Left = tbx.Left + tbx.Width + 10
cmd.Width = 20
cmd.Height = tbx.Height
cmd.Caption = "X"
'add delete commandbutton to the event class so they all share
'the same click event code
Set clsEventClass = New CEventClass
Set clsEventClass.cmdEvent = cmd
mEventButtons.Add clsEventClass
Next i
End Sub
I have a custom class named CEventClass.
Public WithEvents cmdEvent As MSForms.CommandButton
Private Sub cmdEvent_Click()
Dim i As Long
Dim lThisIndex As Long
Dim tbxThis As MSForms.TextBox
Dim tbxPrev As MSForms.TextBox
Dim uf As UClassList
Set uf = cmdEvent.Parent
'get the number that was clicked
lThisIndex = Val(Split(cmdEvent.Name, "_")(1))
'loop from the next textbox to the end
For i = lThisIndex + 1 To uf.ClassMax
Set tbxThis = uf.Controls("tbxClass_" & i)
Set tbxPrev = uf.Controls("tbxClass_" & i - 1)
'if it's not visible, clear and hide
'the previous textbox
If Not tbxThis.Visible Then
tbxPrev.Text = vbNullString
tbxPrev.Visible = False
uf.Controls("cmdClass_" & i - 1).Visible = False
Else
'if it's visible, copy it's text to the one above
tbxPrev.Text = tbxThis.Text
End If
Next i
End Sub
Instead of adding and deleting and keeping track of a bunch of textboxes, I create all 75 (or fewer) at launch (or design time). Then I just make then visible or hide them as needed.
You can see the workbook I did this on here http://dailydoseofexcel.com/excel/ControlEventClass.xlsm
Probably a silly question with a simple answer but I am a real novice when it comes to userforms.
I have "Frame 3" with 5 different option buttons (Dest1, Dest2, Dest3, Dest4, Dest5) After an option is selected, where is the caption value of the selected option stored? How can I access that with vba.
Thank you,
Josh
Here's just some example code you can use. Add your Option Buttons to groups, and then you can go from there. I used groups since you had multiple frames, and you can check based on group, and have multiple groups, and check which one's selected for each group.
Private Sub CommandButton1_Click()
Dim x As Control
' Loop through ALL the controls on the UserForm.
For Each x In Me.Controls
' Check to see if "Option" is in the Name of each control.
If InStr(x.Name, "Option") Then
' Check Group name.
If x.GroupName = "Grp1" Then
' Check the status of the OptionButton.
If x.Value = True Then
MsgBox x.Caption
Exit For
End If
End If
End If
Next
End Sub
You can also access the option buttons through the frame-ojbect that holds them (if you have other frames and controls you don't want to go through):
Option Explicit
Sub Test()
Dim oCtrl As Control
'***** Try only controls in Frame3
For Each oCtrl In Frame3.Controls
'***** Try only option buttons
If TypeName(oCtrl) = "OptionButton" Then
'***** Which one is checked?
If oCtrl.Value = True Then
'***** What's the caption?
Debug.Print "You have checked option " & oCtrl.Caption
Exit For
End If
End If
Next
End Sub
The Label Text associated with an Option Button is obtainable by using OptionButton1.Caption
If you are using a loop, just substitute the OptionButton1 with your variable for option buttons and it will pull through the one you need when conditions are met. eg:
For xitem = 1 To 5
xFrm = "OptionButton" & xitem
For Each fItem In Me.Controls
If fItem.Name Like xFrm Then
If fItem.Value Then
k = fitem.Caption
End If
End If
Next fItem
Next xitem
In my case, I wanted the caption of the toggle that was selected in an option group to be passed on to a subform filter. e.g. choosing toggle "black" filters subform to all cars where strColour = "black".
I ended up with this:
Private Sub OptionGroupName_Click()
Dim Caption As String
Caption = OptionGroupName.Controls.Item(OptionGroupName.Value - 1).Caption
Me.SubformName.Form.Filter = "[SubformField] = """ & Caption & """"
Me.SubformName.Form.FilterOn = True
End Sub
Not to dog pile on everyone else's options but I created a function that takes the radio group name and spits out the selected radios coresponding label caption. Was using it in Access not Excel.
Only works provided you name your controls similarly....
i.e. (lblRadioButton1 & optRadioButton1)
Function GetSelectedRadioButtonCaption(ByVal optionGroupName As OptionGroup) As String
Dim oCtrl As Control
Dim oCtrl2 As Control
Dim optionLabelName As String
Dim optionLabelObject As Label
Dim optionButtonObject As OptionButton
For Each oCtrl In optionGroupName.Controls
'***** Try only option buttons
If TypeOf oCtrl Is OptionButton Then
'***** Which one is checked?
Set optionButtonObject = oCtrl
If optionButtonObject.OptionValue = optionGroupName.Value Then
'***** What's the caption?
optionLabelName = Replace(oCtrl.Name, "opt", "lbl")
For Each oCtrl2 In optionGroupName.Controls
If oCtrl2.Name = optionLabelName Then
Set optionLabelObject = oCtrl2
GetSelectedRadioButtonCaption = optionLabelObject.caption
Exit For
End If
Next
End If
If GetSelectedRadioButtonCaption <> "" Then
Exit For
End If
End If
Next
Exit_GetSelectedRadioButtonCaption:
End Function