Edit (adjacent) cells with Find() - excel

I'm writing a small macro for searching and sorting barcodes.
The idea is that barcodes are scanned into cell C1, then the macro is suppose to count the amount of times the same code is scanned. If the barcode is not already in the list (column B:B) it should add the new barcode in the list (column B:B).
I've managed utilised the Find() syntax, however I can't manage to edit any cells with it. Only thing I am able to do is MsgBox " " Ive tried:
Range("a5").Value = 5
It doesn't work
This is the code I currently have:
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("C1") = "" Then MsgBox "No input"
Dim barcodes As Range
Set barcodes = Range("B:B").Find(What:=Range("C1").Value, After:=Range("B2"), LookIn:=xlFormulas, LookAt:=xlWhole, SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=True, MatchByte:=True, SearchFormat:=False)
If Not barcodes Is Nothing And Not Range("C1") = "" Then
MsgBox "Found"
End If
If barcodes Is Nothing And Not Range("C1") = "" Then
MsgBox "New product"
End If
End Sub
For MsgBox "Found" I want instead a code that counts the amount of times the same barcode has been scanned in the adjacent cell to the right.
And for Msgbox "New product" I want to write a part that adds the new code to the list in this case Column B:B

The the below will A) verify that you don't have a match (using IsError, which returns boolean) to determine if you need to add a value and start the scan count at 1, or B) if you need to find the previous entry (using Match()) and add to the counter:
If IsError(Application.Match(Cells(1,3).Value,Columns(2),0)) Then
lr = cells(rows.count,2).end(xlup).row
Cells(lr+1,2).Value = Cells(1,3).Value
Cells(lr+1,1).Value = 1
Else
r = Application.match(Cells(1,3).Value,Columns(2),0)
cells(r,1).value = cells(r,1).value + 1
End If
Edit1:
Updated column #s for second subroutine per comment from OP, while stripping out the first subroutine and rewording.

With this code you will need a sheet called "DataBase" where you will store each scan and later will be the source for example for a pivot table:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Barcode As String, i As Long, wsDB As Worksheet, LastRow As Long
Dim DictBarcodes As New Scripting.Dictionary 'You need to check the Microsoft Scripting Runtime reference for this to work
With Application
.EnableEvents = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
Set wsDB = ThisWorkbook.Sheets("DataBase")
With Target
If .Range("C1") = vbNullString Then MsgBox "No input"
On Error Resume Next
'loop through all the barcodes and store them into a dictionary
For i = 1 To .Rows.Count
If .Cells(i, 2) = vbNullString Then Exit For 'skip the loop once a blank cell is found
DictBarcodes.Add .Cells(i, 1), i 'this will raise an error if there are duplicates
Next i
'If the value doesn't exist we add it to the list
If Not DictBarcodes.Exists(.Cells(1, 3)) Then
LastRow = .Cells(.Rows.Count, 2).End(xlUp).Row + 1
.Cells(LastRow, 2) = .Cells(1, 3)
End If
End With
'Either it exists or not, store it to the data base to keep tracking
With wsDB
.Cells(1, 1) = "Barcode"
.Cells(1, 2) = "Date Scan"
LastRow = .Cells(.Rows.Count, 1).End(xlUp).Row + 1
.Cells(LastRow, 1) = .Cells(1, 3)
.Cells(LastRow, 2) = Now
End With
'Finally the output on the adjacent cell
Target.Cells(1, 4) = Application.CountIf(wsDB.Range("A:A"), Target.Cells(1, 3))
With Application
.EnableEvents = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub

Related

VBA Userform posting data twice....sometimes

