Save undo stack during macro run - vba

I am wondering if there is a way to save ability to undo actions after macro has been run.
I do not care about results of macro - just need to undo actions that were done by user before macro.
Background:
I have a macro on the worksheet_change event that logs who and when made the change on this worksheet. I do not want it to restrict user's ability to undo his/her actions.

There is no easy way to do this, but it's possible. The approach to this is to create three macros, and use some global variables to save state:
MyMacro
MyStateSavingMacro
MyStateRevertingMacro
E.g. My macro changes Cells in Range A1:A10 of the active sheet. So, whenever the code to run my macro is called, it executes
Sub MyMacro()
Call MyStateSavingMacro()
' Copies contents and formulae in range A1:A10 to a global data object
'... Code for MyMacro goes here
'
'................
Call Application.OnUndo("Undo MyMacro", "MyStateRevertingMacro")
'This puts MyStateRevertingMacro() in the Undo queue
'So pressing ctrl-Z invokes code in that procedure
End Sub
Sub MyStateSavingMacro()
' Code to copy into global data structures anything you might change
End Sub
Sub MyStateRevertingMacro
' Code to copy onto the spreadsheet the original state stored in the global variables
End Sub
So there it is. It's not pretty, but can be done.
Ref: http://msdn.microsoft.com/en-us/library/office/ff194135%28v=office.15%29.aspx
Edit:
To preserve the Undo queue prior to your MyMacro being run, the inelegant solution would be to create a chain of 4-5 MyStateRevertingMacro_1, _2, etc. where you can apply the information from your Worksheet_Change logging system and then chain-up the Application.OnUndo in each of those, so Application.OnUndo for each of those Reverting Macros would refer the previous state reversion code.

You can use the hidden mirror sheet to do this. Of course it will work if your worksheet is simple enough. You must decide which cells are editable and copy them TO mirror sheet, which are not editable and copy them FROM mirror sheet. And your macro should work only in the mirror sheet. That's it.

This one is a bit old now but in case anyone is still trying to get this to work - I just tried setting the undo stack to be able undo the formatting on a column that a macro had reformatted and noticed that by doing that the full undo command worked (before I added this bit the undo was unavailable) - does not matter what the custom undo code contains (in my case I had not even created the routine yet), the application undo works perfectly
Application.OnUndo "Undo Amount Format", "sUndo_Col2"

Related

Range of object _worksheet failed + object invoked has disconnected from its clients errors

I have very simple code:
Private Sub Worksheet_Change(ByVal Target As Range)
Worksheets("PickList").Range("AN2:AN14").Copy Destination:=Worksheets("PickList").Range("AR2:AR14")
End Sub
I am simply moving some data from one column to the next. I'm running this code off of the PickList worksheet. I also have another worksheet, Config, that works together with PickList and depending on what was done in Config, some data may change in PickList.
Anyways if the code is put in PickList. I get the Range of Object error and shortly after it gives me the object invoked error and it crashes Excel 100% of the time. Now if I put this code in Config it works fine without error.
Now my thinking is that there is an issue with how my two worksheets work together. On Config there are some dropdowns that the user can select, and depending on how these dropdowns are selected, some data will change in PickList. I think the issue lies with me physically being on the Config worksheet while the Config sheet makes changes to the PickList which activates the Worksheet_Change function and maybe that is where the error stems from. But I am a novice and I'd like some advice on how to go about fixing this problem. Thanks in advance.
If you are looking for changes due to equations being updated, a Change_Event will not work. This will only trigger when a cell is physically changed.
-(Likely explanation of why this works fine on Config and not PickList)
You may need to re-work your logic to apply this. Run this code from Config. Determine what changes on Config will lead to changes on PickList. When this change is made on Config, then execute your worksheet change. You need to analyze your Target (changed cell)
Also, you need to disable Events before you make a change. Every time you make a change, you re-activate your macro (leading to an infinite loop and your instance of excel crashing).
Application.EnableEvents = False
'Physical changes to worksheet go here
Application.EnableEvents = True

Updating a macro to be identical across all worksheets (or making the code more global?)

