How to avoid using select in VBA for variable cell ranges? - vba

I have heard of the dislike for using .select in VBA for excel macros, but I am wondering how my particular goal can be achieved without its use? For example, say there is a cell(used as a header) with the value "Commodity". Beneath it, all cells need to have a VLookup function. However, on each and every iteration of the macro, the column will shift (as new columns are added) and new rows will be added (so that newly added rows will need to have the function added as well). How is it possible to consistently locate this Commodity column and find its lowest unfilled row? It is very simple to do using select:
Do Until ActiveCell.Value = "Commodity"
Activecell.offset(0,1).select
loop
Do Until ActiveCell.Value = ""
ActiveCell.offset(1,0).select
loop
Obviously, I would prefer to avoid using this type of syntax, but I do not know how to get around it. All answers I have seen regarding the avoidance of select appear to set, for example, rng = Cell(x,y) or something, but they are always known-location cells. I do not know how to do this without utilizing select to check cell values.

First find the column that your Sting is located, then count the rows beside it, set your range and enter the formula.
Sub FindColumn()
Dim f As Range, c As Integer
Dim LstRw As Long, rng As Range
Set f = Rows(1).Find(what:="Commodity", lookat:=xlWhole)
If Not f Is Nothing Then
c = f.Column
Else: MsgBox "Not Found"
Exit sub
End If
LstRw = Cells(Rows.Count, c - 1).End(xlUp).Row
Set rng = Range(Cells(2, c), Cells(LstRw, c))
rng = "My Formula"
End Sub

Here are two iterate rows to based on the ActiveCell.
Sub Examples()
Dim Target As Range
Dim x As Long
Set Target = ActiveCell
Do Until Target.Value = "Commodity"
Set Target = Target.Offset(0, 1)
Loop
Do Until ActiveCell.Offset(x, 1).Value = ""
x = x + 1
Loop
End Sub

Assuming the wanted header IS there, you can use this function:
Function FindLowestUnfilledCell(headerRow As Range, header As String) As Range
With headerRow.Find(What:=header, lookat:=xlWhole, LookIn:=xlValues, MatchCase:=False) '<--| look for header in passed row
Set FindLowestUnfilledCell = headerRow.Parent.Cells(headerRow.Parent.Rows.Count, .Column).End(xlUp)
End With
End Function
to be used by your main sub as follows
Sub main()
FindLowestUnfilledCell(Rows(1), "Commodity").Formula = "myformula"
End Sub
should the absence of the wanted header be handled, the same function gets a little longer like follows
Function FindLowestUnfilledCell(headerRow As Range, header As String) As Range
Dim r As Range
Set r = headerRow.Find(What:=header, lookat:=xlWhole, LookIn:=xlValues, MatchCase:=False) '<--| look for "Commodity" in row 1
If Not r Is Nothing Then Set FindLowestUnfilledCell = headerRow.Parent.Cells(headerRow.Parent.Rows.Count, r.Column).End(xlUp)
End Function
and its exploitation would consequently take into account the possibility of not founding the wanted header:
Sub main()
Dim lowestUnfilledRange As Range
Set lowestUnfilledRange = FindLowestUnfilledCell(Rows(1), "Commodity")
If Not lowestUnfilledRange Is Nothing Then lowestUnfilledRange.Formula = "myformula"
End Sub

I want to simplify the answer a bit. For example
Set r = ActiveCell
MsgBox r.Address ' $A$1
Columns("A").Insert ' insert column before the first column
MsgBox r.Address ' $B$1
so you can change your code to
Dim cell As Range ' optional
Set cell = ActiveCell
While cell = "Commodity"
Set cell = cell(, 2) ' similar to Set cell = cell.Resize(1,1).Offset(, 1)
Wend
While cell = ""
Set cell = cell(, 2)
Wend

Related

VBA: Referring to active cells' row in a For/Each loop

