Automatically SUM the quantity if Duplicate Value entered in same column? - excel

I have a range A1:B10, for data entry, where A1:A10 for Item Name and B1:B10 for there Quantity.
I need the VBA for, If I entered same Item Name in Range A1:A10, they automatically SUM there Quantity in Range B1:B10, and delete the duplicate value entered?
For Example: -
A1 = Apple, B1 = 10
A2 = Coconut, B2 = 5
A3 = Banana, B3 = 8
And When I will enter
A4 = Apple, B4 = 2
Then
B1 Should be 12
and A4:B4 should be clear.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
'On change of item, if Row found and add to receipt
If Not Intersect(Target, Range("E10")) Is Nothing And Range("E10").Value <> Empty Then AddItem
'On Change of Price Or Qty For Added Items
If Not Intersect(Target, Range("F8,F6")) Is Nothing And Range("B4").Value = False And Range("B6").Value <> Empty Then
Dim RecptRow As Long
RecptRow = Range("B6").Value 'Receipt Row
If Not Intersect(Target, Range("F6")) Is Nothing Then Range("M" & RecptRow).Value = Target.Value 'Update Price
If Not Intersect(Target, Range("F8")) Is Nothing Then Range("L" & RecptRow).Value = Target.Value 'Update Qty
End If
End Sub

Something like this should work.
Private Sub Worksheet_Change(ByVal Target As Range)
lastRow = Range("A" & Rows.Count).End(xlUp).Row ' or whatever other method used to determine lastrow
For Each xTarget In Target
If xTarget.Row = lastRow And xTarget.Column = 2 Then 'if B4 was changed
Call addIt(item:=Cells(xTarget.Row, xTarget.Column - 1), xTarget:=xTarget) ' passing in just xTarget so can use the value and the row
End If
Next xTarget
End Sub
Sub addIt(item, xTarget)
For x = 1 To xTarget.Row - 1 ' minus one because obviously dont wanna check the value you just now entered
If LCase(Trim(Cells(x, "A"))) = LCase(Trim(item)) Then ' lcase and trim to allow for whitespaces and case insensitivity
Cells(x, "B") = Cells(x, "B") + xTarget.value
'now will blank out if added to upper item
Application.EnableEvents = False
Cells(xTarget.Row, xTarget.Column) = "" 'b lr
Cells(xTarget.Row, xTarget.Column - 1) = "" 'a lr
Cells(xTarget.Row, xTarget.Column - 1).Activate 'a lr.activate
Application.EnableEvents = True
Exit Sub 'stop looping and get out
End If
Next x
End Sub
I use a for each xTarget in Target just in case you ever modify more than cell at a time(deleting something) so it wont throw an error
If lastrow column b was changed then it fires off the addIt sub
then it disables events so we don't get into an infinite loop if we then set lastrow column b and a to blank.
If you have more questions about this answer please add a comment! And mark as answered if this answers your question

Related

How can I optimize this code in Excel VBA?