I have a workbook with a few dozen worksheets, which is growing. I might also end up with copies of this workbook pretty soon.
Each sheet is based on the same worksheet template, which includes a macro on its Worksheet_Change event for auto-calculations. As I improve the workbook and add capabilities, and sheets are added, it's taking longer and longer to copy-paste the updated macro to all sheets.
I'm wondering if there's a way to either:
Make another macro that copies the updated version of the macro from the template to all other worksheets, overwriting the old versions in the process
And/or
Move the code from worksheet_change to a more global place where I'd only need to update one version of it per workbook (which is fine and much better than updating 20+ worksheets manually across soon-to-be 3 workbooks...)
Solution #2 would be better because more elegant I think? But I have no clue if it's possible. Barring that I'd gladly take #1 for the time-saving aspect!
Thank you in advance.
If we are talking about one workbook with multiple worksheets, then an easy approach (which solves the updating issue) would be:
Add a Module and write a procedure containing the original change events code:
Option Explicit
Public Sub MyGlobalWorksheet_Change(ByVal Target As Range)
' here the code from your orignal Worksheet_Change.
' make sure you reference worksheets correctly
' the worksheet can eg be addressed like
' set ws = Target.Parent
End Sub
So in your worksheets you only need to add a generic call like
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
MyGlobalWorksheet_Change Target
End Sub
to call the global procedure. Therefore the Worksheet_Change event never needs to be changed, however you just need to add it once.
Whenever you need to change something at the code you just need to change one procedure which is MyGlobalWorksheet_Change and it affects all your desired sheets at once (but only sheets you added the call to your global event).
Remember it is always a bad idea to copy the same code over and over again, because it is hard to maintain. Instead always use one procedure you call again and again.
Another way would be using the Workbook_SheetChange event within the ThisWorkbook scope. But this will affect any sheet within the workbook. The previous solution will only affect the workbooks you choose by adding a call.

Excel slowed down after using 'on active cell change' macro

I recently experimented with a macro that runs every time the active cell column changes:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim cell As Integer
cell = ActiveCell.Column
Select Case cell
'code
End Select
End Sub
I quickly realized that this slows down Excel by a lot, so I turned the macro off. The problem is that it must have turned something on, because now every time I change the active cell in any Excel file on my computer, it loads a little bit, like if that macro was still running.
I deleted the macro, restarted the computer, but nothing.
If I manually turn off events (Application.EnableEvents = False), this problem goes away, but as soon as I open another Excel file (any file, not just the one I wrote the macro in), it turns back on.
What have I done and how do I turn it off?
The way you describe it, it seems that you have Saved the Worksheet_SelectionChange event to a personal.xlsb file.
Find it, delete the code and enjoy your Friday!
https://support.office.com/en-us/article/Copy-your-macros-to-a-Personal-Macro-Workbook-aa439b90-f836-4381-97f0-6e4c3f5ee566

Timeout in Excel VBA

I was wondering if it's possible to create a VBA macro in Excel, which will save the file each X minutes. I've already figured how to initialize the macro after excel startup and I found on the google something like this should pause the macro
Application.Wait(Now + TimeValue("0:10:00"))
but this will also block the user input and he cannot make any changes during that time.
This is not a anti-crash protection , the reason why I need this, is that some users are forgetting to save the document regularly...
Thank you
Francis
The 2 examples of Simoco will work great, but if you want to prevent having to deal with unnecessary saves (especially if you're working on network files, or large files), you can do a check everytime there is a change in the worksheet.
Just use the Worksheet_Change function to do that, here is a possible pattern:
Private Sub Worksheet_Change(ByVal Target As Range)
If (Now > TimeStamp)
ThisWorkbook.SaveAs BlaBlaBlaBlaBla
TimeStamp = Now + TimeValue("0:10:00")
End If
End Sub
TimeStamp needs to be a global variable defined in each workbook.
Btw, make sure that saving your file every X minutes doesn't screw with the undo / redo function of excel. I remember I had unwanted behaviors in the past when using an auto-save function.
Other thought: Google document won't require this type of macro as there is no need to save.

Ending undo transaction in macro in PowerPoint

As described in http://support.microsoft.com/kb/278591 PowerPoint will usually combine all changes that a macro or add-in makes to a single undo step. So if you put the following code into VBA and execute it twice by pressing F5 there
will only be one undo step.
Sub Move()
If ActiveWindow.Selection.Type = ppSelectionShapes Then
ActiveWindow.Selection.ShapeRange.IncrementLeft 10
End If
End Sub
I am looking for a way to change this behavior in a more complex scenario where the user can make multiple changes without directly accessing PowerPoint. Ideally, it should be possible to undo a set of modifications that corresponds to one change from the user's perspective.
What I found out is that ExecuteMso seems to break the undo transaction. So if you execute the following code twice it results in 4 undo steps (first increment, ExecuteMso, second increment, ExecuteMso).
Sub Move()
If ActiveWindow.Selection.Type = ppSelectionShapes Then
ActiveWindow.Selection.ShapeRange.IncrementLeft 10
Application.CommandBars.ExecuteMso "Bold"
End If
End Sub
Anyone knows a real solution for the problem or a better workaround? Although I didn't find it maybe there is a solution for Word or Excel that can be ported to PowerPoint?
If you replace the ExecuteMso line with this one, the entire procedure remains one Undo:
ActiveWindow.Selection.ShapeRange.TextEffect.FontBold = msoTrue
Update
If you are using PowerPoint 2010 or later put this line before each block of code that you want the user to be able to undo a step at a time:
Application.StartNewUndoEntry