the aim of my problem is to find a specific value (Text) and then refer to the entire row (or even better only the used range to the right of my active cell) in a For/Each loop.
The first part works fine of finding my value, however, the code for targeting the row of the active cell (so the cell found by the find function), does not work yet:
Sub Search()
Dim cell As Range
Dim Count As Long
Set cell = Cells.Find(what:="Planned Supply at BP|SL (EA)", LookIn:=xlValues, lookat:=xlWhole)
For Each cell In ActiveCell.EntireRow
If cell.Value = "0" Then
Count = Count + 1
End If
Next cell
Range("I1").Value = Count
End Sub
The following code will find the range to the right of your found cell and use your loop to do the comparision for each cell in the range. That part could probably be improved by using WorksheetFunction.CountIf.
Option Explicit
Sub Search()
Dim wks As Worksheet
Set wks = ActiveSheet
Dim cell As Range, sngCell As Range
Dim Count As Long
Set cell = wks.Cells.Find(what:="Planned Supply at BP|SL (EA)", LookIn:=xlValues, lookat:=xlWhole)
If cell Is Nothing Then Exit Sub ' just stop in case no hit
Dim rg As Range, lastColumn As Long
With wks
lastColumn = .Cells(cell.Row, .Columns.Count).End(xlToLeft).Column ' last used column in cell.row
Set rg = Range(cell, .Cells(cell.Row, lastColumn)) ' used rg right from found cell inlcuding found cell
End With
' loop from the original post
For Each sngCell In rg
If sngCell.Value = "0" Then
Count = Count + 1
End If
Next
Range("I1").Value = Count
End Sub

Looping through a column to move cells with font size 10 down one row

I have section title cells set at 10 pt font while all other data is set at 9 point font in column A. I am trying to write a vba macro to loop through column A to move each title cell down one row(because the csv leaves a blank cell below them) then move on to the next title cell in the column. Here is my attempt but I'm not sure what I'm doing wrong here.
Sub FontSpacing()
Dim Fnt As Range
For Each Fnt In Range("A8:A5000")
If Fnt.Font.Size = "10" Then
ActiveCell.Cut Destination:=ActiveCell.Offset(",1")
End If
Next
Try this
Sub FontSpacing()
Dim r As Range
For Each r In ThisWorkbook.Worksheets("Sheet1").Range("A8:A5000")
If r.Font.Size = 10 Then
r.Offset(1,0).Value = r.Value
r.Value = vbNullString
End If
Next r
End Sub
The issues:
Offset(",1") shouldn't have the speech marks. I.e. it should be Offset(0,1). In fact, if you want to paste to the row below, then it should be Offset(1,0).
Avoid using ActiveCell. It's not the cell that is looping through your range, it's just the cell that was active on the worksheet when you ran the sub.
Fnt is a bad name for a range, it's probably the reason you got confused. When declaring (dimensioning) a range, try to give it a name that makes it clear you're working with a range.
Extra:
Fully qualify your range reference to avoid an implicit reference to the ActiveSheet e.g. ThisWorkbook.Worksheets("Sheet1").Range("A1").
Avoid cutting an pasting by setting the Value directly
Your indentation is out, which makes it look like a complete Sub, but it's missing the End Sub.
Not sure if you meant 1 Row below or 1 Column right so:
To shift 1 Column:
Sub FontSpacing()
Dim rng As Range, cell As Range
Set rng = Range("A1:A5000")
For Each cell In rng
If cell.Font.Size = "10" Then
cell.Offset(0, 1).Value = cell.Value
cell.Clear
End If
Next
End Sub
To shift 1 Row:
Sub FontSpacing()
Dim rng As Range, cell As Range
Set rng = Range("A1:A5000")
For Each cell In rng
If cell.Font.Size = "10" Then
a = cell.Row + 1
Rows(a & ":" & a).Insert Shift:=xlDown, CopyOrigin:=1
cell.Offset(1, 0).Value = cell.Value
cell.Offset(1, 0).Font.Size = "11"
cell.Clear
End If
Next
End Sub

Find Column Header By Name And Select All Data Below Column Header (Excel-VBA)

