I am using a userform which is used for both submitting entries and editing them if needed. I am auto populating the form if a person enters a unique id (Material verification checklist number) with the help of TextBox After Update function
Then the user can change any parameters and press edit button to edit the changes. All these things work fine.
There is a requirement to maintain the edit history of all the changes in a different sheet. I am stuck there. This is how the sheet will look:
I am stuck with the C, D and E columns which should capture what parameters were edited, and their old and new values.
This is my code for the edit function right now:
Sub Edit()
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.AutomationSecurity = msoAutomationSecurityLow
Dim id As Range
Set id = Worksheets("Transport Assesment").Range("A:A").Find(what:=frm3.TextBox1.Value, LookIn:=xlValues)
Worksheets("Transport Assesment").Unprotect "Ramu#lt12"
id.Offset(, 1).Value = frm3.TextBox13.Value
id.Offset(, 2).Value = frm3.DTPicker2.Value
id.Offset(, 4).Value = frm3.ComboBox7.Value
id.Offset(, 3).Value = frm3.ComboBox8.Value
id.Offset(, 5).Value = frm3.ComboBox6.Value
id.Offset(, 25).Value = frm3.TextBox15.Value
id.Offset(, 6).Value = frm3.ComboBox1.Value
id.Offset(, 7).Value = frm3.TextBox2.Value
id.Offset(, 8).Value = frm3.ComboBox2.Value
id.Offset(, 9).Value = frm3.TextBox3.Value
id.Offset(, 10).Value = frm3.ComboBox3.Value
id.Offset(, 11).Value = frm3.TextBox4.Value
id.Offset(, 12).Value = frm3.ComboBox4.Value
id.Offset(, 13).Value = frm3.TextBox5.Value
id.Offset(, 14).Value = frm3.ComboBox5.Value
id.Offset(, 15).Value = frm3.TextBox6.Value
id.Offset(, 16).Value = frm3.DTPicker3.Value
id.Offset(, 17).Value = frm3.TextBox14.Value
id.Offset(, 18).Value = frm3.DTPicker4.Value
id.Offset(, 19).Value = frm3.TextBox9.Value
id.Offset(, 20).Value = frm3.DTPicker1.Value
id.Offset(, 26).Value = frm3.TextBox16.Value
Worksheets("Transport Assesment").Protect "Ramu#lt12"
ThisWorkbook.Save
End Sub
Please guide me on how should I capture the parameters changed, and their old and new values. Thanks
Suggest using a separate sub to handle comparing the values and updating the tracking info where there's a difference:
'globals for tracking any changes
Dim oldValues, newValues, titles
Sub Edit()
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.AutomationSecurity = msoAutomationSecurityLow
Dim id As Range
oldValues = ""
newValues = ""
titles = ""
Set id = Worksheets("Transport Assesment").Range("A:A").Find( _
what:=frm3.TextBox1.Value, LookIn:=xlValues)
If Not id Is Nothing Then
Worksheets("Transport Assesment").Unprotect "Ramu#lt12"
LogChanges id.Offset(, 1), frm3.TextBox13.Value
LogChanges id.Offset(, 2), frm3.DTPicker2.Value
LogChanges id.Offset(, 4), frm3.ComboBox7.Value
LogChanges id.Offset(, 3), frm3.ComboBox8.Value
LogChanges id.Offset(, 5), frm3.ComboBox6.Value
'etc etc for rest of cells
If Len(titles) > 0 Then
'have changes - update the tracking sheet
End If
Worksheets("Transport Assesment").Protect "Ramu#lt12"
Else
MsgBox "No match found!"
End If
ThisWorkbook.Save
End Sub
'compare form value with existing: log any change and update cell if required
Sub LogChanges(c As Range, vNew)
With c
sep = IIf(Len(titles) > 0, ";", "") 'need a separator?
If .Value <> vNew Then
'track the changes
titles = titles & sep & .Parent.Cells(1, .Column).Value 'column titles in Row1
oldValues = oldValues & sep & ValueOrBlank(.Value) 'track old value
newValues = newValues & sep & ValueOrBlank(vNew) 'track new value
.Value = vNew 'update the cell
End If
End With
End Sub
Function ValueOrBlank(v)
ValueOrBlank = IIf(Len(v)>0, v, "[blank]")
End Function
Related
Please I have an issue, everytime a change occcurs on the sheet it affects all the rows instead of the row (i) concerned. Confused. Don't for-loops work for worksheet_change ? Pls help. Thanks.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim LR As Long
'create a variable for last row of column C, LR
LR = Cells(Rows.Count, "C").End(xlUp).Row
For i = 2 To LR
If Cells(i, 6) = "Yes" And Cells(i, 7).Value = "Full" Then
Target.Value = Cells(i, 3).Value
Cells(i, 9).ClearContents
Cells(i, 10).Value = Cells(i, 8).Value + Cells(i, 9).Value
End If
If Not Intersect(Target, Range("G" & i & ":G" & LR)) Is Nothing And Range("F" & i) = "Yes"
And Target.Value = "Full" Then
Application.EnableEvents = False
Cells(i, 8).Value = Cells(i, 3).Value
Cells(i, 9).ClearContents
Cells(i, 10).Value = Cells(i, 8).Value + Cells(i, 9).Value
Application.EnableEvents = True
End If
If Not Intersect(Target, Range("G" & i & ":G" & LR)) Is Nothing And Range("F" & i) = "Yes" And
Target.Value = "Portion" Then
Application.EnableEvents = False
Cells(i, 8).Value = Cells(i, 3).Value
Cells(i, 10).Value = Cells(i, 8).Value + Cells(i, 9).Value
Application.EnableEvents = True
End If
Next i
End Sub
It seems you need to launch this event for the columns A-E. So, you can start your macro with:
IF Target.Column <= 5 THEN
...
END IF 'at the end of your macro
Like this, when you launch code like Cells(i, 8).Value = ..., Cells(i, 10).Value = ..., ... this macro will be called but it will be stopped immediately.
Apparently you are checking on column, maximum 10, which is in the range of the cells you are changing within your macro. Let's go for another approach:
At the very beginning of your macro, put this line:
Application.EnableEvents = False
At the very end of your macro, put this line:
Application.EnableEvents = True
(and remove the other occurences).
This will make sure you don't call your macro while running it.
I am a complete novice at Excel VBA and I am currently attempting a project on Excel VBA. I have created a UserForm that would allow the user to enter data onto the Excel Sheet by completing the fields in the UserForm. I have tested all the codes individually and they have worked fine.
For the user to access the UserForm, I have added an ActiveX Command Button on a separate sheet on the same workbook. However, when accessing the UserForm from the ActiveX Command Button, some of the codes do not run (mainly the code that flags out the duplicate entry, as well as the code that generates serial numbers).
Where did I go wrong in my code?
This is my code to adding new data as well as the code to flag out duplicate entries. When opening the UserForm from the ActiveX Command Button, adding new data works fine but it does not flag out duplicate entries in the data. (However, testing the code itself in VBA works perfectly fine).
Private Sub cmdAddNewCustomer_Click()
Dim count As Long
Dim lastrow As Long
Dim lCustomerID As String
Dim ws As Worksheet
Set ws = Worksheets("Customer Data")
'find first empty row in database
lrow = ws.Cells.Find(what:="*", searchorder:=xlRows, _
Searchdirection:=xlPrevious, LookIn:=xlValues).Row + 1
lCustomerID = txtCustomerID
count = 0
With ws
For currentrow = 1 To lrow
If lCustomerID = Cells(currentrow, 1) Then
count = count + 1
End If
If count > 1 Then
.Cells(currentrow, 1).Value = ""
.Cells(currentrow, 2).Value = ""
.Cells(currentrow, 3).Value = ""
.Cells(currentrow, 4).Value = ""
.Cells(currentrow, 5).Value = ""
.Cells(currentrow, 6).Value = ""
.Cells(currentrow, 7).Value = ""
.Cells(currentrow, 8).Value = ""
.Cells(currentrow, 9).Value = ""
.Cells(currentrow, 10).Value = ""
.Cells(currentrow, 11).Value = ""
.Cells(currentrow, 12).Value = ""
.Cells(currentrow, 13).Value = ""
.Cells(currentrow, 14).Value = ""
MsgBox ("CustomerID already exists!")
End If
If count = 0 Then
.Cells(lrow, 1).Value = Me.txtCustomerID.Value
.Cells(lrow, 2).Value = Me.txtCustomerName.Value
.Cells(lrow, 3).Value = Me.cboCustomerStatus.Value
.Cells(lrow, 4).Value = Me.txtContactPerson.Value
.Cells(lrow, 5).Value = Me.cboDepartment.Value
.Cells(lrow, 6).Value = Me.txtPosition.Value
.Cells(lrow, 7).Value = Me.cboRoleType.Value
.Cells(lrow, 8).Value = Me.txtofficeHP1.Value
.Cells(lrow, 9).Value = Me.txtOfficeHP2.Value
.Cells(lrow, 10).Value = Me.txtMobileHP1.Value
.Cells(lrow, 11).Value = Me.txtMobileHP2.Value
.Cells(lrow, 12).Value = Me.txtEmail1.Value
.Cells(lrow, 13).Value = Me.txtEmail2.Value
.Cells(lrow, 14).Value = Me.txtEmail3.Value
End If
Next currentrow
End With
'clear the data
Me.txtCustomerName.Value = ""
Me.cboCustomerStatus.Value = ""
Me.txtContactPerson.Value = ""
Me.cboDepartment.Value = ""
Me.txtPosition.Value = ""
Me.cboRoleType.Value = ""
Me.txtofficeHP1.Value = ""
Me.txtOfficeHP2.Value = ""
Me.txtMobileHP1.Value = ""
Me.txtMobileHP2.Value = ""
Me.txtEmail1.Value = ""
Me.txtEmail2.Value = ""
Me.txtEmail3.Value = ""
End Sub
This is the code to generate serial numbers. (Same problem, does not work when accessed via ActiveX Command Button but works fine when tested individually in VBA).
Sub FindCustomerID()
Dim lastrow
Dim lastnum As Long
Dim ws As Worksheet
Set ws = Worksheets("Customer Data")
If Me.cboCountry = "" Or Me.txtCustomerName = "" Then
Exit Sub
End If
serialno = 1
lastrow = ws.Cells(Rows.count, 1).End(xlUp).Row
CountryCode = UCase(Left(Me.cboCountry, 3))
CustomerCode = UCase(Left(Me.txtCustomerName, 10))
'assemble them into CustomerID
CustomerID = CountryCode & CustomerCode & serialno
For currentrow = 2 To lastrow
If CustomerID = Cells(currentrow, 1) Then
'find last number that applies
serialno = serialno + 1
End If
're-assign customerID with new serial number
CustomerID = CountryCode & CustomerCode & serialno
Next currentrow
Me.lblCustomerID = CustomerID
End Sub
And lastly, this is the code from the ActiveX Command Button that brings out the UserForm.
Private Sub cmdNCustomerData_Click()
frmCustomerdata.Show
End Sub
The cause of the problem you described is a missing . to qualify Cells(currentrow, 1). Because you added the ActiveX button to a different sheet, the line
If lCustomerID = Cells(currentrow, 1) Then
accesses Cells(currentrow, 1) of that sheet. To fix this the range needs to be qualified with a . to become
If lCustomerID = .Cells(currentrow, 1) Then
I would also take
If count = 0 Then
.
.
.
End If
outside the loop. You are repeating these lines many times unnecessarily.
The first block of code then becomes:
Private Sub cmdAddNewCustomer_Click()
Dim count As Long
Dim lastrow As Long
Dim lCustomerID As String
Dim ws As Worksheet
Set ws = Worksheets("Customer Data")
'find first empty row in database
lrow = ws.Cells.Find(what:="*", searchorder:=xlRows, _
Searchdirection:=xlPrevious, LookIn:=xlValues).Row + 1
lCustomerID = txtCustomerID
count = 0
With ws
' Count backward to delete rows completely
For currentrow = lrow - 1 To 1 Step -1
If lCustomerID = .Cells(currentrow, 1) Then
count = count + 1
End If
If count > 1 Then
.Cells(currentrow, 1).Resize(1, 14).ClearContents
' Uncomment the following line to delete the whole row completely
'.Rows(currentrow).Delete
End If
Next currentrow
If count > 1 Then
MsgBox (count - 1 " duplicates of CustomerID found and cleared!")
ElseIf count = 0 Then
.Cells(lrow, 1).Value = Me.txtCustomerID.Value
.Cells(lrow, 2).Value = Me.txtCustomerName.Value
.Cells(lrow, 3).Value = Me.cboCustomerStatus.Value
.Cells(lrow, 4).Value = Me.txtContactPerson.Value
.Cells(lrow, 5).Value = Me.cboDepartment.Value
.Cells(lrow, 6).Value = Me.txtPosition.Value
.Cells(lrow, 7).Value = Me.cboRoleType.Value
.Cells(lrow, 8).Value = Me.txtofficeHP1.Value
.Cells(lrow, 9).Value = Me.txtOfficeHP2.Value
.Cells(lrow, 10).Value = Me.txtMobileHP1.Value
.Cells(lrow, 11).Value = Me.txtMobileHP2.Value
.Cells(lrow, 12).Value = Me.txtEmail1.Value
.Cells(lrow, 13).Value = Me.txtEmail2.Value
.Cells(lrow, 14).Value = Me.txtEmail3.Value
End If
End With
'clear the data
Me.txtCustomerName.Value = ""
Me.cboCustomerStatus.Value = ""
Me.txtContactPerson.Value = ""
Me.cboDepartment.Value = ""
Me.txtPosition.Value = ""
Me.cboRoleType.Value = ""
Me.txtofficeHP1.Value = ""
Me.txtOfficeHP2.Value = ""
Me.txtMobileHP1.Value = ""
Me.txtMobileHP2.Value = ""
Me.txtEmail1.Value = ""
Me.txtEmail2.Value = ""
Me.txtEmail3.Value = ""
End Sub
In the FindCustomerID subroutine you have exactly the same problem with the line
If CustomerID = Cells(currentrow, 1) Then
as Cells(currentrow, 1) is not qualified and therefore, should become
If CustomerID = ws.Cells(currentrow, 1) Then
You are also reassigning the CustomerID many times unnecessarily. I would take the reassignment inside the If statement and the loop will become
For currentrow = 2 To lastrow
If CustomerID = ws.Cells(currentrow, 1) Then
'find last number that applies
serialno = serialno + 1
're-assign customerID with new serial number
CustomerID = CountryCode & CustomerCode & serialno
End If
Next currentrow
This way CustomerID is only reassigned if and only if serialno changes.
I have a workbook with monthly worksheets. One for Emails and one for Calls and I have created two userForms for data entry, one for Emails and one for Calls.
The forms do the job and they enter date in the right place but if I have selected the "August 18 Email" sheet and use the Email form, once the form is submitted it jumps to display the "August 18 Calls" sheet.
I just want it to stay in the selected worksheet, in this case "August 18 Email".
The code for the Emails form is the one below and the one for the Calls is nearly the same but only changing this line : Set ws = Sheets(Format(Date, "mmmm yy") & " calls")
Private Sub CommandButton2_Click()
Dim lRow As Long
Dim ws As Worksheet
Set ws = ActiveSheet
Set ws = Sheets(Format(Date, "mmmm yy") & " emails")
lRow = ws.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
With ws
If Me.txtDateBox.Value = "" Then
.Cells(lRow, 1).Value = Format(Date, "dd/mmm/yy")
Else
.Cells(lRow, 1).Value = Me.txtDateBox.Value
End If
myVar = ""
For x = 0 To Me.ListBox2.ListCount - 1
If Me.ListBox2.Selected(x) Then
If myVar = "" Then
myVar = Me.ListBox2.List(x, 0)
Else
myVar = myVar & "," & Me.ListBox2.List(x, 0)
End If
End If
Next x
.Cells(lRow, 11).Value = myVar
myVarSign = ""
For x = 0 To Me.ListBox3.ListCount - 1
If Me.ListBox3.Selected(x) Then
If myVarSign = "" Then
myVarSign = Me.ListBox3.List(x, 0)
Else
myVarSign = myVarSign & "," & Me.ListBox3.List(x, 0)
End If
End If
Next x
.Cells(lRow, 12).Value = myVarSign
myVarTheme = ""
For x = 0 To Me.ListBox1.ListCount - 1
If Me.ListBox1.Selected(x) Then
If myVarTheme = "" Then
myVarTheme = Me.ListBox1.List(x, 0)
Else
myVarTheme = myVarTheme & "," & Me.ListBox1.List(x, 0)
End If
End If
Next x
.Cells(lRow, 14).Value = myVarTheme
.Cells(lRow, 2).Value = Me.Time.Value
.Cells(lRow, 3).Value = Me.ComboBox1.Value
.Cells(lRow, 4).Value = Me.ComboBox2.Value
.Cells(lRow, 5).Value = Me.ComboBox3.Value
.Cells(lRow, 6).Value = Me.ComboBox4.Value
.Cells(lRow, 7).Value = Me.ComboBox5.Value
.Cells(lRow, 8).Value = Me.ComboBox15.Value
.Cells(lRow, 9).Value = Me.ComboBox6.Value
.Cells(lRow, 10).Value = Me.ComboBox7.Value
.Cells(lRow, 13).Value = Me.ComboBox11.Value
.Cells(lRow, 15).Value = Me.ComboBox16.Value
.Cells(lRow, 16).Value = Me.TextBox2.Value
End With
Me.txtDateBox.Value = ""
Me.Time.Value = ""
Me.ComboBox1.Value = ""
Me.ComboBox2.Value = ""
Me.ComboBox3.Value = ""
Me.ComboBox4.Value = ""
Me.ComboBox5.Value = ""
Me.ComboBox6.Value = ""
Me.ComboBox7.Value = ""
Me.ComboBox11.Value = ""
Me.ComboBox16.Value = ""
Me.ComboBox15.Value = ""
Me.TextBox2.Value = ""
Dim iCount As Integer
For iCount = 0 To Me!ListBox1.ListCount
Me!ListBox1.Selected(iCount) = False
Next iCount
For iCount = 0 To Me!ListBox2.ListCount
Me!ListBox2.Selected(iCount) = False
Next iCount
For iCount = 0 To Me!ListBox3.ListCount
Me!ListBox3.Selected(iCount) = False
Next iCount
End Sub
It could be improved a lot but I am happy if after submission the worksheet in view stays instead to jumping to another one.
As you can see I am only beginning (I have managed to create this with help of others).
If you remove any instances of .Select or .Activate on worksheet, range, or cell objects, your sheet shouldn't change.
If that is not an option, another solution would be to note what sheet you are on when the code is called and then Activate that sheet before ending your sub. Since we do not see all of the userform code, you will have to strategically decide where this goes (as mentioned by #K.Davis, nothing shown switches the sheet so it must be happening in some other code).
When the macro/userform is launched:
Dim StartSheet as Worksheet
Set StartSheet = ActiveSheet
Then, before exiting macro/userform:
StartSheet.Activate
You may have to pass this along as a parameter depending on how your code is structured.
I am using the following code to enter data from Userform to Excel sheet and works fine.
The problem is that it overwrites the same row of data. But if I change:
.Cells(RowCount, 4).Value = Me.DepSectDrop.Value to contain a 1 --> .Cells(RowCount, 1).Value = Me.DepSectDrop.Value, and likewise for the rest (2 fore SiteFacOpen, 3 for CaseStartOpen, etc), it does not overwrite.
Private Sub cmdAdd_Click()
'Copy input values to sheet.
Dim RowCount As Long
Dim ws As Worksheet
Set ws = Worksheets("TRACK")
RowCount = ws.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
With ws
.Cells(RowCount, 4).Value = Me.DepSectDrop.Value
.Cells(RowCount, 5).Value = Me.SiteFacOpen.Value
.Cells(RowCount, 6).Value = Me.CaseStartOpen.Value
.Cells(RowCount, 7).Value = Me.TypeDrop.Value
.Cells(RowCount, 8).Value = Me.ProcessDrop.Value
.Cells(RowCount, 9).Value = Me.CompNameOpen.Value
.Cells(RowCount, 10).Value = Me.CompEIDOpen.Value
.Cells(RowCount, 11).Value = Me.RespNameOpen.Value
.Cells(RowCount, 12).Value = Me.RespEIDOpen.Value
.Cells(RowCount, 13).Value = Me.DescOpen.Value
End With
'Clear input controls.
Me.DepSectDrop.Value = ""
Me.SiteFacOpen.Value = ""
Me.CaseStartOpen.Value = ""
Me.TypeDrop.Value = ""
Me.ProcessDrop.Value = ""
Me.CompNameOpen.Value = ""
Me.CompEIDOpen.Value = ""
Me.RespNameOpen.Value = ""
Me.RespEIDOpen.Value = ""
Me.DescOpen.Value = ""
End Sub
What do I need to do to so I maintain the right columns for it all to be entered, but not be overwritten? Thank you
You need to change all lines that start
.Cells(RowCount, 5).Value ...
To
.Cells(RowCount + 1, 5).Value
The '+1' bit means you're using the next blank line.
Also, as Samuel pointed out, you should also change to
RowCount = ws.Cells (Rows.Count, 4).End (xlUp).Offset (1,0).Row
so that you're testing a column that's guaranteed to have data in it!
Sorry, I missed the offset bit ... No need to '+1' if you're offsetting by 1 ... It amounts to the same thing.
I currently have a UserForm "UserForm1" that takes values (Lesson, Instructor, Start Time, etc.) from ComboBoxes and saves them to a designated sheet "InstructorHours". The CommandButton "Save" saves the selected values in the next available line on the "InstructorHours" sheet. What I'm having trouble with is having the CommandButton save the same information into a currently selected row on another Worksheet "Calendar". Ideally I would like to click within a row on my "Calendar" sheet and input the values from the User from into the row I've selected.
Private Sub CommandButton1_Click()
'Copy input values to sheet.
Dim lRow As Long
'next available row
Dim ws As Worksheet
Set ws = Worksheets("InstructorHours")
lRow = ws.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
With ws
.Cells(lRow, 1).Value = Me.ComboBox1.Value
.Cells(lRow, 2).Value = Me.ComboBox2.Value
.Cells(lRow, 3).Value = Me.ComboBox3.Value
.Cells(lRow, 4).Value = Me.ComboBox4.Value
.Cells(lRow, 5).Value = Me.ComboBox5.Value
.Cells(lRow, 6).Value = Me.ComboBox6.Value
.Cells(lRow, 7).Value = Me.TextBox1.Value
End With
'Clear input controls.
Me.ComboBox1.Value = ""
Me.ComboBox2.Value = ""
Me.ComboBox3.Value = ""
Me.ComboBox4.Value = ""
Me.ComboBox5.Value = ""
Me.ComboBox6.Value = ""
End Sub
Any and all help is much appreciated. Thank you.
In general, if you want to work on something active, you would want something "Active" (e.g. ActiveSheet or ActiveCell) for your active-selection activities.
I would recommend another Command Button for the following (Writing this up off the top of my head, haven't verified the code):
Private Sub CommandButton2_Click()
'Input based on the selection
ActiveCell.Value = Me.ComboBox1.Value
ActiveCell.Offset(1,0).Value = Me.ComboBox2.Value
ActiveCell.Offset(2,0).Value = Me.ComboBox3.Value
ActiveCell.Offset(3,0).Value = Me.ComboBox4.Value
ActiveCell.Offset(4,0).Value = Me.ComboBox5.Value
ActiveCell.Offset(5,0).Value = Me.ComboBox6.Value
ActiveCell.Offset(6,0).Value = Me.TextBox1.Value
'Clear input controls.
Me.ComboBox1.Value = ""
Me.ComboBox2.Value = ""
Me.ComboBox3.Value = ""
Me.ComboBox4.Value = ""
Me.ComboBox5.Value = ""
Me.ComboBox6.Value = ""
End Sub
That should provide, at least, a starting point.