Set excel print areas based on row heights - excel

I have been battling with this code for several days now and would appreciate some guidance on where I am going wrong.
My project is to create a printable document format containing manufacturing instructions and spaces for operators to write manual entries, which requires a minimum cell size when printed onto A4. These instructions will be varied but in all cases will be signed in column B, and in some cases will be countersigned. The marker in column B for a signature is "Op" and the marker for a countersignature is "Check".
In order to regulate cell size for the printed document I am attempting to count row heights up until a fixed total (832), from that point I want the code to go up and look for the first "Op", if the "Op" has a "Check" in the cell below then insert a page break below "Check", if not then insert a page break below "Op". From there I want the code to continue to the bottom of the document inserting page breaks every time it counts 832 total rows.
I am not sure if that methodology is the best for achieving what I am aiming for but would appreciate some feedback on what I have so far, I am getting an run time error 1004 on this code and it is inserting page breaks in the wrong places.
Sub TotalHeight()
Dim HowTall As Long
Dim Count As Long
Dim TotalHeight As Long
HowTall = 0
Count = 0
TotalHeight = 0
Dim cell As Range
For Each cell In Columns("B").Cells.SpecialCells(xlCellTypeConstants)
'If Not cell.Hidden Then
HowTall = HowTall + cell.RowHeight
'Count = Count + 1
If HowTall > 832 Then
Debug.Print cell.Row
For tmpcounter = 0 To 100
' If (Range(cell.Row).Offset(-tmpcounter, 0).Value) = "Op" Then
If cell.Offset(-tmpcounter, 0).Value = "Op" Then
If cell.Offset(-tmpcounter + 1, 0).Value = "Check" Then
'Found Check - get current row
PageBreakRowNo = cell.Offset(-tmpcounter + 1, 0).Row
Debug.Print "Check found at row " & PageBreakRowNo
Sheets("Dispensary").HPageBreaks.Add Before:=cell.Offset(-tmpcounter + 2, 0)
Else
'Only found Op - Get current row
PageBreakRowNo = cell.Offset(-tmpcounter, 0).Row
Debug.Print "Op found at row " & PageBreakRowNo
Sheets("Dispensary").HPageBreaks.Add Before:=cell.Offset(-tmpcounter + 1, 0)
End If
End If
Next tmpcounter
End If ' end of of HowTall >832 loop
Next 'end of for each cell in Column B loop
End Sub

Added my suggested improvements in your original code:
Sub TotalHeight()
Dim HowTall As Long
Dim Count As Long
Dim TotalHeight As Long
Dim tmpcounter1 As Long
HowTall = 0
Count = 0
TotalHeight = 0
Dim cell As Range
For Each cell In Columns("B").Cells.SpecialCells(xlCellTypeConstants)
'If Not cell.Hidden Then
HowTall = HowTall + cell.RowHeight
'Count = Count + 1
If HowTall > 832 Then
Debug.Print cell.Row
For tmpcounter = 0 To 100
If cell.Offset(-tmpcounter, 0).Value = "Op" Or cell.Offset(-tmpcounter, 0).Value = "Check" Then
PageBreakRowNo = cell.Offset(-tmpcounter + 1, 0).Row
tmpcounter1 = tmpcounter
Debug.Print "Op / Check found at row " & PageBreakRowNo
Sheets("Dispensary").HPageBreaks.Add Before:=cell.Offset(-tmpcounter + 2, 0)
Exit for 'to stop looking upwards
End If
Next tmpcounter
HowTall = 0 'reset HowTall to start again for the next page
For i = tmpcounter1 +1 to 0 Step -1 'Calculate the HowTall lost due to stepping back
HowTall = HowTall + cell.Offset(i,0).RowHeight
Next i
End If ' end of of HowTall >832 loop
Next 'end of for each cell in Column B loop
End Sub

Related

Find cell before last that is greater than 0

