I have been running with this problem for a while, I leave the sample file code, written below, the thing is that when I run it on the computer from where I work at, in the Sub CommandButton1_Click() when it starts to run the 3 lines after the commented line with i=1, each line starts the ListBox1_Click() and resets the textbox values making a mess, how I overcome this?, by using a conditional so it doesn't overwrite anything when working in other functions.
I want to know if anyone had before this problem, and know how to fix it. Running it from my personal computer is not an option, but the if conditional makes the thing, however I don't think is the optimal way to solve the problem.
Private Sub CommandButton1_Click()
Dim Ultima_Fila As Integer
Ultima_Fila = Sheets("Sheet1").Range("E2") + 1
Sheets("Sheet1").Range("A" & Ultima_Fila).EntireRow.Insert
Sheets("Sheet1").Range("A" & Ultima_Fila) = Sheets("Sheet1").Range("E2")
'i=1 'Required i to be 1 in order to avoid the code to jump and read the textbox from the ListBox1_Click
Sheets("Sheet1").Range("B" & Ultima_Fila) = TextBox1.Value
Sheets("Sheet1").Range("C" & Ultima_Fila) = TextBox2.Value
Sheets("Sheet1").Range("D" & Ultima_Fila) = TextBox3.Value
'i=0 Restarts i value
End Sub
Private Sub ListBox1_Click()
'If i = 0 Then 'When the sub is initialized directly from the listbox click it stores the values displayed on the textboxes
TextBox1 = ListBox1.List(ListBox1.ListIndex, 1)
TextBox2 = ListBox1.List(ListBox1.ListIndex, 2)
TextBox3 = ListBox1.List(ListBox1.ListIndex, 3)
'End If 'This conditional jumps the following instruction when it is reached from the CommandButton1_Click
End Sub
Private Sub UserForm_Activate()
With ListBox1
.ColumnCount = 4
.RowSource = "Table1"
.ColumnHeads = True
End With
End Sub
Please don't be disheartened by the changes I made to your code. In essence it remains the same. I just wanted to be sure that what I tell you is 100% correct:-
The ListBox1_Click event is triggered by a change to ListBox1.ListIndex. My code below contains such a change and disables the event it causes in the way you already found. The explanation for the procedure being fired on another computer is that the ListIndex is changed. Perhaps the code being run there is different and perhaps it's a Mac or Google sheets, in short, a non-Microsoft engine interpreting the same code.
However, you may still like my code because it has some features that yours didn't have. Please try it. It should run on both systems.
Option Explicit
Private DisableListBoxEvents As Boolean
Private Sub UserForm_Activate()
' 263
With ListBox1
.ColumnCount = 4
.RowSource = "Table1"
.ColumnHeads = True
End With
End Sub
Private Sub CommandButton1_Click()
' 263
Dim Ultima_Fila As Integer
Dim Tbx As Control
Dim i As Integer
With Worksheets("Sheet1")
Ultima_Fila = .Range("E2").Value + 1
With .ListObjects(1)
' inserts a table row before table row Ultima_Fila
' Omit the number to append a row.
' To convert sheet row to table row enable this line:-
' Ultima_Fila = Ultima_Fila - .Range.Row - 1
With .ListRows.Add(Ultima_Fila).Range
For i = 1 To 3
Set Tbx = Me.Controls("TextBox" & i)
.Cells(i + 1).Value = Tbx.Value
Tbx.Value = ""
Next i
End With
End With
End With
With ListBox1
DisableListBoxEvents = True
.RowSource = "Table1"
.ListIndex = -1
DisableListBoxEvents = False
End With
End Sub
Private Sub ListBox1_Click()
' 263
Dim i As Integer
If DisableListBoxEvents Then Exit Sub
With ListBox1
For i = 1 To 3
Me.Controls("TextBox" & i).Value = .List(.ListIndex, i)
Next i
End With
End Sub
Related
Why doesn't this code open the form with the items selected already? I set the selection flag to true with this code.
Private Sub UserForm_Initialize()
Dim i, InStrRes, k
With ActiveCell
If .Value <> "" Then
For i = 0 To Me.lstDV.ListCount - 1
InStrRes = InStr(1, ActiveCell.Value, Me.lstDV.List(i))
If InStrRes <> 0 And InStrRes <> Null Then
Me.lstDV.Selected(i) = True
End If
Next i
End If
End With
End Sub
Private Sub UserForm_Initialize()
Dim i, InStrRes, k
With ActiveCell
If .Value <> "" Then
For i = 0 To Me.lstDV.ListCount - 1
Me.lstDV.Selected(i) = False '<~~ add this code. Should be set to false in advance.
InStrRes = InStr(1, ActiveCell.Value, Me.lstDV.List(i))
'If InStrRes <> 0 And InStrRes <> Null Then
If InStrRes Then
Me.lstDV.Selected(i) = True
End If
Next i
End If
End With
End Sub
The presumption must be that the listbox hasn't been loaded at the time it is initialized. I tried the Activate event with similar lack of success. Please try this code instead.
Sub ShowMyForm()
Dim MyForm As New TryForm
Dim CellVal As Variant
CellVal = ActiveCell.Value
With MyForm
If Len(CellVal) Then
On Error Resume Next
.lstDV.ListIndex = Application.Match(CellVal, Range(.lstDV.RowSource), 0) - 1
End If
.Show
' code continues here when the form is closed
End With
Unload MyForm
End Sub
This code must be in a standard code module, not the form's code sheet. It calls the form TryForm (replace with the name you gave to your form), and modifies it before showing. Note that you still have full access to the form to take out values you may want after it is hidden. Just avoid Unload Me anywhere in the form's code because the unloading is done by the above code after you have taken everything you wanted.
Note that the form's Initialize event is triggered by the New key word in Dim MyForm As New TryForm, long before you gain access. The Activate event follows with some delay. Please make sure that the procedures you may run with these events don't interfere with what the above code does.
A form I am working with has 10 checkboxes, with values 1 through 10, used to answer a multiple choice question.
Multiple values are technically possible (clicking on multiple boxes), but they are not allowed (while filling, only one value should be given). I cannot modify this form so I have to work with this setup.
I need to extract the given choice and paste it into a different worksheet.
Using this question I can extract the value of every single checkbox and develop a IF Loop.
If ExtractionSheet.Shapes("Check Box 1").OLEFormat.Object.Value = 1 Then
Database.Cells(5, 9).Value = 1
ElseIf ExtractionSheet.Shapes("Check Box 2").OLEFormat.Object.Value = 1 Then
Database.Cells(5, 9).Value = 2
ElseIf ExtractionSheet.Shapes("Check Box 3").OLEFormat.Object.Value = 1 Then
Database.Cells(5, 9).Value = 3
...
However, this does not look very efficient (I have 3 sets of 1-10 checkboxes per form and 100+ forms). Given the setup, I cannot figure out a better way to do it.
How can I improve the extraction without using an IF loop?
EDIT A better description of the form, following comments
This is a simple excel worksheet, in which 3 groups of 10 check box elements were pasted.
Each form/worksheet relates to a single item. During the assessment, for each item we will assign a value between 1 and 10 to Property 1 (first 10 check boxes), a value between 1 and 10 to Property 2 (second 10 check boxes) and a value between 1 and 10 to Property 3 (third 10 check boxes).
I will do the filling (physically clicking the box) while in front of the client who is giving me data to fill it. The possibility of clicking multiple boxes naturally exists; I do not think it will be critical because many people will be looking at the screen while I do it, but I can always add a check later on.
Updated after comments:
I have used the following naming convention for the checkboxes (Using just e.g. A1 is a cell reference and could cause problems)
ChkBox_A1
Where the first part denotes that it is a checkbox (ChkBox), second the group A and third the position 1. With this naming convention and how the code is currently written you will be able to have a maximum of 26 groups (i.e. one for every letter of the alphabet)
I use the immediate window for the results which can be accessed in the VBA editor by going to View->Immediate Window or Ctrl+G
This code will handle single select per group. i.e. If a checkbox is selected in the group it will un-select all other ones
For a worksheet
This code goes in the worksheet object
Replace all of the click statements (e.g. ChkBox_A1_Click() with reference to your own. This can be easily done by calling the GenerateChkBoxClickStmt sub and copying and pasting the output in the immediate window into your code (replacing my ones)
Option Explicit
Dim ChkBoxChange As Boolean
Private Sub ChkBox_A1_Click()
If ChkBoxChange = False Then UnselectPreviousChkBox Me.ChkBox_A1
End Sub
Private Sub ChkBox_A2_Click()
If ChkBoxChange = False Then UnselectPreviousChkBox Me.ChkBox_A2
End Sub
Private Sub ChkBox_B1_Click()
If ChkBoxChange = False Then UnselectPreviousChkBox Me.ChkBox_B1
End Sub
Private Sub UnselectPreviousChkBox(selected As Object)
Dim ChkBox As OLEObject
ChkBoxChange = True
For Each ChkBox In Me.OLEObjects
If ChkBox.progID = "Forms.CheckBox.1" Then
If ChkBox.Name <> selected.Name And Mid(ChkBox.Name, 8, 1) = Mid(selected.Name, 8, 1) Then
ChkBox.Object.Value = False
End If
End If
Next ChkBox
ChkBoxChange = False
End Sub
Private Sub GenerateChkBoxClickStmt()
Dim ChkBox As OLEObject
' Copy and paste output to immediate window into here
For Each ChkBox In Me.OLEObjects
If ChkBox.progID = "Forms.CheckBox.1" Then
Debug.Print "Private Sub " & ChkBox.Name & "_Click()"
Debug.Print vbTab & "If ChkBoxChange = False Then UnselectPreviousChkBox Me." & ChkBox.Name
Debug.Print "End Sub"
End If
Next ChkBox
End Sub
Producing the following:
This code goes into a Module
Option Explicit
Private Function GetChkBoxValues(ChkBoxGroup As Variant) As Long
Dim ChkBox As OLEObject
' Update with your sheet reference
For Each ChkBox In ActiveSheet.OLEObjects
If ChkBox.progID = "Forms.CheckBox.1" Then
If ChkBox.Object.Value = True And Mid(ChkBox.Name, 8, 1) = ChkBoxGroup Then
GetChkBoxValues = Right(ChkBox.Name, Len(ChkBox.Name) - (Len("ChkBox_") + 1))
Exit For
End If
End If
Next ChkBox
End Function
Public Sub GetSelectedChkBoxes()
Dim ChkBoxGroups() As Variant
Dim Grp As Variant
ChkBoxGroups = Array("A", "B", "C")
For Each Grp In ChkBoxGroups
Debug.Print "Group " & Grp, GetChkBoxValues(Grp)
Next Grp
End Sub
By running the GetSelectedChkBoxes the code will output to the immediate window:
For a UserForm
Similarly the statements for the click events can be generated by uncommenting the line in the Userform_Initalize sub
Option Explicit
Dim ChkBoxChange As Boolean
Private Function GetChkBoxValues(Group As Variant) As Long
Dim ChkBox As Control
For Each ChkBox In Me.Controls
If TypeName(ChkBox) = "CheckBox" Then
If ChkBox.Object.Value = True And Mid(ChkBox.Name, 8, 1) = Group Then
GetChkBoxValues = Right(ChkBox.Name, Len(ChkBox.Name) - (Len("ChkBox_") + 1))
Exit For
End If
End If
Next ChkBox
End Function
Private Sub UnselectPreviousChkBox(selected As Control)
Dim ChkBox As Control
ChkBoxChange = True
For Each ChkBox In Me.Controls
If TypeName(ChkBox) = "CheckBox" Then
If ChkBox.Name <> selected.Name And Mid(ChkBox.Name, 8, 1) = Mid(selected.Name, 8, 1) Then
ChkBox.Value = False
End If
End If
Next ChkBox
ChkBoxChange = False
End Sub
Private Sub ChkBox_A1_Click()
If ChkBoxChange = False Then UnselectPreviousChkBox Me.ChkBox_A1
End Sub
Private Sub ChkBox_A2_Click()
If ChkBoxChange = False Then UnselectPreviousChkBox Me.ChkBox_A2
End Sub
Private Sub ChkBox_B1_Click()
If ChkBoxChange = False Then UnselectPreviousChkBox Me.ChkBox_B1
End Sub
Private Sub userform_initialize()
' Comment out once written
' GenerateChkBoxClickStmt
End Sub
Private Sub UserForm_Terminate()
Dim ChkBoxGroups() As Variant
Dim Grp As Variant
ChkBoxGroups = Array("A", "B", "C")
For Each Grp In ChkBoxGroups
Debug.Print "Group " & Grp, GetChkBoxValues(Grp)
Next Grp
End Sub
Private Sub GenerateChkBoxClickStmt()
Dim ChkBox As Control
' Copy and paste output to immediate window into here
For Each ChkBox In Me.Controls
If TypeName(ChkBox) = "CheckBox" Then
Debug.Print "Private Sub " & ChkBox.Name & "_Click()"
Debug.Print vbTab & "If ChkBoxChange = False Then UnselectPreviousChkBox Me." & ChkBox.Name
Debug.Print "End Sub"
End If
Next ChkBox
End Sub
Producing:
and outputting the following on exit:
I create listbox in excel with VBA userform. Its values are obtained from the Sheet in Excel.
How can I delete the values in the sheet "database" while deleting the box list item?
please help me.
Private Sub UserForm_Initialize()
Dim ws As Worksheet
Dim rng As Range
Dim MyArray
Set ws = Sheets("Database")
Set rng = ws.Range("K2:L" & ws.Range("K" & ws.Rows.Count).End(xlUp).Row)
With Me.ListBox1
.Clear
.ColumnHeads = False
.ColumnCount = rng.Columns.Count
MyArray = rng
.List = MyArray
.ColumnWidths = "90;90"
.TopIndex = 0
End With
End Sub
Private Sub CommandButton2_Click()
For lItem = Me.ListBox1.ListCount - 1 To 0 Step -1
If ListBox1.Selected(lItem) Then
ListBox1.RemoveItem lItem
If Me.ListBox1.MultiSelect = fmMultiSelectSingle Then
Exit For
End If
End If
Next
End Sub
How do I delete the values in the sheet "database"?
As you assign database items via the array method (not using ControlSource), you want to know how to synchronize listbox items with your data base after manual deletion.
Approach A) - Write the entire Listbox1.List
If you want a mirror image of the listbox items after the For- Next loop, you could simply write these items back to a given range (of course you should clear 'surplus rows', too) via the following one liner
rng.Resize(Me.ListBox1.ListCount, 2) = Me.ListBox1.List
Instead of reduplicating the data range declaration in CommandButton2_Click, I'd suggest to declare it ONCE in the declaration head of the Userform code module (and omit it in Userform_Initialize):
Thus the complete code would be as follows:
â–ºAdditional notes due to comment
Insert these two code lines on top of your UserForm code module (and before any procedures).
Option Explicit is strictly recommended in any code to force the declaration of variable types (but you can't use this statement within a Sub as you did). The declaration Dim rng As Range OUTSIDE the other procedures (i.e. on top) allows that any procedure within this code module knows the rng variable.
Option Explicit ' declaration head of the UserForm module
Dim rng as Range ' ONE database declaration only!
' << OUTSIDE of following procedures
' << Start of regular procedures
Private Sub UserForm_Initialize()
Dim ws As Worksheet
' Dim rng As Range ' << not needed here, see top declaration
Dim MyArray
Set ws = Sheets("Database")
Set rng = ws.Range("K2:L" & ws.Range("K" & ws.Rows.Count).End(xlUp).Row)
With Me.ListBox1
.Clear
.ColumnHeads = False
.ColumnCount = rng.Columns.Count
MyArray = rng
.List = MyArray
.ColumnWidths = "90;90"
.TopIndex = 0
End With
End Sub
Private Sub CommandButton3_Click()
Dim lItem&
For lItem = Me.ListBox1.ListCount - 1 To 0 Step -1
If ListBox1.Selected(lItem) Then
ListBox1.RemoveItem lItem ' remove item from listbox
If Me.ListBox1.MultiSelect = fmMultiSelectSingle Then
Exit For
End If
End If
Next
rng.Offset(Me.ListBox1.ListCount, 0).Resize(rng.Rows.Count, 2) = "" ' clear rows
rng.Resize(Me.ListBox1.ListCount, 2) = Me.ListBox1.List ' write list back
End Sub
Note that no rows are deleted physically, the resulting listbox items in the two target columns K:L are shifted up only (approach B allows to delete entire rows as well).
Approach B) - Help procedure within main loop
Using the same data range declaration in the declaration head of the UserForm â–º as shown above (i.e. OUTSIDE the procedures as Subs or Functions), you could use a help procedure DelData allowing to distinguish between two principal cases:
[1] Shift up deleted cells in your database
[2] Delete the entire row
Event procedure CommandButton2_Click
Private Sub CommandButton2_Click()
' Purpose: delete items both from database and listbox
Dim lItem&
For lItem = Me.ListBox1.ListCount - 1 To 0 Step -1
If ListBox1.Selected(lItem) Then
DelData lItem, True ' [1] True=delete items and shift up
'DelData lItem, False ' [2] False=delete entire row
ListBox1.RemoveItem lItem ' remove item from listbox
If Me.ListBox1.MultiSelect = fmMultiSelectSingle Then
Exit For ' do it once in single select case
End If
End If
Next
End Sub
Help procedure DelData
Sub DelData(ByVal indx&, Optional ByVal bShiftUp As Boolean = True)
' Purpose: delete indicated row items in database
' Note: data set in OP includes header
If bShiftUp Then ' [1] bShiftUp = True: delete row items and shift up
rng.Offset(indx).Resize(1, rng.Columns.Count).Delete xlShiftUp
Else ' [2] bShiftUp = False: delete entire row of indicated items
rng.Offset(indx).Resize(1, rng.Columns.Count).EntireRow.Delete
End If
End Sub
Side note
It's recommended to fully qualify range references to avoid getting data from wrong workbooks, so I'd suggest the following statement in your UserForm_Initialize procedure:
Set ws = ThisWorkbook.Worksheets("Database")
Enjoy it :-)
Before removing the item from the ListBox you need to use the located value at the ListBox.Selected to find and remove the item from your "database".
Something like this:
Private Sub CommandButton2_Click()
For lItem = Me.ListBox1.ListCount - 1 To 0 Step -1
If ListBox1.Selected(lItem) Then
DeleteItemFromDatabase ListBox1.Selected(lItem).Value
ListBox1.RemoveItem lItem
If Me.ListBox1.MultiSelect = fmMultiSelectSingle Then
Exit For
End If
End If
Next
End Sub
Then your Sub DeleteItemFromDatabase(ByVal itemToDelete As [type]) would find itemToDelete in your "database" and remove it.
As an additional note, you may want to consider using Access as your database since it's actually designed to be one. I realize this isn't always possible, but thought I'd throw it out there as a thought for you.
Is it possible to use a listbox to change the order of rows in a worksheet? I have searched the Web for about an hour without ressults. I am using the following code for the task:
Private Sub UserForm_Initialize()
ListBox1.RowSource = "Sheet1!A1:A15"
End Sub
Option Explicit
Const UP As Integer = -1
Const DOWN As Integer = 1
Private Sub SpinButton1_SpinUp()
If Me.ListBox1.ListCount > 1 Then
Call MoveListItem(UP, Me.ListBox1)
End If
End Sub
Private Sub SpinButton1_SpinDown()
If Me.ListBox1.ListCount > 1 Then
Call MoveListItem(DOWN, Me.ListBox1)
End If
End Sub
Private Sub MoveListItem(ByVal intDirection As Integer, _
ByRef ListBox1 As ListBox)
Dim intNewPosition As Integer
Dim strTemp As String
intNewPosition = ListBox1.ListIndex + intDirection
strTemp = ListBox1.List(intNewPosition)
If intNewPosition > -1 _
And intNewPosition < ListBox1.ListCount Then
'Swap listbox items
ListBox1.List(intNewPosition) = ListBox1.Value
ListBox1.List(intNewPosition - intDirection) = strTemp
ListBox1.Selected(intNewPosition) = True
End If
End Sub
Hope somone can give me a hint!
UPDATE!!
What i want is, if I for example have these values in a column in my worksheet:
week1
week2
week3
week4
Then I would like to re-arrenge the order of these values with the SpinButton in the ListBox;
week2
week4
week1
week3
You most certainly can do that!
Here is a quick code that does this in general, I will leave it to you to place this where it is needed:
For i = 0 To ListBox1.ListCount - 1
ActiveWorkbook.Sheets("Sheet1").Range("A" & CStr(i + 1)).Value = ListBox1.List(i)
Next i
You'll probably need to change what is inside the for loop to better reflect your own code. For writing to a specific range just add whatever starting row number you want!
basically I have a userform which I would like to use to enter 2 data into another macro which I already have. The userform is as below:
Basically, I would like the OK button to be clicked and the data in the two boxes will be entered into another macro that I have. It would also be great if the OK button can help in a sense that it will prompt a warning if one of the boxes is not filled up.
So far, I do not have much of a code for this..
Private Sub UserForm_Click()
TextBox1.SetFocus
Sub Enterval()
End Sub
Private Sub TextBox1_Change()
Dim ID As String
ID = UserForm3.TextBox1.Value
End Sub
Private Sub TextBox2_Change()
Dim ID2 As String
ID2 = UserForm3.TextBox2.Value
End Sub
Private Sub OKay_Click()
Enterval
End Sub
Would appreciate any tips and help. Thanks!
My other macro
Private Sub CommandButton1_Click()
Dim Name As String
Dim Problem As Integer
Dim Source As Worksheet, Target As Worksheet
Dim ItsAMatch As Boolean
Dim i As Integer
Set Source = ThisWorkbook.Worksheets("Sheet1")
Set Target = ThisWorkbook.Worksheets("Sheet2")
Name = Source.Range("A3")
Problem = Source.Range("I13")
Do Until IsEmpty(Target.Cells(4 + i, 6)) ' This will loop down through non empty cells from row 5 of column 2
If Target.Cells(4 + i, 6) = Name Then
ItsAMatch = True
Target.Cells(4 + i, 7) = Problem ' This will overwrite your "Problem" value if the name was already in the column
Exit Do
End If
i = i + 1
Loop
' This will write new records if the name hasn't been already found
If ItsAMatch = False Then
Target.Cells(3, 6).End(xlDown).Offset(1, 0) = Name
Target.Cells(4, 6).End(xlDown).Offset(0, 1) = Problem
End If
Set Source = Nothing
Set Target = Nothing
End Sub
Thats the macro i have. As u said, i change the
othermacro
to CommandButton1_Click()
but it doesn't work
Quoting geoB except for one thing: when you .Show your UserForm from a main Sub, you can also .Hide it at the end and the macro that called it will continue its procedures.
Sub Okay_Click()
Dim sID1 As String, sID2 As String
' A little variation
If Me.TextBox1 = "" Or Me.TextBox2 = "" Then
MsgBox "Please fill all the input fields"
Exit Sub
End If
Me.Hide
End Sub
To address your TextBox, you can write in your main Sub UserForm3.TextBox1 for example
There is no need for an Enterval function. Instead, assume the user can read and follow instructions, then test whether that indeed is the case. Note that in your code ID and ID2 will never be used because they exist only within the scope of the subroutines in which they are declared and receive values.
To get started:
Sub Okay_Click()
Dim sID1 As String, sID2 As String
sID1 = UserForm3.TextBox1.Value
sID2 = UserForm3.TextBox2.Value
If Len(sID1 & vbNullString) = 0 Then
MsgBox "Box A is empty"
Exit Sub
End If
If Len(sID2 & vbNullString) = 0 Then
MsgBox "Box B is empty"
Exit Sub
End If
'Now do something with sID1, sID2
otherMacro(sID1, sID2)
End Sub
For your other macro, declare it like this:
Sub otherMacro(ID1, ID2)
...
End Sub
Also, the SetFocus method should occur in the form open event.