I know about conditional formatting, but it doesn't give me the options I'm looking for: namely, the possibility to manually change the cell fill color (in affected cells) based on how a color another cell, and with that, a standard fill color if I don't do anything. I have this VBA code for a single row (see below) and it works, though I have a feeling it's complicated in itself. Now, I want the same thing for another 149 rows, but the code obviously gets to complex. How can I achieve this? Is it wrong to put this in a SelectionChange?
Code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim Cell As Range
For Each Cell In Range("F7:PB7")
If Cell.Value < Range("D8").Value Or Cell.Value > Range("E8").Value Then
Cell.Offset(1, 0).Interior.ColorIndex = 0
End If
If Cell.Value >= Range("D8").Value And Cell.Value <= Range("E8").Value Then
If Range("B8").Interior.ColorIndex < 0 Then
Cell.Offset(1, 0).Interior.ColorIndex = 15
Else
If Range("B8").Interior.ColorIndex >= 0 Then
Cell.Offset(1, 0).Interior.Color = Range("B8").Interior.Color
End If
End If
End If
... et cetera next row ...
Next Cell
End Sub
Best regards!
Try this out. I'm getting the default color for each row from ColA.
This is all in the worksheet code module:
Option Explicit
Const RW_DATES As Long = 7 'row with headers and dates
Const COL_NAME As Long = 2 'column with person's name
Const COL_START_DATE As Long = 4 'column with start date
Const COL_DATE1 As Long = 6 '1st date on header row
Const NUM_ROWS As Long = 150 'how many rows?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim c As Range, rng As Range, rngDates As Range, i As Long
Dim startDate, endDate, rw As Range, arrDates, rngRowDates As Range
Dim CheckAll As Boolean, hiliteColor As Long, hiliteName As String
Dim cName As Range, selName, selColor As Long
CheckAll = Target Is Nothing 'called from selection_change?
If Not CheckAll Then
'Was a cell changed? see if any start/end date cells were changed
Set rng = Application.Intersect(Target, _
Me.Cells(RW_DATES + 1, COL_START_DATE).Resize(NUM_ROWS, 2))
If rng Is Nothing Then Exit Sub 'nothing to do in this case
Else
'called from Selection_change: checking *all* rows
Set rng = Me.Cells(RW_DATES + 1, COL_START_DATE).Resize(NUM_ROWS)
End If
Debug.Print "ran", "checkall=" & CheckAll
'header range with dates
Set rngDates = Me.Range(Me.Cells(RW_DATES, COL_DATE1), _
Me.Cells(RW_DATES, Columns.Count).End(xlToLeft))
arrDates = rngDates.Value 'read dates to array
Set cName = NameHiliteCell() 'see if there's a hilited name
If Not cName Is Nothing Then
selName = cName.Value
selColor = cName.Interior.Color
End If
'loop over each changed row
For Each rw In rng.EntireRow.Rows
Set rngRowDates = rw.Cells(COL_DATE1).Resize(1, rngDates.Columns.Count)
rngRowDates.Interior.ColorIndex = xlNone 'clear by default
startDate = rw.Cells(COL_START_DATE).Value 'read the dates for this row
endDate = rw.Cells(COL_START_DATE + 1).Value
'determine what color the bar should be
If Len(selName) > 0 And selName = rw.Cells(COL_NAME).Value Then
hiliteColor = selColor
Else
hiliteColor = rw.Cells(1).Interior.Color
End If
If startDate > 0 And endDate > 0 Then
i = 0
For Each c In rngRowDates.Cells
i = i + 1
If arrDates(1, i) >= startDate And arrDates(1, i) <= endDate Then
c.Interior.Color = hiliteColor
End If
Next c
End If
Next rw
End Sub
'just calls Worksheet_Change; add some delay to reduce frequency of firing
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Static lastrun As Date
If lastrun = 0 Then lastrun = Now
If Now - lastrun > (1 / 86400) Then
lastrun = Now
Worksheet_Change Nothing
End If
End Sub
'find the first name cell which has any fill and return it
Function NameHiliteCell() As Range
Dim c As Range
For Each c In Me.Cells(RW_DATES + 1, COL_NAME).Resize(NUM_ROWS)
If Not c.Interior.ColorIndex = xlNone Then
Set NameHiliteCell = c
Exit Function
End If
Next c
End Function
My test range:
Would something like this be better? It will only fire when you change a value in the range F7:PB7.
It won't fire if the cell value is updated through a formula (for that you'd want to look at the cell that you changed to make the formula update).
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count = 1 Then 'Only fire if a single cell is changed.
If Not Intersect(Target, Range("F7:PB154")) Is Nothing Then
MsgBox Target.Address 'Test
'Your code - looking at Target rather than each Cell in range.
End If
End If
End Sub
Edit: Updated the range so it looks at more than one row, but now thinking I should delete the answer due to the odd/even rows that #Cyril indicates, etc.... this isn't looking like a complete answer now.

How can I force user to enter negative number in Excel?

