Looping through a range to find a value - excel

I have a worksheet that has columns 1-8, rows 3 through the last row. I would like to loop through each cell to find out if a value of 1 is present. If it is then that row is copied and inserted for each value of 1, additionally that new row will have a text inserted in cell (13,row) then moved to the next row. This is as far as I got....thanks!
Sub Workcenter()
Application.ScreenUpdating = False
On Error Resume Next
Application.DisplayAlerts = False
On Error GoTo 0
Dim Test As Worksheet
Set TS = Worksheets("Test")
Application.DisplayAlerts = True
For k = 1 To 8
For j = 4 To TS.Cells(Rows.Count, k).End(xlUp).Row
If TS.Cells(j, k).Value = 1 Then TS.Cells.Activate
'TS.Cells.Activate.Row.Select
Rows(ActiveCell.Row).Select
Selection.Copy
Selection.Insert Shift:=xlDown
'ShopOrderNumRow = j
Next j
Next k
End Sub

Will try giving some example knowing that I still don't understand how the inserting is occurring for each cell of a row.
Providing more detail, or example of before/after in your post may help.
As for an example, since you're marking only a single cell in each row, I would suggest Find() for value of 1 to determine if you need to write to that specific cell.
'untested code
sub test()
toggle false
dim rowNum as long
for rowNum = firstRow to lastRow Step 1
with sheets(1)
with .range(.cells(rowNum,1),.cells(rowNum,8))
dim foundCell as range
set foundCell = .find(1)
if not foundCell is nothing then .cells(rowNum,13).value = "text"
end with
end with
next iterator
toggle true
end sub
private sub toggle(val as boolean)
with application
.screenupdating = val
.enableevents = val
end with
end sub
Edit1: Looks like countif() may be the saviour here.
Edit2: Tested code input (untested code part of Edit1)
Sub test()
Dim lastRow As Long: lastRow = 10
Dim firstRow As Long: firstRow = 1
toggle False
Dim rowNum As Long
For rowNum = lastRow To firstRow Step -1
With Sheets(1)
Dim countRange As Range
Set countRange = .Range(.Cells(rowNum, 1), .Cells(rowNum, 8))
Dim countOfOnes As Long
countOfOnes = Application.CountIf(countRange, 1)
If countOfOnes > 0 Then
With .Rows(rowNum)
.Copy
.Offset(1).Resize(countOfOnes).Insert Shift:=xlDown
End With
.Cells(rowNum, 13).Value = "text"
End If
End With
Next rowNum
toggle True
End Sub
Private Sub toggle(val As Boolean)
With Application
.ScreenUpdating = val
.EnableEvents = val
End With
End Sub
Tested using this data:
Output from running code:

Related

Excel VBA - How to remove text of a certain color from every cell in a column