I have a userform with a combobox on a sheet "PostHistory" that draws it's data from the "Staff" sheet. When you press Add on the userform it's suppose to locate the name on the Staff Sheet and replace the date next to the name. Occasionally, it will replace the date and the date next to the name below it. Using Excel 2016
Private Sub CommandButton7_Click()
Application.ScreenUpdating = False
Sheets("Staff").Visible = True
Sheets("Engine").Visible = True
Dim TargetRow As Integer
Dim nameRange As Range
Set nameRange = Sheets("Staff").Range("C3:C200")
TargetRow = Sheets("Engine").Range("D3").Value
Sheets("PostHistory").Range("B3").EntireRow.Insert Shift:=xlDown
Sheets("PostHistory").Range("B3").Value = txt_date
Sheets("PostHistory").Range("C3").Value = cb_staff
Sheets("PostHistory").Range("D3").Value = txt_post
Sheets("PostHistory").Range("E3").Value = txt_notes
If (Augment.txt_date.Text) = "" Then
GoTo Skip1
ElseIf IsNull(Augment.txt_date.Value) = False Then
End If
For Each cell In nameRange.Cells
If cell.Text = [cb_staff] Then
cell.Offset(0, -1).Value = txt_date
End If
Next
Skip1:
Unload Augment
Sheets("Staff").Visible = False
Sheets("Engine").Visible = False
Sheets("List").Visible = False
Application.ScreenUpdating = True
Augment.Show
End Sub
To start: I didn't find the reason why your code should write more than once. But I believe the code below will not write anything twice.
Private Sub CommandButton7_Click()
' 209
Dim nameRange As Range
Dim Fnd As Range
Dim Ctls() As String
Dim i As Integer
Ctls = Split("txt_Date,cb_Staff,txt_Post,txt_Notes", ",")
If Len(txt_Date) Then
With Worksheets("Staff")
Set nameRange = .Range(.Cells(3, 3), .Cells(.Rows.Count, 3).End(xlUp))
End With
Set Fnd = nameRange.Find(cb_Staff.Value, , xlValues, xlWhole)
If Not Fnd Is Nothing Then Fnd.Offset(0, -1).Value = txt_Date.Value
End If
With Worksheets("PostHistory")
.Rows(3).EntireRow.Insert Shift:=xlDown
With .Rows(3)
For i = 0 To UBound(Ctls)
.Cells(3 + i).Value = Me.Controls(Ctls(i)).Value
Me.Controls(Ctls(i)).Value = ""
Next i
End With
End With
End Sub
In principle, you don't need to unhide a sheet in order to read from or write to it. Also, if the sheet to which you write is hidden, there is no point in stopping ScreenUpdating. Finally, I did like the way you found to clear all controls but believe that it will interfere with your management of the list in the combo box. Therefore I showed you another method above.
Oh, yes. I created a userform called Augment with one combo box, 3 text boxes and one CommandButton7. I hope that is what you also have.

Keep column creation to a limit in VBA

