I have an Excel 2010 workbook that insists on "calculating" every time I enter, then exit a cell (blank) even when nothing is changed.
If the above workbook is open it spreads the problem to any other workbook is that is open. Problem will disappear when the above workbook is closed.
The problematic workbook contains 3 Sheets;
1 has a table that pulls external data with an SQL command,
2 has various formulas that summarise the data from Sheet 1,
3 has various graphs that use data from Sheet2.
The problematic workbook contains no macros.
I've searched Google high and low to no avail, any ideas?
As the TODAY function is considered a volatile¹ function, it isn't wise to use it repeatedly within long columns and/or rows of formulas, particularly within calculation-intensive array formulas. Calculation lag can quickly build up and the function's volatile nature will force a calculation cycle whenever anything in the entire workbook changes, not just when something that actually affects its outcome changes as is the norm with non-volatile functions.
A formula based defined name that does not actually refer to any worksheet call is a good fit here. Using something like =DATE(YEAR(TODAY()), MONTH(TODAY()), DAY(TODAY())) is counterproductive because you have not removed the volatile TODAY() function. However, VBA can write a hardcoded native worksheet function into the RefersTo: of the defined name. In this case, the current date will be constructed using the worksheets' DATE function.
The following code belongs in the ThisWorkbook code page. Tap Alt+F11 and when the VBE opens, locate ThisWorkbook in the Project Explorer. If the project explorer is not visible, tap Ctrl+R or use the pull-down menus to choose View ► Project Explorer.
Double-click ThisWorkbook or right-click and choose View Code. Paste the following into the code sheet titled something like Book1 - ThisWorkbook (Code).
Private Const dnCURRENT As String = "Current"
Private Sub Workbook_Open()
'force a Workbook_SheetChange on open to check Current date
Call Workbook_SheetChange(Worksheets(1), Worksheets(1).Cells(1))
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
On Error GoTo no_Current
With ThisWorkbook.Names(dnCURRENT)
On Error GoTo bm_Safe_Exit
If Application.Evaluate(.RefersTo) <> Date Then
Application.EnableEvents = False
.RefersTo = "=DATE(" & Year(Date) & ", " & Month(Date) & ", " & Day(Date) & ")"
End If
End With
GoTo bm_Safe_Exit
no_Current:
ThisWorkbook.Names.Add Name:=dnCURRENT, _
RefersTo:="=DATE(" & Year(Date) & ", " & Month(Date) & ", " & Day(Date) & ")", _
Visible:=True
Resume
bm_Safe_Exit:
If CBool(Err.Number) Then _
Debug.Print Err.Number & ":" & Err.Description
Application.EnableEvents = True
End Sub
If you wish to change the name of the defined name, do so in the private constant variable declaration at the top. Tap Alt+Q to return to your worksheet(s).
The first time that any change is made on any worksheet, a new defined name with workbook scope will be created called Current. It will have a RefersTo: with a hardcoded DATE function that returns the current system date (e.g. =DATE(2015, 10, 14)). If the current date is not equal to the defined names value returned by that formula, a new formula is written using the current date and this forces a new workbook-wide calculation cycle on all formulas that reference the defined name.
The defined name Current is a direct replacement for anywhere that the TODAY() function can be used. In practise, the formula(s) involving Current will only be recalculated once per day. The following image demonstrates how it will appear as an option as you start to type it into a formula.
¹ Volatile functions recalculate whenever anything in the entire workbook changes, not just when something that affects their outcome changes. Examples of volatile functions are INDIRECT, OFFSET, TODAY, NOW, RAND and RANDBETWEEN. Some sub-functions of the CELL and INFO worksheet functions will make them volatile as well.
Related
I am working on an excel file that will work as a calendar with specifications.
I want to have a button at each day. Since I want this to be reusable for other years, I will have buttons on columns with no days (for example, if January starts on a tewsday, Monday will have a button, but nothing on the day, since it is from December).
I know it is possible to set a button enable = False, but I don't know where to put that code. I don't want it to be disabled when another button is clicked but at the opening of the file.
I am new to vba, I'm sorry if this is something really simple.
My approach needs those cells with days from previous month to be empty or "", if theres any value inside it wont work (instead you change the logic to treat cells values like numbers instead of strings).
I noticed that days in your calendar are in string format or so (i.e: "01") that's why I use Len() to evaluate length of string.
This code will set buttons visibility based on TopLeftCell value. Visible = True to days with some value, and Visible = False to empty values.
There is a way to make a button "Enable" but that property is for buttons inside an UserForm.
Tell me if it works for your case, since Sheet.CurrentRegion may cause some issues if your cells are way to much separate from each other, plus it could also hide some other buttons you have. If any of those scenarios do happen let me know, I'll continue helping you anyways!
Sub Set_Buttons_Visibility()
Dim Sheet As Worksheet
Dim Calendar_DataBodyRange As Range
Dim Shape As Shape
'Set Calendar range
Set Sheet = ActiveSheet 'Set Sheet
Set Calendar_DataBodyRange = Sheet.Cells(1, 1).CurrentRegion 'Set current region
Calendar_DataBodyRange.Select '<- comment this after you tested everything[']
'Hide buttons from previous month
For Each Shape In Sheet.Shapes
'If Shape.Visible Then Shape.Select
'Get variables
'Get Button day, as string
strTemp = CStr(Shape.TopLeftCell)
'Get range occupied by button
Set rngTemp = Sheet.Range(Shape.TopLeftCell, Shape.BottomRightCell)
'rngTemp.Select
'Test conditions
'Test rngTemp is part of Calendar_DataBodyRange
bInRange = Not Intersect(Calendar_DataBodyRange, rngTemp) Is Nothing
'Test TopLeftCell has some string
bString = (Len(strTemp) > 0)
'Test bInRange and bShow (True and True)
bCondition = (bString = False) And bInRange
'Perform action
'Set shape visibility
Shape.Visible = Not (bCondition)
'Delete shape (only if you have another procedure to rebuild all buttons)
''''Shape.delete
Next
End Sub
Run code when workbooks opens
To start this function when workbook opens, go to VBA Project Explorer > ThisWorkbook then inside the module you can bind your code to Workbook_Open event. Later on (depending in where you've have stored your code) use the following Run function.
Important:
According to your case you might need to store your code 1) inside the sheet you are working on, in other cases you store your code 2) in a single sheet usually called PERSONAL.XLSB that is always open when Excel itself Opens (Know more about this) so your functions can be accesible for all sheets that you work on.
Pros and Cons:
On the first case is perfect for sharing your work with your boss or colleagues since your code is locally stored in the sheet (but is harder to update, and hard to back up) and the second case is optimal for your own use since all your functions are in the same workbook so you can call it like "[Workbook.Name]![FunctionName],[FunctionParameters]" (allows you to do better updating and an easier backup just by copy-pasting). In any case you can addapt to your necessities.
Private Sub Workbook_Open()
'Run sintax needs Workbook [extension] and string [!]
'Function is stored in current workbook (case 1)
Run ThisWorkbook.Name & "!Set_Buttons_Visibility"
'Function is stored in PERSONAL (case 2)
Run "PERSONAL.XLSB!Set_Buttons_Visibility"
End Sub
I made a macro that combines three reports in to one.
I first find the dynamic name by looking at open workbooks to find a matching name
For Each wk In Workbooks
If Left(wk.Name, 14) = "PayrollSummary" Then
Set wbpay = Workbooks(wk.Name)
End If
If Left(wk.Name, 12) = "PunchedHours" Then
Set wbpun = Workbooks(wk.Name)
End If
Next
And from the start this line worked (ws is the report it's working on).
ws.Range("K5").Formula = "=IFERROR(VLOOKUP(A5,['" & wbpay.Name & "']payrollsummary!$B:$B,1,FALSE),""Fel"")"
Then that line started acting up and this worked:
ws.Range("K5").Formula = "=IFERROR(VLOOKUP(A5,[" & wbpay.Name & "]payrollsummary!$B:$B,1,FALSE),""Fel"")"
Now I have added a third:
On Error Resume Next
ws.Range("K5").Formula = "=IFERROR(VLOOKUP(A5,['" & wbpay.Name & "']payrollsummary!$B:$B,1,FALSE),""Fel"")"
ws.Range("K5").Formula = "=IFERROR(VLOOKUP(A5,[" & wbpay.Name & "]payrollsummary!$B:$B,1,FALSE),""Fel"")"
ws.Range("K5").Formula = "=IFERROR(VLOOKUP(A5,'[" & wbpay.Name & "]payrollsummary'!$B:$B,1,FALSE),""Fel"")"
On Error GoTo 0
Because today only the third line worked.
Here is an example of the formula in the Excel:
The workbook name will always be ParollSummary_DateFrom_DateTo_SomeRandomStuff.xlsx.
Looking at the image it seems I have accidentally downloaded the file twice (1).
But either way, I still don't see the reason why three different lines of code works randomly (my impression) with different files.
Is there any way to make sure it will always work so that I don't need to find out what will be the correct way next week?
Apologies for posting an "Answer" here but the discussion is running out of space. Let's look at your code in detail.
For Each wk In Workbooks
If Left(wk.Name, 14) = "PayrollSummary" Then
Set wbpay = Workbooks(wk.Name)
End If
If Left(wk.Name, 12) = "PunchedHours" Then
Set wbpun = Workbooks(wk.Name)
End If
Next
It's not clear why a workbook name that starts with "PayrollSummary" should be checked whether it also starts with "PunchedHours". The two are mutually exclusive. When both are found the search should stop and when one of them isn't found the rest of your macro shouldn't be executed. Either of these things could happen with your above code leading to the errors that follow later. The code below wouldn't have the faults just described.
Sub Trial()
Dim WbPay As Workbook
Dim WbPun As Workbook
If GetWorkbook(WbPay, "PayrollSummary") Then
If Not GetWorkbook(WbPun, "PunchedHours") Then Exit Sub
' continue your code here
Debug.Print WbPay.Name
Debug.Print WbPun.Name
End If
End Sub
Private Function GetWorkbook(Wb As Workbook, _
WbName As String) As Boolean
For Each Wb In Workbooks
If InStr(1, Wb.Name, WbName, vbTextCompare) = 1 Then
GetWorkbook = True
Exit For
Next Wb
End Function
Now we know that the rest of the code can't fail because one of the workbooks wasn't found. Both WbPay and WbPun actually exist and are open.
That leads to the question why we need to use a worksheet function to access them. Since all their content is accessible, why not just get it? But you want this:-
=IFERROR(VLOOKUP(A5,['ParollSummary_DateFrom_DateTo_SomeRandomStuff.xlsx']payrollsummary!$B:$B,1,FALSE),"Fel")
There are three questions attached to this demand.
In which workbook is the formula? Bear in mind that A5 is on the ActiveSheet of that workbook. What will happen if the sheet on which the formula is entered isn't active at that time? I don't know but if Excel would attempt to execute the formula in such a scenario an error must occur - probably an error 1004.
'ParollSummary_DateFrom_DateTo_SomeRandomStuff.xlsx' should be WbPay.Name. Why not use that definition? It would guarantee that the referenced workbook really exists and is open. We don't know that of 'ParollSummary_DateFrom_DateTo_SomeRandomStuff.xlsx'. In fact, the name contains a spelling error even here (lifted from your published code).
Why do you wish to return a value from columns(1) of the look-up array? That would be the same value as you have in A5. Not that this would cause an error in itself but it adds to the general confusion.
The verdict, therefore, is that your plan is high in risk and low in precision. The solution must be in reducing the risk and increasing the precision.
I am new at vba and I have a rather simple issue. I want to obtain the address of the last active cell there was. For example, if I was at A5 and moved to B6, is there a command to obtain the address A5?
Any type of tip or suggestion is highly appreciated!
I've tried ActiveCell.Previous but that provides the address of the cell on the left of the active cell. Offsets are no use for me since the address A5 is unknown until the user changes something inside a grid of cells.
The most obvious way would be to use Excel's events. You could have a look at the SelectionChange event, which would enable you to store the previous selection at module-level and then retrieve that value on subsequent firings of the event.
In the example below I've used the code-behind of the Workbook object, as it enables you to register selections on any sheet, but you could do the same on just one Worksheet.
If you're only interested in certain cells, then look at the Intersect function to refine the routine.
Option Explicit
Private pPreviousWorksheet As Worksheet
Private pPreviousSelection As Range
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
'Check SH object is a worksheet.
If TypeOf Sh Is Worksheet Then
'Check we have instances of previous objects.
If Not pPreviousWorksheet Is Nothing And Not pPreviousSelection Is Nothing Then
'Process code here...
MsgBox "Previous was " & _
pPreviousWorksheet.Name & "!" & _
pPreviousSelection.Address(False, False)
End If
'Re-set the previous objects.
Set pPreviousWorksheet = Sh
Set pPreviousSelection = Target
End If
End Sub
I am looking for a formula which can directly be used in cells to read all the active tabs' name. Please refer the screen shot for the same.
There is also a =MID(CELL("filename"),FIND("]",CELL("filename"))+1,255) formula, but it is only giving the current tab name.
Though this is easily possible using macro, but would be great if can get formula for that.
There is a way to do this through formula's only,
Have a look here
It feels a bit double to post exactly how it's done, but the approach makes use of a named range and a lookup formula
It's fairly easy to do
I note you say formula but you could use a very simple User Defined Function (UDF) which goes in a standard module in the VBE (which you open with Alt+F11)
Option Explicit
Public Function GetTabName(ByVal tabIndex As Long) As String
GetTabName = ThisWorkbook.Worksheets(tabIndex).Name
End Function
The sheet index gets passed into the UDF as a parameter and the associated sheetname is returned.
If testing for visible sheet you could use the following, which has additional handling for sheet not found:
Option Explicit
Public Function GetTabName(ByVal tabIndex As Long) As String
Dim ws As Worksheet
On Error GoTo Errhand
Set ws = ThisWorkbook.Worksheets(tabIndex)
If ws.Visible Then
GetTabName = ThisWorkbook.Worksheets(tabIndex).Name
Else
GetTabName = "N/A"
End If
Errhand:
If Err.Number <> 0 Then
Select Case Err.Number
Case 9
GetTabName = "Sheet not found"
End Select
End If
End Function
UDF Limitations
I have an Excel workbook in which I have tabs representing dates along with sum in each tab. Although I can take the sum of all these in the final sheet, I want a formula/macro to get the sum in the total named sheet, when a new spreadsheet is being added.
Note:- the cell in all would remain the same (E56)
I do not understand what you are attempting. Until the user has placed information in the new sheet that results in a value in E56, I see little point to adding the value of NewSheet!E56 to the total sheet.
However I suspect you need to use events. Below are a number of event routines which must be placed in the Microsoft Excel Object ThisWorkbook for the workbook. These just output to the Immediate window so you can see when they are fired. Note: several can be fired for one user event. For example, creating a new worksheet, triggers: "Create for new sheet", "Deactivate for old sheet" and "Activate for new sheet".
Do not forget to include
Application.EnableEvents = False
Application.EnableEvents = True
around any statement within one of these routine that will trigger an event.
Perhaps you need to use SheetDeactivate. When the users leaves a sheet, check for a value in E56. If present, check for its inclusion in the totals sheet. Have a play. Do what your users do. Add to these routines to investigate further. Good luck.
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Debug.Print "Workbook_SheetActivate " & Sh.Name
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Call MsgBox("Workbook_BeforeClose", vbOKOnly)
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Source As Range)
Debug.Print "Workbook_SheetChange " & Sh.Name & " " & Source.Address
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
Debug.Print "Workbook_SheetDeactivate " & Sh.Name
End Sub
Private Sub Workbook_NewSheet(ByVal Sh As Object)
Debug.Print "Workbook_NewSheet " & Sh.Name
End Sub
Sub Workbook_Open()
Debug.Print "Workbook_Open"
End Sub
Extra section in response to clarification of requirement
The code below recalculates the grand total of cell E56 for all worksheets except TOTAL and stores the result in worksheet TOTAL every time the workbook is opened and every time the user changes the current worksheet.
It is difficult to get consistent timings with Excel but according to my experimentation you would need between 500 and 1,000 worksheets before the user would notice a delay switching worksheets because of this recalculation.
I am not sure if you know how to install this code so here are brief instructions. Ask if they are too brief.
Open the relevant workbook.
Click Alt+F11. The VBA editor displays. Down the left you should see the Project Explorer. Click Ctrl+R if you do not. The Project Explorer display will look something like:
.
VBAProject (Xxxxxxxx.xls)
Microsoft Excel Objects
Sheet1 (Xxxxxxxxx)
Sheet10 (Xxxxxxxxx)
Sheet11 (Xxxxxxx)
:
ThisWorkbook
Click ThisWorkbook. The top right of the screen with turn white.
Copy the code below into that white area.
No further action is required. The macros Workbook_Open() and Workbook_SheetDeactivate() execute automatically when appropriate.
Good luck.
Option Explicit
Sub CalcAndSaveGrandTotal()
Dim InxWksht As Long
Dim TotalGrand As Double
TotalGrand = 0#
For InxWksht = 1 To Worksheets.Count
If Not UCase(Worksheets(InxWksht).Name) = "TOTAL" Then
' This worksheet is not the totals worksheet
If IsNumeric(Worksheets(InxWksht).Range("E56").Value) Then '###
TotalGrand = TotalGrand + Worksheets(InxWksht).Range("E56").Value
End If '###
End If
Next
'Write grand total to worksheet TOTAL
' ##### Change the address of the destination cell as required
Worksheets("TOTAL").Range("D6").Value = TotalGrand
End Sub
Sub Workbook_Open()
' The workbook has just been opened.
Call CalcAndSaveGrandTotal
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
' The user has selected a new worksheet or has created a new worksheet.
Call CalcAndSaveGrandTotal
End Sub
I know this is the programming forum, but this particular "need" seems to be solvable without all the plumbing.
I like the old hidden FIRST and LAST sheets trick.
Create a sheet called First
Create a sheet called Last
Place your current data sheets between these two sheets.
Hide the sheets First and Last
Now you can use 3D formulas to sum cells from all these sheets, like so:
=SUM(First:Last!E56)
Now just add sheets to your workbook AFTER the last visible data sheet and Excel will still slip it in ahead of the hidden LAST sheet, so your formula just expands itself that way