Edit: Now its working much better but the code starts deleting non-black text from other columns as well ?_? the code works for other worksheets so I'm not sure why it only doesn't work for this one... :"( pls help
I have an excel sheet with text that has multiple colors in the same cell e.g. blue and black words in the same cell. I want to remove all the blue words. I wrote a loop that loops through the cells and every character in the cells in the entire column and writes the black words back to each cell. However it takes a really long time so its not very feasible. Also I tried using arrays but I'm not sure how to store the format alongside the value into the array :"( Thanks!
Sub deletecommentsRight_New()
Dim lrow As Long
Dim textOut As String
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
lrow = LastRow()
Range("M1:M" & lrow).Select
For Each Cell In Selection
textOut = ""
For i = 1 To Len(Cell)
If (((Cell.Characters(i, 1).Font.ColorIndex = 1) Or (Cell.Characters(i, 1).Font.ColorIndex = -4105)) And Not (Cell.Characters(i, 1).Font.Strikethrough)) Then
textOut = textOut & Mid(Cell, i, 1)
End If
Next
Cell.Value = textOut
Cell.Font.ColorIndex = 1
Next Cell
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Public Function LastRow() As Long
'Finds the last non-blank cell on a sheet/range.
Dim lrow As Long
Dim lCol As Long
lrow = Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
LastRow = lrow
End Function
First of all, you should always use Option Explicit at the top of your modules so that it forces you to declare all variables properly.
There is no need to loop through an entire column. Only loop through cells that actually have values. For that we can use the Worksheet.UsedRange property and do an Intersect with the desired range.
Also code should be able to ignore errors and numbers since you are only interested in texts.
Also, there is no need to read the cell value multiple times so best is to read them just once using an array. A With construct can help in reading the cell font colors easily.
Here is what I came up with - kept the original method name:
Option Explicit
Public Sub DeleteComments(ByVal rng As Range)
Dim tempRng As Range
Dim tempArea As Range
Set tempRng = GetUsedRange(rng)
If tempRng Is Nothing Then Exit Sub
'Store app state and turn off some features
Dim scrUpdate As Boolean: scrUpdate = Application.ScreenUpdating
Dim calcMode As XlCalculation: calcMode = Application.Calculation
Dim evEnabled As Boolean: evEnabled = Application.EnableEvents
With Application
If .ScreenUpdating Then .ScreenUpdating = False
If calcMode <> xlCalculationManual Then .Calculation = xlCalculationManual
If .EnableEvents Then .EnableEvents = False
End With
'Loop through all areas. Check/update only relevant values
For Each tempArea In tempRng.Areas
If tempArea.Count = 1 Then
UpdateCell tempArea, tempArea.Value2
Else
Dim arr() As Variant: arr = tempArea.Value2 'Read whole range into array
Dim rowsCount As Long: rowsCount = tempArea.Rows.Count
Dim i As Long: i = 1
Dim j As Long: j = 1
Dim v As Variant
'For Each... loop is faster than using 2 For... Next loops on a 2D array
For Each v In arr 'Column-major order
If VarType(v) = vbString Then 'Only check strings - ignore numbers and errors
If Len(v) > 0 Then UpdateCell tempArea.Cells(i, j), v
End If
i = i + 1
If i > rowsCount Then 'Switch to the next column
j = j + 1
i = 1
End If
Next v
End If
Next tempArea
'Restore app state
With Application
If scrUpdate Then .ScreenUpdating = True
If calcMode <> xlCalculationManual Then .Calculation = calcMode
If evEnabled Then .EnableEvents = True
End With
End Sub
Private Function GetUsedRange(ByVal rng As Range) As Range
If rng Is Nothing Then Exit Function
On Error Resume Next
Set GetUsedRange = Intersect(rng, rng.Worksheet.UsedRange)
On Error GoTo 0
End Function
Private Function UpdateCell(ByVal cell As Range, ByVal value As Variant)
Dim textOut As String
Dim charExcluded As Boolean
Dim i As Long
For i = 1 To Len(value)
With cell.Characters(i, 1).Font
If (.ColorIndex = 1 Or .ColorIndex = -4105) And Not .Strikethrough Then
textOut = textOut & Mid$(value, i, 1)
Else
charExcluded = True
End If
End With
Next i
If charExcluded Then cell.Value2 = textOut
If IsNull(cell.Font.ColorIndex) Then
cell.Font.ColorIndex = 1
ElseIf cell.Font.ColorIndex <> 1 Then
cell.Font.ColorIndex = 1
End If
End Function
As you can see, I've split the code in a few auxiliary functions so that is easier to maintain.
To use it just call it on the desired range. For example:
DeleteComments Selection 'if you already have a selected range
'Or
DeleteComments Range("M:M") 'as in your original post
An added benefit is that this code works regardless if your desired range is a column, a row, multiple columns/rows or even multi-area ranges. Gives you a lot of flexibility and is as fast as you could make it.
Edit #1
The UpdateCell function could be faster if we only check cells with mixed colors:
Private Function UpdateCell(ByVal cell As Range, ByVal value As Variant)
Dim textOut As String
Dim charExcluded As Boolean
Dim i As Long
If IsNull(cell.Font.ColorIndex) Then
For i = 1 To Len(value)
With cell.Characters(i, 1).Font
If (.ColorIndex = 1 Or .ColorIndex = -4105) And Not .Strikethrough Then
textOut = textOut & Mid$(value, i, 1)
Else
charExcluded = True
End If
End With
Next i
If charExcluded Then cell.Value2 = textOut
cell.Font.ColorIndex = 1
ElseIf cell.Font.ColorIndex <> 1 Then
cell.Value2 = Empty
cell.Font.ColorIndex = 1
End If
End Function
You are doing this for over a million cells, most of them are empty. If you start by checking that the cell is not empty, you might heavily improve the performance.
Building on the suggestions provided, here is the modified code. Since the original code worked on selection, an option to ask the user to select a range is opted than defining fixed ranges.
Sub deletecomments()
Dim textOut As String
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'----------------------------
Dim myrange As Range
ThisWorkbook.Sheets("Sheet1").Activate 'Change Workbook and Sheet name accordingly
Set myrange = Application.InputBox(Title:="Range Selector", Prompt:="Please select your Range.", Type:=8)
'--------------------------
For Each Cell In myrange 'Replace selection with myRange
textOut = ""
For i = 1 To Len(Cell)
If (((Cell.Characters(i, 1).Font.ColorIndex = 1) Or (Cell.Characters(i, 1).Font.ColorIndex = -4105)) And Not (Cell.Characters(i, 1).Font.Strikethrough)) Then
textOut = textOut & Mid(Cell, i, 1)
End If
Next
Cell.value = textOut
Cell.Font.ColorIndex = 1
Next Cell
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

