Worksheet_Change Macro - Changing multiple cells - vba

I wrote a macro and while it works, functionally its not what is needed. It's an interactive checklist that breaks down multiple areas of machines and if they are working checks them off and then this updates a master list with multiple sections. However, it only works with one cell at a time and it needs to be able to work with multiple cells at a time (both with rows and columns). Here's my current code:
'Updates needed:
' Make so more than one cell works at a time
' in both x and y directions
Private Sub Worksheet_Change(ByVal Target As Excel.range)
Dim wb As Workbook
Dim mWS As Worksheet
Dim conName As String
Dim mCol As range
Dim mCon As Integer
Dim count As Long
Dim cell As range
Dim y As String
count = 1
y = ""
Set wb = ActiveWorkbook
Set mWS = wb.Sheets("Master")
Set mCol = mWS.range("B:B")
mCon = 0
'Selects the name of the string value in which we need to search for in master list
If Target.Column < 100 Then
ThisRow = Target.Row
conName = ActiveSheet.Cells(ThisRow, "B")
y = Target.Value
End If
'search for matching string value in master list
For Each cell In mCol
If cell.Value = conName Then
mCon = count
Exit For
End If
count = count + 1
Next
'mark as "x" in Master list
Dim cVal As Variant
Set cVal = mWS.Cells(count, Target.Column)
cVal.Value = y
End Sub
What is happening - If I drag down "x" for multiple rows or columns my codes breaks at y = Target.Value and will only update the cell I first selected and its counterpart on the master list. What it should do is if I drag and drop the "x" onto multiple rows of columns it should update all of them in the sheet I'm working on and the master list. I only set up the macro for one cell at a time and I have no idea how to set it up for dragging and dropping the "x" value for multiple rows

I think you need a For ... Each iteration over the Target in order to work with multiple cells. As Michael noted in the comments, the _Change event fires only once, but the Target reflects all cell(s) that changed, so you should be able to iterate over the Target range. I tested using this simple event handler:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim myRange As Range
Dim myCell As Range
Set myRange = Target
For Each myCell In myRange.Cells
Debug.Print myCell.Address
Next
End Sub
I am not able to test obviously on your data/worksheet, but I think it should put you on the right track.
Private Sub Worksheet_Change(ByVal Target As Excel.range)
Dim wb As Workbook
Dim mWS As Worksheet
Dim conName As String
Dim mCol As range
Dim mCon As Integer
Dim count As Long
Dim cell As range
Dim y As String
count = 1
y = ""
Set wb = ActiveWorkbook
Set mWS = wb.Sheets("Master")
Set mCol = mWS.range("B:B")
mCon = 0
'Add some new variables:
Dim myRange as Range
Dim myCell as Range
Set myRange = Target
Application.EnableEvents = False '## prevents infinite loop
For each myCell in myRange.Cells
If myCell.Column < 100 Then
ThisRow = myCell.Row
conName = ActiveSheet.Cells(ThisRow, "B")
y = myCell.Value
End If
'search for matching string value in master list
For Each cell In mCol
If cell.Value = conName Then
mCon = count
Exit For
End If
count = count + 1
Next
'mark as "x" in Master list
Dim cVal As Variant
Set cVal = mWS.Cells(count, Target.Column)
cVal.Value = y
Next
Application.EnableEvents = True '## restores event handling to True
End Sub

You need to iterate through the cells using a ForEach loop.
Also, you may be better using the Selection object rather than Target
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For Each cell In Selection
Debug.Print cell.Address
Next cell
Application.EnableEvents = True
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub

Related

Excel VBA run macro across dynamic range of sheets

