I'm trying to programmatically copy a large range of cells from one workbook to another in VBA. I want to copy the formatting (including the entire source theme) and values, but NOT formulas. The following is my VBA code:
fromCells.Copy
toCells.PasteSpecial Paste:=xlPasteFormats
toCells.PasteSpecial Paste:=xlPasteColumnWidths
toCells.PasteSpecial Paste:=xlPasteValuesAndNumberFormats
Application.CutCopyMode = False
Unfortunately, there are occasions when the above code doesn't work. This is usually with Font face and size. I noticed that whenever this happens, the only way to copy the font formatting across is to use xlPasteAllUsingSourceTheme, so it seems the font formatting is somehow registered to a 'source theme'. Unfortunately, xlPasteAllUsingSourceTheme doesn't work for me because it's copying formulas as well.
So is there a way to copy the source theme (formatting only) across? Or maybe a way to force copy all the font formatting across?
Note: Copying using xlPasteAllUsingSourceTheme and then overwriting it with xlPasteValues won't work for me because when the formulas is copied it keeps popping up message boxes telling me about issues with the formulas (such as conflicting named ranges used in the formulas, etc.).
I'm using Excel 2013. I noticed this problem doesn't seem to arise in Excel 2007 or earlier. Any help is appreciated.
Edit: I've also tried the following code (added to the beginning of the above code), it still doesn't work...
Dim themeTempFilePath As String
themeTempFilePath = Environ("temp") & "\" & fromWorkbook.Name & "Theme.xml"
fromWorkbook.Theme.ThemeFontScheme.Save themeTempFilePath
toWorkbook.Theme.ThemeFontScheme.Load themeTempFilePath
fromWorkbook.Theme.ThemeColorScheme.Save themeTempFilePath
toWorkbook.Theme.ThemeColorScheme.Load themeTempFilePath
Update: It seems the above code for saving and loading themes does work. The problematic text that I was looking at came from a different place - a form control. It was copied as a picture (using Shape.CopyPicture) but somehow the font gets changed in the process. However, I'll post this issue as another question.
For this question, I'll put up the theme saving and loading mechanism as an answer.
Try 1 or 2
Option Explicit
Public Sub copyWithoutFormulas_1()
xlEnabled False
With Sheet2
.EnableCalculation = False
.EnableFormatConditionsCalculation = False
.UsedRange.EntireColumn.Delete
Sheet1.UsedRange.Copy .Cells(1, 1)
.UsedRange.Value2 = .UsedRange.Value2
.EnableCalculation = True
.EnableFormatConditionsCalculation = True
End With
Application.CutCopyMode = False
xlEnabled True
End Sub
Public Sub copyWithoutFormulas_2()
xlEnabled False
Sheet1.Copy After:=Worksheets(Worksheets.Count)
With Worksheets(Worksheets.Count).UsedRange
.Value2 = .Value2
End With
xlEnabled True
End Sub
Private Sub xlEnabled(ByVal opt As Boolean)
With Application
.EnableEvents = opt
.DisplayAlerts = opt
.ScreenUpdating = opt
.Calculation = IIf(opt, xlCalculationAutomatic, xlCalculationManual)
End With
End Sub
To force copy the source theme to the destination cells, one can do the following. Unfortunately, this method will apply the source theme to the entire destination workbook, which is OK in my situation. Not sure if it's useful for anyone else.
Sub CopyText(fromCells As Range, toCells As Range, Optional copyTheme As Boolean = False)
If copyTheme Then
Dim fromWorkbook As Workbook
Dim toWorkbook As Workbook
Dim themeTempFilePath As String
Set fromWorkbook = fromCells.Worksheet.Parent
Set toWorkbook = toCells.Worksheet.Parent
themeTempFilePath = Environ("temp") & "\" & fromWorkbook.Name & "Theme.xml"
fromWorkbook.Theme.ThemeFontScheme.Save themeTempFilePath
toWorkbook.Theme.ThemeFontScheme.Load themeTempFilePath
fromWorkbook.Theme.ThemeColorScheme.Save themeTempFilePath
toWorkbook.Theme.ThemeColorScheme.Load themeTempFilePath
End If
Set toCells = toCells.Cells(1, 1).Resize(fromCells.Rows.Count, fromCells.Columns.Count)
fromCells.Copy
toCells.PasteSpecial Paste:=xlPasteFormats
toCells.PasteSpecial Paste:=xlPasteColumnWidths
toCells.PasteSpecial Paste:=xlPasteValuesAndNumberFormats
Application.CutCopyMode = False
End Sub
Related
I have spent a lot of time here and elsewhere searching, and I haven't uncovered an answer. I have a spreadsheet created and used in Excel O365 I use to manage a backlog of testing to be done, and it also includes a tab to handle forecasting.
In two separate macros, the process of selecting a worksheet causes Excel to crash.
It behaves exactly as though the worksheets had been renamed in the file but not updated in the vba (an error I have previously made and learned the hard way) but nothing has been renamed, and as far as I can tell, nothing has changed at all for months. Yet, this started misbehaving about a week ago and I can't figure it out.
Example code that is failing:
Sub Send_Range_Or_Whole_Worksheet_with_MailEnvelope()
Dim AWorksheet As Worksheet
Dim Sendrng As Range
Dim rng As Range
'On Error GoTo StopMacro
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
'Fill in the Worksheet/range you want to mail
'Note: if you use one cell it will send the whole worksheet
Set Sendrng = ThisWorkbook.Sheets("Weekly Forecast").Range("A10:A78")
'Remember the activesheet
Set AWorksheet = ActiveSheet
With Sendrng
' Select the worksheet with the range you want to send
.Parent.Select
'Remember the ActiveCell on that worksheet
Set rng = ActiveCell
'Select the range you want to mail
.Select
' Create the mail and send it
ActiveWorkbook.EnvelopeVisible = True
With .Parent.MailEnvelope
' Set the optional introduction field thats adds
' some header text to the email body.
.Introduction = "See below for the weekly forecast. Thank you!"
With .Item
.To = "(redacted email address)"
.Subject = "Weekly Forecast"
.Send
End With
End With
'select the original ActiveCell
rng.Select
End With
'Activate the sheet that was active before you run the macro
AWorksheet.Select
StopMacro:
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
ActiveWorkbook.EnvelopeVisible = False
End Sub
The "Set Sendrng = " statement is where Excel crashed when stepping through this macro.
Example 2 is a poor man's log of some data where it is simply copied from one worksheet and pasted into another for purpose of a 'snapshot':
Sub RefreshAllData()
' Updated 28Nov2017: Added "add to backlog log" ability
' RefreshAllData Macro
' Turn off screen updating
Application.ScreenUpdating = False
' Refresh all data / queries
ActiveWorkbook.RefreshAll
'Calculate should update all pivot tables
Calculate
' Append latest backlog to the backlog log
Sheets("2.7").Select
Range("A60:D73").Select
Selection.Copy
Sheets("Backlog Log").Select
Range("A" & Rows.Count).End(xlUp).Offset(1).Select
Selection.PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:= _
xlNone, SkipBlanks:=False, Transpose:=False
Sheets("2.7").Select
Range("A1").Select
' Resume screen updating
Application.ScreenUpdating = True
End Sub
The Sheets("2.7").Select statement is where Excel crashes.
Things I have tried:
You can see I've got one instance where the sheet select is direct, and one where it is a named range, and both fail.
I have attempted renaming of sheets and updating the code to reflect the new names, but those also fail.
I have reset macro security to force it to re-ask / re-enable macros, and this did not have an affect.
I'm at wit's end on this seemingly trivial issue, but these save enough manual time that I would really like to figure them out. Any help or pointers would be greatly appreciated.
You can try this for your first sub. Modify the MailMe range to your Forecast range.
Option Explicit
Sub ForeCast()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Weekly Forecast")
'Modify the below range to send
Dim MailMe As Range: Set MailMe = ws.Range("A10:A78")
Application.ScreenUpdating = False
With MailMe
ThisWorkbook.EnvelopeVisible = True
With MailEnvelope
.Introduction = "See below for the weekly forecast. Thank you!"
With .Item
.To = "(redacted email address)"
.Subject = "Weekly Forecast"
.Send
End With
End With
End With
ThisWorkbook.EnvelopeVisible = False
Application.ScreenUpdating = True
End Sub
If the code is housed in the book in that holds your sheets, the below should work. Notice that you never need .Select or .Selection to move/add/change/delete a cell/range/sheet/book.
Sub RefreshAllData()
Dim ws1 As Worksheet: Set ws1 = ThisWorkbook.Sheets("2.7")
Dim ws2 As Worksheet: Set ws2 = ThisWorkbook.Sheets("Backlog Log")
Application.ScreenUpdating = False
ThisWorkbook.RefreshAll
ws1.Range("A60:D73").Copy
ws2.Range("A" & ws2.Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValuesAndNumberFormats
Application.ScreenUpdating = True
End Sub
I'm copying values as part of one sub process and pasting value through an update button on userform.
To copy values:
Private Sub Month1_Click()
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.AskToUpdateLinks = False
Dim wkb As Workbook
Dim wks As Worksheet
Set wkb = Workbooks.Open("Place on drive")
Set wks = wkb.Sheets("Training1")
wks.Range("Start:Finish").Copy
wkb.Close
Application.DisplayAlerts = True
Application.AskToUpdateLinks = True
Application.ScreenUpdating = True
End Sub
To paste values in current sheet:
Private Sub UpdateActuals_Click()
For i = 1 To 12
If Me.Controls("Month" & i).Value = True Then
ThisWorkbook.Sheets("2017 Actuals").Range(i+1, 5).PasteSpecial xlPasteValues
End If
Next i
End Sub
If I replace "i+1, 5" with "B5", it errors with
"PasteSpecial method of Range class failed".
I feel as if values copied in one sub process are not brought to second one, would that be correct?
Also, how do I reduce processing time given that I have 12 months (12 files) in various places that I can't change the location for...
Range usually likes a starting cell and an ending cell. I suggest since you are looking at just one cell that you change .Range to .Cells. If you really want to use a range with RC format, .Range(Cells(row1, col1), Cells(row2, col2)), if you want just one cell then you can make the two parts the same. I have run into problems before using Range and only one cell definition before, either make it .Cells for your target or fill out Range the way I have explained.. Cheers.
Dim 2017actWS AS Worksheet
Set 2017actWS = ThisWorkbook.Worksheets("2017 Actuals")
1)
2017actWS.Cells(i+1, 5).PasteSpecial xlPasteValues
-or-
2)
2017actWS.Range(2017actWS.Cells(i+1, 5), 2017actWS.Cells(i+1,5)).PasteSpecial xlPasteValues
When using Ranges excel will often throw errors if they are not the same size in a copy and paste, you can eliminate that by using a single cell as the starting target of your paste with .Cells
Also I don't see you call your function. You will want your paste close to your copy or you might find things get strange (suggestion: just after your copy).
Edited to be sure there is not worksheeet ambiguity. Thank you Scott C.
Cheers, WWC
I want to open a workbook and then copy a column to another workbook side by side (column from each file adjacent to each other).
However I do not want to change the cell reference (i.e. if refrence in range being copied is C15:C17 i do not want it to move). How could I approach this?
Currently I am using the following code:
Sub dane_wolne()
'Application.DisplayAlerts = False
'Application.ScreenUpdating = False
Application.Calculation = xlManual
Wiersz2 = 20
For i = 1 To Wiersz2
Workbooks.Open FileName:=Katalog & "U" & i & ".xlsx", ReadOnly:=True
Range("D11:D210").Copy
ThisWorkbook.Worksheets("Obliczenia").Range("E11:E210").Offset(0, i).PasteSpecial xlPasteFormulas
Workbooks("U" & i & ".xlsx").Close
Next i
'Application.DisplayAlerts = True
'Application.ScreenUpdating = True
Application.Calculation = xlAutomatic
End Sub
Range.Formula property
You can use:
ThisWorkbook.Worksheets("Obliczenia").Range("E11:E210").Offset(0, i).Formula = Range("D11:D210").Formula
Copy formats in separate step.
Off topic tips
Make the ThisWorkbook.Worksheets("Obliczenia").Range("E11:E210") a range variable once, then apply just .Offset to make your code a bit cleaner and faster (nanoseconds :))
Aslo, it is a good practice to set the new opened workbook into a variable, to make it more explicit and reliable. E.g.: Set SourceWB = Workbooks.Open FileName:=Katalog & "U" & i & ".xlsx", ReadOnly:=True (Not always necessary.)
Then use the SourceWB.Range("D11:D210")
i've searched multiple forums to see if i can discover why a peice of code isnt working but havent found an answer yet.
My VBA isnt great and i inherited this section of code from a predecessor.
This part of the code saves each indivisual worksheet as a new workbook by using the worksheet names.
Dim NewBook As Workbook, OldBook As Workbook, sh As Worksheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False
Set OldBook = ThisWorkbook
For Each sh In OldBook.Worksheets
If sh.Visible = True Then
sh.Copy
ActiveWorkbook.SaveAs Filename:="Pathway" & "\" & sh.Name, FileFormat:=xlExcel8
ActiveWorkbook.Close
End If
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
ActiveWorkbook.Close False
This works fine and will create the sheets for me but I now need to add to the file name by using the same cell value from each worksheet (B1) so i tried adding to the code.
ActiveWorkbook.SaveAs Filename:="Pathway" & "\" & sh.Range("B1").Value & sh.Name, FileFormat:=xlExcel8
However doing so results in a "Run-time error '1004': There is no active Protected View Window" but i'm not too sure why that would be?
Any help here would be much appreciated.
This is likely due to your path not being a string accepted by Windows as file name. As the problem appears when you add B1 to the path, this seems to be the source of your problem. Check if cell B1 contains any characters not allowed in file names.
The print functionality of Excel (using VBA) is extremely slow. I'm hoping someone has a way of speeding the printing up (without using the Excel 4 Macro trick). Here's how I do it now:
Application.ScreenUpdating = False
With ActiveSheet.PageSetup
-various setup statements which I've already minimized-
End With
ActiveSheet.PrintOut
Application.ScreenUpdating = True
Yes, the PageSetup properties are very slow when you set them.
You have already set Application.ScreenUpdating = False, which is good, but an equally (or more) important step in this case is to set Application.Calculation = xlCalculationManual. (It is best if you save these settings and then restore them to the original at the end.)
Additionally, the property get for each PageSetup property is very fast, while it is only the property set that is so slow. Therefore, you should test the new property setting to make sure it isn't already the same as the existing property value in order to prevent an unnecessary (and expensive) call.
With all this in mind, you should be able to use code that looks something like the following:
Dim origScreenUpdating As Boolean
origScreenUpdating = Application.ScreenUpdating
Application.ScreenUpdating = False
Dim origCalcMode As xlCalculation
origCalcMode = Application.Calculation
Application.Calculation = xlCalculationManual
With ActiveSheet.PageSetup
If .PrintHeadings <> False Then .PrintHeadings = False
If .PrintGridlines <> False Then .PrintGridlines = False
If .PrintComments <> xlPrintNoComments Then .PrintComments = xlPrintNoComments
' Etc...
End With
Application.ScreenUpdating = origScreenUpdating
Application.Calculation = origCalcMode
Edit: A couple of updates:
For Excel 2010 and above you can make use of the 'Application.PrintCommunication' property, while for Excel 2007 and below, you can make use of 'ExecuteExcel4Macro'. For more details, see Migrating Excel 4 Macros to VBA.
For Excel 2007 and below, another interesting trick is to temporarily assign the printer driver to the 'Microsoft XPS Document Writer' and then set it back. Printing speed can improve by 3x. See: Slow Excel PageSetup Methods.
Hope this helps...
In furthering Michael's post and answering #rhc's question, the following code may also help you if need to copy Page Setup customizations from a single worksheet to multiple worksheets in a workbook:
Public Sub CopyPageSetupToAll(ByRef SourceSheet As Worksheet)
' Raise error if invalid source sheet is passed to procedure
'
If (SourceSheet Is Nothing) Then
Err.Raise _
Number:=vbErrorObjectVariableNotSet, _
Source:="CopyPageSetupToAll", _
Description:="Unable to copy Page Setup settings: " _
& "invalid reference to source sheet."
Exit Sub
End If
SourceSheet.Activate
With SourceSheet.PageSetup
' ...
' place PageSetup customizations here
' ...
End With
SourceSheet.Parent.Worksheets.Select
Application.SendKeys "{ENTER}", True
Application.Dialogs(xlDialogPageSetup).Show
End Sub
Alternatively, you could also modify the procedure to create a temporary worksheet to host your Page Setup changes, and then propagate those changes out to the other worksheets in your workbook:
Public Sub CopyPageSetupToAll(ByRef SourceBook As Workbook)
Dim tempSheet As Worksheet
' Raise error if invalid workbook is passed to procedure
'
If (SourceBook Is Nothing) Then
Err.Raise _
Number:=vbErrorObjectVariableNotSet, _
Source:="CopyPageSetupToAll", _
Description:="Unable to copy Page Setup settings: " _
& "invalid reference to source workbook."
Exit Sub
End If
Set tempSheet = SourceBook.Worksheets.Add
tempSheet.Activate
With tempSheet.PageSetup
' ...
' place PageSetup customizations here
' ...
End With
SourceBook.Worksheets.Select
Application.SendKeys "{ENTER}", True
Application.Dialogs(xlDialogPageSetup).Show
tempSheet.Delete
Set tempSheet = Nothing
End Sub
Due to the use of the SendKeys() function and Application.Dialogs functionality, this code does not offer the cleanest possible solution. However, it gets the job done. :)
if you want to have basicly the same page settings for every tab in a workbook can you speed things up by setting up one workshet and then copying that worksheet's settings somehow to the other worksheets? Is this possible?