I'm trying to solve a relatively simple problem but I can't realize it. My aim is to copy a range of cells in a worksheet of the main Excel Application to another range (of the same size) in a worksheet of a second newly created Excel Application. I create the second Application by using
Set secondExApp = CreateObject("Excel.Application")
I'm using this reference for further handling. Until now I've tried two different ways. Both don't work properly.
0.: Preparation / Introduction
Set srcWb = Application.ActiveWorkbook
Set srcSheet = srcWb.Worksheets("example")
Set dstApp = CreateObject("Excel.Application")
Set dstWb = dstApp.Workbooks(1)
Set dstSheet = dstWb.Worksheets(1)
1.: PasteSpecial - delivers an image(!) instead of just the range
srcSheet.Range("A1:B2").Copy
dstSheet.Range("A1:B2").PasteSpecial xlPasteAll
2.: Range.Copy [Destination] - does not work - Is it right that I can only use this method for sheets in the same application?
srcSheet.Range(srcSheet.Cells(..., ...), srcSheet.Cells(..., ...)).Copy _
dstSheet.Range(dstSheet.Cells(..., ...), dstSheet.Cells(..., ...))
Any help is appreciated.
Edit:
I've already played with the "record macro" functionality but I prefer coding it on my own without "selecting" or "activating" cells / sheets / etc.
Edit (solved):
Thank you both GSerg and iDevlop very much, you delivered a good further starting point for me. I did some research as far as the Excel constants as xlClipboardFormatDspText are concerned.
What really helped me was the fact that opening a new Excel instance changes the Paste(Special) menu.
So instead of creating a new instance I now simply add a workbook (which can be hidden) and use this object to add my content. Since it is held in the same instance (also have a look at the task manager) the Paste(Special) menu is completely the same.
Now it is possible to use Range.Copy [destination] even without select!
Result:
'Hides the new workbook
Application.ScreenUpdating = False
Set dstWb = Workbooks.Add
Set dstSheet = dstWb.Worksheets(1)
srcSheet.Range(srcSheet.Cells(..., ...), srcSheet.Cells(..., ...)).Copy
dstSheet.Paste dstSheet.Range(dstSheet.Cells(..., ...), dstSheet.Cells(..., ...))
'Avoids the often seen dashed border around the copied range
Application.CutCopyMode = False
'Setting the initial change back
Application.ScreenUpdating = True
Software: Excel 2007
As determined by poking Excel with a stick, you have to use Worksheet.Paste for inter-excel stuff:
srcSheet.Range("A1:B2").Copy
dstSheet.Paste dstSheet.Range("A1")
Poking Excel with a thicker stick revealed that formulas get preserved when pasting from Clipboard as xlClipboardFormatDspText:
srcSheet.Range("A1:B2").Copy
dstSheet.Range("A1").Select
dstSheet.PasteSpecial xlClipboardFormatDspText, False
However, this does require selecting a cell on dstSheet first, because Worksheet.PasteSpecial uses active cell.
Related
The code is:
Sub Copy_Filtered_Table()
Dim aSheet As Worksheet
Dim i As Long
i = ActiveSheet.Index
ActiveSheet.AutoFilter.Range.Copy
-> Set aSheet = ActiveWorkbook.Worksheets.Add(After:=i)
aSheet.Range("A1").PasteSpecial
End Sub
The workbook format is .xlsm Excel 2016
it has sheets after and before the active sheet
Also I Have tried doing it with out the aSheet variable like this
ActiveWorkbook.Worksheets.Add After:=i
It did not work too. both cases gives error 1004 Method 'Add' of object 'Sheet' faild.
If I ommited the After parameter it works but putting the result new sheet before the active sheet which exactly I am avoiding.
In order to insert relative to an existing sheet, you need to provide the actual sheet rather than its index, using one of:
Set aSheet = ActiveWorkbook.Sheets.Add(After:=ActiveWorkbook.Worksheets(i))
ActiveWorkbook.Sheets.Add After:=ActiveWorkbook.Worksheets(i)
Which one you choose depends on whether you immediately want to do something with the sheet without searching for it again but I suspect you already know that, given your question.
As an aside (though I haven't tried), I suspect getting the index of the current sheets then turning that back into the current sheet object is a bit unnecessary. You may want to just try:
Set aSheet = ActiveWorkbook.Sheets.Add(After:=ActiveWorkbook.ActiveSheet)
I have a macro in Excel 2019 which runs in less than one second directly through VBE (by pressing F5), or when I configure a button to the macro in the Ribbon (through options>customize ribbon).
When I create a button (FormControlButton) inside the sheet area, and associate the macro, it takes at least seven seconds.
The macro runs without any error message. Other macros are slower as well, but this one is the most noticeable.
My macro builds a jagged array with data (~4000 records) that is in another sheet, then sorts the array by bubble/quicksorting (tested both to check if the problem could be here, and it wasn't), then filters it and returns data in a new sheet.
The macros where designed in Excel 2010, and I noticed the problem right after our company updated Microsoft Office from 2010 to 2019. (Windows was updated the same day from 2007 to 10, but I think the problem is in Excel, as I tested it again in some PCs that still had Office 2010 and the macros worked as fast as if run through VBE). Creating and editing macros is not prohibited by administrators.
Adding more information as requested:
I didn't add code because it's not a problem of a specific macro, but I noticed the ones that slowed most are the ones that interact with arrays. Besides that, as it didn't happen when I used buttons inside a sheet in Office 2010, maybe it's a bug in Office 2019.
One thing in common in all my macros is that I follow Microsoft's recommendations to speed up macros, and I use this chunk of code:
Sub SubName()
Call DeactivateSystemFunctions
'Rest of the code
Call ReactivateSystemFunctions
End Sub
Where
Sub DeactivateSystemFunctions()
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.Calculation = xlCalculationManual
Application.ActiveSheet.DisplayPageBreaks = False
Application.EnableEvents = False
End Sub
Sub ReactivateSystemFunctions()
Application.ScreenUpdating = True
Application.DisplayStatusBar = True
Application.Calculation = xlCalculationAutomatic
Application.ActiveSheet.DisplayPageBreaks = True
Application.EnableEvents = True
End Sub
I don't use .activate or .select in any of my macros, and while formatting I always try to put the max inside a With/End With.
My macro was working fine through VBE but taking too many time when activated through a FormControlButton inside my sheet. As #RonRosenfeld suggested, I had to set a timer to each specific part of my code to find where the problem was. I put the timer at the beggining of my code and I had to move the command to stop the timer to each part of it until I found where it was getting slow.
My macro creates a jagged array and then sorts it through Quicksorting, and as the quicksort I made takes more than one criteria to sort, I thought the problem might be occurring there, as it is a recursive method.
But actually the problem was happening when I was printing the results of the sorted jagged array in another worksheet I create using the same macro.
I print data this way:
NewSheet.Cells(NewSheetRow, Column1) = SortedArray(RecordNumber)(DesiredInfo1 - 1)
NewSheet.Cells(NewSheetRow, Column2) = SortedArray(RecordNumber)(DesiredInfo7 - 1)
NewSheet.Cells(NewSheetRow, Column3) = SortedArray(RecordNumber)(DesiredInfo14 - 1)
'As my jagged array is built with data from a Source Worksheet:
'RecordNumber is the (Row - 1) in the source worksheet
'DesiredInfoX is the Column in the source worksheet
The problem happened only when printing specific Columns. The source sheet has different columns, each with a different data format. The only data format that slowed things down was strings.
I went to the source worksheet and noticed some problems:
As the file went from excel 2000 to 2010 to 2019 and data was not migrated but simply saved from .xls to .xlsm, when I went to the end of the source sheet, I noticed it had only 65536 rows (not 1048576 as expected), but had 16384 columns (last=XFD).
It was only happening with the source sheet, which is the one we have more data in. Other sheets in the same workbook had the expected 1048576 rows and 16384 columns.
After we started using excel 2019, some of the data that was supposed to be String(Text), was formatted as GENERAL/NUMBER. I can't affirm it was not human error, but our source sheet is filled by macro, not by human, and the macro forces formatting of each data.
What I did to solve the problem:
I migrated all data from all sheets, to a new workbook using VBA, not copy/paste. After passing the values to the new source sheet, I forced the formatting of each column. All macros had to be migrated as well.
After that, the FormControlButton inside the sheet is working as fast as activating the macro directly through VBE by pressing F5.
If anybody needs:
'###Timer code
'Got it from https://www.thespreadsheetguru.com/the-code-vault/2015/1/28/vba-calculate-macro-run-time
'Put this part in the beggining of your code
Dim StartTime As Double
Dim SecondsElapsed As Double
'Remember time when macro starts
StartTime = Timer
'Put this part where you want the timer to stop
'Determine how many seconds code took to run
SecondsElapsed = Round(Timer - StartTime, 2)
'Notify user in seconds
MsgBox "This code ran successfully in " & SecondsElapsed & " seconds", vbInformation
'###Migration macro:
Sub Migrate()
Call DeactivateSystemFunctions
'Source File
Dim XLApp As Object
Dim WbSource As Object
Dim WsSource As Object
Set XLApp = CreateObject("Excel.Application")
XLApp.Visible = False
Set WbSource = XLApp.Workbooks.Open("C:\FolderFoo\FolderBar\Desktop\SourceFileName.Extension")
Set WsSource = WbSource.Worksheets("SourceWorksheetName")
'Destination File. May be set as source file or if using this workbook by simply:
Dim WsDest As Worksheet
Set WsDest = ThisWorkbook.Worksheets("DestinationSheetName")
Dim BDR As Long
Dim BDC As Long
Dim UltR As Long
Dim UltC As Long
UltR = WsSource.Cells(Rows.Count, 1).End(xlUp).Row
UltC = WsSource.Cells(1, Columns.Count).End(xlToLeft).Column
For BDR = 1 To UltR
For BDC = 1 To UltC
If WsSource.Cells(BDR, BDC) <> vbEmpty Then
WsDest.Cells(BDR, BDC) = WsSource.Cells(BDR, BDC)
End If
Next BDC
Next BDR
'Format your columns as needed
With WsDest
.Columns(Column1Number).NumberFormat = "0"
.Columns(Column2Number).NumberFormat = "dd/mm/yyyy"
.Columns(Column3Number).NumberFormat = "#"
.Columns(Column4Number).NumberFormat = "#"
.Columns(Column5Number).NumberFormat = "0.000"
End With
WbSource.Close SaveChanges:=False
Call ReactivateSystemFunctions
End Sub
TLDR: I had the same problem and I think it's the fault of the mouse pointer.
My solution:
Dim Cursor As XlMousePointer
Cursor = Application.Cursor
Application.Cursor = xlWait
-- YOUR CODE HERE --
Application.Cursor = Cursor
Long Version:
I noticed a similar problem in one of my macros and it seems to occur when lots of cells are manipulated individually (i.e. looping through hundreds or thousands of cells and changing their values).
You might notice the mousepointer changing eratically when you start it from the FormButton or ActiveX Button, but when you start the macro from VBE, it does not do that.
In my case, the macro even started to run faster if I took the focus off the excel window - something as simple as moving the mouse over the windows start-menu button or over an application in the windows taskbar improved the performance of the macro.
I did not research this further, but I concluded that the erratic changes to the mousepointer were in fact the killer to the performance, so I did the following:
Before changing the cells, I set the mousepointer to something other than "Default". When you do that it stays there and does not change while you manipulate the cells. After that I set the mousepointer to whatever status it had before my macro.
I did not have the same problem since.
We have 2 different files with basic vlookup infos:
Source file is: JP2-CSV CRAWLER
Destination file is: JP2-CATEGORIES
We are trying to copy one full column from the Source file to the first column of the destination file automatically ( It should work when opening it or using it)
That's our code:
Sub Copysubcat()
Dim wbSource As Workbook
Dim wbDestination As Workbook
Set wbSource = Workbooks.Open( _
Filename:="C:\Users\user\Desktop\crawler file\JP2-CSV CRAWLER.xlsx")
Set wbDestination = Workbooks("C:\Users\user\Desktop\crawler file\JP2-CATEGORIES.xlsx")
wbSource.Sheets("CSV Crawler").Range("P2:P10000").Copy
wbDestination.Sheets("Cats & Subcats").Range("A2:A10000").PasteSpecial (xlPasteValues)
Application.CutCopyMode = False
ActiveWorkbook.Save
End Sub
We are having an error "Subscript out of range"
Anybody could help on that?
Workbooks isn't a method, so it cannot take any parameters. This is class, which represents workbooks, which contains methods to handle them. One of them is Open which you will need in this case. So you'll need to switch this:
Set wbDestination = Workbooks("C:\Users\user\Desktop\crawler file\JP2-CATEGORIES.xlsx")
to this:
Set wbDestination = Workbooks.Open("C:\Users\user\Desktop\crawler file\JP2-CATEGORIES.xlsx")
Also, there is an event such as opening the workbook, you can place your corrected code there, so the macro would run every time this event is raised (i.e. on every opening). If you are using standard VBA development environment in Excel, click on This_workbook on right side (in tree view), you will have to dropdown lists at the top, in the one on the left choose Workbook, in the other one select Open (this is list of events, that is raised by Workbook). Then, inside generated method place youre code :)
Is there a way to calculate entire workbook but not all open workbooks in VBA?
I know about
worksheet.Calculate
but it will only do 1 sheet. I also know about
Application.CalculateFull
But I think this recalculate all open workbooks at the same time.
Is it possible to do only 1 workbook?
Edit:
I get the approach:
For Each wks In ActiveWorkbook.Worksheets
wks.Calculate
Next
but this is very dangerous if sheets have links between them, then the order of calculation is important. I know I could find the exact sequence and apply it, problem is that on very large workbooks this can get somewhat tricky
After some investigation and testing, selecting all sheets first and calculating afterward works :
Application.Calculation = xlManual
ThisWorkbook.Sheets.Select
ActiveSheet.Calculate
this calculate all selected sheet at the same time creating the right order of calculation
The answer by Steven G doesn't actually work, it just looks like it does.(I can't reply to that post as I don't have 50+ rep, which is annoying).
It still calculates the sheets in 1 directional order. I tested this by creating 5 sheets and having them all + 1 to another cell, on each sheet in different directions. Using the select sheets then calculate method will give the wrong result.
As it requires that the sheets are visible, elow is function I built to replace the calculate function using that method. (Requires dictionaries reference library). Do not use though.
EXAMPLE, METHOD DOES NOT WORK
Function Calculate_Workbook(Optional Wb As Workbook)
'Application.calculate will calculate all open books, which can be very slow.
'Looping through all sheets in workbook to calculate can be risky due to formula referencing a cell thats not yet calculated, calculation order may be important.
Dim DicShtVisible As New Dictionary
DicShtVisible.CompareMode = TextCompare
Dim Asht As Worksheet, AWkbk As Workbook 'Previously selected books
Dim Sht As Worksheet
Set Asht = ActiveSheet
Set AWkbk = ActiveWorkbook
If Wb Is Nothing Then Set Wb = ThisWorkbook
Wb.Activate
'Unhide all sheets as can't select very hidden stuff
For Each Sht In Wb.Sheets
DicShtVisible.Add Sht.Name, Sht.Visible
Sht.Visible = xlSheetVisible
Next Sht
'Select all sheets and calculate the lot
Wb.Sheets.Select
ActiveSheet.Calculate
'Reapply visibility settings
Dim Key As Variant
For Each Key In DicShtVisible.Keys
Wb.Sheets(Key).Visible = DicShtVisible.Item(Key)
Next Key
'Reset selections
AWkbk.Activate
Asht.Select
End Function
As far as I can see there is no solution, the only answer is to either know the calculation order of your workbook and manually calculate sheets in that order, or accept using Calculate and that it will calculate all open workbooks.
I would love to be proven wrong however. It's such an annoying and stupid issue.
None that I know about, Steven.
You actually lose time doing Application.Worksheets(1).Calculate
I ran a timing test: 3 workbooks open in the same instance of Excel.
One with 8200 volatile functions, 8000 in one worksheet on 4 non-contiguous ranges, 200 in other worksheets.
Two other workbooks with a combined total of 100 functions. The results:
Application.Calculate - 4.41 ms
ThisWorkbook.Worksheets(1).Calculate - 52.05 ms for each worksheet
ThisWorkbook.Worksheets(1).Range("R1").Calculate (repeated 4 times) - 84.64 ms
It's counter-intuitive, but true.
You also lose time declaring Dim wks as Worksheet. Unless you do For ... Each referring to it as ThisWorkbook.Worksheets(1) - is faster
Also, be careful referring to ActiveWorkbook - when you code runs your primary workbook might not be active one. ThisWorkbook (the one with code) is safer.
ThisWorkbook will use only the workbook that the code resides in
For Each wks In ThisWorkbook.Worksheets
wks.Calculate
Next wks
Another option is to create a new instance of Excel, open the workbook is that instance and then calculate that. This will leave any workbooks in the original instance uncalculated, potentially saving you time if another workbook is very slow to calculated (100k+ formulas for instance).
Dim xlApp As Excel.Application
Dim wbTest As Workbook
Set xlApp = New Excel.Application
Set wbTest = xlApp.Workbooks.Open("c:\temp\test.xlsx")
' do a bunch of stuff in wbTest
xlApp.Calculate
Whether this is better depends on your circumstances. Opening a second instance of Excel involves a little extra time and probably more memory (although I haven't tested this and it may not be a significant amount).
You may have to change more code to take account of the fact this workbook is in a different instance and there may be issues with some functions (for example if you check if a workbook is already open by spooling through all the workbooks in the original instance of Excel).
Just use Calculate
eg:
Sub Cal_()
Calculate
End Sub
We can use
Application.CalculateBeforeSave = True
and then save the workbook? May be more finessed to trap the current value, set if not true, save, reset if was not true.
Try this:
Sub CalcBook()
Dim wks As Worksheet
Application.Calculation = xlManual
For Each wks In ActiveWorkbook.Worksheets
wks.Calculate
Next
Set wks = Nothing
End Sub
I am using this function to copy a sheet from a Workbook to another Workbook.
I works, but it seems that if I disable the macros from Workbook,
it can not copy the sheet anymore.
If I chage this line : m_objExcel.AutomationSecurity = msoAutomationSecurityForceDisable
to this m_objExcel.AutomationSecurity = msoAutomationSecurityByUI
it works but gets a warning message.
Also if I comment the line it works perfectly but with macros On.
Private Sub CopyFunction()
Set m_objExcel = New Excel.Application 'creare obiect
m_objExcel.DisplayAlerts = False
g_secAutomation = m_objExcel.AutomationSecurity
m_objExcel.AutomationSecurity = msoAutomationSecurityForceDisable
m_objExcel.Visible = True
Dim CopyFrom As Workbook
Dim CopyTo As Workbook
Dim CopyThis As Object
Set CopyFrom = m_objExcel.Workbooks.Open("D:\FromFile.xls")
Set CopyTo = m_objExcel.Workbooks.Open("D:\ToFile.xls")
Set CopyThis = CopyFrom.Sheets(1) ''Sheet number 1
CopyThis.Copy After:=CopyTo.Sheets(1)
m_objExcel.Workbooks(CopyTo.FullName).Save
CopyFrom.Close
CopyTo.Close
m_objExcel.Workbooks.Close
MsgBox "ok"
End Sub
I am using it from Access and work with a Excel file.
What is the problem?
Is there a way to still be able to copy a sheet from a workbook to another, and have the macros disabled?
That's what the disable macro is typically for: Security.
It is meant for your own protection; in case you receive a workbook from an unknown author, this person may want to send you harmful software.
Imagine for example that code starts deleting files as soon as you open the workbook.
Disabling macro's prevents this and allows you to look into code before you decide to execute it.
As a solution, the user can trust you as author while you sign your project as developer.
I suggest that you check out this link:
http://msdn.microsoft.com/en-us/library/aa190098(v=office.10)
(You can decide to turn off the security entirely (Enable macro's), but this is generally not recommended.)
I found that the problem is here:
m_objExcel.Workbooks(CopyTo.Name).Save