Remove Duplicates from Random Cells using Excel VBA - 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

Related

ActiveSheet.AutoFilter.Range sometimes returns table's AutoFilter based on selected cell

Edit: The best I can do is to find the last used cell and then offset it by one row and one column. This will fail if the user is using the entire sheet, but I'm willing to take that chance. I can select this cell temporarily and then switch back to the user's previous cell.
Public Function getCellOutsideUsedRange(ws As Worksheet) As Range
Dim rng As Range
Set rng = getLastCellOnSheet(ws)
If rng.Row = ws.Rows.CountLarge Then
If rng.Column = ws.Columns.CountLarge Then
Set getCellOutsideUsedRange = Nothing
Else
Set getCellOutsideUsedRange = rng.offset(0, 1)
End If
ElseIf rng.Column = ws.Columns.CountLarge Then
If rng.Row = ws.Rows.CountLarge Then
Set getCellOutsideUsedRange = Nothing
Else
Set getCellOutsideUsedRange = rng.offset(1, 0)
End If
Else
Set getCellOutsideUsedRange = rng.offset(1, 1)
End If
End Function
Public Function getLastCellOnSheet(ByRef ws As Worksheet) As Range
Set getLastCellOnSheet = ws.Cells.Find(What:="*", After:=Cells(1, 1), LookIn:=xlFormulas, LookAt:= xlPart, SearchOrder:=xlByRows, SearchDirection:=xlPrevious, MatchCase:=False)
End Function
If you have a cell selected inside a table, ActiveSheet.AutoFilter.Range returns the autofilter applied to the table, not the worksheet. Example:
Sub test()
Dim af As AutoFilter
Range("C1").Select
Set af = ActiveSheet.AutoFilter
MsgBox af.Range(1).Value2
MsgBox af.Parent.Name
Range("C12").Select
Set af = ActiveSheet.AutoFilter
MsgBox af.Range(1).Value2
MsgBox af.Parent.Name
End Sub
The documentation doesn't really help me. How can I ensure that I can get to the sheet autofilter, even if the user happens to have a cell selected in a table, without having to cycle through cells until I get one outside a table?
Context: I have a short macro that returns a list of all the filters that have been applied to the sheet. In summary, it looks through the worksheet and any tables and, for each, uses the AutoFilter.Range property to get the range that is filtered, selects the top row of this range to get the filter headings, and then checks each cell in the filter headings for any criteria. If a user has a cell selected in a table, they won't get back any worksheet filters.
In general, using Select in VBA and depending on it is a bad practice. How to avoid using Select in Excel VBA
Concerning the AutoFilters - you are allowed to have only one AutoFilter per worksheet, which is outside a table. Thus, it is accessible through ActiveSheet.AutoFilter.Range, which could be Nothing or Not Nothing.
Concerning the autofilters in the tables, you may simply loop through the tables and check one by one whether the AutoFilter is there or not:
Sub TestMe()
Dim cntAutoFilters As Long
Dim cnt As Long
Dim wks As Worksheet
Dim tbl As ListObject
Set wks = Worksheets(1)
If Not wks.AutoFilter.Range Is Nothing Then
Debug.Print wks.AutoFilter.Range.Address
End If
For Each tbl In wks.ListObjects
If Not tbl.AutoFilter Is Nothing Then
Debug.Print tbl.Range.Address
End If
Next tbl
End Sub
Concerning the MSDN documentation:
AutoFilter.Range is a property of AutoFilter.
Range.AutoFilter is a method.
Edit:
It really seems that if you have a cell selected within a table in a worksheet, then the AutoFilter outside this table somehow cannot be referred.

Loop Through Non Blank Cells

I just want to know how to loop through the non blank cells on Column A. What I'm trying to do is copy the contents on [A1:B1] to be added on top of each non blank cells on Column A. So far I have counted the non blank cells on column A but I'm stuck. I know that an Offset function should be used for this.
Here's my code so far:
Dim NonBlank as Long
NonBlank = WorksheetFunction.CountA(Worksheet(1).[A:A])
For i = 1 to NonBlank
[A1:B1].Copy Offset(1,0). "I'm stuck here"
Next i
If you are trying to fill the headers for each Product, try this...
Sub FillHeaders()
Dim lr As Long
Dim Rng As Range
lr = ActiveSheet.UsedRange.Rows.Count
Application.ScreenUpdating = False
On Error Resume Next
Range("A1:B1").Copy
For Each Rng In Range("A3:A" & lr).SpecialCells(xlCellTypeConstants, 2).Areas
If Rng.Cells(1).Value <> Range("A1").Value Then
Rng.Cells(1).Offset(-1, 0).PasteSpecial xlPasteAll
End If
Next Rng
Application.CutCopyMode = 0
Application.ScreenUpdating = True
End Sub
As example to simulate the effect of Ctrl-Down from Cell A1 and display the Address, Value in the Immediate Window:
Sub HopToNextNonBlankCellBelow()
Dim oRng As Range
Set oRng = Range("A1")
Debug.Print "Cell Address", "Cell Value"
Do
Set oRng = oRng.End(xlDown)
If Not IsEmpty(oRng) Then Debug.Print oRng.Address(0, 0), oRng.Value
Loop Until oRng.Row = Rows.Count
Set oRng = Nothing
End Sub
Try this... I've (probably) overcounted the rows at 1000, but it likely won't make a difference with your performance. If you wanted to be more precise, there are hundreds of articles on how to find the last row of a range. As for the Offset function, it references a cell in relation to the one we're looping through. In the example below, the code is saying cell.offset(0,1) which means one cell to the right of the cell we are currently looping through. A clearer (less loopy!) example would be if you typed: Range("A10").offset(0,1) it would be the same as typing Range("B10")
Dim Cell As Range
For Each Cell In Range("A2:A1000").Cells
If Not IsEmpty(Cell) Then
Cell.Offset(0, 1).Value = Cell.Value
End If
Next Cell

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

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

