I have a data table with column headings. I have a list of column headings that I don't want.
I want to delete the unwanted column headings no matter where they are in the worksheet and the ability for users to add other columns to delete.
I get
run time 91 error
on this line: ws.Rows("1:1").Select.Find(T).EntireColumn.Delete
Sometimes I will get an error in the first loop of the code, sometimes it will be part way through.
I have looked at other posts but the problems have not be related enough for me to problem solve my way through. I tried reading some articles on defining objects. I have been using the msgbox command to make sure the code is finding the values and that seems to be working all the time but it breaks down at the Find command.
Sub DeleteBadHeaders2()
Dim FirstHeading As Range
Set FirstHeading = Worksheets("Headings_To_Delete").Range("a2")
'Worksheet that has all the column headings I want deleted
Dim x As Integer
'x is for the do while loop to individually highlight each cell
Dim y As Long
y = Worksheets("Headings_To_Delete").Range("A:A").Cells.SpecialCells(xlCellTypeConstants).Count
'y acts as the upper bound to the headings to delete column for the while loop
Dim T As Variant
'T acts as a temporary value holder that will be used to delete the proper columns
Dim ws As Worksheet
Set ws = ActiveSheet
x = 0
Do While x < (y - 1)
Worksheets("Headings_To_Delete").Range("a2").Offset(x, 0).Interior.Color = RGB(224, 0, 0)
'Calling the rage as above fixes the active cell problem
Let T = Worksheets("Headings_To_Delete").Range("a2").Offset(x, 0).Value
'MsgBox T & " is found."
ws.Rows("1:1").Select.Find(T).EntireColumn.Select
'for testing switch the last part of the code to EntireColumn.Interior.Color = RGB(0, 225, 0)
x = x + 1
Loop
'The loop is highlighting the cells incrementally based on the first active cell until the upper limit of how many cells are in the column
End Sub
ws.Rows("1:1").Select.Find(T).EntireColumn.Select
should be
ws.Rows(1).Find(T).EntireColumn.Select 'Delete?
Typically though whenever using Find() it's a good idea to check you actually found anything, by testing the return value for Nothing before trying to do anything like Select or Delete.
Also a good idea to be explicit about some of the other parameters in Find, such as lookAt for example.
Something like this:
Sub DeleteBadHeaders()
Dim r As Long, lastRow As Long
Dim T As Variant
Dim ws As Worksheet, wsList As Worksheet, f As Range
Set ws = ActiveSheet
Set wsList = Worksheets("Headings_To_Delete")
lastRow = wsList.Cells(Rows.Count, 1).End(xlUp).Row 'last row
For r = 2 To lastRow
T = wsList.Cells(r, "A").Value
If Len(T) > 0 Then
Set f = ws.Rows(1).Find(what:=T, lookat:=xlWhole)
'check to see if the heading was found
If Not f Is Nothing Then
Debug.Print "Found header '" & T & "' at " & f.Address
f.EntireColumn.Interior.Color = vbRed '<< for testing
'f.EntireColumn.Delete '<< uncomment when done testing
End If 'was found
End If 'any heading
Next r 'next in list
End Sub
Related
I have a table with numbers from 1 to 10. (Starting from D2 to M2)
Suppose in A1 there is 03/09/2019
AND in B1 there is 06/09/2019
AND in C1 there is Hello
In COLUMN A I have a multiple series of words starting from A3 to A10
Here is an Example of the Excel Table
What I would like to do is: Search for the word Student in Column A, when I find it, get the numbers from A1 --> 3
and A2 --> 6 and write the word Hello that is in C1 in the cells that go to 3 to 6 in the row of the finded word Student
So my output would be like:
This is my code so far:
Dim Cell As Range
Columns("A:A").Select
Set Cell = Selection.Find(What:="Student", After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Cell Is Nothing Then
MsgBox "Word not found"
Else
MsgBox "Word found"
End If
Basically I can find the word Student but don't know how to write the word Hello in the cells between 3 to 6
A few notes regarding the code below (not tested!).
1) Always try use worksheet qualifiers when working with VBA. This will allow for cleaner code with less room for unnecessary errors
2) When using .Find method I use LookAt:=xlWhole because if you do not explicitly define this your code will use the last known method that you would have used in Excel. Again, explicit definition leaves less room for error.
3) Try include error handling when you code. This provides “break points” for easier debugging in the future.
4) You can make the below much more dynamic that it currently is. But I'll leave that up to you to learn how to do!
Option Explicit
Sub SearchAndBuild()
Dim rSearch As Range
Dim lDayOne As Long, lDayTwo As Long
Dim lColOne As Long, lColTwo As Long
Dim sHello As String
Dim wsS1 As Worksheet
Dim i As Long
'set the worksheet object
Set wsS1 = ThisWorkbook.Sheets("Sheet1")
'store variables
lDayOne = Day(wsS1.Range("A1").Value)
lDayTwo = Day(wsS1.Range("B1").Value)
sHello = wsS1.Range("C1").Value
'find the student first
Set rSearch = wsS1.Range("A:A").Find(What:="Student", LookAt:=xlWhole)
'error handling
If rSearch Is Nothing Then
MsgBox "Error, could not find Student."
Exit Sub
End If
'now loop forwards to find first date and second date - store column naumbers
'adjust these limits where necessary - can make dynamic
For i = 4 To 13
If wsS1.Cells(2, i).Value = lDayOne Then
lColOne = i
End If
If wsS1.Cells(2, i).Value = lDayTwo Then
lColTwo = i
Exit For
End If
Next i
'now merge the range
wsS1.Range(wsS1.Cells(rSearch.Row, lColOne), wsS1.Cells(rSearch.Row, lColTwo)).Merge
'set the vvalue
wsS1.Cells(rSearch.Row, lColOne).Value = sHello
End Sub
This is just one way to approach the problem. Hopefully this helps your understanding!
No need for a loop here - just find your value and parse the dates. Assuming your value to be found exists in Column A and your table starts in Column D, there is clear relationship between the columns which is Day(date) + 3.
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim lr As Long, Found As Range
Dim date_a As Long, date_b As Long
lr = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set Found = ws.Range("A2:A" & lr).Find("Student", LookIn:=xlValues)
If Not Found Is Nothing Then
date_a = Day(Range("A1")) + 3
date_b = Day(Range("B1")) + 3
With ws.Range(ws.Cells(Found.Row, date_a), ws.Cells(Found.Row, date_b))
.Merge
.Value = ws.Range("C1")
End With
Else
MsgBox "Value 'Student' Not Found"
End If
End Sub
I've tried this:
Dim ThisRow As Long
Dim FindWhat As String
FindWhat = "Student"
Dim MyStart As Byte
Dim MyEnd As Byte
MyStart = Day(Range("A1").Value) + 3 'we add +3 because starting 1 is in the fourth column
MyEnd = Day(Range("B1").Value) + 3 'we add +3 because starting 1 is in the fourth column
Dim SearchRange As Range
Set SearchRange = Range("A3:A10") 'range of values
With Application.WorksheetFunction
'we first if the value exists with a count.
If .CountIf(SearchRange, FindWhat) > 0 Then 'it means findwhat exists
ThisRow = .Match(FindWhat, Range("A:A"), 0) 'we find row number of value
Range(Cells(ThisRow, MyStart), Cells(ThisRow, MyEnd)).Value = Range("C1").Value
Application.DisplayAlerts = False
Range(Cells(ThisRow, MyStart), Cells(ThisRow, MyEnd)).Merge
Application.DisplayAlerts = True
Else
MsgBox "Value 'Student' Not Found"
End If
End With
Note I've used worksheets function COUNTIF and MATCH. MATCH will find the position of an element in a range, so if you check the whole column, it will tell you the row number. But if it finds nothing, it will rise an error. Easy way to avoid that is, first, counting if the value exists in that range with COUNTIF, and if it does, then you can use MATCH safely
Also, note that because we are using MATCH, this function only finds first coincidence, so if your list of values in column A got duplicates, this method won't work for you!.
I have data regarding benefits. At the bottom of these worksheets are adjustments. These should not be used in my Macros and formulas.
Instead of manually deleting, I'd like VBA to find "Adjustments" and delete that row and all rows below this. I have 3 worksheets I needs to this to repeat on.
I've googled and tried various codes but cannot seem to get it to read properly. Can anyone help?
First step is to find the first row of your Adjustments. You'll use .Find method to get that. Below is a sample line of code. You can google for more details and examples.
Once you have that, you'll find the last row, and then delete rows from start of adjustment rows to last row. I've included functions below that should help.
Set foundCell = rng.Cells.Find(varSearchValue, celStartCell, enuXlFindLookIn, enuXlLookAt)
Public Function LastUsedRow(wks As Worksheet) As Long
Dim rng As Range: Set rng = wks.UsedRange ' Excel will recalc used range
LastUsedRow = rng.Row + rng.Rows.Count - 1
End Function
Public Sub DeleteRows(wks As Worksheet, lngRowStart As Long, Optional ByVal lngRowEnd As Long = 0)
If lngRowEnd = 0 Then lngRowEnd = lngRowStart
wks.Rows(lngRowStart & ":" & lngRowEnd).Delete
End Sub
I've inferred that Adjustments is some sort of sub-level header row label. I'll assume that it is always in column A.
sub ScrubAdjustments()
dim w as long, wss as variant, m as variant
wss = array("sheet1", "sheet2", "sheet3")
for w = lbound(wss) to ubound(wss)
with worksheets(wss(w))
m = application.match("adjustments", .range("a:a"), 0)
if not iserror(m) then
.range(.cells(m, "A"), .cells(.rows.count, "A")).entirerow.delete
end with
end with
next w
end sub
I'm trying to delete all rows on my worksheet that have a unique value in column B.
I know this can be done with a filter or conditioned formatting, but I would like to know if the following is possible as well, since it could be useful in other situations:
I want to loop through all rows and store the row number in an Array if the row has a unique value in column B. Then delete all the rows whose number is stored in the Array in one single action.
The reasoning for storing the row numbers in an Array instead of deleting the desired rows in the loop is to reduce runtime.
My data varies in number of rows but is always in column A:K and it always begins on row 6.
Below is the code I've written with inspiration from the following links:
Dynamically adding values to the array on the go.
Deleting rows whose number is stored in array in one single action (see Tim Williams answer).
I get the error message: Run-time error '5': Invalid procedure call or Argument
Sub DeleteRows()
Dim ws4 As Worksheet: Set ws4 = Worksheets("Sheet1")
Dim LastRow As Long
Dim CurrentRow As Long
Dim GroupValue
Dim GroupTotal As Long
Dim MyArray()
Dim y As Long
Application.ScreenUpdating = False
ws4.Activate
GroupValue = ws4.Range("B6").Value ' Sets the first GroupValue
CurrentRow = 6 ' Sets the starting row
y = 0
LastRow = ws4.Cells(Rows.Count, "B").End(xlUp).Row
For x = 1 To LastRow
GroupTotal=Application.WorksheetFunction.CountIf(Range("B6:B"&LastRow), _
GroupValue) ' Searches for the GroupValue and finds number of matches
If GroupTotal = 1 Then ' If GroupTotal = 1 then add the row# to the array
ReDim Preserve MyArray(y)
MyArray(y) = CurrentRow
y = y + 1
End If
CurrentRow = CurrentRow + GroupTotal 'set the next row to work with
GroupValue = Range("B" & CurrentRow).Value 'set next GroupValue to find
If GroupValue = "" Then ' Checks to see if the loop can stop
Exit For
End If
Next x
'***This should delete all the desired rows but instead produces the error.***
ws4.Range("B" & Join(MyArray, ",B")).EntireRow.Delete
Application.ScreenUpdating = True
End Sub
I've researched for hours and tried to manipulate the code with no luck.
Use a variable defined as a Range and Union each row to it.
In the example below MyArray is the array of row numbers that should be deleted.
Public Sub Test()
Dim MyArray() As Variant
MyArray = Array(2, 4, 5, 8, 10, 15)
DeleteRows MyArray
End Sub
Public Sub DeleteRows(RowNumbers As Variant, Optional SheetName As String = "")
Dim wrkSht As Worksheet
Dim rRange As Range
Dim x As Long
On Error GoTo ERROR_HANDLER
If SheetName = "" Then
Set wrkSht = ActiveSheet
Else
Set wrkSht = ThisWorkbook.Worksheets(SheetName)
End If
For x = LBound(RowNumbers) To UBound(RowNumbers)
If rRange Is Nothing Then
Set rRange = wrkSht.Rows(RowNumbers(x))
Else
Set rRange = Union(rRange, wrkSht.Rows(RowNumbers(x)))
End If
Next x
If Not rRange Is Nothing Then rRange.Delete
On Error GoTo 0
Exit Sub
ERROR_HANDLER:
Select Case Err.Number
Case Else
MsgBox "Error " & Err.Number & vbCr & _
" (" & Err.Description & ") in procedure DeleteColumns."
Err.Clear
Application.EnableEvents = True
End Select
End Sub
Edit
The Test procedure can be replaced with any code that creates an array of row numbers. The array is then passed to the DeleteRows procedure. You could also pass it a sheet name to delete the rows from: DeleteRows MyArray, "Sheet2".
The DeleteRows procedure sets up the variables, turns error checking on and then checks if a sheet name was passed to it. It then sets a reference to either the active sheet or the named sheet. You could also check if the passed sheet actually exists here.
Next a loop starts going from the first to last element of the array. The first is usually 0 so you could replace LBOUND(RowNumbers) with 0.
rRange is the variable that's going to hold the row references to delete and Union won't work if it doesn't already hold a range reference.
On the first pass of the loop it won't hold a reference so will be nothing and the first row in the array will be set as the first row reference on the sheet held in wrkSht.
On subsequent passes rRange will already hold a reference so the next row will be unioned to it.
Those two decisions are made in an IF...END IF block seperated by an ELSE statement.
After the loop has finished a single line IF statement - no END IF required on single line - checks if rRange holds any references. If it does then those rows are deleted.
The procedure exits the main body of code, deals with the error handling and then ends.
I am trying to create a form which hopefully updates the list of values for a particular dropdown list automatically (without VBA codes) upon user's input immediately.
Here is the form that the user will see:
Currently, both Columns F and H is based on a data-validation formula:
INDIRECT("VList!"&SUBSTITUTE(ADDRESS(1,MATCH($B11,VList!$1:$1,0),1),"1","")&"2:"&SUBSTITUTE(ADDRESS(1,MATCH($B11,VList!$1:$1,0),1),"1","")&COUNTA(INDIRECT("VList!"&ADDRESS(1,MATCH($B11,VList!$1:$1,0),4)&":"&ADDRESS(100,MATCH($B11,VList!$1:$1),4))))
... where VList refers to the sheet as shown below:
So my question here is, based on the Project Name in Column B, is there a way to update the list in sheet VList with the value "Cost Per Unit" [Cell E11], so that the dropdown list in F12 and H12 get automatically updated with the value "Cost Per Unit"?
Been researching a long time for this with no avail, so I'm hoping to seek some experts here to see if such a scenario is even possible without VBA. Thanks!
Edit: So I've been told that VBA codes can be triggered automatically upon changes in the cell value, so I am open to any solutions/help with VBA as well. Will be researching on that direction in the meantime!
Edit2: Added a simple illustration below that hopefully better depicts what I'm trying to achieve on excel:
*Edit3: I'm starting to explore the Worksheet_SelectionChange method, and this is what I've come out so far:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim projectName As String
Dim VariableList As Worksheet
Dim Form As Worksheet
Dim thisRow As Integer
Dim correctColumn As Integer
Dim lastRow As Integer
Set VariableList = ThisWorkbook.Sheets("VList")
Set Form = ThisWorkbook.Sheets("Form")
On Error GoTo EndingSub
If Target.Column = 5 Then
thisRow = Target.Row
projectName = Form.Cells(thisRow, 2)
correctColumn = Application.Match(projectName, VariableList.Range("1:1"), 0)
lastRow = VariableList.Columns(correctColumn).Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
VariableList.Cells(lastRow + 1, correctColumn).value = Form.Cells(5, thisRow).value
End If
EndingSub:
End Sub
Somehow the value of Form.Cells(5, thisRow).Value is always empty.
If I change it to Target.Value it still takes the previous value that was being input (e.g. I first put "ABC" as New Variable, it doesn't get updated. I changed New Variable to "DEF", it updates the list with "ABC" instead of "DEF"). It also takes ALL the values that are under Column E somehow.
Also, pressing Enter after I placed one input in E11 also causes both values of E11 and E12 to be updated when only E12 has been changed. However if I click away after E11 is being input, then only E11's value gets updated.
What exactly am I doing wrong here?
I was almost having fun with this one, if anyone can refine the screwed-up parts feel free to amend.
I furthermore recommend using tables. I do realise you can write lengthy formulae to refer to ranges but giving a name to your table gives an expanding list with a simple reference.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim NewVar As Range
On Error GoTo Err
Set NewVar = Range("C:C") 'data entered here, could be a referstorange kind of named range reference
If Application.WorksheetFunction.CountA(Intersect(Target, NewVar)) <> 0 Then Call ertdfgcvb(Target, NewVar) 'only run if there's an intersect, f*ed up but works anyway
Err:
End Sub
Sub ertdfgcvb(Target As Range, NewVar As Range)
Dim ws As Worksheet, Valid As Long, project As String, ListElmnt As String, Unlisted As Boolean, rng1 As Range, rng2 As Range
Set ws = Sheets("VList") 'the data that you refresh
Valid = 2 'projects in column B
HeaderRow = 1 'headers in Vlist are in row #1
uRow = Cells.Rows.Count 'f* yeah, compatibility considerations
For Each Cell In Intersect(Target, NewVar) 'will evaluate for each cell individually, in case you were to insert columns
ListElmnt = Cell.Value2 'stores the prospective list element
r = Cell.Row 'stores the list element's row to...
project = Cells(r, Valid).Value2 'identify the related project
HeaderRowRef = HeaderRow & ":" & HeaderRow
ColumnNum = ws.Range(HeaderRowRef).Find(What:=project, SearchDirection:=xlPrevious, SearchOrder:=xlByColumns, LookAt:=xlWhole).Column 'finds the project in VList
'MsgBox ws.Name
Set rng1 = ws.Cells(HeaderRow + 1, ColumnNum)
Set rng2 = ws.Cells(uRow, ColumnNum)
LastRow = ws.Range(ws.Cells(HeaderRow + 1, ColumnNum), ws.Cells(uRow, ColumnNum)).Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row 'finds the last row for the project in VList 'f*ed up but works
Unlisted = True 'assumes it's unlisted
For x = HeaderRow + 1 To LastRow
If ListElmnt = CStr(ws.Cells(x, ColumnNum).Value2) Then Unlisted = False 'unless proven otherwise
Next
If Unlisted Then ws.Cells(LastRow + 1, ColumnNum) = ListElmnt 'if it's unlisted it gets appended to the end of the list
Next
End Sub
EDIT:
How to purge the table, example:
Sub ert()
Dim rng As Range
Set rng = Range("Táblázat1") 'obviously the table name
Do While x < rng.Rows.Count 'for each row
If rng(x, 1).Value2 = "" Then 'if it's empty
rng(x, 1).Delete Shift:=xlUp 'then delete but retaining the table format
Else
x = x + 1 'else go to the next line (note: with deletion comes a shift up!)
End If
Loop
End Sub
I am trying to search for a "/" in a cell within the first column. I need to go through 13 worksheets, find the cell that contains that "/" (which may also contain other text), and highlight that row. Ive been testing out code I've found online and haven't had much luck in getting through the whole workbook.
Dim value As String
value = "/"
x = 1
For x = 1 To 13 Step -1
lastrow = Cells(Rows.Count, "A").End(xlUp).Row
For y = 1 To lastrow
Range("a" & i).Find (value)
Range("a" & i).Rows.Interior.Color = RGB(256, 1, 1)
Next y
Next x
This is the code I'm using. If anyone could offer some assistance I'd greatly appreciate it.
It's irrelevant to your question, but the first thing I noticed was:
For x = 1 To 13 Step -1
This will never work; a negative step will only work if the order of the values is descending.
You're missing any selection of a worksheet. If you are iterating through all worksheets in the workbook, then easiest way to do it is with For each wks in Worksheets.
Find in Excel VBA is a little convoluted. A call to Find returns the first cell in the range that contains the specified text. You then need to make subsequent calls to FindNext to get any other matching cells. But, FindNext will run forever -- you need to save a reference to the first matched cell, then compare the result you get from FindNext until the first matched cell shows up again.
Here's how I would do it:
Sub foo()
Dim value As String: value = "/"
Dim rSearch As Range
Dim firstFound As Range
Dim nextFound As Range
Dim wks As Worksheet
For Each wks In Worksheets
wks.Activate
Set rSearch = Range("a1", Cells(Rows.Count, "a").End(xlUp))
Set firstFound = rSearch.Find(value)
If Not firstFound Is Nothing Then
Set nextFound = firstFound
Do
nextFound.EntireRow.Interior.Color = RGB(256, 1, 1)
Set nextFound = rSearch.FindNext(nextFound)
Loop While nextFound.Address <> firstFound.Address
End If
Next
End Sub