I have a macro that does what I want but it runs on a set range of cells. I have been trying to make it run only on cells that are selected (highlighted) by the user. I've tried various combinations of defining the range using Dim Rng as Range and also Selection method. Me:No VBA experience to speak of, some python experience.
Working code (defined range)
Sub NoHalve()
'
' Macro to remove less-than sign and report only the LOR formatted grey and underlined .
' x = columns, y = rows
For x = 1 To 200
For y = 2 To 3000
If Left(Cells(y, x), 1) = "<" Then
Cells(y, x) = (Right(Cells(y, x), Len(Cells(y, x)) - 1))
Cells(y, x).Select
Selection.Font.ColorIndex = 16
Selection.Font.Underline = xlUnderlineStyleSingle
End If
Next y
Next x
End Sub
This is my attempt to make it run on user selected cells that gives me object required error for the r.Select line:
Sub NoHalve_selection()
Set Rng = Selection
For Each r In Rng
If Left(r, 1) = "<" Then
r = (Right(r, Len(r) - 1))
r.Select
Selection.Font.ColorIndex = 16
Selection.Font.Underline = xlUnderlineStyleSingle
End If
Next
End Sub
You're nearly there
Sub NoHalve_selection()
Dim r As Range, Rng As Range
Set Rng = Selection
For Each r In Rng.Cells ' .Cells is implied in For Each r in Rng
With r 'Using With block is more efficient as it does fewer lookups to Excel
If Left$(.Value, 1) = "<" Then ' .Value uses the With block (so is the same as r.Value). Value is the default property of a Range
.Value = Mid$(.Value, 2)
.Font.ColorIndex = 16
.Font.Underline = xlUnderlineStyleSingle
End If
End With
Next
End Sub
Original post for comparison
Sub NoHalve_selection()
Dim r As Range, Rng As Range
Set Rng = Selection
For Each r In Rng
If Left(r, 1) = "<" Then
r = (Right(r, Len(r) - 1))
r.Font.ColorIndex = 16
r.Font.Underline = xlUnderlineStyleSingle
End If
Next
End Sub
This should be relatively easy by replacing the hard coded numbers with Selection.Columns.Count and Selection.Rows.Count.
Sub NoHalve()
'
' Macro to remove less-than sign and report only the LOR formatted grey and underlined .
' x = columns, y = rows
For x = 1 To Selection.Columns.Count
For y = 2 To Selection.Rows.Count
If Left(Cells(y, x), 1) = "<" Then
Cells(y, x) = (Right(Cells(y, x), Len(Cells(y, x)) - 1))
Cells(y, x).Font.ColorIndex = 16
Cells(y, x).Font.Underline = xlUnderlineStyleSingle
End If
Next y
Next x
End Sub
If you are making a simple text substitution (removing a sign), as long as that sign is left most then I'd also suggest maybe using something that doesn't rely so heavily on the position of the characters in a string. So something like replace:
Cells(y, x) = replace(Cells(y, x),"<","",,1)
Also I don't believe the line Cells(y, x).Select is required and may change the active selection unnecessarily.
Related
i'm working on a macro to generate comparison between numbers and i have the specific task to add some shapes near the text of every cell.
I tried to figure out how to calculate x, y, width, height in order to nicely place them in the cell, but without success.
I asked about that in a previous question and also obtaines a nice answer, but since i have a specific request i need the vba way to do this.
Any suggestions?
Dim s As Shape, sh As Worksheet
Set sh = ActiveSheet
If arrType = "Up" Then
Set s = sh.Shapes.AddShape(msoShapeUpArrow, x, y, width, height)
Else
Set s = sh.Shapes.AddShape(msoShapeDownArrow, x, y, width, height)
End If
Try the next adapted code, please. It happens I know your previous question on this theme, too:
Sub Compare_numbers()
Dim sh As Worksheet, i As Long, lastRow As Long
Dim arrA, txt As String
Set sh = ActiveSheet
lastRow = sh.cells(rows.count, "L").End(xlUp).row
For i = 2 To lastRow
If sh.cells(i, "L").Value = sh.cells(i, "M").Value Then
sh.cells(i, "N").Value = "they are equal"
arrA = isArrow(sh.Range("N" & i), "")
ElseIf sh.cells(i, "L").Value > sh.cells(i, "M").Value Then
With sh.cells(i, "N")
.Value = "L is greater than M ."
.EntireColumn.AutoFit
End With
arrA = isArrow(sh.Range("N" & i), "Up")
If arrA(0) = "OK" Then
If arrA(1) <> "Up" Then
insertArrow sh.Range("N" & i), "Up"
End If
Else
insertArrow sh.Range("N" & i), "Up"
End If
Else
With sh.cells(i, "N")
.Value = "L is greater than M ." 'Used this solution to Autofit on the larger text...
.EntireColumn.AutoFit
.Value = "L is less than M ."
End With
arrA = isArrow(sh.Range("N" & i), "Down")
If arrA(0) = "OK" Then
If arrA(1) <> "Down" Then
insertArrow sh.Range("N" & i), "Down"
End If
Else
insertArrow sh.Range("N" & i), "Down"
End If
End If
Next i
End Sub
It needs the following Sub inserting the appropriate arrow:
Sub insertArrow(rng As Range, arrType As String)
Dim sh As Worksheet, s As Shape
Dim leftP As Double, topP As Double, W As Double, H As Double
Set sh = rng.Parent
W = 8: H = 12 'set the arrow width and height (you can change them)
leftP = rng.left + rng.width - W - 1 'calculate the horiz position
topP = rng.top + (rng.height - H) / 2 'calculate the vert position
If arrType = "Up" Then
Set s = sh.Shapes.AddShape(msoShapeUpArrow, leftP, topP, W, H)
Else
Set s = sh.Shapes.AddShape(msoShapeDownArrow, leftP, topP, W, H)
End If
s.Name = s.Name & "-" & rng.Address 'add the cell address to be able
'to bring back the arrows moved by mistake
s.LockAspectRatio = msoFalse: s.placement = xlMoveAndSize
End Sub
And the next Function able to check if a shape is an arrow and what type:
Function isArrow(rng As Range, typeArr As String) As Variant
Dim s As Shape, sh As Worksheet, arr
Set sh = rng.Parent 'extract the range sheet where it belongs
For Each s In sh.Shapes
If s.TopLeftCell.Address = rng.Address Then 'match the range address with the shape TLCell address
If left(s.Name, 2) = typeArr Or left(s.Name, 4) = typeArr Then
isArrow = Array("OK", typeArr): Exit Function
Else
If left(s.Name, 2) = "UP" Or left(s.Name, 4) = "Down" Then
isArrow = Array("OK", IIf(typeArr = "Up", "Down", "Up"))
s.Delete: Exit Function
End If
Exit For
End If
End If
Next
isArrow = Array("No", "")
'the function creates an array able to 'tell' if the shape is an arrow and its type
End Function
Unfortunately, there is no event able to be triggered by the cell sizes change. But, try the next event, which act when you double click a cell. Please, copy it in the sheet code module, where you need to insert arrows:
Option Explicit
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim lastR As Long, s As Shape, i As Long, addr As String
'bring back the arrows moved by mistakes:
For Each s In Me.Shapes
If left(s.Name, 2) = "Up" Or left(s.Name, 4) = "Down" Then
addr = Split(s.Name, "-")(UBound(Split(s.Name, "-")))
If addr <> s.TopLeftCell.Address Then
s.left = Me.Range(addr).left + 10
s.top = Me.Range(addr).top + 1
End If
End If
Next
'last row on the column to be processed (N:N):
lastR = Me.Range("N" & Me.rows.count).End(xlUp).row
Me.Range("L:N").VerticalAlignment = xlCenter 'to look nicer
For i = 2 To lastR
arrangeSh Me.Range("N" & i)
Next i
End Sub
Sub arrangeSh(rng As Range)
Dim sh As Shape
For Each sh In rng.Parent.Shapes
If sh.TopLeftCell.Address = rng.Address Then
'bring back the row height if is less then the arrow height:
If rng.height < 12 Then rng.EntireRow.height = 12
sh.width = 8: sh.height = 12 'reset the arrow dimensions
'reposition the arrows:
sh.top = rng.top + (rng.height - sh.height) / 2
sh.left = rng.left + rng.width - sh.width - 1
Exit For
End If
Next
End Sub
It will firstly check if the arrow has been moved by mistake and bring it back on the 'mother' cell, then place it centered, at 1 point from the right cell side.
The event code can be placed in a sub, let us say reArrangeShapes, the event will have a single line reArrangeShapes, and this sub may be called from different sheet events (Calculate, Activate, Deactivate etc.). Doing its job when the events in discussion are triggered.
The subs can be optimized using Appliction.ScreenUpdating = True and Application.Calculation = xlCalculationManual at the beginning followed by Appliction.ScreenUpdating = True and and Application.Calculation = xlCalculationAutomatic at the end.
If something unclear, please, do not hesitate to ask for clarifications...
Edited:
In order to better understand what's happening the isArrow function can be tested using the next sub. You should select a cell (having or not an arrow and run the code:
Sub testIsArrow()
Debug.Print isArrow(ActiveCell, "Up")(0)
End Sub
What does it return in Immediate Window (being in IDE, press Ctrl + G)?
You must understand that if the active cell keeps an arrow not being "Up" type, the shape will be deleted...
I have created a method for defining range between two words and iterate through it to copy paste values from one worksheet to another. There is some strange reason it does not work.
I specify row, it is 18, my code starts from row 20? So it copies everything starting from row 20. O_o
It does not detect range correctly as it copies values below my words as well? I have checked that I don't have same words elsewhere.
Any suggestions?
Here is code for calling method:
Sub dsfdsfdsfds()
copyOptionsToTable 18, CalculationItemOM1
End Sub
Here is method:
Private Sub copyOptionsToTable(RowToPaste As Integer, OperatingWorksheet As Worksheet)
'Dim FirstWord, SecondWord
Dim OptionsRange As Range
Dim cell, x
'Set FirstWord = OperatingWorksheet.Range("W:W").Find("OPTIOONS START", LookIn:=xlValues, lookat:=xlWhole)
'Set SecondWord = OperatingWorksheet.Range("W:W").Find("OPTIOONS END", LookIn:=xlValues, lookat:=xlWhole)
Set OptionsRange = OperatingWorksheet.Range(OperatingWorksheet.Cells.Find("[OPTIOONS START]"), OperatingWorksheet.Cells.Find("[OPTIOONS END]"))
x = 0
' Copy - Paste process
For Each cell In OptionsRange
If Not IsEmpty(cell.Value) Or cell.Value <> "OPT" Then
ThisWorkbook.Worksheets("TableForOL").Range("B" & RowToPaste).Offset(0 + x, 0).Value = cell.Offset(0 + x, -20).Value
ThisWorkbook.Worksheets("TableForOL").Range("B" & RowToPaste).Offset(0 + x, 3).Value = cell.Offset(0 + x, 2).Value
End If
x = x + 1
Next cell
End Sub
Source sheet:
Output sheet:
EDIT:
Output still looks like this?
You're already incrementing cell by one row inside the loop - you don't need to further offset that using x
Set OptionsRange = OperatingWorksheet.Range( _
OperatingWorksheet.Cells.Find("[OPTIOONS START]").Offset(1,0), _
OperatingWorksheet.Cells.Find("[OPTIOONS END]").Offset(-1, 0))
x = 0
' Copy - Paste process
For Each cell In OptionsRange.Cells
If Not IsEmpty(cell.Value) Or cell.Value <> "OPT" Then
With ThisWorkbook.Worksheets("TableForOL").Range("B" & RowToPaste)
.Offset(x, 0).Value = cell.Offset(0, -20).Value
.Offset(x, 3).Value = cell.Offset(0, 2).Value
End With
x = x + 1 '<< only increment if you copied values...
End If
Next cell
Also I'm not sure this line does what you intend?
If Not IsEmpty(cell.Value) Or cell.Value <> "OPT" Then
maybe
If Not IsEmpty(cell.Value) And cell.Value <> "OPT" Then
This part of my macro is for coloring the cells in row B, depending on their value and the value of the corresponding cell in row Q. It works well, but when the file is large (sometimes over 500,000 rows), this step can really slow down the entire execution of the macro. There is also the likelyhood that I will need to add more colors down the road, which will mean more IF statement lines which will slow it down even more.
Dim LastRow As Long
LastRow = Cells(Rows.Count, 2).End(xlUp).Row
Dim i As Long, r1 As Range, r2 As Range
For i = 11 To LastRow
Set r1 = Range("B" & i)
Set r2 = Range("Q" & i)
If r2 = "001111" Then r1.Interior.Color = vbGreen
If (r1 < 4 Or r1 > 0) And (r2 <> "001111") Then r1.Interior.Color = vbYellow
If (r1 > 3 Or r1 < 1) And (r2 <> "001111") Then r1.Interior.Color = vbRed
Next i
I tried using some code for conditional formatting on the entire row. This is much faster, but I wasn't able to figure out how to include the value of the cell in column Q as a condition. I was also limited to no more conditions than three.
Is there a way to accomplish this task in a way that is faster than my current code that will also allow for more conditions/colors in the future?
Scratch my previous attempt. I do agree that Range.AutoFilter might even be better:
Sub Test()
Dim lr As Long, rng As Range
With Sheet1
'Get last used row of data and set range
lr = .Cells(.Rows.Count, 2).End(xlUp).Row
Set rng = .Range("B10:Q" & lr)
'Apply first filter and color Green
rng.AutoFilter 16, "001111"
If rng.Columns(1).SpecialCells(12).Count > 1 Then rng.Columns(1).Offset(1).Resize(lr - 10).Interior.Color = vbGreen
'Apply second filter and color Yellow
rng.AutoFilter 16, "<>*001111*"
rng.AutoFilter 1, "<4", xlAnd, ">0"
If rng.Columns(1).SpecialCells(12).Count > 1 Then rng.Columns(1).Offset(1).Resize(lr - 10).Interior.Color = vbYellow
'Apply third filter and color Red
rng.AutoFilter 1, ">3", xlOr, "<1"
If rng.Columns(1).SpecialCells(12).Count > 1 Then rng.Columns(1).Offset(1).Resize(lr - 10).Interior.Color = vbRed
'Remove AutoFilter
rng.AutoFilter
End With
End Sub
I guess the fastest would be to use an array? Maaaaybe some filter but I'm just gonna do the array for now:
Application.ScreenUpdating = False
ThisWorkbook.Sheets("Tabelle1").Range("B11:B500000") = 1
ThisWorkbook.Sheets("Tabelle1").Range("Q11:Q500000") = 2
LastRow = Cells(Rows.Count, 2).End(xlUp).Row
Dim r1
Dim r2
r1 = ThisWorkbook.Sheets("Tabelle1").Range("B11:B" & LastRow)
r2 = ThisWorkbook.Sheets("Tabelle1").Range("Q11:Q" & LastRow)
For i = LBound(r1) To UBound(r1)
If r2(i, 1) = "001111" Then r1(i, 1) = vbGreen
If (r1(i, 1) < 4 Or r1(i, 1) > 0) And (r2(i, 1) <> "001111") Then r1(i, 1) = vbYellow
If (r1(i, 1) > 3 Or r1(i, 1) < 1) And (r2(i, 1) <> "001111") Then r1(i, 1) = vbRed
Next i
With ThisWorkbook.Sheets("Tabelle1")
For i = LBound(r1) To UBound(r1)
.Range("B" & 10 + i).Interior.Color = r1(i, 1)
Next
End With
Application.ScreenUpdating = True
I wish we could apply the .Interior.Color all in one go but I can't get that to work. If someone else does, I'd really like to know too! This executes in 24.75s on my machine. Oh and I didn't check your logic for the <, > things, I just added an array. It will likely break if something unexpected is written in one of the cells, like a string or something.
Also I assume you use IFs instead of elseif for a reason? Not like it really matters if done in an array, just curious.
Need any help on how I can achieve as in the image below.
I want to combine multiple rows of the same person into one while transposing the column value of the person into a single row. I would like to achieve doing it preferable via VBA but if not then by formula.
Sorry I don't have anything to show. I already have the codes to generate a unique list for the names but I don't know how to transpose the data in the respective columns. I don't have any idea on how to approach this problem. Seeking any guidance or even ideas.
Public Sub extractUniques(rngSource As Range, rngTarget As Range)
Application.ScreenUpdating = False
rngSource.AdvancedFilter Action:=xlFilterCopy, _
copytorange:=rngTarget, Unique:=True
Application.ScreenUpdating = True
End Sub
Try this!
Sub specialTransfer()
Dim inp As Range, outp As Range, rng As Range, c As Range, data(), u, r, x, i, j
Set inp = [A1] 'Change this to the top left cell of your input
Set outp = [F1] 'Change this to the top left cell of your output
Set rng = Range(inp.Offset(1, 1), Cells(Rows.Count, 2).End(xlUp))
data = rng.Value
Set u = CreateObject("Scripting.Dictionary")
For r = 1 To UBound(data)
u(data(r, 1)) = Empty
Next r
x = u.Keys()
'Option to clear out everything past the outputcell
'Range(outp, Cells(Rows.Count, Columns.Count)).ClearContents
outp = "Name"
For i = 0 To u.Count - 1
j = 1
outp.Offset(i + 1) = x(i)
For Each c In rng
Range(outp.Offset(, j), outp.Offset(, j + 2)) = Array("Day", "Time out", "Time in")
If WorksheetFunction.CountA(c.Offset(, -1).Resize(, 4)) = 4 Then
If c = x(i) Then
outp.Offset(i + 1, j).Value = Format(Mid(c.Offset(, -1), 4, 10), "General Number")
outp.Offset(i + 1, j + 1).Value = Format(c.Offset(, 1), "h:mm AM/PM")
outp.Offset(i + 1, j + 2).Value = Format(c.Offset(, 2), "h:mm AM/PM")
j = j + 3
End If
End If
Next c
Next i
End Sub
Hoping to be able to achieve it via VBA but don't think that I can. It's not the exact result I was hoping for but it works. Did it using array formula below.
=IFERROR(INDEX("table data",MATCH(1,("criteria1 column"="criteria1")*("criteria2 column"="criteria2")*("criteria3 column"<>""),0), "criteria3 colNum"),"")
Ctrl + Shift + Enter
multiple row into one
I want to copy some values in a for-loop in VBA. Therefore I calculate the limits by:
For iCounter = 1 To (Range(...).End(xlDown).Row - Range(...).Row + 1)
Sadly Range(...).End(xlDown).Row returns 1048576. When I debug, suddenly the value changes to the right one. The same Code works well in some other locations in the VBA Code.
The main thing to keep in mind is that the End method reproduces in VBA the functionality of using Ctrl+Arrow Keys. These are meant to navigate between blocks of values.
Start at A1 here:
Hit Ctrl+Down:
This illustrates what happens when you use Ctrl+Down at the beginning or end of a block consisting of multiple cells -- you go to the end of that block. This is the case that you are implicitly referring to when you said that sometimes the code works well.
Now -- hit Ctrl+Down again:
You jumped to the next block. Now, do it again:
The bottom of that block. Finally, again:
Well -- there is no next block to go to -- so it goes all the way down. This corresponds to the case that strikes you as weird. It puts you here:
But -- something cool happens now: Press Ctrl + Up -- and Excel searches up for the next block:
And this is the last cell with data in column A.
For this reason -- you see the following code a lot in Excel VBA:
Cells(Rows.Count,1).End(xlUp)
To get the last used cell in a column (1 in this case) or the first cell in the column if the overall column is blank.
It's curious what you're saying about getting the right value when debugging. That makes me think that you may need to show more of your code. Anyway, doesn't matter, I just decided to have some fun with this. Here are multiple sample code snippets to do what you're asking about. Hope it gives you or anyone else interested some ideas about how to approach this problem because it is very common.
I'd definitely avoid using End(xlwhatever) on its own. It makes for non-robust code because it's too easy for values to get shuffled. It's not hard to just take the time to create a more elegant solution. You should be able to come up with something pretty robust by tailoring and combining the methods below for your particular use.
Sub CellLoop1()
'Colors cells from row i to last value in column
For i = 1 To Cells(Rows.Count, 1).End(xlUp).Row
Cells(i, 1).Interior.Color = RGB(0, 200, 0)
Next
End Sub
Sub CellLoop2()
'Colors cells from row i to end of continuous values or until row 100
Dim i As Integer
i = 1
Dim r As Range
Set r = Cells(i, 1)
Do Until r.Value2 = "" Or i > 100
r.Interior.Color = 123
i = i + 1
Set r = Cells(i, 1)
Loop
End Sub
Sub CellLoop3()
'Colors cells from row i until occurance of 5 continuous blanks
Dim i As Integer
i = 1
Dim r As Range
Set r = Cells(i, 1)
Dim BlankChain As Integer
Do Until BlankChain >= 5
r.Interior.Color = 123
If r.Value = Empty Then
BlankChain = BlankChain + 1
Else
BlankChain = 0
End If
i = i + 1
Set r = Cells(i, 1)
Loop
End Sub
Sub CellLoop4()
'Colors cells from row i until no value in sight (in next k number of rows)
Dim i, k, BlankCount As Integer
i = 1
k = 10
Dim r, SightRange As Range
Set r = Cells(i, 1)
Dim NoValueInSight As Boolean: NoValueInSight = False
Do Until NoValueInSight
Set SightRange = Range(r, r.Offset(k - 1, 0))
BlankCount = Application.WorksheetFunction.CountBlank(SightRange)
If BlankCount = SightRange.Rows.Count Then
NoValueInSight = True
Else
r.Interior.Color = RGB(255, 50, 255)
End If
i = i + 1
Set r = Cells(i, 1)
Loop
End Sub
Sub CellLoop5()
'Colors all values under range r (set as "A1")
Dim r, UnderRange As Range
Set r = Range("A3")
Set UnderRange = Range(r, Cells(Rows.Count, 1))
Dim i, n, BlankCount As Double: i = r.Row: n = 0
BlankCount = Application.WorksheetFunction.CountBlank(UnderRange)
Do Until n = (UnderRange.Rows.Count - BlankCount)
If Cells(i, 1) <> "" Then
n = n + 1
Cells(i, 1).Interior.Color = RGB(200, 200, 200)
End If
i = i + 1
Loop
End Sub
Sub CellLoop6()
'Colors all values under range r (set as "A1")
Dim r As Range
Set r = Range("A1")
If r.Value = "" Then Set r = r.End(xlDown)
Do Until r.Value = ""
r.Interior.Color = RGB(255, 100, 100)
If r.Offset(1, 0).Value <> "" Then
For i = r.Row To r.End(xlDown).Row
Cells(i, 1).Interior.Color = RGB(255, 100, 100)
Next
End If
Set r = r.End(xlDown)
Loop
End Sub