How to code to fill down a range with the above value? - excel

I am creating a macro where in column B I need to fill and copy down the value above until the next value is found and again it is copied down until the next one and so and so.
Right now I have the following syntaxis:
Range("B:B").CurrentRegion.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C"
It is working, but the "currentregion" is creating some data that I do not want. How can I replace that or change my syntaxis to make it work only in column B:B

First things first, be careful with Range("B:B").CurrentRegion - it will not necessarily include everything in column B as my example below shows. Next, this macro will copy cell values down to empty cells. Application.ScreenUpdating will speed it up if the range is large.
Sub CopyDown()
Dim rAll, r As Range
Set rAll = Range("B:B").CurrentRegion
Set r = Range("B2")
Application.ScreenUpdating = False
While Not Intersect(r, rAll) Is Nothing ' ie. r in the "current region"
If IsEmpty(r) Then r.Value = r.Offset(-1).Value ' copy down the value above
Set r = r.Offset(1) ' move down 1 row
Wend
Application.ScreenUpdating = True
End Sub

Related

Hiding/unhiding excel sheets based of a matrix

I have a macro that works, but it's not very effective and could be done a lot better.
I simply have a list with all sheet names(they could change so it needs to be dynamic) in one row and in the next row I have a "yes/no" answer that displays if the sheet should be hidden or not.
Example:
Sheet 1, sheet2, sheet3, sheet4,
yes, yes, no, yes
My code so far:
Sub HidingSheets()
'Checking the first sheet
'-------------------------------------------------------------------------------------------
Sheets(Worksheets("Sheet1").Range("E9").Value).Visible = True
Sheets(Worksheets("Sheet1").Range("E9").Value).Activate
If ActiveSheet.Range("A1") = "NO" Then
ActiveSheet.Visible = False
End If
'-------------------------------------------------------------------------------------------
'Checking the second sheet
'-------------------------------------------------------------------------------------------
Sheets(Worksheets("Sheet1").Range("F9").Value).Visible = True
Sheets(Worksheets("Sheet1").Range("F9").Value).Activate
If ActiveSheet.Range("A1") = "NO" Then
ActiveSheet.Visible = False
End If
'-------------------------------------------------------------------------------------------
End Sub
I basically do it manually per every sheet instead of a loop, and this also requires that I need the "yes/no" displayed in every sheet(the "if" formula checking if A1 = "no"). The "yes/no" that s displayed in cell A1 is taken from the matrix that I explained before.
Note: The matrix could be "tranposed", the direction of it doesn't matter.
Thank you in advance if you can help me.
My second attempt is this:
Sub Hiding2()
Dim i As interger
For i = 1 To 10
a = ActiveSheet.Range("E9").Value
If Offset(a(1, 0)) = YES Then
Sheets(a).Visible = True
Else
Sheets(a).Visible = False
End If
Next i
End Sub
But I dont know how to reference the cells that I need, and then get them to move over for every "i".
Sub HideWorksheets()
Dim Cell As Range
Dim Data As Range: Set Data = Worksheets("Sheet1").Range("E9:N9")
On Error Resume Next
For Each Cell In Data
Worksheets(Cell.value).Visible = IIf(Cell.Offset(1, 0) = "YES", xlSheetHidden, xlSheetVisible)
Next Cell
End Sub
You can use Cells instead of Range. With it, you can use column numbers to iterate over some range of columns. There is also other possibilities to exit the code... it depends on the data in your worksheet.
Sub Hiding()
Dim sh as Worksheet, col as Integer
For col = 5 to 100
shName = Worksheets("Sheet1").Cells(9, col).Value
On Error GoTo TheEnd ' in case there is no such sheet
Set sh = Worksheets(shName)
If UCase(sh.Range("A1").Value) = "YES" Then
sh.Visible = xlSheetVisible
Else
sh.Visible = xlSheetHidden
End If
Next col
TheEnd:
End Sub