I have the following code in VBA to find the last cell inside a range that is greater than 0:
Set myRange = .Range(.Cells(1, 14), .Cells(1, 23))
count = 0 'Counter
For Each cll In myRange
If cll.Value > 0 Then
count = count + 1
NoZeroDir = cll.Address
End If
Next
It gets the address of the last cell greater than 0 in that range.
But, how could I get the address from the cell greater than 0 before this last one?
I was thinking of using an offset but that way I'd get the cell before the last > 0 but this cell could not be > 0.
To illustrate it a bit, as an example I have:
2 3 5 0 1 7 0 8 1 0 1
The address from the last cell > 0 would be (1,11) but I want the cell before that one > 0, that is (1,9), not (1,10) as this is 0.
To find the second last number that is >0
Option Explicit
Public Sub FindSecondLastValueGreaterZero()
Dim MyRange As Range
Set MyRange = Range("A1:K1")
Const MAXSKIPS As Long = 1 ' skip 1 number that is >0
Dim Skips As Long
Dim iCol As Long
For iCol = MyRange.Columns.Count To 1 Step -1
If MyRange(1, iCol).Value > 0 And Skips < MAXSKIPS Then
Skips = Skips + 1
ElseIf MyRange(1, iCol).Value > 0 Then
Debug.Print "Found at: " & MyRange(1, iCol).Address
Exit For
End If
Next iCol
End Sub
This will start in K loop backwards until it finds a 0 then keeps doing it until skipped >0 is 1 and print the address I1 as result.
Since this loops backwards from right to left it should find the result (in most cases) faster than your code.
Alternative using Worksheetfunction Filter() (vs. MS 365)
Based upon the newer WorksheetFunction Filter() (available since version MS/Excel 365) and using OP's range indication
=FILTER(COLUMN(A1:K1),A1:K1>0)
you are able to get an array of column numbers from cells greater than zero (0) via an evaluation of the generalized formula pattern.
If you get at least two remaining columns (i.e. an upper boundary UBound() > 1) you get the wanted 2nd last column number by i = cols(UBound(cols) - 1) and can translate it into an address via Cells(1, i).Address.
Public Sub SecondLastValGreaterZero()
'a) construct formula to evaluate
Const FormulaPattern As String = "=FILTER(COLUMN($),$>0)"
Dim rng As Range
Set rng = Sheet1.Range("A1:K1") ' << change to your needs
Dim myFormula As String
myFormula = Replace(FormulaPattern, "$", rng.Address(False, False, external:=True))
'b) get tabular column numbers via Evaluate
Dim cols As Variant
cols = Evaluate(myFormula)
'c) get the 2nd last column number of cell values > 0
Dim i As Long
If Not IsError(cols) Then
If UBound(cols) > 1 Then i = cols(UBound(cols) - 1)
End If
'd) display result
If i > 0 Then
Debug.Print "Found at column #" & i & ": " & Cells(1, i).Address
Else
Debug.Print "Invalid column number " & CStr(i)
End If
End Sub
Example result in VB Editor's immediate window
Found at column #9: $I$1

VBA (RFC) SAP export to excel