How can I force user to enter negative number in Excel?
Basically column A can only be "W" or "X". Whenever column A has "W", i want column B to reflect a negative number, even if the user has keyed in a positive number.
"W" in column A corresponds to a negative value in column B
"X" in column B corresponds to a positive value in column B.
Thanks for the help!
No VBA needed. Just use data validation with the following formula
=OR(AND(A1="W",B1<0),AND(A1="X",B1>0))
Image 1: Using data validation W in column A only allows negatives in column B, X in column A only allows positives in column B.
Install the code below in the code module of the worksheet on which you want to control the input. It's a module that already exists in your VB Project. Any module you have to create is the wrong one and won't work. Look for a module with a double name like Sheet1 (Sheet1).
Private Sub Worksheet_Change(ByVal Target As Range)
' 058
Dim Rng As Range
Dim Numb As Variant
Dim NewNumb As Double
' ignore changes to more than one cell (such as pasting)
If Target.CountLarge > 1 Then Exit Sub
' this range starts in A2 and covers all used cells in columns A:B
Set Rng = Range(Cells(2, "A"), Cells(Rows.Count, "A").End(xlUp)) _
.Resize(, 2)
' skip if the changed cell is not within the defiend range
If Not Application.Intersect(Target, Rng) Is Nothing Then
' take no action of the value in column A isn't "X" or "W"
With Target
Numb = Cells(.Row, "B").Value
' take no action if the cell in column B has no value
If Numb Then
If Cells(.Row, "A").Value = "W" Then
NewNumb = Abs(Val(Numb)) * -1
ElseIf Cells(.Row, "A").Value = "X" Then
NewNumb = Abs(Val(Numb))
End If
' prevent changes made from calling this procedure
Application.EnableEvents = False
' don't take action if the value in column A
' was neither X nor W
If Numb And (Numb <> NewNumb) Then _
Cells(.Row, "B").Value = NewNumb
Application.EnableEvents = True
End If
End With
End If
End Sub
The code works on columns A and B. To modify these targets isn't difficult. For now, when a cell in either column is changed the procedure may take action. For the rules by which it will not take action please read the comments in the code. When it does take action it will make sure that any entry in column B is negative if the letter in column A is W and positive when it's X, regardless of what sign the user entered.
A little VBA in your worksheet module will take care of that:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const SourceColumn As Long = 1
Const TargetColumn As Long = 2
Const NegatorSymbol As String = "W"
Dim SourceRange As Excel.Range
Dim TargetRange As Excel.Range
Dim Sign As Long
Dim TargetValue As Long
If Target.Column = TargetColumn Then
Set SourceRange = Cells(Target.Row, SourceColumn)
If UCase(SourceRange.Value) = NegatorSymbol Then
Sign = -1
Else
Sign = 1
End If
TargetValue = Sign * Abs(Target.Value)
If Target.Value <> TargetValue Then
Target.Value = TargetValue
End If
ElseIf Target.Column = SourceColumn Then
Set TargetRange = Cells(Target.Row, TargetColumn)
If UCase(Target.Value) = NegatorSymbol Then
Sign = -1
Else
Sign = 1
End If
TargetValue = Sign * Abs(TargetRange.Value)
If TargetRange.Value <> TargetValue Then
TargetRange.Value = TargetValue
End If
End If
End Sub
You can set on column B a data validation Custom with this formula:
=OR(AND(A1="W";B1<0);AND(A1<>"W";B1>0))
[EDIT]
I was late to the party...

Copy values as they're entered into a column & paste to another worksheet - where am I going wrong?