How can I concatenate column B contents if the cell under autofilter identifier is blank?

I am using autofilter to look up a criteria in column A in Sheet1 and return the corresponding value from column B in a table, however I want to be able to make it concatenate if column B contents are in two cells. Column A is blank under the identifier in cases like this.
Sub ReturnTIResults()
Dim r As Range
Application.ScreenUpdating = True
With Worksheets("Sheet1") ' reference results sheet
If IsEmpty(.Range("A1")) Then .Range("A1").Value = "dummy header"
' if A1 is empty, put a "dummy" header to make AutoFilter work properly
.AutoFilterMode = False
With .Range("B1", .Cells(.Rows.Count, 2).End(xlUp)).Offset(, -1) 'reference referenced sheet column A range from row 1 down to column B last not empty cell
.SpecialCells(xlCellTypeBlanks).Formula = "=R[-1]C" ' fill referenced range blank cells with the same value as the not empty cell above
.AutoFilter Field:=1, Criteria1:="=TI"
On Error Resume Next
Set r = .Resize(.Rows.Count - 1, 1).Offset(1,1).SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If Not r Is Nothing Then r.Copy Worksheets("Search Results").Range("B7")
.Parent.AutoFilterMode = False
.SpecialCells(xlCellTypeFormulas).ClearContents ' clear cell with formulas
If .Range("A1").Value = "dummy header" Then
.Range("A1").ClearContents ' remove any "dummy" header
End With
End With
Application.ScreenUpdating = True
End Sub
Image
Untested, I basically just inserted some code within your If branch.
Few things to note:
Application.Transpose (used below to convert 2-d array to 1-d array) can only handle arrays of length ~65.5k. So if you have more filtered items than that, not all of them may get concatenated.
Cells have a character limit of ~32.8k I think. If the result of your concatenation violates this limit, you may get an error when trying to assign the result.
But other than that, should work fine. Also, both of your Application.ScreenUpdating assignments seem to be True. You might want to look into that.
Option Explicit
Sub ReturnTIResults()
Dim r As Range
Application.ScreenUpdating = True
With Worksheets("Sheet1") ' reference results sheet
If IsEmpty(.Range("A1")) Then .Range("A1").Value = "dummy header"
' if A1 is empty, put a "dummy" header to make AutoFilter work properly
.AutoFilterMode = False
With .Range("B1", .Cells(.Rows.Count, 2).End(xlUp)).Offset(, -1) 'reference referenced sheet column A range from row 1 down to column B last not empty cell
.SpecialCells(xlCellTypeBlanks).Formula = "=R[-1]C" ' fill referenced range blank cells with the same value as the not empty cell above
.AutoFilter Field:=1, Criteria1:="=TI"
On Error Resume Next
Set r = .Resize(.Rows.Count - 1, 1).Offset(1, 1).SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If Not r Is Nothing Then
If r.Rows.Count > 1 Then
Dim toConcatenate As Variant
toConcatenate = Application.Transpose(r.Value2)
toConcatenate = VBA.Strings.Join(toConcatenate, ", ") ' <-- Change to whatever delimiter you want
Worksheets("Search Results").Range("B7").Value2 = toConcatenate
Else
Worksheets("Search Results").Range("B7").Value2 = r.Value2
End If
End If
.Parent.AutoFilterMode = False
.SpecialCells(xlCellTypeFormulas).ClearContents ' clear cell with formulas
If .Range("A1").Value = "dummy header" Then .Range("A1").ClearContents ' remove any "dummy" header
End With
End With
Application.ScreenUpdating = True
End Sub

Move one column to the left inside a formula using Excel's vba