I am writing a VB application for connecting to a sap system (using rfc).
Everything works fine and I do get a connection and the data as well.
Nevertheless the code for saving the accessed data and writing it to a excel file is really slow.
After the connection I call RFC_READ_TABLE, which returns with a result in <5 secs, which is perfect. Writing to excel (cell by cell) is pretty slow.
Is there any way to 'export' the whole tblData to excel and not being dependent on writing cell by cell?
Thanks in advance!
If RFC_READ_TABLE.Call = True Then
MsgBox tblData.RowCount
If tblData.RowCount > 0 Then
' Write table header
For j = 1 To Size
Cells(1, j).Value = ColumnNames(j)
Next j
Size = UBound(ColumnNames, 1) - LBound(ColumnNames, 1) + 1
For i = 1 To tblData.RowCount
DoEvents
Textzeile = tblData(i, "WA")
For j = 1 To Size
Cells(i + 1, j).Value = LTrim(RTrim(getPieceOfTextzeile(Textzeile)))
Next j
Next
Else
MsgBox "No entries found in system " & SYSID, vbInformation
End If
Else
MsgBox "ERROR CALLING SAP REMOTE FUNCTION CALL"
End If
Arrays: Faster Than Ranges
If the data was ready (need not to be processed) something like this could be a solution:
Sub Sap()
Const cStrStart As String = "A1" 'First cell of the resulting data
Dim tbldata
Dim arrSap As Variant 'Will become a one-based two dimensional array
Dim oRng As Range
arrSap = tbldata 'Data is in the array.
'Calculate the range: Must be the same size as arrSap
Set oRng = Range(Cells(Range(cStrStart).Row, UBound(arrSap)), _
Cells(Range(cStrStart)).Column, UBound(arrSap, 2))
oRng = arrSap 'Paste array into range.
End Sub
Since you need to process your data from tbldata do what you do not to the range, but to an array which should be much faster:
Sub Sap()
Const cStrStart As String = "A1" 'First cell of the resulting data
Dim arrSap() As Variant
Dim oRng As Range
Dim Size As Integer
If RFC_READ_TABLE.Call = True Then
'-------------------------------------------------------------------------------
MsgBox tbldata.RowCount
If tbldata.RowCount > 0 Then
Size = UBound(ColumnNames, 1) - LBound(ColumnNames, 1) + 1
ReDim arrSap(1 To tbldata.RowCount + 1, 1 To Size) '+ 1 for header
' Write table header
For j = 1 To Size
arrSap(1, j).Value = ColumnNames(j)
Next j
' Write data
For i = 1 + 1 To tbldata.RowCount + 1 '+ 1 for header
DoEvents
'- 1 due to header, don't know what "WA" is
Textzeile = tbldata(i - 1, "WA")
For j = 1 To Size
arrSap(i, j) = _
LTrim(RTrim(getPieceOfTextzeile(Textzeile)))
Next j
Next
'-------------------------------------------------------------------------------
'Calculate the range: Must be the same size as arrSap
Set oRng = Range(Cells(Range(cStrStart).Row, Range(cStrStart).Column), _
Cells(UBound(arrSap) + Range(cStrStart).Row -1, _
UBound(arrSap, 2) + Range(cStrStart).Column -1))
oRng = arrSap
'-------------------------------------------------------------------------------
Else
MsgBox "No entries found in system " & SYSID, vbInformation
End If
Else
MsgBox "ERROR CALLING SAP REMOTE FUNCTION CALL"
End If
End Sub
Now adjust the cStrStart, check the rest of the code and you're good to go.
I haven't created a working example so I edited this code a few times. Check it carefully not to lose data.

Sum every odd row in a single Column VBA