vAs the title suggests, I'm trying to copy and paste values to keep as a log as these values will be deleted shortly after they've been entered.
Long story short, a user will scan a barcode and a unique string of characters that correspond to the scanned barcode will be automatically entered into whichever cell is selected (usually A2). These strings are parent barcodes, so immediately after one has been scanned into A2 the user will start scanning their related children barcodes into B2, B3 and so on depending on how many children there are (always a completely random number).
Here's what I have so far:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Dim intRow As Integer
Dim intCount As Integer
Set ws = Worksheets("log")
intRow = ws.Range("A1").CurrentRegion.Rows.Count + 1
'Check if WandID exist in log.
If Target.Address = "$A$2" Then
intCount = Application.WorksheetFunction.CountIf(ws.Range("D:D"), Target.Value)
If intCount <> 0 Then
Target.Interior.Color = RGB(255, 0, 0)
MsgBox "this wandID has already been used, please try a different barcode"
Range("A2").ClearContents
End
Else
Target.Interior.Color = xlNone
ws.Cells(intRow, "D") = Target.Parent.Range("A2")
ws.Cells(intRow, "E") = Target
End If
End If
End Sub
On the destination worksheet (named 'log') I've set up a simple table to organise the copied / scanned data and added some dummy data as a first entry - seeing as this workbook will be used regularly I wanted this process to keep adding scanned codes beneath the previous (dummy parent code in D2, dummy child codes in E2:E9), so ideally when I enter a new parent code into A2 of the source sheet, it should be copied to D10 on the destination sheet, and any corresponding child codes added to B2 etc. of the source sheet should be copied to E10 etc. of the destination sheet. Instead what's happening is the parent code will be added to D10:E10.
I have a feeling the line
"ws.Cells(intRow, "E") = Target"
is the problem, but not sure of what to change it to.
Any help or advice would be hugely appreciated!
I believe the code below will help you achieve your objective, it checks for the Next Free Row on Column D and that's where the Parent ID will be entered, similarly it will check the Child IDs in Column B and find the Next available row on Column E to paste the list into, the main changes are the use of Application.EnableEvents and the copy of data from Column B to Column E:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet: Set ws = Worksheets("log")
Dim NextRow As Long, ChildRow As Long, ChildNextRow As Long
NextRow = ws.Cells(ws.Rows.Count, "D").End(xlUp).Offset(1, 0).Row
'get the Next Free row in Column D
ChildRow = ws.Cells(ws.Rows.Count, "B").End(xlUp).Row
'get the Last Row with data on Column B
ChildNextRow = ws.Cells(ws.Rows.Count, "E").End(xlUp).Offset(1, 0).Row
'get the Next Free Row on Column E
'Check if WandID exist in log (Column D).
If Target.Address = "$A$2" Then
Application.EnableEvents = False
'Turn Events off, so it doesn't fire the Change Event while we write to it
intCount = Application.WorksheetFunction.CountIf(ws.Range("D:D"), Target.Value)
'check if Parent ID has been used previously by check Column D
If intCount <> 0 Then
Target.Interior.Color = RGB(255, 0, 0)
MsgBox "This wandID has already been used, please try a different barcode", vbInformation, "Duplicate Entry"
ws.Range("A2").ClearContents
Else
Target.Interior.Color = xlNone
ws.Cells(NextRow, "D") = ws.Range("A2")
ws.Range("B2:B" & ChildRow).Copy Destination:=ws.Range("E" & ChildNextRow)
End If
Application.EnableEvents = True
End If
End Sub

Updating value of a non-Target cell in Excel VBA

I found the attached when looking for how to due an event change to correct user data based on the values in two columns. I'm not a programmer, so I may have butchered the code as I combined two different solutions together.
Right now, it's working exactly as I want it to. Changing the offset cell value forces Excel to replace the target value with what I've specified. What I'm looking to achieve (and am not sure is possible), is to reverse the code. Basically, I want to change the offset cell, if the values are entered in the opposite order. The code will change the cell value to "Beta" if a user enters "Bravo" in column A, and then "Gamma" in column C.
What I'm trying to achieve is that if the user enters "Bravo" in column A second, that Excel still sees the combination of these cells and still replaces the value with "Beta". I know this is additional code, but I couldn't find anything to support replacing cell when the target cell isn't the value being updated.
Thanks in advance!
Dim oldCellAddress As String
Dim oldCellValue As String
Private Sub Worksheet_Change(ByVal Target As Range)
oldCellValue = "Bravo"
If Target = "Bravo" And Target.Offset(0, -2) = "Gamma" Then
Target.Value = "Beta"
Application.EnableEvents = True
End If
End Sub
This may meet your needs:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim colnum As Long, v As Variant
colnum = Target.Column
v = Target.Value
If colnum = 1 Then
If v = "Bravo" And Target.Offset(0, 2) = "Gamma" Then
Application.EnableEvents = False
Target.Value = "Beta"
Application.EnableEvents = True
End If
Exit Sub
End If
If colnum = 3 And v = "Gamma" And Target.Offset(0, -2) = "Bravo" Then
Application.EnableEvents = False
Target.Offset(0, -2).Value = "Beta"
Application.EnableEvents = True
End If
End Sub
For example if the user puts Bravo in cell A1 and C1 already contained Gamma, the code puts Beta in A1 (the code corrects the A1 entry).If the user puts Gamma in cell C1 and cell A1 already contained Bravo, the code corrects A1.
There are two possible scenarios like below...
Scenario 1:
If ANY CELL on the sheet is changed, the following code will check the content of column A and C in the corresponding row and change the content of the Target Cell.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub
Dim r As Long
r = Target.Row
On Error GoTo Skip:
Application.EnableEvents = False
If Cells(r, "A") = "Bravo" And Cells(r, "C") = "Gamma" Then
Target.Value = "Beta"
End If
Skip:
Application.EnableEvents = True
End Sub
Scenario 1:
If a cell in column D is changed, the change event will be triggered and check the content in column A and C in the corresponding row and change the Target Cell in Column D.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub
Dim r As Long
On Error GoTo Skip:
'The below line ensures that the sheet change event will be triggered when a cell in colunm D is changed
'Change it as per your requirement.
If Not Intersect(Target, Range("D:D")) Is Nothing Then
Application.EnableEvents = False
r = Target.Row
If Cells(r, "A") = "Bravo" And Cells(r, "C") = "Gamma" Then
Target.Value = "Beta"
End If
End If
Skip:
Application.EnableEvents = True
End Sub