I'm attempting to create a macro to do the following:
Search a spreadsheet column header by name.
Select all data from the selected column, except column header.
Take Number Stored As Text & Convert to Number.
Converting to Number to use for VLookup.
For Example:
Visual Spreadsheet Example:
I've discovered the following code online:
With ActiveSheet.UsedRange
Set c = .Find("Employee ID", LookIn:=xlValues)
If Not c Is Nothing Then
ActiveSheet.Range(c.Address).Offset(1, 0).Select
End If
End With
However, I'm still experiencing some issues.
I just stumbled upon this, for me the answer was pretty straightforward, in any case If you're dealing with a ListObject then this is the way to go:
YOURLISTOBJECT.HeaderRowRange.Cells.Find("A_VALUE").Column
It is good to avoid looping through all cells. If the data set grows the macro can become too slow. Using special cells and paste special operation of multiplying by 1 is an efficient way of accomplishing the task.
This works...
Dim SelRange As Range
Dim ColNum As Integer
Dim CWS As Worksheet, TmpWS As Worksheet
'Find the column number where the column header is
Set CWS = ActiveSheet
ColNum = Application.WorksheetFunction.Match("Employee ID", CWS.Rows(1), 0)
'Set the column range to work with
Set SelRange = CWS.Columns(ColNum)
'Add a worksheet to put '1' onto the clipboard, ensures no issues on activesheet
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Set TmpWS = ThisWorkbook.Worksheets.Add
With TmpWS
.Cells(1, 1) = 1
.Cells(1, 1).Copy
End With
'Select none blank cells using special cells...much faster than looping through all cells
Set SelRange = SelRange.SpecialCells(xlCellTypeConstants, 23)
SelRange.PasteSpecial Paste:=xlPasteAll, Operation:=xlMultiply
TmpWS.Delete
CWS.Select
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Try this out. Simply add all the column header names you want to find into the collection. I'm assuming you don't have more than 200 columns, if you do simply update the for i = 1 to 200 section to a larger number.
Public Sub FindAndConvert()
Dim i As Integer
Dim lastRow As Long
Dim myRng As Range
Dim mycell As Range
Dim MyColl As Collection
Dim myIterator As Variant
Set MyColl = New Collection
MyColl.Add "Some Value"
MyColl.Add "Another Value"
lastRow = ActiveSheet.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
For i = 1 To 200
For Each myIterator In MyColl
If Cells(1, i) = myIterator Then
Set myRng = Range(Cells(2, i), Cells(lastRow, i))
For Each mycell In myRng
mycell.Value = Val(mycell.Value)
Next
End If
Next
Next
End Sub
Ok, here's a brief way of achieving your goal. First, locate the column that holds the Employee IDs. Then simply set the entire Column to be formatted as Number instead of Text?
With Worksheets(1) ' Change this sheet to the one you are using if not the first sheet
Set c = .Find("Employee ID", LookIn:=xlValues)
If Not c Is Nothing Then
' The column we want is c's Column.
Columns(c.Column).NumberFormat = 0
End If
End With
Add a dim for the range that you want:
Dim MyRng, RngStart, RngEnd as Range
Then change:
ActiveSheet.Range(c.Address).Offset(1, 0).Select
to the below so that all data in that column is found.
set RngStart = ActiveSheet.Cells(1, c.column)
set RngEnd = ActiveSheet.Cells(rows.count, c.column).end(xlup)
set MyRng = ActiveSheet.Range(RngStart & ":" & RngEnd)
Now you can play about with the data. If you want to paste this somewhere which is formatted as number:
MyRng.copy
Sheets("Wherever").Range("Wherever").pastespecial xlvalues
If you want to change the format of the cells you have now found (How to format column to number format in Excel sheet?) that is whole number format, if you want decimal points then use "number" instead of "0":
MyRng.NumberFormat = "0"
or the new destination:
Sheets("Wherever").Range("Wherever").NumberFormat = "0"
General formatting which matches exactly the convert to number function:
MyRng.NumberFormat = "General"
MyRng.Value = MyRng.Value

Copy information from one workbook to another with a specific condition