I have a code that searches an excel fill for the word distance, takes the value of the cell next to it, paste it into a new cell, then sums all the cells up. Which works great, but i now need to find a way to only sum ever even row number. Does that make sense?
Sub Distance_Check()
Dim DistanceCheck As String
Dim DistanceNumber As String
Dim DistanceSum As String
Dim DistanceTotal As String
DistanceCheck = MsgBox("Would you like to check the distance?", vbYesNo)
If DistanceCheck = vbYes Then
If IsArray(fname) Then Workbooks.OpenText fname(1)
i = 1
findStr = "Distance"
Set foundCel = Range("A:A").Find(what:=findStr)
If Not foundCel Is Nothing Then
firstAddress = foundCel.Address
Do
Range("J" & i).Value = foundCel.Offset(0, 1).Value
Set foundCel = Range("A:A").FindNext(foundCel)
i = i + 1
Loop While Not foundCel Is Nothing And foundCel.Address <> firstAddress
End If
Set wkbSourceBook = ActiveWorkbook
DistanceNumber = i - 2
DistanceSum = WorksheetFunction.Sum(Range(Cells(2, 15), (Cells(DistanceNumber + 1, 15))))
DistanceTotal = DistanceSum / DistanceNumber
If DistanceNumber = Cells(2, 12) Then
MsgBox ("No error found wihin distance")
Else
MsgBox ("Error found with distance")
End If
Else
End If
Call Save_Data
End Sub
Would the way youd go about this be using a for loop on the
cells(DistanceNumber(j,+1)
Where j = 0,
j = j +2 ,
Until j > DistanceNumber,
Would that work? If so how would you go about it?
Thanks
A quick way to step through a loop in the desired increments is to use the Mod operator which divides two numbers and returns any remainder (e.g. 7 mod 2 = 1, as two sixes fit into seven, leaving one).
You can use the row property of the range you identify with the Find method, and since you want to jump by two the modulo should be zero:
If foundcel.Row Mod 2 = 0 Then Range("J" & i).value = foundcel.Offset(0, 1).Value
That said, there is a 'built in' way to step through a loop if using a For loop like this
For x = 2 to 10 Step 2
' Do stuff
Next x
You can also use this method to step backwards, e.g.
For x = 100 to 0 Step -10
' Do stuff backwards!
Next x

Inserting new row in excel by VBA

I have an excel spreadsheet. In a column of the spreadsheet I have a list of codes (numbers).These codes (numbers) are sorted from highest to lowest values.(some of these codes has been repeated. For example I have three consecutive line with code 1001200).I want to insert new rows between each codes (in case of having repeated codes i just need one new row (for example i Just need one new row for 1001200 not 3 rows) .
I have written the following code but it does not work.
Sub addspace()
Dim space_1(5000), Space_2(5000)
For n = 1 To 5000
Debug.Print space_1(n) = Worksheets("sheet3").Cells(1 + n, 1).Value
Debug.Print Space_2(n) = Worksheets("sheet3").Cells(2 + n, 1).Value
Next
For n = 1 To 5000
If space_1(n) <> Space_2(n) Then
Range("space_1(n)").EntireRow.Insert
End If
Next
End Sub
How can I fix it? (From the code you can see that I am so beginner :)))
Cheers
To insert one empty row between each unique value try this:
Option Explicit
Public Sub addspace()
Dim i As Long
Application.ScreenUpdating = False
With Worksheets("sheet3")
For i = 5000 To 2 Step -1
While .Range("A" & i - 1) = .Range("A" & i)
i = i - 1
Wend
.Rows(i).Insert Shift:=xlDown
Next
End With
Application.ScreenUpdating = True
End Sub
It starts from the end row and moves up, skipping duplicates
The Range("space_1(n)") is invalid. Arg of range object should be a column name like "A1", you can use Range("A" & n).EntireRow.Insert in your code. But I recommend my code.
Please try,
Sub addspace()
Dim n As Integer
For n = 1 To 5000
If Worksheets("sheet3").Cells(n, 1).Value <> Worksheets("sheet3").Cells(n + 1, 1).Value Then
Worksheets("sheet3").Cells(n + 1, 1).EntireRow.Insert
n = n + 1
End If
Next
End Sub

VBA-Excel and large data sets causes program to crash