Find to cells in range with specific colors and add comments

I am trying to create a macro that will search a column of text (A:A) for a specific interior color. In this case the interior color is 55. Normally I'd create a range of A1:A101 but the data that is added changes daily so there may be more or less.
Essentially once the macro identifies the cells with the colors I want the macro to add a comment to the cell. Something simple like "Hello World!".
So far this is what I have:
Sub AddCommentBasedOnColor()
Dim rng As Range, cell As Range
Set rng = Range("G:G")
Application.ScreenUpdating = False
Application.Calculation = xlManual
For Each cell In rng
If cell.Interior.ColorIndex = 55 Then
If rng.Comment Is Nothing Then rng.AddComment
rng.Comment.Text "Possible Aux Stacking"
End
End If
Next cell
Application.ScreenUpdating = True
Application.Calculation = xlAutomatic
End Sub
The problem that I am running into is that when I am running the code, the comment portion does not work at all. No comments are made and for some reason I get a debug code but did not have one before. Not sure what I did that changed it.
Additionally, when I remove the commenting section of this code it does take some time to run, any assistance with shortening that length of time would be appreciated as well.
Your code has logical problems.
With rng.AddComment you try setting a comment to the whole column G as rng is the whole column G. This is not possible.
And your inner If statement works as follows:
...
If rng.Comment Is Nothing Then rng.AddComment
rng.Comment.Text "Possible Aux Stacking"
End
...
If rng.Comment Is Nothing Then rng.AddComment. Here the If ends. The next program row is processing ever without additional conditions and the End then ends the Sub at this point.
To shortening the processing time you have not to run over all rows in column G. This is possible by calculation the last used row. How to do this differs on how you define the last used row. Since you are working with the cell's interior, I have defined the last used row as the last row having cells with not default content of empty cells.
Sub AddCommentBasedOnColor()
Dim rng As Range, cell As Range, lastUsedRow As Long
With ActiveSheet
lastUsedRow = .UsedRange.Rows(.UsedRange.Rows.Count).Row
Set rng = .Range("G1:G" & lastUsedRow)
For Each cell In rng
If cell.Interior.ColorIndex = 55 Then
If cell.Comment Is Nothing Then
cell.AddComment
cell.Comment.Text "Possible Aux Stacking"
End If
End If
Next cell
End With
End Sub
You can use Find rather than loop through each cell:
Sub AddCommentBasedOnColor()
Dim rng1 As Range
Dim rng2 As Range
Dim strFirst As String
Application.FindFormat.Interior.ColorIndex = 55
Set rng1 = Columns("G:G").Find(What:="", SearchDirection:=xlNext, SearchFormat:=True)
If Not rng1 Is Nothing Then
strFirst = rng1.Address
Set rng2 = rng1
Do
Set rng2 = Columns("G:G").Find(What:="", After:=rng2, SearchDirection:=xlNext, SearchFormat:=True)
If rng2.Comment Is Nothing Then
rng2.AddComment
rng2.Comment.Text "Possible Aux Stacking"
End If
Loop Until rng2.Address = strFirst
End If
End Sub

How to find a specific cell and make it the ActiveCell

I am writing a VBA code that will open a specific worksheet in my workbook in Excel, and then find the cell in Column A that has the value "TOTAL". This then must be set as the ActiveCell, so that the rest of my macro can perform actions on the row containing this cell.
I want it so that when the user runs the macro, this cell is specifically chosen right off the bat. The position of this cell will change after the macro is run, so I need it to work no matter what cell this value is in. Everytime the macro runs, a new row is added above the row containing "TOTAL" and therefore the position of this cell is ever-changing.
So far I have come up with this, just from readin through forums. It still doesn't work, but I'm new to this language and I can't determine where the error is.
Sub Macro2()
Dim C As Range
Worksheets("Project Total").Select
With Selection
C = .Find("TOTAL", After:=Range("A2"), MatchCase:=True)
End With
End Sub
Try this:
Sub Macro2()
Dim cl As Range
With Worksheets("Project Total").Cells
Set cl = .Find("TOTAL", After:=.Range("A2"), LookIn:=xlValues)
If Not cl Is Nothing Then
cl.Select
End If
End With
End Sub
Try this:
Sub activateCellContainingTOTAL()
'Go to the worksheet
Worsheets("Project Total").Activate
'Start going down column A to see if you find the total
dim loopBool as Boolean
loopBool = True
Worksheets("Project Total").Range("A1").Activate
Do While loopBool=True
if Activecell.value = "TOTAL" then
loop = false
else
activecell.offset(1, 0).Activate
end if
loop
End sub
Sub Getvaluesfromeachcolumns()
Dim loopcounter As Integer
Dim loopcounter1 As Integer
Dim dumvalue As String
Dim rrange As Range
dumvalue = Activecell.value 'you can replace your cell reference or any value which you want to search. also you can use input method.'
loopcounter1 = Range("A1:C1").Count
For loopcounter = 1 To loopcounter1
Cells(1, loopcounter).Select
Range(ActiveCell.Address).Select ' to know the active cell and address
Set rrange = Range(ActiveCell.Address, ActiveCell.End(xlDown)).Find(dumvalue)
rrange.Select
Next loopcounter
End Sub