Clean trim cells from column A and delete entire row with blank cell in column A

I am trying to trim clean column A then filter the blank cells in column A and delete the entire row based on blank cell present in column A.
Column A has a few blank cells. Those blank cells might have a space hence I first want to clean and trim column A and then filter on column A blank cell and delete the entire row.
Desired output:
Sub trimclean()
Dim lRow As Integer, i As Long
With Worksheets("Sandy")
lRow = .Range("A1").End(xlDown).Row
For i = 2 To lRow
.Cells(i, "A").Value = Trim(.Cells(i, "A").Value)
Next i
End With
End Sub
Sub DeleteBlanks()
Dim rDataToProcess As Range
Set rDataToProcess = ActiveWorkbook.Worksheets("Sandy").Range("A1").End(xlDown).Row.CurrentRegion
'Field in the below method refers to the column that is being filtered, so the second colum
rDataToProcess.AutoFilter field:=2, Criteria1:=""
rDataToProcess.Offset(1).Resize(rDataToProcess.Rows.Count).EntireRow.Delete
Sheet1.AutoFilterMode = False
End Sub
Problem in my code
For a start, it looks to be that your "Range" is actually a qualified table. If so, you can refer to the ListObjects in the worksheet and it makes it easier to modify the table.
It loops but at least you can see what it's doing. If you wanted it to delete all rows in a single call then that is possible but too many rows in the table and the deletion would need to be broken out and packetised.
Also, I'm not sure if you want to do it in two steps but I've provided for that here ...
2 Steps
Public Sub TrimCells()
Dim objTable As ListObject, lngRow As Long, lngColumnToTrim As Long
lngColumnToTrim = 1
Set objTable = GetTable
TogglePerformance False
With objTable.DataBodyRange
For lngRow = 1 To .Rows.Count
.Cells(lngRow, lngColumnToTrim) = Trim(.Cells(lngRow, lngColumnToTrim))
Next
End With
TogglePerformance True
End Sub
Public Sub DeleteBlankRows()
Dim objTable As ListObject, lngRow As Long, lngColumnToCheckForBlank As Long
lngColumnToCheckForBlank = 1
Set objTable = GetTable
TogglePerformance False
With objTable.DataBodyRange
For lngRow = .Rows.Count To 1 Step -1
If Len(.Cells(lngRow, lngColumnToCheckForBlank).Value) = 0 Then
.Rows(lngRow).Delete xlShiftUp
End If
Next
End With
TogglePerformance True
End Sub
Private Function GetTable() As ListObject
Set GetTable = ThisWorkbook.Worksheets("Sandy").ListObjects("MyTable")
End Function
Private Sub TogglePerformance(ByVal bOn As Boolean)
Application.ScreenUpdating = bOn
Application.EnableEvents = bOn
If bOn Then
Application.Calculation = xlCalculationAutomatic
Else
Application.Calculation = xlCalculationManual
End If
End Sub
... but if you're happy to do it one one step then that'd be easier I would've though.
1 Step
Public Sub DeleteBlankRows()
Dim objTable As ListObject, lngRow As Long, lngColumnToCheckForBlank As Long
lngColumnToCheckForBlank = 1
Set objTable = ThisWorkbook.Worksheets("Sandy").ListObjects("MyTable")
TogglePerformance False
With objTable.DataBodyRange
For lngRow = .Rows.Count To 1 Step -1
If Len(Trim(.Cells(lngRow, lngColumnToCheckForBlank).Value)) = 0 Then
.Rows(lngRow).Delete xlShiftUp
End If
Next
End With
TogglePerformance True
End Sub
Private Sub TogglePerformance(ByVal bOn As Boolean)
Application.ScreenUpdating = bOn
Application.EnableEvents = bOn
If bOn Then
Application.Calculation = xlCalculationAutomatic
Else
Application.Calculation = xlCalculationManual
End If
End Sub
... you just need to make sure you change your Table Name in the code or change it on the sheet itself.
I've also assumed that you want to check in the first column of the table. That made sense given you were checking column A on sheet.
Bottom line, your table could be anywhere and this would still work.