How to highlight a row if three conditions are met?

If the following conditions are met:
For any given row between row 10 and row 100 inclusively:
The cell in column A is not empty
The cell in column B is not empty
The cell in column O is empty
I would like to highlight a specific cell (let's say A1).
Example:
I populate A10 and E10 while leaving O10 empty, then cell A1 gets highlighted. If I then populate cell O10, the highlight in cell A1 disappears.
I can proceed to the next row. Any row at any time should generate these actions.
Thanks!
This will do the highlights based on the conditions you specified. When you run it, it'll stop at the first row you need to input something in column O. If you want it to keep running until row 101 and highlight all the rows, then remove then Exit Do command that's between the 2 End If statements.
Sub Highlight()
Dim TheRow As Integer
TheRow = 9
Application.ScreenUpdating = False 'This hides the visual process and speeds up
'the execution
Do
TheRow = TheRow + 1
If TheRow = 101 Then Exit Do
Cells(TheRow, 1).Select
Selection.Interior.Pattern = 0
Cells(TheRow, 2).Select
Selection.Interior.Pattern = 0
If Not Cells(TheRow, 1).Value = "" And Not Cells(TheRow, 2).Value = "" And Cells(TheRow, 15).Value = "" Then
If Cells(TheRow, 1).Value = "" Then
Cells(TheRow, 1).Select
Selection.Interior.Color = 656
End If
If Cells(TheRow, 2).Value = "" Then
Cells(TheRow, 2).Select
Selection.Interior.Color = 656
End If
Exit Do 'this is the line to remove if you want to highlight all cells
End If
Loop
Application.ScreenUpdating = True
End Sub
And then, create an event handler that triggers when a cell in column 15 changes. Put the following code in the module of the actual worksheet (in the VBA project explorer, double click on the sheet you want have this functionality for; don't put this in a different module!)
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 15 Then
If Target.Row > 9 And Target.Row < 101 Then Call Highlight
End Sub
Let me know if this solution works and remember to click "accept solution" and to vote for it!
Happy coding.
You don't need VBA: just use conditional formatting on cell A10 with the following formula:
=AND(NOT(ISBLANK($A10)),NOT(ISBLANK($B10)),ISBLANK($O10))
OK - I misunderstood what you wanted. Here is a VBA UDF to do the checking.
Enter =Checker($A$10:$B$100,$O$10:$O$100) in cell A1, then use conditional formatting on cell A1 that is triggered when it becomes True.
Public Function Checker(theRangeAB As Range, theRangeO As Variant) As Boolean
Dim varAB As Variant
Dim varO As Variant
Dim j As Long
varAB = theRangeAB.Value2
varO = theRangeO.Value2
Checker = False
For j = 1 To UBound(varAB)
If Not IsEmpty(varAB(j, 1)) And Not IsEmpty(varAB(j, 2)) Then
If IsEmpty(varO(j, 1)) Then
Checker = True
Exit For
End If
End If
Next j
End Function

Resources