I have the code below that does the following.
It finds the text “EE Only” in column A and records the row number.
It then adds four rectangles with the first one in the recorded row number and the other three in the three rows below.
It then formats the rectangles with no fill and a black border.
I have dim c as Integer and c = 2. I then use that as the column. So far everything is working as it should. The problem I’m having is that I need the column number to increase by one for every column after B that has something in row 3. In other words; the first set of shapes will always be in column B. After that if there is something in C3 then I need the column number to be increase by 1 and the shapes added to column C. If something is in D3 increase c by 1 and add shapes to column D and so forth. The first time row 3 is blank the loop would stop.
I’ve tried a couple of different things and I’m at a completely loss. The other issue I’m having is, if I run the code with c = 2 the shapes are formatted properly. If I then leave those shapes and manually change to c = 3 and run the code again, the new set of shapes have a blue fill. Again, tried everything I could find and nothing works.
Sub AddShapes()
Const TextToFind As String = "EE Only"
Dim ws As Worksheet
Dim RowNum As Range
Dim SSLeft As Double
Dim SSTop As Double
Dim SS As Range
Set ws = ActiveSheet
Dim c As Integer
c = 2
Set RowNum = ws.Range("A:A").Find(what:=TextToFind, lookat:=xlWhole)
Set SS = Cells(RowNum.Row, c)
SSLeft = Cells(RowNum.Row, c).Left + (Cells(RowNum.Row, c).Width) / 4
'Add four rectangles
Dim y As Integer
For y = 0 To 3
SSTop = Cells(RowNum.Row + y, c).Top + ((Cells(RowNum.Row + y, c).Height) / 2) - 5
Call ActiveSheet.Shapes.AddShape(msoShapeRectangle, SSLeft, SSTop, 10, 10)
Next
'Format them
ws.DrawingObjects.Select
Selection.ShapeRange.Fill.Visible = msoFalse
With Selection.ShapeRange.Line
.Visible = msoTrue
.Weight = 1
.ForeColor.RGB = RGB(0, 0, 0)
.Transparency = 0
End With
End Sub
I wasn't 100% sure about your requirements, but here's my best interpretation of it. Not I defined a new subroutine for the rectangles section, see comments for details
Sub AddShapes()
Const TextToFind As String = "EE Only"
Dim ws As Worksheet
Dim RowNum As Range
Set ws = ActiveSheet
Dim c As Integer
c = 2
Set RowNum = ws.Range("A:A").Find(what:=TextToFind, lookat:=xlWhole)
Call Rectangles(RowNum.row, c, ws) ' call the rectangles function for our first instance
c = c+1 ' increment the column by one so we're not on the same column
Do While Not IsEmpty(Cells(3,c).Value) 'Loop through each column until the 3rd row is empty
Call Rectangles(3,c,ws) ' call our rectangles function on the 3rd row in the current column (c)
c=c+1 ' increment the column
Loop
End Sub
Sub Rectangles(row As Integer, c As Integer, ws As Worksheet) ' we define a separate sub to draw the rectangles so that we can call it again and again
Dim SSLeft As Double
Dim SSTop As Double
Dim SS As Range
Set SS = Cells(row, c)
SSLeft = Cells(row, c).Left + (Cells(row, c).Width) / 4
'Add four rectangles
Dim y As Integer
For y = 0 To 3
SSTop = Cells(row + y, c).Top + ((Cells(row + y, c).Height) / 2) - 5
Call ActiveSheet.Shapes.AddShape(msoShapeRectangle, SSLeft, SSTop, 10, 10)
Next
'Format them
ws.DrawingObjects.Select
Selection.ShapeRange.Fill.Visible = msoFalse
With Selection.ShapeRange.Line
.Visible = msoTrue
.Weight = 1
.ForeColor.RGB = RGB(0, 0, 0)
.Transparency = 0
End With
End Sub
Related
I am trying to copy data (cca. 70*25*15 Cells) out of a txt file into libre office spreadsheet with a macro. Right now im accesing cells with this code :
Example:
Dim CellValue As Object
CellValue = ThisComponent.Sheets(1).getCellByPosition(i, j)
CellValue.String = "Whatever data i get from txt"
By running the above code and if text file have (70*25*15) cell entries, it needs 2min and 18 sec to copy all the data from text file to spreadsheet. Is there a better way to acess cells and speed up the process ?
You could set the DataArray of an object which implements XCellRangeData form an array of arrays.
Example:
Sub Test()
aDataArray = array(array("A1", "B1", "C1"), array("A2", 2.2, 2.3), array("A3", 3.2, 3.3))
oDoc = ThisComponent
oSheet = oDoc.getSheets().getByIndex(1)
oRange = oSheet.getCellRangeByName("A1:C3")
'xray oRange
oRange.setDataArray(aDataArray)
End Sub
https://www.openoffice.org/api/docs/common/ref/com/sun/star/sheet/XCellRangeData.html#setDataArray
Edit:
More complex example:
Sub Test()
lRowCount = 1800
lColCount = 40
dim aDataArray() as variant
dim aColumnArray() as variant
redim aDataArray(lRowCount-1) 'the main array is for rows; 0-based so count-1
for lRow = lbound(aDataArray) to ubound(aDataArray)
redim aColumnArray(lColCount-1) 'this array is for column data
for lCol = lbound(aColumnArray) to ubound(aColumnArray)
'create some sample data
select case lCol
case 1
aColumnArray(lCol) = CInt(int((10 * rnd()) + 1) 'integer in column 2
case 2
aColumnArray(lCol) = CDbl(1000 * rnd()) 'double in column 3, later formatted as currency
case 3
aColumnArray(lCol) = CDbl(date + int(365*rnd())) 'date in column 4, must be double in the array, will be later formatted as date
case 4
aColumnArray(lCol) = CInt(lRow mod 2 = 0) 'boolean in column 5, must be integer in the array, will be later formatted as booleann
case else
aColumnArray(lCol) = "r" & lRow & "c" & lCol 'all other columns string
end select
next
aDataArray(lRow) = aColumnArray
next
oDoc = ThisComponent
oSheet = oDoc.getSheets().getByIndex(0)
lStartRow = 1
lStartCol = 0
'make sure, the size of the range will exactly match the array size
oRange = oSheet.getCellRangeByPosition(lStartCol, lStartRow, lStartCol+lColCount-1, lStartRow+lRowCount-1)
oRange.setDataArray(aDataArray) 'now the data is in the sheet
'this code is for formatting
oLocale = new com.sun.star.lang.Locale 'create empty locale
oNumberFormats = thiscomponent.getNumberFormats() 'get NumberFormats from Calc
'format column 3 as currency
oRange = oSheet.getCellRangeByPosition(2, lStartRow, 2, lStartRow+lRowCount-1)
lCurrencyFormat = oNumberFormats.getStandardFormat(com.sun.star.util.NumberFormat.CURRENCY, oLocale)
oRange.NumberFormat = lCurrencyFormat
'format column 4 as date
oRange = oSheet.getCellRangeByPosition(3, lStartRow, 3, lStartRow+lRowCount-1)
lFormat = oNumberFormats.getStandardFormat(com.sun.star.util.NumberFormat.DATE, oLocale)
oRange.NumberFormat = lFormat
'format column 5 as boolean
oRange = oSheet.getCellRangeByPosition(4, lStartRow, 4, lStartRow+lRowCount-1)
lFormat = oNumberFormats.getStandardFormat(com.sun.star.util.NumberFormat.LOGICAL, oLocale)
oRange.NumberFormat = lFormat
End Sub
Excel File Format
I have master data table with attribute ID which is unique and unit.
I have another table where I add the product values associated with the attributes.
If the units match based on the attribute ID, then the cells are highlighted in green else red.
To achieve the above, I have written some basic code, but it doesn't seem to work. The vlookup works normally but using vba it just exits. Please see image for example and code below. Column a and b contains attribute master data and column d to g contain product attribute values.
Eg. For product p1, value 'IN' is valid for attribute 'A1' but 'm' is not valid.
Also, there can be multiple units separated by comma for each attribute. Need help to solve issue.
Code:
Sub UnitCheck()
Dim AttrIDrange As range, AttrIDcell As range
Dim attrID
Dim Lookup_Range As range
Dim I, J As Variant
Dim UNIT As Variant
Set Lookup_Range = range("A2:B4")
Set AttrIDrange = range("E1:G1")
For Each AttrIDcell In AttrIDrange
attrID = AttrIDcell.Value
For I = 2 To 3
For J = 5 To 7
If Application.WorksheetFunction.VLookup(attrID, Lookup_Range, 2, False) = UNIT Then
Worksheets("Sheet4").Cells(I, J).Font.Color = vbGreen
Else
Worksheets("Sheet4").Cells(I, J).Font.Color = vbRed
End If
Next
Next
Next
End Sub
New Scenario:
If there are multiple units defined for any attribute, then even if only 1 of the unit is present in the product value, it should be highlighed in green. If 1 is correct and rest are incorrect then since it is partially correct, it should be highlighted in red or even yellow. Please see image. Need help for this scenario.
Scenario 2 Image
When cycling through the I and J loops, you need to set UNIT to something before you can compare it to the results passed back from the worksheet VLOOKUP function.
Sub UnitCheck()
Dim AttrIDrange As Range, AttrIDcell As Range
Dim attrID
Dim Lookup_Range As Range
Dim I As Long, J As Long
Dim UNIT As Variant
Dim bCHECK_P1
With Worksheets("Sheet4")
bCHECK_P1 = False
Set Lookup_Range = .Range("A2:B4")
Set AttrIDrange = .Range("E1:G1")
For Each AttrIDcell In AttrIDrange '.Range("E1:G1")
attrID = AttrIDcell.Value
For I = 2 To 3
UNIT = AttrIDcell.Offset(I - 1, 0).Value '<~~ set UNIT here!
For J = 5 To 7
.Cells(I, J).Font.Color = xlAutomatic
If Application.WorksheetFunction.VLookup(attrID, Lookup_Range, 2, False) = UNIT Then
.Cells(I, J).Font.Color = vbGreen
Else
.Cells(I, J).Font.Color = vbRed
End If
Next
Next
Next
End With
End Sub
Here's something that I think does what the OP is asking. Also, this replaces the triple For/next and VLookUp with a dictionary which is hopefully more amenable.
In regard to the OP, I see two fundamental problems, UNIT is never assigned, but also, it sounds like the = operator isn't correct... instead, it sounds like a lookup value must be confirmed present in a comma separated string. The code below uses InStr to check if the lookup value is present.
Sub UnitCheck()
Application.ScreenUpdating = False
Dim UNIT As String
Dim R, c, AttrID As Integer
Dim ProdRange, ProdCell As Range
Set ProdRange = Range("E2:G4")
'Assign LookUp values to array
Dim LookUpArray(), ProdAttrIDArray() As Variant
LookUpArray = Range("A2:B4").Value2
'Create dictionary from Lookup values
Set D = CreateObject("Scripting.Dictionary")
For R = 1 To UBound(LookUpArray)
D.Add LookUpArray(R, 1), LookUpArray(R, 2)
Next
'Loop through product table
For Each ProdCell In ProdRange
'Get attribute ID from row 1 of corresponding column
AttrID = Cells(1, ProdCell.Column).Value2
If D(AttrID) <> Empty Then
'If AttrID found in LookUp Dictionary then get UNIT from it
UNIT = D(AttrID)
'If UNIT found in product cell then color cell green, else red
If (InStr(1, ProdCell.Value2, UNIT) > 0) Then
ProdCell.Interior.Color = vbGreen
Else
ProdCell.Interior.Color = vbRed
End If
End If
Next
End Sub
The result is this
New Scenario
Ok, I think this will cover your new scenario. Notice that I also added Trim() and VBTextCompare so that spaces are ignored and the comparison is case-insensitive. I'm not sure if you want that behavior or not. Also, notice that the order of units does not matter. For example, "IN, km" matches "KM, IN" because the spaces are ignored, capitalization is ignored, and the order is ignored.
Sub UnitCheck()
Application.ScreenUpdating = False
Dim UNIT, PUnits() As String
Dim r, c, AttrID, i, n As Integer
Dim ProdRange, ProdCell As Range
Set ProdRange = Range("E2:G3")
'Assign LookUp values to array
Dim LookUpArray(), ProdAttrIDArray() As Variant
LookUpArray = Range("A2:B4").Value2
'Create dictionary from Lookup values
Set D = CreateObject("Scripting.Dictionary")
For r = 1 To UBound(LookUpArray)
D.Add LookUpArray(r, 1), LookUpArray(r, 2)
Next
'Loop through product table
For Each ProdCell In ProdRange
'Get attribute ID from row 1 of correspdniong column
AttrID = Cells(1, ProdCell.Column).Value2
If D(AttrID) <> Empty Then
'If AttrID found in LoopUp Dictionary then get UNIT from it
UNIT = D(AttrID)
PUnits = Split(ProdCell.Value2, ",")
'reset counter
n = 0
'Count the number of product units found in the lookup value
For i = 0 To UBound(PUnits)
If InStr(1, Trim(UNIT), Trim(PUnits(i)), vbTextCompare) > 0 Then
n = n + 1
End If
Next
'prevent division by zero
If i = 0 Then i = 1
'select action based on percent matched
Select Case n / i
Case Is >= 1
ProdCell.Interior.Color = vbGreen
Case Is > 0
ProdCell.Interior.Color = vbYellow
Case Else
ProdCell.Interior.Color = vbRed
End Select
End If
Next
End Sub
I am trying to create two arrays: one having the row numbers of orange cells and the other with the row numbers of blue cells. I've been trying to debug this code for a while but it is giving me the error: "Compile error: Object required"; whilst highlighting in yellow the first line of the function: "Function ArrayOrangeBlue()". I'm new to VBA and I'm pretty sure I'm missing something in the syntax.
Do you have any feedback please?
Sub CommandButton1_Clicked()
ArrayOrangeBlue
End Sub
Function ArrayOrangeBlue()
Dim i As Integer 'row number'
Dim j As Integer 'orange counter'
Dim k As Integer 'blue counter'
Dim l As Integer
Dim m As Integer
Dim blue(1 To 1000) As Double
Dim orange(1 To 1000) As Double
'Starting Row'
Set i = 10
'Initialize orange and blue counters to 1'
Set j = 1
Set k = 1
Set l = 10
Set m = 10
'Loop until Row 1000'
Do While i <= 1000
'Dim cell As Range
'Set cell = ActiveSheet.Cells(i, 1)
'If cell colour is Orange- note absolute row number (i) in array: orange'
If Cells(i, 1).Interior.Color = 9420794 Then
orange(j) = i
Sheets("Detail analysis").Cells(l, 15) = i
j = j + 1
l = l + 1
'MsgBox ("This one is Orange")
Else
'If cell colour is Blue- note absolute row number (i) in array: blue'
If Cells(i, 1).Interior.Color = 13995347 Then
blue(k) = i
Sheets("Detail analysis").Cells(m, 16) = i
k = k + 1
m = m + 1
'MsgBox ("This one is Blue")
End If
End If
i = i + 1
Loop
End Function
so finally I managed to resolve my code to find and note cells with certain colours.
here the code stores the row numbers of the Orange cells in the array orange() and in column 1 of array Arr() and Blue cells in the array blue() and in column 2 of array Arr().
I removed the "Set" command when initialising the variables.
However, more critically, I was required to specify the sheet in each If statement.
I hope this helps someone else.
'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' ArrayOrangeBlue Function
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
Dim i As Integer ' row number '
Dim j As Integer ' orange counter '
Dim k As Integer ' blue counter '
Dim l As Integer
Dim m As Integer
Dim blue(1 To 50) As Double
Dim orange(1 To 50) As Double
Dim green As Double
' Starting Row '
i = 10
' Initialize orange and blue counters to 1 '
j = 1
k = 1
l = 10
m = 10
Dim Arr(100, 2) As Double
' Loop until Row 1000 '
Do While i <= 1000
''''' If cell colour is Orange- note absolute row number (i) in array: orange '
If Sheets("Detail analysis").Cells(i, 1).Interior.Color = 9420794 Then
orange(j) = i
Arr(j, 1) = i
j = j + 1
Else
''''''''' If cell colour is Blue- note absolute row number (i) in array: blue '
If Sheets("Detail analysis").Cells(i, 1).Interior.Color = 13995347 Then
blue(k) = i
Arr(k, 2) = i
k = k + 1
Else
''''''''''''' If cell colour is Gren- store the absolute row number (i) in: green '
If Sheets("Detail analysis").Cells(i, 1).Interior.Color = 5296274 Then
Arr(j, 1) = i
green = i
End If
End If
End If
i = i + 1
Loop
I have an Excel Worksheet consisting of two columns, one of which is filled with strings and the other is emtpy. I would like to use VBA to assign the value of the cells in the empty column based on the value of the adjacent string in the other column.
I have the following code:
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
Dim j As Integer
For j = 1 To 2
If regexAdmin.test(Cells(i, j).Value) Then
Cells(i, j + 1).Value = "Exploitation"
End If
Next j
Next i
The problem is that when using this loop for a big amount of data, it takes way too long to work and, most of the time, it simply crashes Excel.
Anyone knows a better way to this?
You have an unnecessary loop, where you test the just completed column (j) too. Dropping that should improve the speed by 10-50%
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
If regexAdmin.test(Cells(i, 1).Value) Then
Cells(i, 1).offset(0,1).Value = "Exploitation"
End If
Next i
If the regex pattern really is simply "Admin", then you could also just use a worksheet formula for this, instead of writing a macro. The formula, which you'd place next to the text column (assuming your string/num col is A) would be:
=IF(NOT(ISERR(FIND("Admin",A1))),"Exploitation","")
In general, if it can be done with a formula, then you'd be better off doing it so. it's easier to maintain.
Try this:
Public Sub ProcessUsers()
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim r As Range, N As Integer, i As Integer
Set r = Range("A1") '1st row is headers
N = CountRows(r) - 1 'Count data rows
Dim inputs() As Variant, outputs() As Variant
inputs = r.Offset(1, 0).Resize(N, 1) ' Get all rows and 1 columns
ReDim outputs(1 To N, 1 To 1)
For i = 1 To N
If regexAdmin.test(inputs(i, 1)) Then
outputs(i, 1) = "Exploitation"
End If
Next i
'Output values
r.Offset(1, 1).Resize(N, 1).Value = outputs
End Sub
Public Function CountRows(ByRef r As Range) As Long
If IsEmpty(r) Then
CountRows = 0
ElseIf IsEmpty(r.Offset(1, 0)) Then
CountRows = 1
Else
CountRows = r.Worksheet.Range(r, r.End(xlDown)).Rows.Count
End If
End Function
I have a macro that iterates through some rows, to update the colouring of data points in a related chart. The rows can be hidden by the user, so it checks the hidden value, i.e.
Do While wsGraph.Cells(RowCounter, 1) <> ""
If wsGraph.Rows(RowCounter).Hidden = False Then
'code here
End If
RowCounter = RowCounter + 1
Loop
This code takes 69 seconds to run. If I take the test for the hidden row out, it takes 1 second to run.
Is there a better way to do this test, otherwise I will have to tell the users they can't use the hide function (or deal with a 69 second delay).
Thanks
Here's the full code, as requested.
The graph is a bar graph, and I colour the points based on the values being in certain ranges, eg: over 75% = green, over 50% = yellow, over 25% = orange, else red. There's a button on the form to recolour the graph, that executes this code.
If someone filters the data table, what's happening is this: say the first 20 rows were over 75%, and were initially coloured green. After filtering the table, say only the first 5 are over 75%. The graph still shows the first 20 as green. So this button with the macro recolours the bars.
' --- set the colour of the items
Dim iPoint As Long
Dim RowCounter As Integer, iPointCounter As Integer
Dim wsGraph As Excel.Worksheet
Set wsGraph = ThisWorkbook.Worksheets(cGraph5)
wsGraph.ChartObjects("Chart 1").Activate
' for each point in the series...
For iPoint = 1 To UBound(wsGraph.ChartObjects("Chart 1").Chart.SeriesCollection(1).Values)
RowCounter = 26
iPointCounter = 0
' loop through the rows in the table
Do While wsGraph.Cells(RowCounter, 1) <> ""
' if it's a visible row, add it to the counter, if it's the same counter as in the series, exit do
If wsGraph.Rows(RowCounter).Hidden = False Then
iPointCounter = iPointCounter + 1
If iPointCounter = iPoint Then Exit Do
End If
RowCounter = RowCounter + 1
Loop
' colour the point from the matched row in the data table
Dim ColorIndex As Integer
If wsGraph.Cells(RowCounter, 5) >= 0.75 Then
ColorIndex = ScoreGreen
ElseIf wsGraph.Cells(RowCounter, 5) >= 0.5 Then
ColorIndex = ScoreYellow
ElseIf wsGraph.Cells(RowCounter, 5) >= 0.25 Then
ColorIndex = ScoreOrange
ElseIf wsGraph.Cells(RowCounter, 5) >= 0 Then
ColorIndex = ScoreRed
Else
ColorIndex = 1
End If
ActiveChart.SeriesCollection(1).Points(iPoint).Interior.ColorIndex = ColorIndex
Next
Try Special Cells
Sub LoopOverVisibleCells()
Dim r As Range
Dim a As Range
dim cl As Range
Set r = ActiveSheet.UsedRange.Columns(1).SpecialCells(xlCellTypeVisible)
For Each a In r.Areas
For Each cl In a
' code here
Next
Next
End Sub
This is what I've done, using Chris's suggestion. It doesn't answer why the hidden check is so slow, but it's a more efficient way of doing the recolouring:
Dim myrange As range
Set myrange = wsGraph.range("E26:E304").SpecialCells(xlCellTypeVisible)
Dim i As Integer
For i = 1 To myrange.Rows.Count
If myrange.Cells(i, 1) >= 0.75 Then
ColorIndex = ScoreGreen
ElseIf myrange.Cells(i, 1) >= 0.5 Then
ColorIndex = ScoreYellow
ElseIf myrange.Cells(i, 1) >= 0.25 Then
ColorIndex = ScoreOrange
ElseIf myrange.Cells(i, 1) >= 0 Then
ColorIndex = ScoreRed
Else
ColorIndex = 1
End If
ActiveChart.SeriesCollection(1).Points(i).Interior.ColorIndex = ColorIndex
Next i