As an extension from the last question I asked, I'm trying to run a macro across all worksheets, which you guys successfully helped me to do.
I've been told that the worksheet names can't be hardcoded, so I'm going to have to modify my current solution.
Sub RemoveCarriageReturns()
Dim MyRange As Range
Dim NameList() As Variant
NameList = Array("OTCUEXTR", "OTFBCUDS", "OTFBCUEL")
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For i = 0 To 2
With Worksheets(NameList(i))
For Each MyRange In .UsedRange
If 0 < InStr(MyRange, Chr(10)) Then
MyRange = Replace(MyRange, Chr(10), "")
End If
Next MyRange
End With
Next i
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
I've tried to populate the array with a For loop that gathers names of each worksheet however I feel after 2 days blankly staring at this, my limited VBA knowledge has run out and I'm stuck, I would really appreciate some pointers on how to get this macro to work across an range of sheets that can change in quantity and names.
Happy to provide any more information you need in a comment
You can do it like this (or could use the index along the lines of your original code).
Sub RemoveCarriageReturns()
Dim MyRange As Range
Dim ws As Worksheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For Each ws In Worksheets
With ws
For Each MyRange In .UsedRange
If 0 < InStr(MyRange, Chr(10)) Then
MyRange = Replace(MyRange, Chr(10), "")
End If
Next MyRange
End With
Next ws
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Public Function GetSheetNames(ByVal wbk As workbook) As String()
Dim names() As String
Dim count As Integer
Dim i As Integer
count = wbk.Worksheets.count
ReDim names(count - 1)
For i = 1 To wbk.Worksheets.count
names(i - 1) = wbk.Worksheets(i).Name
Next
GetSheetNames = names
End Function
Usage: GetSheetNames(Application.ActiveWorkbook)
UPDATE: For selected sheets only:
Public Function GetActiveSheetNames(ByVal wbk As workbook) As String()
Dim names() As String
Dim count As Integer
Dim i As Integer
count = wbk.Windows(1).SelectedSheets.count
ReDim names(count - 1)
For i = 1 To wbk.Windows(1).SelectedSheets.count
names(i - 1) = wbk.Windows(1).SelectedSheets(i).Name
Next
GetActiveSheetNames = names
End Function

Excel VBA Remove content from cells then delete total rows

I am programming to review a range, remove content from the range of cells, then delete the entire row of cells that have been cleared of content. Currently, I run the code and all the rows are getting deleted. Also, I appreciate suggestions to make the code DRY.
Option Explicit
Sub Alfredo()
Dim msg As String
Dim VarCase As Range
Dim ws As Worksheets
Set ws = Sheets("Data")
For Each VarCase In ws.Range("D1:D11000")
If VarCase.Value2 = "John" Or VarCase.Value2 = "Thompson" Or VarCase.Value2 =
"Mattson" Then
VarCase.ClearContents
End If
Next VarCase
For Each VarCase In ws.Range("D1:D11000")
If VarCase.Value = "" Then
Rows.EntireRow.Delete
End If
Next VarCase
End Sub
In your final For loop, you have
Rows.EntireRow.Delete
where you should have
VarCase.EntireRow.Delete
which probably accounts for the general deletion.
The For Each construct doesn't always work happily with a range that is being changed (here by row deletion), so beware that. You could potentially accumulate a Range of deletion targets via Union and delete in one statement at the end for DRYness, without any clearing of contents.
Also, indentation is your friend.
Edit to add illustration of Union approach:
Sub TestRowDelete()
Dim ARange As Range
Dim DRange As Range
Set DRange = Nothing
For Each ARange In ActiveSheet.UsedRange.Rows
If ARange(1).Value = "d" Then ' testing first cell on each row
If DRange Is Nothing Then
Set DRange = ARange
Else
Set DRange = Union(DRange, ARange)
End If
End If
Next ARange
If Not DRange Is Nothing Then DRange.EntireRow.Delete
End Sub
Just put this code under this code under the data worksheet in VBA
Sub Alfredo()
Dim msg As String
Dim VarCase As Range
For Each VarCase In ActiveSheet.Range("D:D")
If VarCase.Value2 = "John" Or VarCase.Value2 = "Thompson" Or VarCase.Value2 = "Mattson" Then
VarCase.ClearContents
End If
Next VarCase
For Each VarCase In ActiveSheet.Range("D:D")
If VarCase.Value = "" Then
VarCase.EntireRow.Delete
End If
Next VarCase
End Sub
Why loop twice?
Application.ScreenUpdating = False
Dim VarCase As Range
Dim ws As Worksheet
Set ws = Sheets("Data")
For Each VarCase In ws.Range("D1:D11000")
If VarCase.Value2 = "John" Or VarCase.Value2 = "Thompson" Or VarCase.Value2 = "Mattson" Then
VarCase.ClearContents
VarCase.EntireRow.Delete
End If
Next VarCase

How to hide/unhide columns added at the borders of the range