First time poster and new to programming in general. I have a project in which i have to build a financial model to mine for data in excel. I have succeeded in building said model on VBA. I have ran tests on 3,000 line dataset and it was successful. I will briefly explain what it does.
I track a given stock on a given day on multiple exchanges. I download the data (roughly 935,000 lines) The first step is copy all the data for a given exchange (roughly 290,000) onto a new sheet (this takes roughly 8min), then I create a new column to log the bid ask spread (12secs), the next step is what Im having trouble with, I basically rank each line of data twice, one column for Bid size and one column for Ask size. I created a function which uses excel Percentile function and ranks based on where the given bid and ask size lands. As of right now, I have been running the Macro for the last 35min and has yet to execute. I cant attempt the other macros since each macro depends on the previous one.
So my basic issue is that since my data set is large, my model keeps crashing.The code seems to be fine when working with the test data, and it doesn't throw any errors when I run the program, but with the larger data set it just crashes. Does anyone have any suggestions? Is this normal with such large amounts of data?
Thanks in advance.
Sham
Here is the sub and function thats giving me the trouble, the sub takes in the required inputs to run the function and then pops into the assigned cell. The code is suppose to repeat the process for three separate sheets. For now, Id like it to work on one sheet, hence used the comments to not include the loop
Sub Bucketting()
Dim firstRow As Long
Dim lastRow As Long
Dim counter As Long
Dim bidRange As Range
Dim offerRange As Range
Dim bidScroll As Range
Dim offerScroll As Range
Dim Ex As String
Dim i As Integer
'For i = 1 To 1 Step 1 'Sheet Selection Process
' If i = 1 Then
' Ex = "Z"
' ElseIf i = 2 Then
' Ex = "P"
' Else
' Ex = "T"
' End If
Sheets("Z").Select 'Sheet selected
With ActiveSheet
firstRow = .UsedRange.Cells(1).Row + 1
lastRow = .UsedRange.Rows.Count
Set bidRange = .Range("F2:F" & lastRow)
Set offerRange = .Range("G2:G" & lastRow)
For counter = lastRow To firstRow Step -1
Set bidScroll = .Range("F" & counter)
Set offerScroll = .Range("G" & counter)
With .Cells(counter, "J")
.Value = DECILE_RANK(bidRange, bidScroll)
End With
With .Cells(counter, "K")
.Value = DECILE_RANK(offerRange, offerScroll)
End With
Next counter
End With
Range("J1").Select
ActiveCell = "Bid Rank"
ActiveCell.Offset(0, 1) = "Offer Rank"
'Next i
End Sub
Function DECILE_RANK(DataRange, RefCell)
'Credit: BJRaid
'DECILE_RANK(The Range of data)
'Declares the function that can be called in the spreadsheet cell - enter '=DECILE_RANK(A5:A50,A5)
'Using the percentile worksheet function calculate where the 10th, 20th etc percentile of the reference range are
DEC1 = Application.WorksheetFunction.Percentile(DataRange, 0.1)
DEC2 = Application.WorksheetFunction.Percentile(DataRange, 0.2)
DEC3 = Application.WorksheetFunction.Percentile(DataRange, 0.3)
DEC4 = Application.WorksheetFunction.Percentile(DataRange, 0.4)
DEC5 = Application.WorksheetFunction.Percentile(DataRange, 0.5)
DEC6 = Application.WorksheetFunction.Percentile(DataRange, 0.6)
DEC7 = Application.WorksheetFunction.Percentile(DataRange, 0.7)
DEC8 = Application.WorksheetFunction.Percentile(DataRange, 0.8)
DEC9 = Application.WorksheetFunction.Percentile(DataRange, 0.9)
' Calculate the Decile rank that the reference cell value sits within
If (RefCell <= DEC1) Then DECILE_RANK = 1
If (RefCell > DEC1) And (RefCell <= DEC2) Then DECILE_RANK = 2
If (RefCell > DEC2) And (RefCell <= DEC3) Then DECILE_RANK = 3
If (RefCell > DEC3) And (RefCell <= DEC4) Then DECILE_RANK = 4
If (RefCell > DEC4) And (RefCell <= DEC5) Then DECILE_RANK = 5
If (RefCell > DEC5) And (RefCell <= DEC6) Then DECILE_RANK = 6
If (RefCell > DEC6) And (RefCell <= DEC7) Then DECILE_RANK = 7
If (RefCell > DEC7) And (RefCell <= DEC8) Then DECILE_RANK = 8
If (RefCell > DEC8) And (RefCell <= DEC9) Then DECILE_RANK = 9
If (RefCell > DEC9) Then DECILE_RANK = 10
End Function
935,000 lines is a lot for excel. Like, really a lot. Barring saying using a real database, If your application is literally putting a =Percentile(...) in each cell, I would recommend Trying to use another tool for that. Perhaps something within VBA itself. More generally, use something outside of a cell - then store the result value in the cell. There is a lot of overhead in maintaining those formulas that are interdependent on 935k rows of data.
The problem is that your looping through each row individually, the Excel way is to try and work with whole ranges at once whenever possible. I would load the ranges into arrays, then modify your DECILE_RANK code to work with the items in the array.
Note that variant arrays that read ranges in are 2-D.
Here is the fully functioning code including my custom VBA array slicer. Note that it was only tested on a small dataset:
Sub Bucketting()
Dim lastRow As Long
Dim bidArray As Variant
Dim offerArray As Variant
Sheets("Sheet1").Select 'Sheet selected
With ActiveSheet
lastRow = .UsedRange.Rows.Count + 1
bidArray = .Range("F2:F" & lastRow)
offerArray = .Range("G2:G" & lastRow)
Range("J2:J" & lastRow).Value = GetArraySlice2D(DECILE_RANK(bidArray), "column", 1, 1, 0)
Range("K2:K" & lastRow).Value = GetArraySlice2D(DECILE_RANK(offerArray), "column", 1, 1, 0)
End With
Range("J1").Select
ActiveCell = "Bid Rank"
ActiveCell.Offset(0, 1) = "Offer Rank"
End Sub
Function DECILE_RANK(DataRange As Variant) As Variant
' Credit: BJRaid
' DECILE_RANK(The Range of data)
' Declares the function that can be called in the spreadsheet cell - enter '=DECILE_RANK(A5:A50,A5)
Dim DEC(0 To 10) As Variant
Dim i As Integer, j As Integer
'Using the percentile worksheet function calculate where the 10th, 20th etc percentile of the reference range are
DEC(0) = 0
For i = 1 To 9
DEC(i) = Application.WorksheetFunction.Percentile(DataRange, 0.1 * i)
Next i
DEC(10) = Application.WorksheetFunction.Max(DataRange)
' Calculate the Decile rank that the reference cell value sits within
For i = 1 To UBound(DataRange, 1)
For j = 1 To 10
If ((DataRange(i, 1) > DEC(j - 1)) And (DataRange(i, 1) <= DEC(j))) Then
DataRange(i, 1) = j
Exit For
End If
Next j
Next i
DECILE_RANK = DataRange
End Function
Public Function GetArraySlice2D(Sarray As Variant, Stype As String, Sindex As Integer, Sstart As Integer, Sfinish As Integer) As Variant
' this function returns a slice of an array, Stype is either row or column
' Sstart is beginning of slice, Sfinish is end of slice (Sfinish = 0 means entire
' row or column is taken), Sindex is the row or column to be sliced (NOTE:
' 1 is always the first row or first column)
' an Sindex value of 0 means that the array is one dimensional 3/20/09 Lance Roberts
Dim vtemp() As Variant
Dim i As Integer
On Err GoTo ErrHandler
Select Case Sindex
Case 0
If Sfinish - Sstart = UBound(Sarray) - LBound(Sarray) Then
vtemp = Sarray
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(i + Sstart - 1)
Next i
End If
Case Else
Select Case Stype
Case "row"
If Sfinish = 0 Or (Sstart = LBound(Sarray, 2) And Sfinish = UBound(Sarray, 2)) Then
vtemp = Application.WorksheetFunction.Index(Sarray, Sindex, 0)
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(Sindex, i + Sstart - 1)
Next i
End If
Case "column"
If Sfinish = 0 Or (Sstart = LBound(Sarray, 1) And Sfinish = UBound(Sarray, 1)) Then
vtemp = Application.WorksheetFunction.Index(Sarray, 0, Sindex)
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(i + Sstart - 1, Sindex)
Next i
End If
End Select
End Select
GetArraySlice2D = vtemp
Exit Function
ErrHandler:
Dim M As Integer
M = MsgBox("Bad Array Input", vbOKOnly, "GetArraySlice2D")
End Function
I'm not sure if this will directly address your problem, but have you considered using Application.ScreenUpdating = False? Don't forget to set it back to true once your data has processed.

Resources