Hi I would need a code to allow me to copy paste the information from a workbook called "Target" to another workbook called "Source" based on a specific condition.
This condition is based on the unique Project ID found in the code.
I tried doing some coding but it does not seems to allow me to get the result that I wanted.
The code would only read the first row and copy the information to the other workbook instead of looking through the project ID "10000327" in the "Project ID" column in the Target workbook and copy the information to the Source workbook.
Below is the code that I have tried and gave the result that I mentioned earlier.
Really hope that anyone could help me as I am very new to VBA. Thank you:)
Sub AAA()
Dim source As Worksheet
Dim target As Worksheet
Dim cellFound As Range
Set target = Workbooks("Target.xlsm").Sheets("Sheet1")
Set source = Workbooks("Source.xlsm").Sheets("Sheet2")
lastrow = source.Range("A" & target.Rows.Count).End(xlUp).Row
lastcol = target.Cells(2, target.Columns.Count).Column
target.Activate
'For a = 2 To 50
For Each cell In target.Range("A2:A500")
' Try to find this value in the source sheet
Set cellFound = source.Range("A:A").Find(What:="10000327", LookIn:=xlValues, LookAt:=xlWhole)
If Not cellFound Is Nothing Then
cell.Offset(ColumnOffset:=1).Copy
cellFound.Offset(ColumnOffset:=1).PasteSpecial xlPasteValues
Else
Exit Sub
End If
Next
I've changed the hard-coded search term to a var that gets it's pid on successive loops.
Sub AAB()
Dim sWS As Worksheet, tWS As Worksheet
Dim pidCol As Long, pidRow As Long, pidStr As String, rw as long
Set tWS = Workbooks("Target.xlsm").Sheets("Sheet1")
Set sWS = Workbooks("Source.xlsm").Sheets("Sheet2")
With sWS
With .Cells(1, 1).CurrentRegion
pidCol = 1
pidStr = "10000327" '.Cells(rw, pidCol).Value
If CBool(Application.CountIf(.Columns(1), pidStr)) Then
rw = Application.Match(pidStr, .Columns(1), 0)
With .Cells(rw, 2).Resize(1, .Columns.Count - 1)
If CBool(Application.CountIf(tWS.Columns(1), pidStr)) Then
pidRow = Application.Match(pidStr, tWS.Columns(1), 0)
.Copy Destination:=tWS.Cells(pidRow, 2)
End If
End With
End If
End With
End With
Set sWS = Nothing
Set tWS = Nothing
End Sub
This loops through all the values in column A (pidCol = 1) on the source worksheet and copies the data to the target worksheet if the associated PID is found on the target worksheet.
If I understand your question correctly, I think what's going on here is that your for loop is running the find command once for each cell, but it runs the same find command, only returning the first match, each time. If you are using the find command, I think you can use a do...while loop more appropriately, then use "findnext." The msdn help gives an example of this which I think is exactly what you want to do:
With Worksheets(1).Range("a1:a500")
Set c = .Find(2, lookin:=xlValues)
If Not c Is Nothing Then
firstAddress = c.Address
Do
c.Value = 5
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
The other option would be to check if each cell you get to in your for loop matches.

Remove Duplicates from Random Cells using Excel VBA

I have an excel sheet where I've duplicate values in difference cells. BUT the catch here is all those cells are not adjacent to one another. I'll randomly select those cells manually from the sheets & want to remove the duplicates.
In below screenshot I've selected random cells with value "test". I would like to remove the duplicates from selected cells.
Apologies : Adding possible scenario. Need only first occurrence of any repetitive cells. Remove remaining occurrences. It means it should give A1=TEST & B6=WEST. all other cell values should be removed.
Assuming that you have already made the random selection:
Sub dural()
Dim v As Variant, r As Range
v = ActiveCell.Text
addy = ActiveCell.Address
For Each r In Selection
If Not addy = r.Address Then
If r.Value = v Then
r.ClearContents
End If
End If
Next r
End Sub
Just for fun, here's a non-looping version. It does wipe out the ActiveCell's value and then reassign it, which worked in all situations in my limited testing:
Sub RemoveAllSelectionCellsExceptActiveCell()
Dim ActiveCellValue As Variant
ActiveCellValue = ActiveCell.Formula
Selection.Clear
ActiveCell.Formula = ActiveCellValue
End Sub
EDIT: Response to your edited question
This relies on the fact that adding a duplicate to a collection generates an error. If that happens, the cell in question is added to a range of cells to delete. Note that it will treat a cell with "=2" as different from a cell with "2":
Sub RemoveAllSelectionCellsExceptActiveCell2()
Dim cell As Excel.Range
Dim collDupes As Collection
Dim DupeCells As Excel.Range
Set collDupes = New Collection
For Each cell In Selection.Cells
On Error Resume Next
collDupes.Add cell.Formula, cell.Formula
If Err.Number <> 0 Then
If DupeCells Is Nothing Then
Set DupeCells = cell
Else
Set DupeCells = Union(DupeCells, cell)
End If
End If
On Error GoTo 0
Next cell
DupeCells.Clear
End Sub
And another...
If you want to clear the cells' contents and formatting and leave the cursor in the ActiveCell with no selected cells highlighting.
Note, when you make your selection, it will be the last cell visited that is the ActiveCell whose contents will remain, and remain selected.
Option Explicit
Sub remSelDup()
Dim ac As Range, c As Range
Set ac = ActiveCell
For Each c In Selection
If c = ac And c.Address <> ac.Address Then
c.Clear
End If
Next c
ac.Select
End Sub
There should be more than a few Find/FindNext examples on this site but here's another one.
Dim fnd As Range, fcl As Range, searchTerm As Variant
With ActiveSheet
Set fcl = ActiveCell
searchTerm = fcl.Value
Set fnd = .Cells.Find(What:=searchTerm, After:=fcl, LookIn:=xlValues, LookAt:= _
xlWhole, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
Do While fcl.Address <> fnd.Address
fnd.ClearContents
Set fnd = .Cells.FindNext(After:=fcl)
Loop
End With