I'm trying to create a macro which will hide/unhide a specified range of columns.
Adding a column within the named range isn't problematic, but when adding a column at the borders of this range - macro doesn't work. For example, AM:BF is the named range ("Furniture") in my sheet. I need to add a column BG which will also be hidden by the macro. Same story when adding a new column on the left border. Could you guide me how to improve the code so that the columns added at the borders of the range will also be hidden/unhidden?
With ThisWorkbook.Sheets("Sheet1").Range("Furniture").EntireColumn
.Hidden = Not .Hidden
End With
I've added a variable RangeName (of type String) that equals to the name of the Name Range = "Furniture".
Code
Option Explicit
Sub DynamicNamedRanges()
Dim WBName As Name
Dim RangeName As String
Dim FurnitureNameRange As Name
Dim Col As Object
Dim i As Long
RangeName = "Furniture" ' <-- a String representing the name of the "Named Range"
' loop through all Names in Workbook
For Each WBName In ThisWorkbook.Names
If WBName.Name Like RangeName Then '<-- search for name "Furniture"
Set FurnitureNameRange = WBName
Exit For
End If
Next WBName
' adding a column to the right of the named range (Column BG)
If Not FurnitureNameRange Is Nothing Then '<-- verify that the Name range "Furnitue" was found in workbook
FurnitureNameRange.RefersTo = FurnitureNameRange.RefersToRange.Resize(Range(RangeName).Rows.Count, Range(RangeName).Columns.Count + 1)
End If
' loop through all columns of Named Range and Hide/Unhide them
For i = 1 To FurnitureNameRange.RefersToRange.Columns.Count
With FurnitureNameRange.RefersToRange.Range(Cells(1, i), Cells(1, i)).EntireColumn
.Hidden = Not .Hidden
End With
Next i
End Sub
place the following in your worksheet code pane:
Option Explicit
Dim FurnitureNameRange As Name
Dim adjacentRng As Range
Dim colOffset As Long
Private Sub Worksheet_Change(ByVal Target As Range)
Dim newRng As Range
If colOffset = 1 Then Exit Sub
On Error GoTo ExitSub
Set adjacentRng = Range(adjacentRng.Address)
With ActiveSheet.Names
With .Item("Furniture")
Set newRng = .RefersToRange
.Delete
End With
.Add Name:="Furniture", RefersTo:="=" & ActiveSheet.Name & "!" & newRng.Offset(, colOffset).Resize(, newRng.Columns.Count + 1).Address
End With
ExitSub:
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
On Error Resume Next
Set FurnitureNameRange = ActiveSheet.Names("Furniture") 'ThisWorkbook.Names("Furniture")
On Error GoTo 0
colOffset = 1
Set adjacentRng = Nothing
If FurnitureNameRange Is Nothing Then Exit Sub
Set adjacentRng = Target.EntireColumn
With FurnitureNameRange.RefersToRange
Select Case Target.EntireColumn.Column
Case .Columns(1).Column - 1
colOffset = -1
Case .Columns(.Columns.Count).Column + 1
colOffset = 0
End Select
End With
End Sub

Test if two range objects refer to the same range