I have some cells that contain the following formulas (there are a lot more but I'm just showing one as an example, they all follow the same pattern of using basic operations like + or - with the values inside certain cells)
=+$O11+$N11+$M11
and I need to move every column one to the left as to end up with something like
=+$N11+$M11+$L11
the thing is that I have already written code that detects whether or not a cell has a formula or only a value
for each cell in Selection // im using a selection for testing purposes only
if cell.hasFormula() = true then
end if
next cell
but I'm still figuring out how to shift all the column references one to the left, the only code I have written trying to do that does not work
auxiliary = "=offset(" + Replace(cell.formula, "=","") + ",0,1)"
cell.formula = auxiliary
Update 1
There are formulas that only use 1 cell to check either if its set or not, up to 8 referenced cells. Numbers or references move around those 2 previously stated numbers
Update 2
I found the following property named Precedents that is returning the range of references, at least that's what doing if I apply it to a formulated reference i.e with the first example, precedents would return $O$11:$M$11
Update 3
There are two more types of formulas besides the one stated above, the first is formulas with a Sum i.e.
=Sum($R20:$AC20)
And with IFs i.e.
=IF($BG20=0,1," ")
All of the cell references inside of this formulas must be shifted to the Left by 1.
So long as you are overwriting the same cell, you can try:
Option Explicit
Sub shiftLeft()
Dim f As String
Dim origCol As Long, newCol As Long
Dim r As Range, c As Range
Dim re As Object, mc As Object, m As Object
Dim I As Long, startNum As Long, numChars As Long
Set re = CreateObject("vbscript.regexp")
With re
.Global = True
.Pattern = "C\[?(-?\d+)"
End With
Set r = Range(Cells(1, 9), Cells(Rows.Count, 9).End(xlUp))
For Each c In r
If c.HasFormula = True Then
f = c.FormulaR1C1
'Debug.Print f, c.Formula
If re.test(f) = True Then
Set mc = re.Execute(f)
For I = mc.Count To 1 Step -1
Set m = mc(I - 1)
startNum = m.firstindex + 1 + Len(m) - Len(m.submatches(0))
numChars = Len(m.submatches(0))
newCol = m.submatches(0) - 1
f = WorksheetFunction.Replace(f, startNum, numChars, newCol)
Next I
End If
End If
c.FormulaR1C1 = f
'Debug.Print f, c.Formula & vbLf
Next c
End Sub
I use regular expressions to find the column designation which will be in the form of Cnn or C[nn] or C[-nn]
We can then subtract one from nn to get the new column number
Use the location and length to decide where to place the replacement.
If the resultant formula refers to a column to the left of column A, this macro will terminate with a run-time error 1004. You should probably add a routine depending on what you want to do in that instance.
EDIT: I did not test to ensure that Cnn is a valid cell address and not a NAME. Mostly that won't matter unless you have some very unusual names (eg Cnnnnnnnnn) since names that conflict with cell addresses will be rejected, but if your C is followed by a large number, it may be accepted. That test could be added if it might be an issue.
you could use a helper temporary sheet to copy/paste data/formula to, delete first column (and have formula shift one column left) and paste back:
Dim tmpSht As Worksheet
Dim rng As Range
Set rng = Selection
Set tmpSht = Worksheets.Add
With rng
.Copy Destination:=tmpSht.Range(rng.Address).Offset(, -1)
End With
With tmpSht
.Columns(1).Delete
.Range(rng.Address).Offset(, -2).Copy Destination:=rng
Application.DisplayAlerts = False
.Delete
Application.DisplayAlerts = True
End With
or you could act on every single cell with formula:
Sub main()
Dim tmpSht As Worksheet
Dim cell As Range, rng As Range
Set rng = Selection
Set tmpSht = Worksheets.Add ' add a "helper" temporary worksheet
For Each cell In rng.SpecialCells(xlCellTypeFormulas) ' loop through selection cells containing a formula
ShiftFormulaOneColumnToTheLeft cell, tmpSht
Next
'delete "helper" worksheet
Application.DisplayAlerts = False
.Delete
Application.DisplayAlerts = True
End Sub
Sub ShiftFormulaOneColumnToTheLeft(rng As Range, hlpSht As Worksheet)
rng.Copy Destination:=hlpSht.Range("B1") ' copy passed range and paste it to passed "helper" worksheet range "B1"
With hlpSht ' reference passed "helper" worksheet
.Columns(1).Delete ' delete referenced worksheet first column and have all its formulas shift one column to the left
.Range("A1").Copy Destination:=rng ' copy "helper" worksheet "A1" cell (where previous "B1" has ended to) content to passed range
End With
End Sub
This would work to move/shift the formula's references as intended:
strFormula$ = "=+$O11+$N11+$M11"
strFormulaMoved$ = Application.ConvertFormula(Application.ConvertFormula(Selection.Formula, XlReferenceStyle.xlA1, XlReferenceStyle.xlR1C1, XlReferenceType.xlRelative, Selection), XlReferenceStyle.xlR1C1, XlReferenceStyle.xlA1, XlReferenceType.xlRelRowAbsColumn, Selection.Offset(0, -1))

Hide table rows *unless* any of 3 columns (in that row) are not blank

I've built this code, and it's working fine. However I expect there must be a more elegant way to embed the range 'c' into the Evaluate function rather than how I've used 'r' to determine the row number, and build that into the reference.
(I'm learning). Copy of (very stripped down) xlsm available here: https://www.dropbox.com/s/e6pcugqs4zizfgn/2018-11-28%20-%20Hide%20table%20rows.xlsm?dl=0
Sub HideTableRows()
Application.ScreenUpdating = False
Dim c As Range
Dim r As Integer
For Each c In Range("ForecastTable[[Group]:[Item]]").Rows
r = c.Row
If Application.Evaluate("=COUNTA(B" & r & ":D" & r & ") = 0") = True Then
c.EntireRow.Hidden = True
Else: c.EntireRow.Hidden = False
End If
Next c
Application.ScreenUpdating = True
End Sub
There's no specific question/problem, but here's my suggested code improvements.
Most notably, I wouldn't execute the Hidden procedure until you have all the rows. That way you don't have repeatedly do something that only need be completed once. This will always be the best practice when looping and manipulating data. Make changes to the sheet AFTER you have identified the range.
With the above change, you don't need to turn off ScreenUpdating.
The Evaluate function is fine, but isEmpty is probably the best option. There are probably slightly faster methods, perhaps checking multiple if-statements, but that's getting into fractions of a second over thousands of rows (probably not worth researching).
Technically you don't really need to loop by rows. You can get by with a single cell in a row, then checking the next two over, see utilization of Offset to generate that range. This also creates a more dynamic than using hard-coded columns ("A"/"B"...etc")
Long is recommended over Integer but this is pretty small, and I'm only mentioning it because I posted about it here.. Technically you don't even need it with the above changes.
Here's the code:
Sub HideTableRows()
Dim c As Range, hIdeRNG As Range, WS As Worksheet
'based on OP xlsm file.
Set WS = Sheet4
'used range outside of used range to avoid an if-statement on every row
Set hIdeRNG = WS.Cells(Rows.Count, 1)
'loops through range of single cells for faster speed
For Each c In Range("ForecastTable[Group]").Cells
If IsEmpty(Range(c, c.Offset(0, 2))) = 0 Then
'only need a single member in the row.
Set hIdeRNG = Union(hIdeRNG, c)
End If
Next c
'Hides rows only if found more than 1 cell in loop
If hIdeRNG.Cells.Count > 1 Then
Intersect(WS.UsedRange, hIdeRNG).EntireRow.Hidden = True
End If
End Sub
Final Thought: There's some major enhancements coming out to Excel supposedly in early 2019 that might be useful for this type of situation if you were looking for a non-VBA solution. Click here for more info from MS.
Flipping the logic a bit, why not just filter those three columns for blanks, then hide all the visible filtered blank rows in one go?
Something like this:
Sub DoTheHide()
Dim myTable As ListObject
Set myTable = Sheet4.ListObjects("ForecastTable")
With myTable.Range
.AutoFilter Field:=1, Criteria1:="="
.AutoFilter Field:=2, Criteria1:="="
.AutoFilter Field:=3, Criteria1:="="
End With
Dim rowsToHide As Range
On Error Resume Next
Set rowsToHide = myTable.DataBodyRange.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
myTable.AutoFilter.ShowAllData
If Not rowsToHide Is Nothing Then
rowsToHide.EntireRow.Hidden = True
End If
End Sub
Since c is used to iterate over the rows and each row contains the 3 cells in question ("=COUNTA(B" & r & ":D" & r & ") = 0") is equivalent to ("=COUNTA(" & c.Address & ") = 0"). But using the WorksheetFunction directly is a better appraoch.
It should be noted that Range("[Table]") will return the proper result as long as the table is in the ActiveWorkbook. It would be better to useThisWorkbook.Worksheets("Sheet1").Range("[Table]")`.
Sub HideTableRows()
Application.ScreenUpdating = False
Dim row As Range, target As Range
With Range("ForecastTable[[Group]:[Item]]")
.EntireRow.Hidden = False
For Each row In .rows
If Application.WorksheetFunction.CountA(row) = 0 Then
If target Is Nothing Then
Set target = row
Else
Set target = Union(target, row)
End If
End If
Next
End With
If Not target Is Nothing Then target.EntireRow.Hidden = True
Application.ScreenUpdating = True
End Sub

Excel odd rows give value

I am trying to assign a value to all the odd cells in a particular column/range. So far I have the following code taken from another question, but it isnt working:
Sub changeClass()
Dim r As Range
Set r = Range("B16").End(xlDown) 'set the range the data resides in
For i = 1 To r.Rows.Count 'merge step
If i Mod 2 = 1 Then 'this checkes to see if i is odd
r.Cells.Value = "value"
End If
Else
r.Cells.Value = "value2"
Next i
End Sub
Basically I need it to add in a value for every cell in the B column from cell 16 down to the last cell i nthe column which has data in. On the even rows the value will be one thing, on the odd it will be another.
Many thanks!
Sub changeClass()
Dim r As Range
Dim i As Integer
For Each r In Range("B16:B24") 'Change this range
i = r.Row
If i Mod 2 = 1 Then 'this checks to see if i is odd
r.Cells.Value = "ODD"
Else
r.Cells.Value = "EVEN"
End If
Next r
End Sub
Try this out, I believe it is not working, because you are not acessing each individual cell inside your loop. In the following macro i use 'rng' to represent the entire range of cells, and 'r' to represent a single cell in each increment of the loop.
Sub changeClass()
Dim rng As Range
Dim r As Range
Set rng = Range(Cells(16,2),Cells(16,2).End(xlDown))
For i = 1 To rng.Rows.Count
Set r = rng.Cells(i)
If i Mod 2 = 1 Then ' You may want to test if it is odd based on the row number (depends on your problem...)
r.Value = "Odd Value"
Else
r.Value = "Even Value"
End If
Next i
End Sub
you've messed up your if statement, don't close it off before else close it only once you are completely done with it! ;) here:
Sub changeClass()
Dim r As Range
Set r = Range("B16").End(xlDown) 'set the range the data resides in
For i = 1 To r.Rows.Count 'merge step
If i Mod 2 = 1 Then 'this checkes to see if i is odd
r.Cells.Value = "value"
Else
r.Cells.Value = "value2"
End if
Next i
End Sub
You don't need a loop for this:
Sub OddRowAlert()
With Range("B16:B100")
.Formula = "=IF((MOD(ROW(B16),2)),""Odd"",""Even"")"
.Formula = .Value
End With
End Sub
Just replace odd and even in the formula with what you want

Resources