VBA Merge Similar Cells

I would like to merge similar cells by columns, as of now I am using this macro
Sub MergeSimilarCells()
Set myRange = Range("A1:Z300")
CheckAgain:
For Each cell In myRange
If cell.Value = cell.Offset(0, 1).Value And Not IsEmpty(cell) Then
Range(cell, cell.Offset(0, 1)).Merge
cell.VerticalAlignment = xlCenter
cell.HorizontalAlignment = xlCenter
GoTo CheckAgain
End If
Next
End Sub
My problem is with hundreds of rows and 40-50 columns, it takes forever.
I am pretty sure a For Loop could help me there but I am not skilled enough to figure it out
I know the following code is wrong but I am lost
Sub SimilarCells()
Set myRange = Range("A1:G4")
Dim count As Integer
CheckAgain:
count = 1
For Each cell In myRange
If cell.Value = cell.Offset(0, 1).Value And Not IsEmpty(cell) Then
count = count + 1
ElseIf cell.Value <> cell.Offset(0, 1).Value Then
Range(cell, cell.Offset(0, -count)).Merge
End If
Next
End Sub
Here is what I would like to accomplish
Sub MergeMe()
Dim wks As Worksheet: Set wks = Worksheets(1)
Dim myRange As Range: Set myRange = wks.Range("B2:H5")
Dim myCell As Range
Dim myCell2 As Range
Dim firstColumn As Long: firstColumn = myRange.Columns(1).column + 1
Dim lastColumn As Long: lastColumn = firstColumn + myRange.Columns.Count - 1
Dim firstRow As Long: firstRow = myRange.Rows(1).row
Dim lastRow As Long: lastRow = firstRow + myRange.Rows.Count - 1
Dim column As Long
Dim row As Long
OnStart
For column = lastColumn To firstColumn Step -1
For row = lastRow To firstRow Step -1
Set myCell = wks.Cells(row, column)
Set myCell2 = myCell.Offset(0, -1)
If myCell.Value = myCell2.Value Then
With wks.Range(myCell, myCell2)
.Merge
.VerticalAlignment = xlCenter
.HorizontalAlignment = xlCenter
End With
End If
Next row
Next column
OnEnd
End Sub
There are quite a few tricks in this code:
we need to get the first and last column and row;
then we should be looping from the last cell (bottom right) to the first one (top left);
we should not enter the first column, because we are using .Offset(0,-1) and we compare every cell with its leftmost one;
the reason for the whole operation, is that by default, the value of a merged cells is kept in its left top cell. The other cells of a merged cell are without a value.
This is why we always compare the merged cells with their "left" neighbour;
These are the OnEnd and OnStart, facilitating the operation.
Public Sub OnEnd()
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.AskToUpdateLinks = True
Application.DisplayAlerts = True
Application.Calculation = xlAutomatic
ThisWorkbook.Date1904 = False
Application.StatusBar = False
End Sub
Public Sub OnStart()
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.AskToUpdateLinks = False
Application.DisplayAlerts = False
Application.Calculation = xlAutomatic
ThisWorkbook.Date1904 = False
ActiveWindow.View = xlNormalView
End Sub
Only one merge per group
EDITED to fix - thanks Vityata for the heads-up
Sub MergeEm()
Dim rw As Range, i As Long, s As Long, v
Range("C21:J33").Copy Range("C5:J17") 'for testing purposes: replace previous run
Application.ScreenUpdating = False
For Each rw In Range("C5:J17").Rows 'or wherever
i = 1
s = 1
Do While i < (rw.Cells.Count)
v = rw.Cells(i).Value
'check for a run of same values
Do While Len(v) > 0 And v = rw.Cells(i + s).Value
s = s + 1
If i + s > rw.Cells.Count Then Exit Do
Loop
'if s>1 then had a run: merge those ells
If s > 1 Then
Application.DisplayAlerts = False
rw.Cells(i).Resize(1, s).Merge
rw.Cells(i).HorizontalAlignment = xlCenter
Application.DisplayAlerts = True
i = i + s 'skip over the merged range
s = 1 'reset s
Else
i = i + 1
End If
Loop
Next rw
End Sub
I'm pretty sure what bloats your processing time is the goto causing you to loop through everything yet again every time after every merge
Edit to take column A into account and prevent first column cells to merge with cells outside of myRange:
Sub MergeSimilarCells()
Dim i As Long
Dim myCol As String
Set myRange = Range("K1:L30")
myCol = Left(myRange.Address(True, False), InStr(myRange.Offset(0, 1).Address(True, False), "$") - 1)
If Not Intersect(myRange, Range(myCol & ":" & myCol)).Address = myRange.Address Then
Set myRange = Range(Replace(myRange.Address, Left(myRange.Address(True, False), _
InStr(myRange.Address(True, False), "$")), Left(myRange.Offset(0, 1).Address(True, False), _
InStr(myRange.Offset(0, 1).Address(True, False), "$"))))
For i = myRange.Cells.Count To 1 Step -1
If myRange.Item(i).Value = myRange.Item(i).Offset(0, -1).Value And Not IsEmpty(myRange.Item(i)) Then
Range(myRange.Item(i), myRange.Item(i).Offset(0, -1)).Merge
myRange.Item(i).VerticalAlignment = xlCenter
myRange.Item(i).HorizontalAlignment = xlCenter
End If
Next
End If
End Sub
To clarify why myRange has to start in column B: Offset(0, -1) of any cell in column A will cause an error since there is no column to the left of A.

