Excel VBA - Find and Fill in Values [duplicate] - excel

This question already has answers here:
Filling any empty cells with the value above
(6 answers)
Closed 8 years ago.
Sorry for the noobish question, but I'm new and I don't have much of a programming background. I tried this on my own for a few hours, but I just don't know enough. I checked this and this, but I couldn't figure out how to modify it enough.
Example data:
The first part is how I get the file, the 2nd part is how I want it to look.
The first 3 columns have values somewhere in the column. I need to find those values, copy them, and paste them down to the next value, then repeat all the way to the bottom of the range. Sometimes there are many values per column, sometimes only 1. The last row of the data could be determined by column 4. Basically I just need to fill in all the blank cells. Note: Row 2 does not always contain the first value.
Here's what I have so far (update):
Sub FindFillIn()
Dim columnValues As Range, i As Long
Dim cellstart As Integer
Dim cellend As Integer
cellstart = 2
cellend = ActiveSheet.Range("E" & ActiveSheet.Rows.Count).End(xlUp).Row
i = 1
For i = cellstart To cellend
If Cells(i, 1).Value = "" Then
Cells(i, 1).Value = Cells(i - 1, 1).Value
End If
Next i
End Sub
Update:
It appears to run correctly on the first column, but it only does one column. How do I get it to run on columns 1, 2, and 3?

Sub FillInBlanks()
Dim rng As Range
On Error Resume Next
With ActiveSheet
Set rng = .Range(.Range("A2"), _
.Cells(Rows.Count, 4).End(xlUp)).SpecialCells(xlCellTypeBlanks)
End With
On Error GoTo 0
If Not rng Is Nothing Then
With rng
.FormulaR1C1 = "=R[-1]C"
.Value = .Value
End With
End If
End Sub

This is easy without using VB.
Select the data of column 1 until the last cell where you want to fill with the data.
Press CTRL+G and click on Special.
Select Blank and press OK.
Now, on the formula bar, type "=" and select (pressing Ctrl+Click) the first Cell where the data start and then Control + Enter.
You can record this process to a macro and then view the code.

Related

Last Row Returns 1 - incorrect value