I have the following VBA code I compiled based on some stuff I found online.
It works (mostly) as I intend it to, as on change in column H it creates a new column to the right of the last column, in the active row, with the latest value.
However, I'd like, if possible, the following:
to keep only the latest 5 changes;
currently, it somehow always ignores the fact that the last column of the active row is empty and just creates a new column after so my updates always look like this, even if each of them is the first update for each row (I can expand if this wasn't clear enough).
Here's the VBA code I have now:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("H:H")) Is Nothing Then
ActiveCell.Offset(-1, 0).Activate
a = Sheets("SORTIES").Cells(ActiveCell.Row, Columns.Count).End(xlToLeft).Column + 1
Column = Split(Cells(1, a).Address, "$")(1)
Sheets("SORTIES").Range(Column & ActiveCell.Row).Value = ActiveCell.Value
ActiveCell.Offset(0, 0).Select
End If
End Sub
Please, try the next code. It determines the last empty column inside the table and uses a Static dictionary variable, to keep records for all the cells in the table, for H:H column. It needs a reference to "Microsoft Scripting Runtime". To add this reference you need to be in VBE (Visual Basic for Applications Editor) and go Tools (menu) -> References..., scroll down until find "Microsoft Scripting Runtime" library, check it and press OK.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Static dictSt As New Dictionary 'it needs a reference to "Microsoft Scripting Runtime"
If Target.cells.count > 1 Then Exit Sub
If Target.Address = "$E$1" And Target.Value = "x" Then dictSt.RemoveAll: Target.Value = "": Exit Sub 'clear the dictionary
If Not Intersect(Target, Range("H:H")) Is Nothing Then
Dim lstO As ListObject: Set lstO = ActiveSheet.ListObjects(1) 'use your table namem if there is not only one
If dictSt.count = 0 Then 'load the dictionary for first time
'load the dictionary referenced cells
Dim arrRng As Range, i As Long, cel As Range
Set arrRng = Intersect(lstO.DataBodyRange, Range("H:H"))
For Each cel In arrRng.cells
dictSt(cel.Address) = 0
Next
End If
If Not Intersect(Target, lstO.DataBodyRange) Is Nothing Then
Dim lastEmptRng As Range
Set lastEmptRng = cells(lastListCol(Target, lstO)(0), lastListCol(Target, lstO)(1))
'operate the change in the dictionary and use the last history value after 5 records
If dictSt(Target.Address) >= 5 Then
Dim arrVal As Variant, arrUpdate As Variant, El
arrVal = Range(Target.Offset(0, 1), cells(Target.row, lastEmptRng.Column - 1)).Value
ReDim arrUpdate(1 To 1, 1 To UBound(arrVal, 2))
For i = 1 To UBound(arrVal, 2) - 1
arrUpdate(1, i) = arrVal(1, i + 1)
Next
arrUpdate(1, 5) = Target.Value
With Application
.EnableEvents = False
.Calculation = xlCalculationManual
Range(Target.Offset(0, 1), cells(Target.row, lastEmptRng.Column - 1)).Value = arrUpdate
.Calculation = xlCalculationAutomatic
.EnableEvents = True
End With
Else
lastEmptRng.Value = Target.Value
dictSt(Target.Address) = dictSt(Target.Address) + 1
End If
Target.Select
End If
End If
End Sub
Private Function lastListCol(tg As Range, lstO As ListObject) As Variant
'this piece of code search the last empty column (of the Target row) inside the table!
Dim listRow As Long, fRng As Range
listRow = tg.row - lstO.DataBodyRange.row + 1
Set fRng = lstO.DataBodyRange.rows(listRow).Find(What:="*", _
After:=lstO.DataBodyRange.cells(listRow, 1), LookIn:=xlValues, _
searchorder:=xlByColumns, SearchDirection:=xlPrevious)
If Not fRng Is Nothing Then
lastListCol = Array(tg.row, fRng.Column + 1) ' Stop
Else
lastListCol = Array(tg.row, lstO.DataBodyRange.cells(listRow, 1).Column)
End If
End Function
The dictionary can be reset by typing "x" in cell "E1"!
Edited:
Now, it keeps the last records for all recording history.

creating a complex macro using vba

I have a complex workbook that i need filtered using vba.
I need to delete rows that have blank cells from column G.
I then need columns C through G hidden.
Then I need Column H filtered to delete all rows greater than 2.
Finally I need Column I sorted from Largest to smallest.
This is what i have so far but It half way works and i don't want to use a command button. I want to be able to paste a document in here and the code automatically works it.
Private Sub CommandButton1_Click()
'Created by William Hinebrick 277096
Dim xRg As Range
Dim xTxt As String
On Error Resume Next
If ActiveWindow.RangeSelection.Count > 1 Then
xTxt = ActiveWindow.RangeSelection.AddressLocal
Else
xTxt = ActiveSheet.UsedRange.AddressLocal
End If
Set xRg = Application.InputBox("Please select range:", "Kutools for Excel", xTxt, , , , , 8)
If xRg Is Nothing Then Exit Sub
If (xRg.Areas.Count > 1) Or (xRg.Columns.Count > 1) Then
MsgBox "You can only select one column per time", vbInformation, "Kutools for Excel"
Exit Sub
End If
xRg.Range("A1").EntireRow.Insert
Set xRg = xRg.Range("A1").Offset(-1).Resize(xRg.Rows.Count + 1)
xRg.Range("A1") = "Temp"
xRg.AutoFilter 1, ">2"
Set xRg = Application.Intersect(xRg, xRg.SpecialCells(xlCellTypeVisible))
On Error GoTo 0
If Not xRg Is Nothing Then xRg.EntireRow.Delete
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
'Created by William Hinebrick 277096
Dim xRg As Range
Application.ScreenUpdating = False
For Each xRg In Range("G1:G10000")
If xRg.Value = "" Then
xRg.EntireRow.Hidden = True
Else
xRg.EntireRow.Hidden = False
End If
Next xRg
Application.ScreenUpdating = True
End Sub
Sub Column_Hide()
'Created by William Hinebrick 277096
Columns("C:G").EntireColumn.Hidden = True
Columns("J").EntireColumn.Hidden = True
End Sub
Private Sub Sort_Drop(ByVal Target As Range)
On Error Resume Next
Range("I1").Sort Key1:=Range("I2"), _
Order1:=xlAscending, Header:=xlYes, _
OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom
End Sub
I would like to be able to use this daily as I will be pasting New spreadsheets to this worksheet to be filtered so I may concise the results
This should do everything listed.
If you require it to perform everytime you copy data in, then the Worksheet_Changeevent from your 2nd sub is the way to go. But this means it also runs every other time you change something in your workbook. I'd personally simply assign a Keyboard shortcut to it. Seems the easiest way to go.
Option Explicit
Sub test()
Dim i As Double
Dim lastrow As Double
lastrow = ActiveSheet.UsedRange.Rows.Count
For i = lastrow To 2 Step (-1) 'delete empty G cells
If ActiveSheet.Cells(i, 7).Value = "" Then Cells(i, 7).EntireRow.Delete
Next
lastrow = ActiveSheet.Cells(Rows.Count, 7).End(xlUp).Row
For i = lastrow To 2 Step (-1) 'delete H >2
If ActiveSheet.Cells(i, 8).Value > 2 Then Cells(i, 8).EntireRow.Delete
Next
Columns("C:G").EntireColumn.Hidden = True 'hide columns
Range("I1").Sort Key1:=Range("I2"), _
Order1:=xlDescending, Header:=xlYes, _
OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom 'Sort by I descending order
End Sub

Connect newly added Sheet to existing one

This is my first post in Stack Overflow so any mistake I make please just ignore.
So i made an button which runs the macro of an application inputbox, the name you enter in the inputbox will create a new sheet with the name you entered, it also will create a table on the new sheet. The name you put on the inputbox are the clients that newly came so i will have specific sheet with table for every client that comes.
On the other hand I got the Workers which will receive incomes from clients, I Got 4 Workers which have their own Sheet and Table of Incomes and Outcomes.
Now the question i am getting to is that, is it possible to creade a code on VBA that will say: If on the new sheet (inside the table, specificly: K8:K23, K28:K43, K49:K64) the name of the Worker is inserted, copy the name of the client and paste it into the existing sheet of the Worker.
The code i tried but did not work: (Only Check the First Sub and the end of line, the between code is just a bunch of macro for table to be created, that parts work, the problem of my code which is located at the end is that it does nothing, and yes I did an commend to the codes on purpose)
Sub KerkimiKlientit()
Dim EmriKlientit As String
Dim rng As Range, cel As Range
Dim OutPut As Integer
retry:
EmriKlientit = Application.InputBox("Shkruani Emrin e Klientit", "Kerkimi")
If Trim(EmriKlientit) <> "" Then
With Sheets("Hyrjet").Range("B10:B200")
Set rng = .Find(What:=EmriKlientit, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not rng Is Nothing Then
sheet:
Flag = 0
Count = ActiveWorkbook.Worksheets.Count
For i = 1 To Count
WS_Name = ActiveWorkbook.Worksheets(i).Name
If WS_Name = EmriKlientit Then Flag = 1
Next i
If Flag = 1 Then
ActiveWorkbook.Sheets(EmriKlientit).Activate
Exit Sub
Else
Sheets.Add(, Sheets(Sheets.Count)).Name = EmriKlientit
Call KrijimiTabeles(EmriKlientit)
Exit Sub
End If
Else
OutPut = MsgBox("Klienti nuk u gjet", vbRetryCancel + vbInformation, "Provoni Perseri")
If (OutPut = vbRetry) Then
GoTo retry:
ElseIf (OutPut = vbCancel) Then
Exit Sub
End If
Exit Sub
End If
End With
End If
If userInputValue = "" Then
OutPut = MsgBox("Rubrika e Emrit e zbrazet", vbRetryCancel + vbExclamation, "Gabim")
If (OutPut = vbRetry) Then
GoTo retry:
ElseIf (OutPut = vbCancel) Then
Exit Sub
End If
Else
GoTo retry:
End If
End Sub
Sub KrijimiTabeles(EmriKlientit As String)
'
' KrijimiTabeles Macro
'
'This was just an middle code, it was too long so I did not paste it. Not an important part tho.
'This is the part that does not work, it just does nothing for some reason, there are multiple codes here and I tried them all.
'Sub Formula(EmriKlientit As String, ByVal Target As Range)
'ActiveWorkbook.Sheets(EmriKlientit).Activate
'If Not Application.Intersect(Range("K8:K23"), Range(Target.Adress)) Is Nothing Then
'Call Formula1
'End If
'End Sub
'Dim LR As Long, i As Long
'Application.ScreenUpdating = False
'Dim Rng As Range
'For Each Rng In Range("K8:K23")
'Select Case Rng.Value
'Case "M"
'Worksheets(EmriKlientit).Range("K2").Copy
'Worksheets("Mustafa").Range("K6").PasteSpecial Paste:=xlPasteFormulas
'End Select
'Next Rng
'Application.ScreenUpdating = True
'For Each cel In Rng
'If cel.Value = "M" Then
'Worksheets(EmriKlientit).Range("K2").Copy
'Worksheets("Mustafa").Range("K6").PasteSpecial Paste:=xlPasteFormulas
'End If
'Next cel
'ActiveWorkbook.Sheets(EmriKlientit).Activate
'If Not Application.Intersect(Range("K8:K23"), Range(Rng.Adress)) Is Nothing Then
'With Sheets(EmriKlientit)
'With .Range("K8:K23")
'If .Text = "M" Then
'Worksheets(EmriKlientit).Range("K2").Copy
'Worksheets("Mustafa").Range("K6").PasteSpecial Paste:=xlPasteFormulas
'End If
'End With
'End With
'End If
'Flag = 0
'Count = ActiveWorkbook.Worksheets.Count
'For i = 1 To Count
'WS_Name = ActiveWorkbook.Worksheets(i).Name
'If WS_Name = EmriKlientit Then Flag = 1
'Next i
'If Flag = 1 Then
'ActiveWorkbook.Sheets(EmriKlientit).Activate
'For Each Cell In Sheets(EmriKllientit).Range("K8:K23")
'If Cell.Value = "M" Then
'Range("K2").Copy
'Worksheets("Mustafa").Range("K6").PasteSpecial Paste:=xlPasteFormulas
'End If
'Next
'End If
End Sub
Thank you
I hope I was clear enough,
Any help would be appreciated.
Welcome to StackOverflow - i agree that your question can be a bit more specific...
I think what you are trying to achieve is something between this lines:
Dim wsClient As Worksheet, wsMustafa As Worksheet
Dim i As Long
Dim fRow As Long, lRow As Long
Set wsClient = ActiveWorkbook.Sheets("Client")
Set wsMustafa= ActiveWorkbook.Sheets("Mustafa")
'you can assign this through better ways, but to start with...
fRow = 8
lRow = 23
For i = fRow To lRow
If wsClient.Range("K" & i).Value = "M" Then
wsMustafa.Range("K6").Value = wsClient.Range("K" & i).Value 'or .Formula if that's what you want
End If
Next i
Hope this helps, good luck.

Using ArrayLists in VBA

I am currently modifying a heavily moded excel schedule workbook. I have a list master list of therapists(Therapist Selector Sheet) that when a check box is checked and the magic button is pressed I want to the list to check for names in the target sheet(All Therapists) in the specified range.
If the therapist has become unchecked then the program then the program erased the necessary data for those rows and sorts everything. That part is working fine.
Next I want the program to put names that aren't already there in the next open spot. I am trying to do this by first populating an ArrayList with all the names that have a check by them.
If the program finds a duplicate I have it remove the duplicate from the array. Then when it is done removing duplicates it loops through the names and prints each one in the first cell in the column with a hypen (all empty cells are turned into hyphens).
I can't get this to work. I keep getting a
runtime error -2146233079 80131509
Does anyone see a better way to handle this? Or at least see where I am going wrong?
The first sub calls the second sub to clear and sort:
Private Sub AddDailyTherapists(PasteToRange As range, TrueFalseRange As range, StartCell As range, SortRange As range)
Call ClearUnselectedTherapists(PasteToRange, TrueFalseRange, StartCell, SortRange)
Dim Names As Object
Set Names = CreateObject("System.Collections.ArrayList")
For Each cel In TrueFalseRange
If cel.value = True Then
Names.Add cel.Parent.Cells(cel.Row, 4).value
End If
Next cel
For Each n In PasteToRange
For Each nm In Names
If nm = n.value Then
Names.Remove nm
End If
Next nm
Next n
StartCell.Activate
For Each nm In Names
Do While (ActiveCell.value <> "-")
ActiveCell.Offset(rowOffset:=1, columnOffset:=0).Activate
Loop
ActiveCell.value = nm
Next nm
End Sub
Public Sub ClearUnselectedTherapists(PasteToRange As range, TrueFalseRange As range, StartCell As range, SortRange As range)
Sheets("All Therapists").Activate
StartCell.Activate
For Each cell In TrueFalseRange
If cell.value = False Then
Name = cell.Parent.Cells(cell.Row, 4).value
For Each cel In PasteToRange
If Name = cel.value Then
cel.value = "-"
cel.Offset(0, 1).range("A1:R1").Select
Selection.ClearContents
Exit For
End If
Next cel
End If
Next cell
With ActiveWorkbook.Worksheets("All Therapists").Sort
.SetRange SortRange
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
The modified spreadsheet:
The sheet with the checkboxes:
Private Sub AddDailyTherapists(PasteToRange As range, TrueFalseRange As range, StartCell As range, SortRange As range)
'
Call ClearUnselectedTherapists(PasteToRange, TrueFalseRange, StartCell, SortRange)
Dim Names(0 To 11) As String
i = 0
For Each cel In TrueFalseRange
If cel.value = True Then
Names(i) = cel.Parent.Cells(cel.Row, 4).value
i = i + 1
End If
Next cel
For Each n In PasteToRange
For j = 0 To UBound(Names)
If Names(j) = n.value Then
Names(j) = ""
End If
Next j
Next n
StartCell.Activate
For k = 0 To UBound(Names)
Do While (ActiveCell.value <> "-")
ActiveCell.Offset(rowOffset:=1, columnOffset:=0).Activate
Loop
If Names(k) <> "" Then
ActiveCell.value = Names(k)
End If
Next k
End Sub

Resources