Output Range same as input range

I have some history working with VBA, but can't seem to find the solution to this problem. I found an iteration process to select a cell, do a process, and then select the next cell and do the process again, until NULL. I am having a problem outputting each of the processes solutions into the next column. Here is what I have:
Sub Name ()
Dim X As Integer
Dim MyString as String
Application.ScreenUpdating = False
NumRows = Range("D2", Range("D2").End(xlDown)).Rows.Count
Range("D2").Select
For X = 1 To NumRows
MyString = ActiveCell.Value
MyString = Right(MyString, Len(MyString)-6)
Range("I2 to I#").Value = MyString
ActiveCell.Offset(1,0).Select
Next X
End Sub
Range("I2 to I#").Value = MyString is the line that I need help with. I need it to increment to I3, I4, I5, etc. until it reaches NumRows count.
When working with Cells the best way to loop through them is For Each Cell in Range so taking this and as comments told you to avoid selecting, this should help you:
Option Explicit
Sub Name()
Dim C As Range, MyRange As Range
Dim LastRow As Long
Application.ScreenUpdating = False
With ThisWorkbook.Sheets("MySheet") 'Change MySheet for your working sheet name
LastRow = .Cells(.Rows.Count, "D").End(xlUp).Row 'last row on column D
Set MyRange = .Range("D2:D" & LastRow) 'declare your working range
For Each C In MyRange
If Not C = vbNullString Then .Cells(C.Row, "I") = Right(C, Len(C) - 6)
Next C
End With
Application.ScreenUpdating = True
End Sub
Another solution is Do Until. You could use this method if you dont have empty cells in the middle of your data.
Option Explicit
Sub Test()
Dim StartingPoint As Long
StartingPoint = 2 'Set the line to begin
With ThisWorkbook.Worksheets("Sheet1") 'Set the worksheet
Do Until .Cells(StartingPoint, "D").Value = "" 'Repeat the process until you find empty cell
.Cells(StartingPoint, "I").Value = Right(.Cells(StartingPoint, "D").Value, Len(.Cells(StartingPoint, "D").Value) - 6)
StartingPoint = StartingPoint + 1
Loop
End With
End Sub

