I have created a worksheet with 40 pages. I know it's impossible to Repeat Bottom Cells when printing, so I just repeated the Top Rows (which contains a Sum formula) to make the sheet more compact. But the problem is, the sum formula in the top row computes only data from the first page. Ist possible to compute the data in every page when printing, somehow like what we could do in Microsoft Access' header?
This should do the trick:
Sub SubSpecialPrinting()
'Declarations.
Dim IntCounter01 As Integer
Dim IntPagesToBePrinted As Integer
Dim LngRow As Long
Dim RngRangeWithFormula As Range
Dim RngSumRange As Range
Dim StrExistingFormula As String
Dim WksWorksheet01 As Worksheet
'Setting variables.
Set RngRangeWithFormula = Range("I1")
Set RngSumRange = Range("A1:A10")
Set WksWorksheet01 = RngSumRange.Parent
StrExistingFormula = RngRangeWithFormula.Formula
IntPagesToBePrinted = ExecuteExcel4Macro("GET.DOCUMENT(50)")
'Disabling interruption in case of error.
On Error Resume Next
'Covering all the pages to be printed.
For IntCounter01 = 1 To IntPagesToBePrinted
'Setting the formula in RngRangeWithFormula.
RngRangeWithFormula.Value = Excel.WorksheetFunction.Sum(RngSumRange)
'Printing the single given page.
WksWorksheet01.PrintOut From:=IntCounter01, _
To:=IntCounter01, _
Copies:=1, _
Collate:=True, _
IgnorePrintAreas:=False
'Setting LngRow equal rows of the page just printed.
LngRow = WksWorksheet01.HPageBreaks(IntCounter01).Location.Row - 1 - LngRow
'Setting RngSumRange for the next page to be printed.
Set RngSumRange = RngSumRange.Offset(LngRow, 0)
Next
'Reactivating interruption in case of error.
On Error GoTo 0
'Reporting the pre-existing formula in RngRangeWithFormula.
RngRangeWithFormula.Formula = StrExistingFormula
End Sub
Change RngRangeWithFormula and RngSumRange value accordingly to your needs. The subroutine will print the pages of the print area one by one. Before printing, it will change the value of the RngRangeWithFormula with the sum of the values in RngSumRange; the latter will change for each pages, shifting down for as many rows as the given page contains. At the end of the subroutine, the RngRangeWithFormula will have the same formula it had before the subroutine was run.
Since one should call the subroutine in order to print the sheet correctly, i've spent some hours looking for a way to obtain the same result via a function. I've found how to know to which page a given cell belongs, but I haven't found a way to dynamically use the page number while printing. In the end i've accepted the subroutine as the way to go. Still in order to avoid the pointless printing of the "uncorrect" sheet and to make it easier for the user, i've written this extra code to be put in the workbook module:
Public PubBlnDedicatedPrinting As Boolean
Private Sub Workbook_BeforePrint(Cancel As Boolean)
'Checking if a dedicated printing is in process.
If PubBlnDedicatedPrinting = False Then
'Checking a specific sheet is active.
If ActiveSheet.Name = "INSERT YOUR SHEET NAME HERE" Then
'Asking if the user want to use the dedicated macro for printing.
Select Case MsgBox("Do you want to run the dedicated macro for printing?", vbYesNoCancel, "Dedicated macro")
Case Is = 6
'Setting PubBlnDedicatedPrinting value is set to true.
PubBlnDedicatedPrinting = True
'Running the dedicated macro.
Call SubSpecialPrinting
'Setting PubBlnDedicatedPrinting value is set to false.
PubBlnDedicatedPrinting = False
'Canceling any other printing procedure.
Cancel = True
Case Is = 7
'Proceeding with a classical printing.
Cancel = False
Case Is = 2
'Canceling any printing procedure.
Cancel = True
End Select
End If
End If
End Sub
To make it effective, you need to sobstitute the "INSERT YOUR SHEET NAME HERE" with the name of your sheet. Once the user will try to print the given sheet, Excel will ask him whether he wants to run the dedicated macro or to print it the classical way or neither or those.
Related
I have code that uses a For loop to hide a number of columns based on a Cell value. The code is used in 7 sheets out of a Workbook that has 17 sheets in total (this information is relevant later).
Private Sub Worksheet_calculate()
Application.EnableEvents = False
On Error GoTo errorHandling
Dim controlCell As Range, tableToHide As Range
Set controlCell = Range("C12") 'Cell contains a formula to retrieve the value from a cell in a seperate sheet
Set tableToHide = Range("Table1") 'The name of the table where columns need to be shown/hidden based on controlCell value
tableToHide.EntireColumn.Hidden = False
For i = controlCell.Value To tableToHide.Columns.Count
tableToHide.Columns(i).EntireColumn.Hidden = True
Next i
errorHandling:
On Error GoTo 0
Application.EnableEvents = True
End Sub
I'm looking for a way to hide the columns without using a loop or a way to change this loop. The reason for wanting the change is because when this is used in its current form, changing any cell throughout the Workbook's 17 sheets results in a loading spinner showing for a few seconds. As you can imagine, that is not a great user experience.
You can hide all columns at once. Various ways to do so, eg
Dim startCol As Long
startCol = controlCell.value
Dim hideRange As Range
Set hideRange = tableToHide.Cells(1, startCol).Resize(1, tableToHide.Columns.Count - startCol + 1)
hideRange.EntireColumn.Hidden = True
I don't understand why you have this code inside the Worksheet_calculate() event: this causes the entire workbook to be checked every time a calculation is made.
Why don't you put that for-loop inside another macro, which you can run on demand, and use the Worksheet_calculate() event only to check the column you're actually calculating?
I am working on excel VBA and I want all rows that does not equal to my cell value/reference (Cell E5) to be deleted but what is happening right, it deletes all rows in that sheet.
Sub DeleteNotEqualTo()
Dim ws As Worksheet
x = Range("E5").Value
Set ws = ThisWorkbook.Worksheets("Conso")
ws.Range("B8:Z5000").AutoFilter Field:=11, Criteria1:="<> & x"
Application.DisplayAlerts = False
ws.Range("B9:Z5000").SpecialCells(xlCellTypeVisible).Delete
Application.DisplayAlerts = True
On Error Resume Next 'Clear Filter
ws.ShowAllData
On Error GoTo 0
End Sub
I'm assuming that you're setting the reference value in cell E5 (row 5) and starting from row 8 ("B8:Z5000") you crosscheck if for each record field 11 corresponds to that specific value.
I don't recommend this approach because:
when manipulating spreadsheets, you shouldn't use the auto filter. The auto filter is rather designed for visibility when you look at a spreadsheet - not for a programmatic approach. You should be aware that, even if data is filtered out of the visible range, if you'd loop through the records, the program would still take the non visible records into consideration.
by setting a static range, you are always going to be limited by the number of rows you set. You can easily use a dynamic range instead, like that your script will always crosscheck all rows that are not empty.
Option Explicit
'always recommend to use this option, it forces you to
'declare the type of every single variable
Sub DeleteNotEqualTo()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Conso")
Dim x As String 'declaring string variable, you may have to adapt
'x = Range("E5").Value you could also with the cells object, check next line
x = ws.Cells(5, 5).Value '5,5 = row 5, column 5
'now comes the loop
'you have to declare a so called iterator variable to loop, in this case "i"
'the iterator drives the loop through the records
'you also need a variable that determines the last row to be checked
'in this case you define this by checking the so called "UsedRange"
'the "UsedRange" counts from the first row that contains to the last
'check out the indicated link for more info regarding finding the last row
Dim i As Integer 'iterator variable
Dim lrow As Integer 'variable for the last row
lrow = ws.UsedRange.Rows.Count + 4 'assuming that row 5 is the first to contain a value
'now loop through the records
'here I'm assuming field 11 of your auto filter correspond to column 11 in the sheet
'important sidenote: as you're deleting rows, you have to loop bottom up
'if you loop top to bottom, the loop may skip rows that shift up
For i = lrow To 8 Step -1 'assuming that the first row to be checked is row 8
If ws.Cells(i, 11).Value = x Then
ws.Cells(i, 11).EntireRow.Delete
End If
Next i
End Sub
Here's a link with more info regarding finding the last row in a spreadsheet. Very helpful with dynamic ranges:
https://www.automateexcel.com/vba/find-last-row-column-cell/
What worked for me in your workbook is changing this line:
ws.Range("B8:Z5000").AutoFilter Field:=11, Criteria1:="<> & x"
to:
ws.Range("B8:Z5000").AutoFilter Field:=11, Criteria1:="<>*" & x & "*"
The presence of * wildcard changes the criteria from an exact match to a partial match. (This shouldn't have been necessary, but specifying an exact match didn't filter the range correctly for reasons I'm unsure of.)
Option Explicit
Private Sub DeleteNotEqualTo()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Conso")
Dim x As Variant
x = Range("E5").Value
ws.Range("B8:Z5000").AutoFilter Field:=11, Criteria1:="<>*" & x & "*"
Application.DisplayAlerts = False
ws.Range("B9:Z5000").SpecialCells(xlCellTypeVisible).Delete
Application.DisplayAlerts = True
On Error Resume Next 'Clear Filter
ws.ShowAllData
On Error GoTo 0
End Sub
Background
I want to write a simple function which swaps the contents of two selected (not necessarily adjacent) cells. I do not want to copy the cell to a temporary cell first. Thus, I really want to swap the cells in place.
Challenge
While simply swaping the content is rather easy by using a Variant temporary variable holding the content of cell 1, overwriting the content of cell 1 with the content of cell 2 and then writing back the content of the variant variable to cell 2, I struggle how to also copy all format related stuff. There are plenty of slots which need consideration (.NumberFormat, .Interior to name just two). Do I really need to copy each of them seperately or is there an easier way to swap the format without using any temporary cell?
Code
Public Sub SwapCells(Optional bolWithFormat As Boolean = True)
'Purpose: switch the content of two cells
On Error GoTo ErrHandler
Dim rngSel As Range
Dim varContent As Variant
Set rngSel = Selection
If (rngSel.Count = 2) Then
With rngSel.Cells(1)
varContent = .Value
.Value = rngSel.Cells(2).Value
rngSel.Cells(2).Value = varContent
End With
Else
'Do nothing, because swap makes only sense for exactly 2 cells
End If
ErrHandler:
Set rngSel = Nothing
End Sub
Per the comments, using a temporary holding cell is much the easiest solution. You can use a cell in your Personal macro workbook to avoid worrying about finding a spare cell in the active workbook. It would probably be wise to set the Saved property of the personal workbook to True afterwards to avoid getting prompted to save that every time you quit Excel after running the macro!
Just for the record, here's the final code (stored in my Personal.xlsb) which I use:
Public Sub SwapCellsGeneral(Optional bolWithFormat As Boolean = False)
'Purpose: switch the content of two cells
'Use Personal.xlsb to use a temporary cell and copy paste
On Error GoTo ErrHandler
Dim rngSel As Range, rngTemp As Range
Dim varContent As Variant
Set rngSel = Selection
Set rngTemp = ThisWorkbook.Sheets(1).Cells(1, 1)
If (rngSel.Count = 2) Then
If (bolWithFormat) Then
rngSel.Cells(1).Copy rngTemp
rngSel.Cells(2).Copy rngSel.Cells(1)
rngTemp.Copy rngSel.Cells(2)
Else
With rngSel.Cells(1)
varContent = .Value
.Value = rngSel.Cells(2).Value
rngSel.Cells(2).Value = varContent
End With
End If
Else
'Do nothing, because swap make only sense for exactly 2 cells
End If
ErrHandler:
'Set this to avoid asking if we want to save personal.xlsb
ThisWorkbook.Saved = True
Set rngSel = Nothing
Set rngTemp = Nothing
End Sub
I run a macro that copies tables from a PDF file and saves them on Excel.
some of the tables contain empty cells and in my analysis I need to know the number of cells that are empty.
I have a function that iterates through each column to check if the value within that cell is numeric or not.
the trouble is when I run this function on an empty cell it returns true. I even tried manually cheeking the cells using the Isblank() function and it returns "false". (if I try this on any cell outside the pasted range it returns "true")
I am guessing that when I copy and paste things from PDF it somehow pastes some value for the empty cells.
did anyone ever encounter a similar problem? if so, any ideas on how it can be solved?
if it is any help here is the code I use to copy and paste
'Initialize Acrobat by creating App object
Set PDFApp = CreateObject("AcroExch.App")
'Set AVDoc object
Set PDFDoc = CreateObject("AcroExch.AVDoc")
'Open the PDF
If PDFDoc.Open(PDFPath, "") = True Then
PDFDoc.BringToFront
'Maximize the document
Call PDFDoc.Maximize(True)
Set PDFPageView = PDFDoc.GetAVPageView()
'Go to the desired page
'The first page is 0
Call PDFPageView.GoTo(DisplayPage - 1)
'-------------
'ZOOM options
'-------------
'0 = AVZoomNoVary
'1 = AVZoomFitPage
'2 = AVZoomFitWidth
'3 = AVZoomFitHeight
'4 = AVZoomFitVisibleWidth
'5 = AVZoomPreferred
'Set the page view of the pdf
Call PDFPageView.ZoomTo(2, 50)
End If
Set PDFApp = Nothing
Set PDFDoc = Nothing
On Error Resume Next
'Show the adobe application
PDFApp.Show
'Set the focus to adobe acrobat pro
AppActivate "Adobe Acrobat Pro"
'Select All Data In The PDF File's Active Page
SendKeys ("^a"), True
'Right-Click Mouse
SendKeys ("+{F10}"), True
'Copy Data As Table
SendKeys ("c"), True
'Minimize Adobe Window
SendKeys ("%n"), True
'Select Next Paste Cell
Range("A" & Range("A1").SpecialCells(xlLastCell).Row).Select
'Cells(1, 1).Select
'Paste Data In This Workbook's Worksheet
ActiveSheet.Paste
There are cases where it is better to check the length of the characters inside cells instead of using the isNumeric(), or check for errors etc...
For example try the below code
it establishes the Range used in the active worksheet then iterates through checking the length (len()) of each cell
you can look at Immediate Window CTRL+G in VBE to see which cell addresses are empty or wait until the macro finishes executing and you will be welcomed with a Message Box saying how many empty cells are within the range
Option Explicit
Sub CheckForEmptyCells()
Dim lastCol As Range
Set lastCol = ActiveSheet.Cells.Find(What:="*", After:=ActiveSheet.Cells(1, 1), LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, MatchCase:=False)
Dim rng As Range
Set rng = Range("A1:" & lastCol.Address)
Dim cnt As Long
cnt = 0
Dim cell As Range
For Each cell In rng
If Len(cell) < 1 Then
Debug.Print cell.Address
cnt = cnt + 1
End If
Next
MsgBox "there are " & cnt & " empty cells within the range " & rng.Address
End Sub
This is kind of a hackish solution, but works as a simple solution.
Since 'IsNumeric(p variant)' uses a variant, you can append a "-" to the input parameter. That means null gets interpreted as "-" which is not a number, where as a true number will get treated as a negative number, and thereby meet the condition of truly being a number. (although now negative)
IsNumeric("-" & string_Value) vs. IsNumeric(str_Value)
I have checked it with an empty cell right now (without involving a PDF file at all) and you are right: IsNumeric returns True for empty cells.
I haven't ever had this problem because, when coding, I intend to not bring the in-built functions "to its limits" (determining whether an empty cell can be considered as numeric or not might be even discussion-worthy). What I do always before performing any kind of analysis on a cell (or a string in general) is making sure that it is not empty:
Dim valIsNumeric As Boolean
If (Not IsEmpty(Range("A1"))) Then
valIsNumeric = IsNumeric(Range("A1"))
End If
Or in a more generic version (highly reliable with any kind of string under any circumstance):
If (Len(Trim(Range("A1").Value))) Then
valIsNumeric = IsNumeric(Range("A1"))
End If
Making sure that the given cell/string is not blank represents just a small bit of code and increases appreciably the reliability of any approach.
I receive an excel file monthly and have to export parts of it to a new file. I have a list of identifier numbers and I am trying to match the list of numbers in the selected list to the full file and then export the rows of relevant data to a new sheet.
Sub Run_All_Macros()
Application.ScreenUpdating = False
Sheets.Add.Name = "Output"
Call Convert_to_Numbers
Call Highlight_Selected_Contractors
End Sub
'Original Spreadsheet is formatted incorrectly
'Convert PSD Codes to Numbers
Sub Convert_to_Numbers()
Dim xCell As Range
Range("A2:A2500").Select
For Each xCell In Selection
xCell.Value = CDec(xCell.Value)
Next xCell
End Sub
'Highlight Selected Contractors
Sub Highlight_Selected_Contractors()
Dim Full, Selection, Code, SelectedCode As Range
Worksheets("Sheet1").Select
'Set all cells in Column A Sheet 1 to Full
Set Full = Worksheets("Sheet1").Range("A1", Range("A1").End(xlDown))
'Set all cells in Column A Sheet 2 to Selection
Worksheets("Sheet2").Select
Set Selection = Worksheets("Sheet2").Range("A1", Range("A1").End(xlDown))
'If the numbers match highlight the cell
For Each Code In Full
For Each SelectedCode In Selection
If Code.Value = SelectedCode.Value Then
*** Code.Select
Selection.Copy
Sheets.Select ("Output")
ActiveSheet.Paste
End If
Next SelectedCode
Next Code
End Sub
After executing this code column A in 'Output' is filled with zeros from A2:A2500. From messing around with breakpoints I've identified the problem to be where I've placed * but I'm not sure what's wrong with what's written there.
Thanks
There few errors in the code above and I also have few suggestions and finally the code.
ERRORS
1) Sheets.Add.Name = "Output" This line will give you an error if there is already a sheet called "Ouput". Delete the sheet first and then create it. You must be wondering that in case the sheet is not there, then how can I delete it? For such scenarios you can use On Error Resume Next which should be avoided in most cases.
2) When working with ranges, always specify which sheet you are referring to else Excel will always assume that you are referring to the "ActiveSheet". As you realized that Sub Convert_to_Numbers() was taking Output Sheet into consideration whereas you want the operation to happen in "Output" Sheet.
3) Dim Full, Selection, Code, SelectedCode As Range As mentioned in my comments earlier avoid using Excel Reserved words as variables. Also unlike VB.Net, if you declare variables as you did in VBA then only the last variable will be declared as Range. The other 3 will be declared as variant. VB defaults the variable to being type Variant. A Variant type variable can hold any kind of data from strings, to integers, to long integers, to dates, to currency etc. By default “Variants” are the “slowest” type of variables. Variants should also be avoided as they are responsible for causing possible “Type Mismatch Errors”. It’s not that we should never use Variants. They should only be used if you are unsure what they might hold on code execution.
4) Avoid the use of words like .ActiveCell, Selection, Select, Activate etc. They are a major cause of errors. Also they slow your code down.
SUGGESTIONS
1) Instead to using Sheets("WhatEver") every time, store it in a variable and then use that variable. Will cut down your code.
2) Indent your code :) it's much easier to read
3) Group tasks together. For example if you have to do with something with a particular sheet then keep it together. It is easier to read and amend if required.
4) Instead of hard coding your values, get actual ranges. Range("A2:A2500") is a classic example. Will you always have data till 2500? What if it is less or more?
5) End(xlDown) will never give you the last row if there is a blank cell in between. To get the last row in a column, say A in "Sheet1", use this
Sheets("Sheet1").Range("A" & Rows.Count).End(xlUp).Row`
6) Instead of looping, you can use the WorksheetFunction CountIf(). Loops should be avoided as much as possible as they slow down your code.
7) Use appropriate Error handling.
8) Comment your code. It's much easier to know what a particular code or section is doing.
CODE
Option Explicit
Sub Run_All_Macros()
Dim ws1I As Worksheet, ws2I As Worksheet, wsO As Worksheet
Dim ws1LRow As Long, ws2LRow As Long, wsOLr As Long
Dim xCell As Range, rFull As Range, rSelection As Range
Dim rCode As Range, rSelectedCode As Range
On Error GoTo Whoa '<~~ Error Handling
Application.ScreenUpdating = False
'~~> Creating the Output Sheet
Application.DisplayAlerts = False
On Error Resume Next
Sheets("Output").Delete
On Error GoTo 0
Sheets.Add.Name = "Output"
Application.DisplayAlerts = True
'~~> Working with 1st Input Sheet
Set ws1I = Sheets("Sheet1")
With ws1I
'~~> Get Last Row of Col A
ws1LRow = .Range("A" & Rows.Count).End(xlUp).Row
'~~> Set the range we want to work with
Set rFull = .Range("A1:A" & ws1LRow)
'~~> The following is not required unless you want to just format the sheet
'~~> This will have no impact on the comparision. If you want you can
'~~> uncomment it
'For Each xCell In .Range("A2:A" & ws1LRow)
'xCell.Value = CDec(xCell.Value)
'Next xCell
End With
'~~> Working with 2nd Input Sheet
Set ws2I = Sheets("Sheet2") '<~~ Input Sheet 2
ws2LRow = ws2I.Range("A" & Rows.Count).End(xlUp).Row
Set rSelection = ws2I.Range("A1:A" & ws2LRow)
'~~> Working with Output Sheet
Set wsO = Sheets("Output")
wsO.Range("A1") = "Common values"
wsOLr = wsO.Range("A" & Rows.Count).End(xlUp).Row + 1
'~~> Comparison : If the numbers match copy them to Output Sheet
For Each rCode In rFull
If Application.WorksheetFunction.CountIf(rSelection, rCode.Value) > 0 Then
rCode.Copy wsO.Range("A" & wsOLr)
wsOLr = wsOLr + 1
End If
Next rCode
MsgBox "Done"
LetsContinue:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
Let me know if you still get any errors :)
HTH