I'm trying to make a button which on click will print out the value of a cell as a string and not the appearance of the cell itself (if that makes sense) using the .PrintOut method in VBA. That cell is the active cell, whose value I set based on the cell next to it. Here is my code:
Sub Graphic2_Click()
Dim MyNumber as Integer
MyNumber = ActiveCell.Offset(-1, 0) + 1
ActiveCell.Value = MyNumber
ActiveCell.Printout
End Sub
I also tried MyNumber.PrintOut but I get an "Invalid Qualifier" error.
Am I missing out something too simple?
Please, try the next code. It use a temporary 'helper cell' where the format to be pasted (and recuperated after printing out):
Sub Graphic2_Click()
Dim helperCell As Range
With ActiveCell
.value = CLng(Offset(-1, 0)) + 1
Set helperCell = .Offset(1) 'it may be any cell to temporarilly be used
.Copy
helperCell.PasteSpecial xlPasteFormats
.ClearFormats
.PrintOut
helperCell.Copy
.PasteSpecial xlPasteFormats: helperCell.ClearFormats
End With
End Sub
To literally print just the contents of the cell:
Clear number formatting for the specified cell
Autofit column width for that column
Turn off gridlines
Turn off row and column headings
Set print area to the single cell, dismissing any warnings
Print out the active sheet
Each of these are straightforward to do in VBA, and probably straightforward to research on SO anyway.
You may also consider a mechanism to return the changed settings to their initial states afterwards. This would involve pushing (storing) the initial state to a variable or variables first, and popping (restoring) it back afterwards.
Explanation:
The VBA method .PrintOut is something you do to a worksheet, not a cell or its contents. Therefore, to get what you need, you need to set up the worksheet for printing so that the only thing that will appear is the contents of your chosen cell. This is what the above steps do.
For more information about the .PrintOut method, see:
https://learn.microsoft.com/en-us/office/vba/api/excel.sheets.printout
Or, to continue what the OP tried:
You could try something like:
ActiveCell.Formula = Range(ActiveCell.Offset(-1,0)).Value2 + 1
If this does not work, try:
ActiveCell.Formula = Range(ActiveCell.Offset(-1,0).Address).Value2 + 1
Or try these without the + 1 on the end, to verify that the rest of the formula is working the way you want it too. As mentioned, you may get a type mismatch issue causing an error if you don't trap first for whether the referenced cell contains a number.
.Formula in this example is how I am setting the content of the cell, and it can be used even when setting a value not necessarily literally a formula. You could use Value instead if you prefer.
.Value2 is a robust method of extracting the evaluated content of the source cell, as a value instead of as a formula.
The PrintOut method is to print a worksheet, not a range or single cell.
Note: This answer is not tested, as I am not near Excel right now.
Also... it's possible that there could be much simpler ways to do what you are trying to accomplish. Could you provide a bit more detail about the context of what you are trying to do.
Related
I have a workbook with many sheets containing CUBEVALUE formulas. My goal is to wrap these in NUMBERVALUE(), so that null values are shown as zero instead and formulas do not break.
Below is the code I have so far, this works for the most part. However, after replacing the original "CUBEVALUE(" with "NUMBERVALUE(CUBEVALUE(", the VBA keeps replacing the new CUBEVALUE again infinitely. I want to make the VBA stop after changing each cell once.
Current: =CUBEVALUE(formula)
Goal: = NUMBERVALUE(CUBEVALUE(formula))
Sub cube_to_numbercube()
Dim ws As Worksheet
On Error Resume Next
For Each ws In ActiveWorkbook.Worksheets
For Each cell In ws.Cells.SpecialCells(xlCellTypeFormulas)
If cell.Formula Like "*CUBEVALUE*" Then
ws.Range("A:C").Replace "=", "placeholder"
ws.Range("A:C").Replace "CUBEVALUE(", "NUMBERVALUE(CUBEVALUE("
ws.Range("A:C").Replace """]"")", """]""))"
ws.Range("A:C").Replace "placeholder", "="
End If
Next cell
Next ws
End Sub
Your problem is that you have a For Each cell, but then within that, the replace action is happening on ws.Range("A:C") not the individual cell.
Replace all instances of ws.Range("A:C"). with cell. and try again. If you only wish the changes to apply to columns A:C, change your For line to For Each cell in ws.Range("A:C").SpecialCells(xlCellTypeFormulas)
So your updated code should be:
Sub cube_to_numbercube()
Dim ws As Worksheet
On Error Resume Next
For Each ws In ActiveWorkbook.Worksheets
For Each cell In ws.Range("A:C").SpecialCells(xlCellTypeFormulas)
If cell.Formula Like "*CUBEVALUE*" Then
cell.Replace "=", "placeholder"
cell.Replace "CUBEVALUE(", "NUMBERVALUE(CUBEVALUE("
cell.Replace """]"")", """]""))"
cell.Replace "placeholder", "="
End If
Next cell
Next ws
End Sub
Your (inner) loop runs for every single cell containing a formula, but within the loop, you replace all formulas at once. Meaning if you have 100 cells with formulas, your loop will run 100 times and replace every formula containing CUBEVALUE 100 times with NUMBERVALUE(CUBEVALUE(.
Likely at a certain moment, the formula gets too long and Excel will refuse to replace it. This will cause a runtime error but you are hiding all runtime errors with On Error Resume Next.
Now replacing all formulas at once is not a bad idea in general as it will speed up your process. You could remove the inner For Each completely (and the If-statement). However, if for some reasons you need to run the code again (for example because some new formulas where added), you would still have the issue that for every run another NUMBERVALUE-function would be added. Furthermore, the Replace-statement could affect also formulas that shouldn't be affected (for example the 3rd replace statement).
So I would suggest you keep the inner For Each and replace the formula for every cell separately. With that, you can check if the formula really needs to be replaced and it wouldn't be an issue if the code runs several times.
Also, it is easier to do the string handling within VBA. Read the formula into a variable, make the replacement and write it back.
Dim cell As Range, formula As String
For Each cell In ws.Cells.SpecialCells(xlCellTypeFormulas)
formula = cell.formula
If formula Like "*CUBEVALUE*" And Not formula Like "*NUMBERVALUE*" Then
formula = Replace(formula, "CUBEVALUE(", "NUMBERVALUE(CUBEVALUE(")
formula = Replace(formula, """]"")", """]""))")
cell.formula = formula
End If
Next cell
And get rid of the On Error Resume Next - this statement will not prevent runtime errors, it simply doesn't show them which means that you will not be informed if something fails.
So I have built a formula that has Absolute Cell References, and wanted to repeat the same formula down 3000 cells with each one referencing increment cells. (1st formula referring to Cell $A$1, 2nd formula referring to $A$2) I know that I could easily do this without referencing exact cells and the Fill Handle and this is currently how it's set up, however there's a very large number of people who work in this spreadsheet that have bad Excel manners, and regularly delete rows and cells or copy and paste, which breaks the formulas.
Rather than manually editing the same formula in each cell to change the references from relative to absolute, I was wanting to run a Macro to automatically run the formula for 3000 cells.
I had at first built a Macro that fills 20 cells with the formula, but it didn't adjust the formula based on the active cell. (Always entered with range $A$1:$A$20, and not $A$21:$a$40 when started further down) I changed the Macro to loop, but it looks with all formulas referencing $A$1 rather than updating.
The Macro set up to loop is as follows:
Sub HDDatesRef()
ActiveCell.Select
ActiveCell.FormulaR1C1 = "=IF(AND(HD!R1C1>0,ISBLANK(HD!R1C4)),HD!R1C1,""n/a"")"
ActiveCell.Offset(1, 0).Range("A1").Select
Loop Until ActiveCell.Value = ""
End Sub
Any and all help with figuring this out would help immensely. Right now I also have access to Liknedin Learning, so if there's any suggestions for courses on there I should look into so I can understand what I need to do will help with this.
The Excel application object has a function called ConvertFormula which you can use to change a formula between reference styles (A1 or R1C1) and to specify whether the rows and columns should be relative references or absolute references.
If you start off by creating the formula in each row as a relative reference then you can use ConvertFormula to turn it into an absolute reference. The only restriction is that the formula cannot be longer than 255 characters.
Adapting your code and following the advice in How to avoid using Select in Excel VBA gives us:
Option Explicit
Sub HDDatesRef()
Dim r As Range
' If we know the cell address we want to start in then we could use that directly
' e.g. Set r = Worksheets("HD").Range("E1")
Set r = ActiveCell
Do
' The With block just saves us typing r.FormulaR1C1 multiple times
With r
' Don't know what your relative formula would be. I've assumed that we are
' working in column E but adjust as appropriate
.FormulaR1C1 = "=IF(AND(HD!RC[-4]>0,ISBLANK(HD!RC[-1])),HD!RC[-4],""n/a"")"
' Take the formula we already have which is in R1C1 format, keep it in R1C1 format,
' change it from a relative reference based on cell r to an absolute reference
' and make that the new formula for this cell
.FormulaR1C1 = Application.ConvertFormula(.FormulaR1C1, xlR1C1, xlR1C1, xlAbsolute, r)
End With
' Move down one row
Set r = r.Offset(1, 0)
Loop Until r.Value = ""
End Sub
In case you aren't familiar with them. here are the references for Option Explicit and With...End With
You can do this without looping, Excel is smart enough to know you want incremental.
As an example do run this on a fresh sheet:
Sub ShowIncremental()
Range("A1:A10").Formula = "=Row(A1)"
Range("B1:B10").Formula = "=A1*2"
Range("C1:C10").Formula = "=sum(B$1:B1)"
End Sub
Notice the formulas created in A1:C10. Notice Excel incremented them even though the code didn't say to except in the case where we absoluted B$1.
I recommend you do something similar with your code to avoid looping, this will be much much faster.
EDITED WITH BETTER EXAMPLE
I'm trying to use the Evaluate function to evaluate a formula reference for a named range. However, when using the Evaluate function, if you do not explicitly state the sheet reference along with the cell reference, it will assume the active sheet as the cell reference. This causes the wrong result
In my real project I'm trying to only evaluate a part of the named range's formula, so it makes it even trickier.
Using a basic example of what I'm trying to do, let's say you have the following formula in Sheet 1 cell A1 whose name is MyCell:
="Don't evaluate this part"&"My Result Is " & A2
If the Active Sheet is Sheet 2 and you run the following code it will give you the wrong results (this is a quick and dirty example to illustrate the problem)
Dim s As String
s = Replace(Range("MyCell").Formula, """Don't evaluate this part""&", "")
Debug.Print Evaluate(s)
Instead of giving me the value that is in cell A2 of Sheet 1, it gives me the value that is in cell A2 of Sheet2.
Any ideas around this?
This is closest I found, but it is not my exact problem (despite similar titles) and it doesn't provide a solution:
Excel VBA evaluate formula from another sheet
The problem you are having is that by design Excel will assume all unspecific cell references are referring to the existing worksheet. This is why whenever possible it is recommended to explicitly state the worksheet in all code.
The cleanest way (verified with some MSDN definintion hunting) is to just explicitly state the worksheet without activating it:
Sub test2()
Debug.Print Range("MyCell").Worksheet.Evaluate(Range("MyCell").Formula)
End Sub
Alternatively this code will change the active worksheet to the correct one and then change it back after evaluation. Not recommended to perform sheet activations like the code below without extenuating circumstances. Not even here.
Sub test()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim s As String
s = Replace(Range("MyCell").Formula, """Don't evaluate this part""&", "")
Range("MyCell").Worksheet.Activate ' Don't remember if .Worksheet or .Parent ??
Debug.Print Evaluate(s)
ws.Activate
End Sub
As pointed out in the comments by ThunderFrame, it is important to remember that this code assumes MyCell is a simple cell reference as stated in the question. Otherwise you will need to use other methods to determine the target worksheet name (or hardcode it).
Its nearly always better to use Worksheet.Evaluate rather than the default Application.Evaluate: as Mark Balhoff points out that allows you to control unqualified references.
But Worksheet.Evaluate is also usually twice as fast as Application.Evaluate.
See my blog post here for details
https://fastexcel.wordpress.com/2011/11/02/evaluate-functions-and-formulas-fun-how-to-make-excels-evaluate-method-twice-as-fast/
Your line:
Debug.Print Evaluate(Range("MyCell").Formula)
is equivalent to:
Debug.Print Evaluate("=""My Result Is "" & A2")
which is why you get results according to the value of A2 in the ActiveSheet.
If you want to inspect the contents of the formula, you can use this line:
Debug.Print [MyCell].Formula
If you want the value of MyCell with respect to Sheet1, then you have 2 options:
1 - Use Debug.Print Range("Sheet1!MyCell").Value
2 - Use Debug.Print Sheet1.Range("MyCell").Value
I am tasked with creating A map of our warehouse.
In the data I have to have model, description and location.
What I am having trouble with is, I am using data from a second sheet to populate the "map"
i.e. ='1'!F2
when I try to drag and use it to fill an entire line it changes to ='1'!g2. I would like it to go to ='1'!F3
I see the logic in what it is doing...but I dont want it to use that logic..I want it to use the next cell below it to populate that cell.
The simplest thing might be to Copy and then Paste Special > Transpose the data on "1" to a new sheet. Then you could drag formulas that refer to the new sheet and they'd behave as expected.
EDIT: Based on your original question, this will fill in the results of columns to the right as you drag it down and vice-versa. This literally does what your original question asked:
=INDEX(Sheet1!$F$2:$Z$8000,COLUMN(),ROW())
Start in A1 and drag in either direction. To add a header line or rows to left just insert rows or columns to top or left (to keep the formula sound).
EDIT: Here's the Transpose function, per #brettdj's suggestion. I find it difficult to work with, but it certainly makes it clearer what's going on:
In cells F2:8000 of your target sheet enter:
=TRANSPOSE(Sheet1!$F2:$Z8000)
Then, with all those cells selected, go into edit mode in one of the cells and do Ctrl Shft Enter to array-enter it. If you have to resize the source range I believe you have to repeat these steps with the correct ranges. I'm an Index fan myself, so would stick with that. Offset is volatile, so I'd avoid it. If I've got any of this last edit wrong, #brettdj will help us.
Since what you want is non-native behaviour, it might be worth writing a small VBA macro to do the copy, and assign it to a keyboard shortcut.
Here's a simple example to copy a formula one cell to right, updating reference one cell down (preserves Absolute/Relative settings in formula).
It assumes A1 style address, work only if the active cell contains a formula referencing a single cell (ends silently if not). Will silently overwrite anything in the destination cell.
Sub CopyToRight()
Dim clFrom As Range
Dim clAddr As Range
Dim addr As String
On Error GoTo EH
Set clFrom = ActiveCell
If clFrom.Formula Like "=*!*" Then
Set clAddr = Range(Mid(clFrom.Formula, 2))
If clAddr.Count = 1 Then
If clFrom.Formula Like "=*!$*$*" Then
addr = clAddr.Offset(1, 0).Address(True, True)
ElseIf clFrom.Formula Like "=*!$**" Then
addr = clAddr.Offset(1, 0).Address(False, True)
ElseIf clFrom.Formula Like "=*!*$*" Then
addr = clAddr.Offset(1, 0).Address(True, False)
Else
addr = clAddr.Offset(1, 0).Address(False, False)
End If
clFrom.Offset(0, 1).Formula = "='" & clAddr.Worksheet.Name & "'!" & addr
End If
End If
clFrom.Offset(0, 1).Select
EH:
End Sub
I'm a new convert from Experts-Exchange since I noticed they increased the 'Free Premium Services' points threshold.
It seems that Excel 2003 has issues with the End(xlup) command when using it in a worksheet that contains an Excel 'List'.. If I select a cell outside the 'list' boundary, and then try to select the last cell in the worksheet by using VBA, I have to call the .Select function twice to make sure I am getting the correct cell. If the original cell is inside the 'list' boundary then i only need one .Select. My hacked together solution is below, with two selects, as I can never be sure what cell may be selected on save. I include a version check at open to run different code in Excel 2007 (this code fails in 2007, where the .End(xlUp) command works properly).
Is there a more eloquent way to handle this?
Thanks for any help!
.Range("A1").Select
.Cells(.Rows.Count, "A").End(xlUp).Select
.Cells(.Rows.Count, "A").End(xlUp).Select
'two .Selects needed to select correct cell in Excel 2003 list because original selection (A1) was not in list'
.Range("A1").Select
.Cells(.Rows.Count, "T").End(xlUp)(-2, 1).Select
.Cells(.Rows.Count, "T").End(xlUp)(-2, 1).Select
'two .Selects needed to select correct cell in Excel 2003 list because original selection (A1) was not in list'
.Cells(.Rows.Count, "T").End(xlUp)(-3, 1).Select
'only one select needed here because original selection above was within the list'
See how this does:
Sub Example()
Dim rngLstCell As Excel.Range
Set rngLstCell = GetLastCell(Excel.Worksheets("Sheet1"))
MsgBox "The last cell is: " & rngLstCell.Address, vbInformation
End Sub
Public Function GetLastCell(ByVal ws As Excel.Worksheet) As Excel.Range
Dim rngRtnVal As Excel.Range
With ws.UsedRange
Set rngRtnVal = .Find("*", .Cells(1, 1), , , xlByRows, xlPrevious)
If rngRtnVal Is Nothing Then
Set rngRtnVal = .Cells(1, 1)
End If
End With
Set GetLastCell = rngRtnVal
End Function
Using find may seem weird at first but it ends up being the most reliable way due to some vagaries in the way empty cells are handled.
This may not be perfect if your data is non-normalized (jagged).
I found that my use of .End(xlUp).Select prior to acting on the .End(xlUp) cell was causing the problem. If I simply avoid the .End(xlUp).Select prior to operating on the .End(xlUp) cell, the problem is less complex. It can be easily solved by preceding any .End(xlUp) operation with .Range("A1").Select. See the code for explanation. This doesn't really fix the problem with improper .End(xlUp) cell 'selection' - but I'm not interested in 'selecting' the cells, just operating on them. I must mention that I use .Range("A1").Select because A1 is outside of the 'list' that I'm manipulating via VBA.
'commented out - just need to add a ".Range("A1").Select" prior to any .End(xlUp) usage (besides .End(xlUp).Select) to make it work in Excel 03
'.Range("A1").Select
'.Cells(.Rows.Count, "A").End(xlUp)(0, 1).Select
'.Cells(.Rows.Count, "A").End(xlUp)(0, 1).Select
''two .Selects needed to select correct cell in Excel 2003 'Lists'
'Set EntryDate = Cells(.Rows.Count, "A").End(xlUp)(0, 1) 'no need to select cell first, then operate on it, as in the code above
'fixed code below
.Range("A1").Select 'needed for Excel 03 to select correct cell
Set EntryDate = Cells(.Rows.Count, "A").End(xlUp) 'just operate on the cell instead of selecting it first
Are you sure the ranges that you were working with were identical? You shouldn't get different results using the End property in Excel 2007 versus 2003.
Looking at your code:
.Range("A1").Select
.Cells(.Rows.Count, "A").End(xlUp).Select
.Cells(.Rows.Count, "A").End(xlUp).Select
Each of these lines of code have exactly zero impact on one another. No honest explanation can be offered about why the End property is giving you different results based on the code you've provided. From what's written, you should get the same results each time. (That's assuming you're working with identical ranges.) I'd be suspicious of any other code that's being executed. I can offer a couple general tips though: If you use End starting with a blank cell, it will stop at the first non-blank cell. If you start with a non-blank cell, the opposite is true. Looking at the screenshot below:
Range("B13").End(xlUp).Select 'selects B12
Range("B12").End(xlUp).Select 'selects B2
Range("A12").End(xlUp).Select 'selects A6
So whether or not your list is contiguous is an issue. Also, it is not necessary to select a range before you do something to it. Telling Excel to select cell A1 does not impact how it executes .Cells(.Rows.Count, "A").End(xlUp).Select. Assuming that line is within a With block that refers to a worksheet, that line of code is the same thing as navigating to cell A65536 (or A1048576 in Excel 2007) and pressing Ctrl + Up. Assuming that cell is blank, Excel will navigate upwards until it finds the first non-blank cell in column A. If your With block refers to a range object, then that line of code will go to the first column, bottom row of that range and navigate upwards until it comes to the first blank or non-blank cell.