Speed up VBA code on extracting relevant rows to new worksheet

I need to copy relevant rows to a new Excel worksheet. The code will loop through each row in the original worksheet and select rows based on relevant countries and products specified in the array into the second worksheet.
Private Sub CommandButton1_Click()
a = Worksheets("worksheet1").Cells(Rows.Count, 2).End(xlUp).Row
Dim countryArray(1 To 17) As Variant
Dim productArray(1 To 17) As Variant
' countryArray(1)= "Australia" and so on...
' productArray(1)= "Product A" and so on...
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For i = 3 To a
For Each j In countryArray
For Each k In productArray
Sheets("worksheet1").Rows(i).Copy Destination:=Sheets("worksheet2").Range("A" & Rows.Count).End(xlUp).Offset(1)
Next
Next
Next
Application.CutCopyMode = False
Application.ScreenUpdating = False
End Sub
Each time I ran the code, the spreadsheet stopped responding in a matter of minutes. Would appreciate if someone could help me on this, thanks in advance!
Here are some pointers:
Remember to declare all your variables and use Option Explicit at the top of your code
Use With statements to ensure working with right sheet and not implicit activesheet or you may end up with wrong end row count
Only i contributes to the loop so you are making unnecessary loop work
Gather qualifying ranges with Union and copy in one go
Remember to switch back on screen-updating
Code:
Option Explicit
Private Sub CommandButton1_Click()
Dim unionRng As Range, a As Long, i As Long
With Worksheets("worksheet1")
a = .Cells(.Rows.Count, 2).End(xlUp).Row
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For i = 3 To a
If Not unionRng Is Nothing Then
Set unionRng = Union(unionRng, .Cells(i, 1))
Else
Set unionRng = .Cells(i, 1)
End If
Next
With Worksheets("worksheet2")
unionRng.EntireRow.Copy .Cells(.Rows.Count, "A").End(xlUp).Row +1
End With
End With
Application.CutCopyMode = False
Application.ScreenUpdating = True
End Sub

Resources