So, I'm back with a more specific answer/problem.
Also, I'm sorry, I picked up excel (hadn't actually used the program) a few weeks back only and minimal usage. So if the code is bad/explanations unclear, comment and i'll do my best to get this together.
I am currently in a situation with two separate (albeit linked) excel files.
Src (Book1)
Dest (Book2)
They each have one worksheet.
The idea is that Src will be modified (including deletion and insertion of rows) and i need these changes to be reflected onto Dest.
I have the insertion so far, scrounged up a little bit of code using Before_DoubleClick.
However i am having a few issues on deletion.
What i have this far is :
Private Sub Worksheet_Change(ByVal Target As Range)
Const destBook As String = "Book2"
Const destSheet As String = "Sheet2"
Dim wb1 As Workbook
Set wb1 = Workbooks(destBook)
' Here is the main problem i have. I need a better way to detect
' deletion of one or more rows. Otherwise the Application.Undo
' re-enters the if and it just loops.
If Target.Address = Target.EntireRow.Address Then
' Here i Undo the deletion to be able to copy the values.
Application.Undo
Target.Copy
' I also haven't found a way to delete again but google can help me on that
' Pasting line
'wb1.Sheets(destSheet).Cells(Target.Row, 1).PasteSpecial Paste:=xlPastValues
End If
End Sub
Ideally, what should happen is, upon deletion of one or more rows, i would like to undo the last action, copy the data, delete the row and finally past the data in another worksheet. However, as of now i am blocked at undoing the last action. It calls my VBA macro again and just starts looping.
Im not sure what you want but you either have to replace formulas with values in the new cells, or if those cells are supposed to have a formula you probably have to do
application.Calculation=xlCalculationManual
make the deletion or change then
application.Calculation=xlCalculationAutomatic
to turn autocalculation back on
Manual is tricky because if code crashed you would want to make sure you didn't leave the sheet in a state that didn't calculate
Related
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.
I've searched online and couldn't find a code to do what I'm looking to do and I hit a tumbling block..
We're currently looking to implement a CRM-like reporting system for our reps in excel. Our reps would fill out customer data in a row and just update the relevant cells when the account status changes one way or another.
The report is all built and works but we're lacking the account history part so what we're looking to do, is to copy the whole row in a new row to a different sheet or workbook any time the data in column I is updated for a row so there's a history of all relevant changes kept automatically on a separate sheet or worksheet.
I've searched and looked online at different codes and the tracking changes option but could not find a code that would automatically copy only the whole relevant row on update and we really need the whole row to be copied for the data to remain meaningful to us so track changes doesn't work for our needs. We'd also require for these steps to happen without action from our users.
Any help on how this could be accomplished would be greatly appreciated.
Use the WorksheetChangeEvent to detect when something has been changed:
Private Sub Worksheet_Change(ByVal Target As Range)
' do stuff
End Sub
Put the above code into the worksheet object (double click on the worksheet in the code editor).
Use Application.Intersect to narrow down what has been changed. For instance if you're only interested in cells I1 to I10000:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Application.Intersect(Target, Range("I1:I1000")) Is Nothing Then
' do stuff
End If
End Sub
Then copy the required row:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Application.Intersect(Target, Range("I1:I10000")) Is Nothing Then
Rows(Target.Row).copy
' paste it where you want it
End If
End Sub
Target is the range for the affected cell. Of course the user can affect more than one cell at once e.g. by autofilling, so you may need to put some additional logic e.g if Target.Rows.Count > 1, but you get the idea.
What I want to do
I want a code in my workbook (wbDestination) that opens another workbook (wbBosOriginal) and copies an entire sheet as values (wbBosOriginal has a lot of code in it, in modules and in the worksheet in question, and I do not want this code because it references stuff in wbB that doesn't exist in wbDestination). I have had great problems pasting as values, because it will not paste columns and rows that are currently hidden. So this is why I decided to import the whole sheet instead.
What I tried and what's wrong with it
Here is a block of code I used to copy the worksheet in the destination workbook, in the last index position. The problem with it is that some links still exist to the old workbook (Formulas, validation lists, conditionnal formatting). I have deleted all these links but STILL when I paste the sheet successfully, save and reopen, I have an error saying some content is unreadable. I believe there are still some elements linked to the old workbook.
Set wbBosOriginal = Workbooks.Open(strChosenPath, ReadOnly:=True)
With wbBosOriginal.Sheets("BOS")
.Visible = True
'Pastes the ws in last position in wbDestination
.Copy after:=wbDestination.Sheets(wbDestination.Worksheets.Count)
Set wsNewBos = Worksheets(Worksheets.Count)
'Deletes VBA code in the copied sheet
ThisWorkbook.VBProject.VBComponents.Item(wsNewBos.CodeName).CodeModule.DeleteLines 1, _
ThisWorkbook.VBProject.VBComponents.Item(wsNewBos.CodeName).CodeModule.CountOfLines
End With
The worksheet is successfully pasted with no code in it, with everything else it had previously. I then remove all formulas, conditionnal formatting, and validation lists. Even after removing those as well, I still get an error when opening the workbook.
My question
Apart from conditional formatting, validation lists, VBA code, and formulas linking a worksheet that was pasted to a new workbook, what other elements could cause the workbook from opening in repair mode every time due to existing links to the old workbook?
If my question is not clear, comment and I will clarify.
Dealing directly with VBE seems a bit heavy-handed to me. If your code is manipulating several workbooks, I would put the code in an add-in and not have it in any workbook. (Technically *.xlam addins are workbooks, but when I say "workbook" I mean normal *.xls, *.xlsx, *.xlsm, etc.)
That said, if you're just copying cell values (which may be formulas) between different workbooks, you shouldn't have any dependencies other than cell references, named ranges, and user-defined functions in the original workbook. I would make sure there are none of those. Please also share how you are ensuring your formulas do not have broken references.
If the issue you are having is caused by trying to avoid hidden columns and rows not allowing pastevalues, why not unhide the rows and columns and then copy only the values to the new book?
Just cycle through each of the sheets in the original book and use the method .UsedRange.Hidden = False. As far as I am aware, this should unhide every cell on the sheet and allow you to do the original pastevalues calls
This works fast and smooth (it's harder to delete ALL the data Imo):
Sub tests()
Dim AllRange As Range: Set AllRange = ActiveSheet.UsedRange
Dim ItemRange As Range
Dim myWbDestination As Workbook: Set myWbDestination = ThisWorkbook
Dim SheetDestination As String: SheetDestination = ("Sheet2")
For Each ItemRange In AllRange
With myWbDestination.Sheets(SheetDestination)
.Range(ItemRange.Address) = ItemRange.Value
End With
Next ItemRange
End Sub
Repair mode can be triggered by many factors, you would need to post the code you are getting to look for an explanation, it would be like asking why vba may broke
So here's what I'm trying to do:
Open file: Pc_Profile
Create new sheet: Sheet1
Copy desired cells from Pc_Profile to Sheet1 (see script below)
Copy entire Sheet1 to new excel file: db.xls
Rename sheet to content of cell A5
Create new sheet for next script run
Basically I'm trying to automate an extraction of a TON of excel files into a single organized file. Each script call should extract to its own sheet so there's no overwritten information.
Here is what I have working so far. It just copies the desired cells to a new sheet within the same file.
' Create Excel object
Set objExcel = CreateObject("Excel.Application")
' Open the workbook
Set objWorkbook = objExcel.Workbooks.Open _
("\\[directory]\Pc_Profile.xls")
' Set to True or False, whatever you like
objExcel.Visible = True
objWorkbook.Worksheets("Pc_Profile").Range("A5:D5").Copy
objWorkbook.Worksheets("Sheet1").Range("A1").PasteSpecial
objWorkbook.Worksheets("Pc_Profile").Range("A8:B8").Copy
objWorkbook.Worksheets("Sheet1").Range("A2").PasteSpecial
objWorkbook.Worksheets("Pc_Profile").Range("A13:B13").Copy
objWorkbook.Worksheets("Sheet1").Range("A3").PasteSpecial
objWorkbook.Worksheets("Pc_Profile").Range("A15:D17").Copy
objWorkbook.Worksheets("Sheet1").Range("A4").PasteSpecial
objWorkbook.Worksheets("Pc_Profile").Range("A24:E26").Copy
objWorkbook.Worksheets("Sheet1").Range("A7").PasteSpecial
objWorkbook.Worksheets("Pc_Profile").Range("A28:B30").Copy
objWorkbook.Worksheets("Sheet1").Range("A10").PasteSpecial
objWorkbook.Worksheets("Pc_Profile").Range("A43:B43").Copy
objWorkbook.Worksheets("Sheet1").Range("A13").PasteSpecial
objWorkbook.Worksheets("Pc_Profile").Range("A45:B45").Copy
objWorkbook.Worksheets("Sheet1").Range("A14").PasteSpecial
' Activate Sheet2 so you can see it actually pasted the data
objWorkbook.Worksheets("Sheet2").Activate
I would really appreciate the extra push. I'm automating this for a work project and have no experience with VB - I just learned that on the go.
A couple things that are good practice to get into before I get to your actual question:
1) Any macro that you expect to run a long time should have Application.ScreenUpdating = False before any actual work is done in the code, this tells Excel not to bother with changing what's displayed on the screen (big performance booster). Be sure to include an Application.ScreenUpdating = True near the end of your code
2) Similar to #1, you generally want to include Application.Calculation = xlManual to boost performance. If you have large ranges of cells that your macro needs accurate up-to-date values from, it may be easier to leave the calculation automatic, but that doesn't appear to be the case in this instance.
3) You don't need to create a new Excel instance (which is what your first line of code does). You're already in a perfectly good instance of Excel. This also saves you having to close the other instance at the end of the macro (or worse from forgetting to do so and having memory get hogged by Excel processes that aren't really in use)
As to your specific problem, it sounds like you have more workbooks that Pc_profile to copy from, and that you're wanting to create a new "db.xls" with each run of the macro. Based on those assumptions all you need to do is nest your code starting with 'Open the workbook and objWorkbook.Worksheets("Sheet1").Range("A14").PasteSpecial inside a Do While loop. The thing I'm not sure about is how to control the loop. If the list of files is always the same, you should just include a list on a sheet in the workbook that holds the macro and just iterate through that.
The other thing you should do for ease of coding, and to make the loop more effective is declare and use a Worksheet variable and set if for each workbook to the appropriate sheet to pull data from. Ex.
Dim ws as Worksheet
'The Dim is outside your loop, but this would be inside it
Set ws = objWorkbook.Worksheets("whatever_the_sheet's_name_is")
This way you can replace each occurrence of objWorkbook.Worksheets("Pc_Profile"). with ws., easier to type, easier to read, easier to update, and less error prone.
Next, you don't actually have code for moving Sheet1 to a new workbook, or renaming it. To move it (as well as the other Sheet1's yet to be created), you should, before getting to the Do While loop, have the following
Dim target as Workbook
Set target = Application.Workbooks.Add
Then at almost the end of the loop, you need objWorkbook.Worksheets("Sheet1").Move Before:=Target.Sheets(1)
Last, you need to include objWorkbook.Close SaveChanges:=False after you've moved Sheet1 out of the Pc_Profile and renamed it.
Can Anyone tell me how do I undo all my changes to my workbook?
I have file excel1.xlsx and I have did sorting and many operations on the excel.xlsx using vba. But at the end I want the excel1.xlsx to be the same which was at the start. How do i Undo all my changes using vba?
activeworkbook.saved = True
I have found that it retains back all the contents as at the begginning but its not working.So is there any command where i can get back my original file after performing operations over it. Well yes
wb1.Sheets(1).Activate
ActiveWorkbook.Close savechanges:=False
It works but I dont want my workbooks to be closed it should be still opened. How do I make it? Thanks in advance.
In order to undo a sub routine, you can either choose not to save the file and just close it, or you have to write a special sub routine to save the state of the file, then restore the state (custom undo). This is one of the pains with sub routines is that they cannot be undone through normal undo. Most people, me including, will reccomend you work off a backup.
When making your custome undo routine, the big question is what do you need to save the state for? Saving all information about the file would be unnessesarily heavy, so it's good to know what you want to save.
Update:
This is a dirty way to backup the sheet if you only have 1 sheet of data. This is more of a proof of concept of one way to create a backup and not finalized perfect code. It just creates a backup copy of the currentsheet and when you'restore' you are simply deleting the original sheet and renaming the backup to what it used to be called. :p
How to test:
Put some data and value in your original sheet then run the Test() sub-routine!
Public backupSheet As Worksheet
Public originalSheet As Worksheet
Public originalSheetName As String
Sub CreateBackup()
Set originalSheet = Application.ActiveSheet
originalSheetName = originalSheet.Name
originalSheet.Copy After:=Sheets(Application.Worksheets.Count)
Set backupSheet = Application.ActiveSheet
backupSheet.Name = "backup"
originalSheet.Activate
End Sub
Sub RestoreBackup()
Application.DisplayAlerts = False
originalSheet.Delete
backupSheet.Name = originalSheetName
Application.DisplayAlerts = True
End Sub
Sub ZerosFromHell()
Range("A1:Z100").Select
Cells.Value = 0
End Sub
Sub Test()
Call CreateBackup
Call ZerosFromHell
MsgBox "look at all those darn 0s!"
Call RestoreBackup
End Sub
Short answer: you can't. Sorry.
Changes you make to your sheet using VBA cannot be undone at the click of a button or with a single, standard VBA statement.
The right thing to do would seem to be: do your VBA-driven work on a copy of the sheet, and delete/don't save this copy if you don't want to keep the changes (and reopen the original if you need to do so). But from your question, it sounds like you don't want to do that.
Your only alternative is then to write your own VBA procedure that backtracks all the changes you've done. Depending on what operations you performed, reversing them could be a ridiculously complicated thing to do, compared to just closing without saving and re-opening. But if you insist, by all means, knock yourself out!
Save a copy of the original workbook prior to running your macro. using the .SaveAs method at the beggining of the sub routine.
Run the VBA macro routine in the original workbook.
Now have a second macro "Undo VBA changes" that opens the workbook copy from step (1) , closes the workbook that ran the macro in Step (2) and calls the .SaveAs method again overwriting the existing workbook from step (2).
Note:
In order to get this UndoMacro to work you will need to put it in an Addin or a seperate workbook (an addin is cleaner). This will allow you to run the .SaveAs method and overwrite teh original workbook from Step (2) which will at this point have been closed to prevent an VBA runtime error message occuring.
If all of your data is cleanly organized, this works pretty well. Much like the OP, I needed to go back to the original state of an Excel file, and didn't want to have to re-load the original (it takes about 25 seconds to load, due to aged infrastructure, and slow PCs).
What I did was to copy all of the data into a variant array, do all of my processing to the workbook, then write back the variant array to the Excel file (data is on a worksheet called "Data", and starts at Row 2, and uses columns A through Z). While this uses a bit of RAM, the reading/writing is nearly instantaneous. Relevant code:
Dim varDataArray As Variant, wb As Workbook, ws As Worksheet, lngEndRow as Long
Set wb = ThisWorkbook
Set ws = ThisWorkbook.Worksheets("Data")
With ws
' This simply finds the last row with data
lngEndRow = .Cells.Find("*", [A1], , , xlByRows, xlPrevious).Row
' This reads all cell data from A2 to Z###, where ### is the last row
varDataArray = .Range("A2:Z" & lngNumberOfRows).Value
... do things ...
' This writes all the data back to Excel
' 26 is the numeric column equivalent to "Z"
.Range("A2").Resize(lngEndRow, 26) = varDataArray
End With