I want to find a smarter way to test if two range objects, in fact, refer to the same range:
Set A = Range("B1:B3,A2:C2")
Set B = Range("B1,A2:C2,B3")
Set C = Range("A2,B1:B3,C2")
Set D = Range("B1,A2,B2,C2,B3")
The function I'm trying to write must return True when comparing any pair of ranges described above, and False when comparing any of those ranges to a range containing cells that are not part of the first range or not containing some cells from the first range.
What algorithm other than going cell by cell and checking that Intersect() is not Nothing is there for this problem?
I wrote this code on another forum some years back as a quick method to add a Subtract Range option, the same approach I used in Fast method for determining unlocked cell range
background
This function accepts two ranges, removes the cells where the two ranges intersect, and then produces a string output containing the address of the reduced range. This is done by:
creating a new one-sheet WorkBook
entering the N/A formula into all the cells on this sheet contained in rng1,
clearing the contents of all cells on this sheet that are contained by rng2,
using SpecialCells to return the remaining N/A formulae which represents the cells in rng1 that are not found in rng2,
If the Boolean variable, bBothRanges, is set to True, then the process is repeated with the cells with the opposite range order,
the code then returns the "reduced" range as a string, then closes the WorkBook.
As an example:
'Return the hidden cell range on the ActiveSheet
Set rngTest1 = ActiveSheet.UsedRange.Cells
Set rngTest2 = ActiveSheet.UsedRange.SpecialCells(xlVisible)
If rngTest1.Cells.Count > rngTest2.Cells.Count Then
strTemp = RemoveIntersect(rngTest1, rngTest2)
MsgBox "Hidden cell range is " & strTemp, vbInformation
Else
MsgBox "No hidden cells", vbInformation
End If
In your case the code runs the bBothRanges option and then checks if the RemoveIntersect returns vbNullStringto see if the ranges are the same.
For very short ranges as you have provided, a simple cell by cell loop would suffice, for larger ranges this shortcut may be useful.
Sub Test()
Dim A As Range, B As Range, C As Range, D As Range
Set A = Range("B1:B3,A2:C2")
Set B = Range("B1,A2:C2,B3")
Set C = Range("A2,B1:B3,C2")
Set D = Range("B1,A2,B2,C2,B3")
MsgBox RemoveIntersect(A, B, True) = vbNullString
End Sub
main
Function RemoveIntersect(ByRef rng1 As Range, ByRef rng2 As Range, Optional bBothRanges As Boolean) As String
Dim wb As Workbook
Dim ws1 As Worksheet
Dim rng3 As Range
Dim lCalc As Long
'disable screenupdating, event code and warning messages.
'set calculation to Manual
With Application
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = False
lCalc = .Calculation
.Calculation = xlCalculationManual
End With
'add a working WorkBook
Set wb = Workbooks.Add(1)
Set ws1 = wb.Sheets(1)
On Error Resume Next
ws1.Range(rng1.Address).Formula = "=NA()"
ws1.Range(rng2.Address).Formula = vbNullString
Set rng3 = ws1.Cells.SpecialCells(xlCellTypeFormulas, 16)
If bBothRanges Then
ws1.UsedRange.Cells.ClearContents
ws1.Range(rng2.Address).Formula = "=NA()"
ws1.Range(rng1.Address).Formula = vbNullString
Set rng3 = Union(rng3, ws1.Cells.SpecialCells(xlCellTypeFormulas, 16))
End If
On Error GoTo 0
If Not rng3 Is Nothing Then RemoveIntersect = rng3.Address(0, 0)
'Close the working file
wb.Close False
'cleanup user interface and settings
'reset calculation
With Application
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = True
lCalc = .Calculation
End With
End Function
You could always do it manually, like this:
Private Function isRangeEquivalent(ByRef range1 As Range, ByRef range2 As Range) As Boolean
isRangeEquivelent = (range1.Cells.Count = range2.Cells.Count)
If isRangeEquivelent Then
Dim addresses As collection
Set addresses = New collection
Dim cell As Range
For Each cell In range1.Cells
Call addresses.Add(cell.Address, cell.Address)
Next cell
For Each cell In range2.Cells
If Not isInCollection(addresses, cell.Address) Then
isRangeEquivelent = False
Exit For
End If
Next cell
End If
End Function
Private Function isInCollection(ByRef collection As collection, ByVal sKey As String)
On Error GoTo Catch
collection.Item sKey
isInCollection = True
Exit Function
Catch:
isInCollection = False
End Function

Splitting data from barcode into different cells in MS Excel

I have a barcode scanner USB plug&play which is giving a string of data in one cell of Excel in this form 4449520450061198001
I want to split this data automatically in different cells everytime my scanner reads the code.
Please help.
Regards,
UPDATED
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const ws_range = "A1:A10"
Dim wb As Workbook
Dim ws As Worksheet
Dim i As Integer, k As Integer
Dim codestr As String
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
codestr = Target.Text
If Target <> "" Then
If Not Intersect(Target, Me.Range(ws_range)) Is Nothing Then
With Target
k = Len(codestr)
i = 2
Do Until i = k + 2
ws.Cells(Target.Row, i).Value = Mid(codestr, i - 1, 1)
i = i + 1
Loop
End With
End If
End If
End Sub
I haven't fully tested this but now after a value is inserted into column a it will be split into the cells to the right. Obviously modify A1:A10 to match what you need.