situation is following:
I have 32 columns with data (various number of rows in columns) and need to delete cells with .value "downloaded" (always last cell in a column).
I have a code looping from column 32 to 1 and searching last_row for "downloaded" value. For 30 columns code seems to be working flawlessly but 2 columns return last_row value 1 even though there are multiple values (in fact hundreds of them) but they are non existent for VBA code.
Code:
Last_Col = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
last_row = ws.Cells(Rows.Count & Last_Col).End(xlUp).Row
For R = Last_Col To 1 Step -1
With ws
Last_Col = R
last_row = ws.Cells(.Rows.Count & Last_Col).End(xlUp).Row
If Cells(last_row, Last_Col).Value Like "*Downloaded*" Then
Cells(last_row, Last_Col).ClearContents
End If
End With
Next R
Data is being drained from another worksheets. For 2 columns where I experience an error, I manually deleted values and inserted another, random batch of values and code worked as intended.
Checked columns formatting, worksheets from which data is taken but I struggle to find a solution.
Thank you for your help.
Clear Last Cell If Criteria Is Met
The main mistake was using Cells(.Rows.Count & Last_Col), where .Rows.Count & Last_Col would have resulted in a 8 or 9-digit string, while it should have been ws.Cells(ws.Rows.Count, Last_Col).End(xlUp).Row which was pointed out by chris neilsen in the comments.
Another important issue is using ws. in front of .cells, .rows, .columns, .range, aka qualifying objects. If you don't do it and e.g. the wrong worksheet is active, you may get unexpected results.
There is no need for looping backwards unless you are deleting.
Although it allows wild characters (*, ?), the Like operator is case-sensitive (a<>A) unless you use Option Compare Text.
The first solution, using the End property, will fail if a number of last columns is hidden or if you insert a new first row e.g. for a title.
The second solution, using the Find method (and the first solution), may fail if the data is filtered.
The Code
Option Explicit
Sub clearLastEnd()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastCol As Long
LastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Dim LastRow As Long
Dim c As Long
For c = 1 To LastCol
LastRow = ws.Cells(ws.Rows.Count, c).End(xlUp).Row
With ws.Cells(LastRow, c)
If InStr(1, .Value, "Downloaded", vbTextCompare) > 0 Then
.ClearContents
End If
End With
Next c
End Sub
Sub clearLastFind()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim cel As Range
Set cel = ws.Cells.Find(What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
If Not cel Is Nothing Then
Dim c As Long
For c = 1 To cel.Column
Set cel = Nothing
Set cel = ws.Columns(c).Find(What:="*", _
SearchDirection:=xlPrevious)
If Not cel Is Nothing Then
If InStr(1, cel.Value, "Downloaded", vbTextCompare) > 0 Then
cel.ClearContents
Else
' The current last non-empty cell does not contain criteria.
End If
Else
' Column is empty.
End If
Next c
Else
' Worksheet is empty.
End If
End Sub
EDIT:
So you are curious why it worked at all. The following should shed a light on it:
Sub test()
Dim i As Long
Debug.Print "Right", "Wrong", "Rows.Count & i"
For i = 1 To 32
Debug.Print Cells(Rows.Count, i).Address, _
Cells(Rows.Count & i).Address, Rows.Count & i
Next i
End Sub
In a nutshell, Cells can have 1 or 2 arguments. When 1 argument is used, it refers to the n-th cell of a range, and it 'counts' by row. The more common usage is with 2 arguments: rows, columns. For example:
Cells(5, 10) ' refers to cell `J5`.
Using one argument is inconvenient here:
Cells(16384 * (5-1) + 10)
i.e.
Cells(65546)
It may be convenient when processing a one-column or a one-row range.
Well , let me see if i understand you have a table in worksheet table have 32 columns and X rows (because you only put WS and i can know if is WS=worksheet or WS= Table-range)
for this i am going to say is selection (if you put worksheet only hace to change for it)
in your code put:
Last_Col = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
but in this you always wil obtein 1st cell so i dont understand why exist?
WS.columns.count
return number of columns you selection have
.End(xlToLeft)
return last cell if star to move to left (like Ctrl - left key)
so
Last_Col ---first go to cells (1,ws.Columns.Count) then go to left (End(xlToLeft)) and the end return number of column where finish (.Column) in this case you always get cell (1,"first column of your table")
NOTE: because you wrote that you have allways value in your cells (I have 32 columns with data (various number of rows in columns)
And for Row you have same question
Then you Wrote you want "Delete" but in your code you put Erase value (.ClearContents) so what do you want? because both are no equal
BUT if you have a table and want to search in any cells that have "Download" and only want to "clear content" you just may to use ".find" instead; or if you want to do all at same time you can use .replace (need to check before if .find return "nothing" or no , because if return nothing you get error)
If you have a table with 32 columns and each row have one cell where you put "Donloaded" and want to "delete" all row your code only need select column where appear "downloaded" (example Column "status").
If you have a table where any cell can take value "downloaded" and want to "delete" that cell you need to take care to resize your table and "move to" (when you delete cells you need to say where you want to move yor data remain "letf, "rigth", "up", down).
However if you say that "Downloaded" always appear in last row you can use For to change for all columns and use .end(xlDown)
For i=1 to 32
if cells(1,i).end(xlDown).value="downloaded" then cells(1,i).end(xlDown).ClearContents
next
BUT you need put more information because if you cant garantize that all cells have values and exist cells with "nothing" you will need

how can I get the row number of the last used cell in a given range [duplicate]

This question already has answers here:
Find last used cell in Excel VBA
(14 answers)
Closed 2 years ago.
Thanks again. I have this code again, I am trying to get the row number of the last used cell. the code doesn't work properly whenever there is a space in between the data in the the specified range. I just stops at the first row.
Sub MyCode()
ActiveSheet.Range("A9:A27").End(xlDown).Select
MsgBox ActiveCell.Row
End Sub
The issue i have with this code is that in the range A9:A27 if there is an empty row somewhere at A18 it stops there and gives the Row as 18 without even going down to A27
Here is the corrected code.
Sub MyCode()
Dim Rng As Range
With ActiveSheet
Set Rng = .Cells(.Rows.Count, "A").End(xlUp)
MsgBox Rng.Row
End With
End Sub
To understand this code you need to know the syntax for defining a cell.
Set MyCell = Cells([Row number], [Column number or ID])
In the above code .Rows.Count is a large number (1048576 to be exact). .Cells(.Rows.Count, "A") defines the cell A1048576. Now, from there you look for the "End" going "xlUp" and arrive at the last used cell in the column.
You would use exactly the same method to find the last used cell horizontally, like Cells(2, Columns.Count).End(xlToLeft). Here the sheet isn't specified and therefore the ActiveSheet by default.
Observe the double reference to ActiveSheet in .Cells(.Rows.Count, "A") as indicated by the leading periods. Both the Rows.Count and the cell itself must be in the same sheet.
Since the code explained here defines a cell, and since a range is defined by its first and last cells, you can define a range in column A as shown below.
Sub MyCode2()
Dim Rng As Range
With ActiveSheet
Set Rng = .Range(.Cells(30, 1), .Cells(.Rows.Count, "A").End(xlUp))
End With
MsgBox Rng.Address(0, 0)
End Sub

Why is user form data not entered row by row?

User form data is overwriting itself rather than entering row by row.
I have tried different combinations of ActiveCell, Cell, Set, and Range. Pulling code from the internet for somewhat similar purposes and tweaking was unsuccessful.
Private Sub CheckOut_Click()
Dim xCell As Range
For Each xCell In ActiveSheet.Columns(1).Cells
If Len(xCell) = 0 Then
xCell.Select
Exit For
End If
Next
Range("B2").Value = TextBox1.Text
Range("C2").Value = TextBox2.Text
Range("D2").Value = ("OUT")
TextBox1 = ""
TextBox2 = ""
End Sub
I want each submission in the user form to populate a new row creating a list. What is actually happening is everything writes to row 2.
Please give feed back with down votes and flags.
Dim ws as Worksheet
Dim freeRow as long
Set ws = ActiveSheet 'Is it really necessary to use the active sheet?
freeRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).row + 1 'End(xlUp) jumps to the next free cell
ws.Cells(freeRow, 2) = TextBox1.Text
ws.Cells(freeRow, 3) = TextBox2.Text
ws.Cells(freeRow, 4) = "OUT"
TextBox1.Text = ""
TextBox2.Text = ""
It is usually considered that ".select" is bad practice since it can lead to weird/nasty errors - rather use variables (this makes your code more reusable and less error prone!)
Your code
Private Sub CheckOut_Click()
Dim xCell As Range
' Loop through cells of first column. You can also use "A" instead of 1.
' Since you haven't used ActiveSheet with the ranges, it is also not
' needed here. It would be better to specify the worksheet e.g. "Sheet1".
For Each xCell In ActiveSheet.Columns(1).Cells
' Check if length of value of current cell is 0.
' Usually 'If xCell = "" Then' is used.
If Len(xCell) = 0 Then
' Select (Go to) current cell. What for?
xCell.Select
' Exit the For Next loop. Will jump to 'Range("B2") ...'
Exit For
End If
Next
' Write certain values to cells of 2nd row.
Range("B2").Value = TextBox1.Text
Range("C2").Value = TextBox2.Text
Range("D2").Value = ("OUT")
' Clear the Text boxes.
TextBox1 = ""
TextBox2 = ""
End Sub
In a nutshell, the code will check for an empty cell in column 1, will select the found cell (for an unknown reason) and will write some data to certain cells in the 2nd row and clear the values of the text boxes.
Questions
You loop through column 1. When an empty cell is found, do you want to write the values to columns B-D in the first found empty row or to columns B-D in the same row where the empty cell in column 1 was found?
Empty or Same?
Do you want this to happen only once, when an empty cell was found or for all found empty cells in the used range of column 1?
Once or All?
The used range of column 1 would be from e.g. A1 or which cell you choose to the last used cell.
You can manually determine the last used cell by selecting the last cell of column 1 ("A") and holding RIGHT CTRL and pressing UP. This will be done in the code, but it is just for you to have a visual of what will be checked for empty cells if you want to find more of them.
A1 or ...?
You should address these questions in your question which you can modify by using the edit button below it.
Possible Solution
Private Sub CheckOut_Click()
Dim xCell As Range ' Current Cell in Column "A"
Dim FER As Long ' First Empty Row
' Loop through cells of Column "A".
For Each xCell In Columns("A")
' Check if value of Current Cell is "".
If xCell.Value = "" Then
' Select Current Cell. If necessary.
xCell.Select
' Calculate First Empty Row using column "B".
FER = Range("B" & Rows.Count).End(xlUp).Row + 1
' Write values to Target Cells in First Empty Row.
Range("B" & FER).Value = TextBox1.Text
Range("C" & FER).Value = TextBox2.Text
Range("D" & FER).Value = ("OUT")
' Clear the Text boxes.
TextBox1 = ""
TextBox2 = ""
Exit For ' Stop looping.
End If
Next
End Sub
Remarks
How would this make any sense?
What if OP didn't tell us that xCell.Select triggers a Selection Change event which will write values to xCell and the text boxes and will restrict this to the used range of column A?

Summing rows above blank cell until previous blank cell

Hello!
I am trying to build a macro, that finds a blank cell in a range in a single column and sums all cells between this blank cell and the previous blank cell.
I've searched the web a lot, and while others have asked this question, i do not find the answers to them particularly helpful in my situation, as i need this to work through 3500+ rows.
E.g:
4
3
4
BLANK 1
2
5
7
1
BLANK 2
1
4
BLANK 3
In this case the cell called "BLANK 1" would be the sum of the 3 previous rows: 4+3+4=11
"Blank 2" would be 15 and so forth.
The range is "G8:G3561".
Edit
For the quick answer see Mr_Nitrogen's answer. It works beautifully!
However, as I am new to VBA and coding in general I do not know how or why the code works.
I am very eager to obtain a better understanding of VBA, which is why I'm continuing this thread (if allowed to).
Furthermore, I would like to provide evidence for the commenters that I have indeed worked on this myself and that I prefer to build my own code.
It's important for me to understand why my code works or doesn't work, which is why i hope that you still want to help me develop my own code.
I finally found an approach that is logical to me. I know that this is not the simplest way to do it, but I would like to know if it could work.
I've written the following code.
Sub Sum_storage()
Dim rng As Range
Dim cell As Range
Dim cell2 As Range
Dim cell3 As Range
Range("G8").End(xlDown).Offset(1, 0).Select
Set cell = Selection
cell.Value = "temp" 'Finds the first blank cell in column G _
and creates a temporary value in order _
to find the second blank cell
Range("G8").End(xlDown).Offset(1, 0).Select
Set cell2 = Selection
cell.Offset(1, 0).Select
Set cell3 = Selection 'The range i need to sum can _
now be described as "cell3:cell2"
Set rng = Range(Range("cell3"), Range("cell2")) 'The code works until this point
cell2.Value = WorksheetFunction.Sum(rng)
The idea is to define the range i want to sum with multiple variables.
My problem is trying to refer to these variables (and setting them in an easier way than using .Offset).
Is it simply not possible to set a range (rng) based on two previously set ranges?
If this is possible the next step for me is creating some kind of loop that could make this work for all 3500+ rows.
You were on the right track with using End(xlDown).
This one should be a way faster than looping through all cells, because this jumps to the next empty cell and sums via WorksheetFunction.Sum.
Option Explicit
Public Sub DoMyStuff()
Dim ws As Worksheet
Set ws = Worksheets("Tabelle8") 'define your worksheet here
Dim FirstCell As Range
Set FirstCell = ws.Range("G8")
Dim VeryLastCell As Range 'get very last cell as stop criteria
Set VeryLastCell = ws.Cells(ws.Rows.Count, "G").End(xlUp)
Do
Dim LastCell As Range
If FirstCell.Offset(1) = vbNullString Then 'test if there is only one cell to sum
Set LastCell = FirstCell
Else
Set LastCell = FirstCell.End(xlDown)
End If
With LastCell.Offset(1, 0) 'this is the cell we want to write the sum
.Value = Application.WorksheetFunction.Sum(ws.Range(FirstCell, LastCell))
.Interior.Color = RGB(255, 0, 0)
End With
Set FirstCell = LastCell.Offset(2, 0)
Loop While FirstCell.Row < VeryLastCell.Row
End Sub
This isnt that complicated to do with for loop, maybe something like
lastrow = Cells(Rows.Count, "G").End(xlUp).Row
firstrow = 8
TempTotal = 0
for x = firstrow to lastrow + 1
If Cells(x, "G") <> "" Then
TempTotal = TempTotal + Cells(x, "G")
Else: Cells(x, "G") = TempTotal
Cells(x, "G").Interior.ColorIndex = 4
TempTotal = 0
End if
Next x
Ive made an edit to make it a bit simpler
The Logic of the Code:
Define the last row with data in column "G"
Move down cell by cell until that row
If the cell has a value in it, add it to the temporary total.
If it is blank, inset the temporary total and reset the tempTotal to zero
This is the first line, in which we select the very last last cell in column "G", use use End(xlup) on it to get to the last cell with data and use .row to get the row number of that cell
Set up a For loop, which runs the code between "for" and "next x" lines repeatedly while incrementing the value of x from "firstrow" to "lastrow + 1" each time it repeats ( so if firsrow is 1 and lastrow is 100) then it will run the code 100 times with x = 1,2,3,4,5 etc.)
this is the "if" statement, "<>" means does not equal, so we are saying if the cell on row x, col "G" is not equal to "" which is an empty string (or nothing) then we do the next line (add its value to tempTotal)
if the "If" statement isnt true (if the cell is blank) then we do what is under the "Else" and make that cell equal to TempTotal, change its color to green (4 is a colorcode, they go between 1 and 50), and reset the temptotal to 0.

Update of sheet does not cause action until I run vba module twice

New to VBA
I'm confused as to why I need to run my module twice to get it to update my cells. My code:
Option Explicit
Sub m_Range_End_Method()
Dim lRow As Long
Dim lCol As Long
Dim currentRow As Long
Dim i As Integer
Dim rng As Range
Set rng = ActiveCell
Range("B:B").Select
lRow = Cells(Rows.Count, 1).End(xlUp).Row
lCol = Cells(1, Columns.Count).End(xlToLeft).Column
Sheets("MySheet").Select
' Loop Through Cells to set description in each cell
Do While rng.Value <> Empty
currentRow = ActiveCell.Row
If InStr(rng.Value, "PETROL") = 0 Then
Set rng = rng.Offset(1)
rng.Select
Else
Worksheets("MySheet").Cells(currentRow, 5) = "Shopping"
Worksheets("MySheet").Cells(currentRow, 6) = "Car"
Set rng = rng.Offset(1)
rng.Select
End If
Loop
End Sub
On the first run what happens in Excel 2016 is that Column B gets highlighted and that's it. I then have to press "Run" again in visual basics editor for it to then update all the entries at which point column B gets unselected. All I want to do is update the cells at the currentRow of a specified worksheet. I've been reading but have got myself into some confusion, someone said I should use the
Range("B:B").Select
statement and for some reason the spreadsheet update works but only if I run it twice. Without this Range command, for reasons I don't understand, the spreadsheet doesn't update - all that happens is that the box selection moves to entries with Petrol and stays there with the program running but not updating.
The aim of the program is to find in a sheet all occurrences of a word in column B, in this initial case that is PETROL (I'm going to expand to include many others). For that match on the same row I want it to update columns 5 and 6 with descriptions. The excel spreadsheet will have hundreds of rows of entries with varying descriptions in column B.
Any help would be much appreciated.
I guess you have to run it twice because the first time you run it, the ActiveCell could be anything, and your loop depends on it not being empty to start with, but after the first run you have selected column B (and other things)...
Read this previous answer on avoiding the use of Select and Activate, it will make your code more robust: How to avoid using Select in Excel VBA macros
Revised Code
See the comments for details, here is a cleaner version of your code which should work first time / every time!
Sub m_Range_End_Method()
Dim col As Range
Dim rng As Range
Dim currentRow As Long
' Use a With block to 'Fully Qualify' the ranges to MySheet
With ThisWorkbook.Sheets("MySheet")
' Set col range to the intersection of used range and column B
Set col = Intersect(.UsedRange, .Columns("B"))
' Loop through cells in col to set description in each row
For Each rng In col
currentRow = rng.Row
' Check upper case value match against upper case string
If InStr(UCase(rng.Value), "PETROL") > 0 Then
.Cells(currentRow, 5) = "Shopping"
.Cells(currentRow, 6) = "Car"
End If
Next rng
End With
End Sub

Resources