PasteSpecial SkipBlanks:=True fails with merged cells - excel

Range1.Copy
Range2.PasteSpecial Paste:=xlPasteFormulas, SkipBlanks:=True
If Range1 and Range2 have the same dimensions, this code executes without any trouble. The expectation is that the formulas in the range you copied will get inserted into the target range, but any blank cells in Range1 will not have their formulas copied to Range2, instead, any current cell values will be left as they were.
I've discovered that this fails on merged cells. The image below demonstrates the equivalent action using the built in Paste Special UI, which fails in an identical fashion:
Can anyone think of an elegant workaround that doesn't involve looping?
Note that simply using a variant of Range1.Formula = Range2.Formula won't suffice since it will overwrite unwanted cells in Range2 with blank (empty) values.
I've removed the no loops restriction because there doesn't seem to be a perfect solution otherwise.

The following was tested and seems to work.
Assumptions:
The first row cells in your post are A1:E1.
The green highlighted row cells in your post are A2:E2.
The range that needs to be partially overwritten is in row 4 (A4:E4).
I have replicated the contents of cells A2:E2 all the way till cell Q2. SoF2:G2 are merged & blank, H2 is blank, I2 has "copy", and so on till Q2 (which has "copy"). I just wanted to make sure that the method works with multiple merged areas.
Sub skipBlanksWithMergedCells()
Dim rngOrigin As Range, rngDestination As Range, rngSkip As Range
Dim varTemp As Variant
Set rngOrigin = Range("A2:Q2")
Set rngDestination = Range("A4:Q4")
' Set pointer to range that needs to be skipped
Set rngSkip = rngOrigin.SpecialCells(xlCellTypeBlanks).Offset(2, 0)
' Store its values into a variant
varTemp = rngSkip.Value
rngOrigin.Copy
rngDestination.PasteSpecial xlPasteFormulas
' Revert original values from the variant
rngSkip.Value = varTemp
End Sub
This will work if rngSkip contains hard numbers or text, but it will fail if it contain formulas.. In that case, we need to set a pointer to the subrange of formulas and store them in another variant, using varTempFormulas=range.formula and then back again range.formula=varTempFormulas.
I hope this helps.

Based on the conclusion that this bug makes it impossible to do this without looping, I've come up with the following solution which I believe to be as elegant as possible with looping.
Dim col as Long
Dim cel as Range
For Each cel In src.Cells
If cel.Formula <> vbNullString Then
col = 1 + src.Column - cel.Column
cel.Copy
dst.Worksheet.Range(dst.Cells(1, col ), dst.Cells(dst.rows, col )).PasteSpecial Paste:=xlPasteFormulas
End If
Next cel
This lets you copy one row of data from a range src and paste formulas over multiple rows in a range dst with only one loop over the columns, while skipping blank. This method never overwrites any destination that should be left alone, so it works in all my use cases.
In a more complex situation where the source data had multiple rows as well as columns, this routine wouldn't work, and I imagine at least 2 levels of nested loops would be required.

Brute Force method:
I had this problem and used this solution.
Copy format for whole page to new temporary page.
Un-merge your page. Do your copy with skip blanks.
Copy format from temporary page to old page.
Delete temporary page.

Related

Pasting Values as Displayed

I have a column of cells in excel that have the following formatting: "0000.00"
FYI, the quotes are not part of formatting.
Basically, four digits followed by two decimals. However, when the numbers are like "600", they need to be displayed as "0600.00". However, the list of numbers provided to me are displayed that way through formatting, so if I am trying to VLOOKUP, it can't process it; it sees "600", not "0600.00" that is displayed to me.
I am aware of PasteSpecial Paste:=xlPasteValues, but this pastes "600", not the "0600.00" that is displayed to me. Currently I can achieve such results by copying the values and pasting them into notepad —which suggests to me there is a way to do this— but I'd like to create a macro to do this for me.
Sorry for any redundant explanation, just wanted to avoid getting answers relating to pasting values only, which is not what I am looking for.
As you said, to use VLOOKUP with formatted text as the lookup value, you'll need the value of the cell to match with the value of the lookup value, so you'll have to convert the value in the cell to text with something like this (example for a single cell):
Dim rng As Range
Set rng = Range("A1")
rng.PasteSpecial xlPasteFormulasAndNumberFormats
Dim TextValue As String
TextValue = Format(rng, rng.NumberFormat)
rng.NumberFormat = "#" 'We need this line to turn the cell content into text
rng.Value2 = TextValue
I'm pretty sure no PasteSpecial options will allow you to do what you want in a single operation, so this solution is a workaround that does it in two steps.
Multiple cells case:
I realize that the code above doesn't address the issue of pasting multiple cells, so here's a procedure that can be used to copy the formatted number as text from one range to another:
Sub CopyAsFormattedText(ByRef SourceRange As Range, ByRef DestinationRange As Range)
'Load values into an array
Dim CellValues() As Variant
CellValues = SourceRange.Value2
'Transform values using number format from source range
Dim i As Long, j As Long
For i = 1 To UBound(CellValues, 1)
For j = 1 To UBound(CellValues, 2)
CellValues(i, j) = Format(CellValues(i, j), SourceRange.Cells(i, j).NumberFormat)
Next j
Next i
'Paste to destination by using the top left cell and resizing the range to be the same size as the source range
Dim TopLeftCell As Range
Set TopLeftCell = DestinationRange.Cells(1, 1)
Dim PasteRange As Range
Set PasteRange = TopLeftCell.Resize(UBound(CellValues, 1), UBound(CellValues, 2))
PasteRange.NumberFormat = "#" 'We need this line to turn the cells content into text
PasteRange.Value2 = CellValues
End Sub
It's basically the same idea, but with a loop.
Note that if the formatting is always the same, you could make it a variable and apply it to every values in the array instead of calling .NumberFormat on every cell which inevitably adds a little bit of overhead.
Sidenote
One could ask why I'm not suggesting to use :
SourceRange.Cells(i, j).Text
instead of
Format(CellValues(i, j), SourceRange.Cells(i, j).NumberFormat)
And that would be a very good question! I guess, the fact that .Text can return "###..." when the column isn't sized properly always makes me afraid of using it, but it certainly would look much cleaner in the code. However, I'm not sure what would be better in terms of performance. (Relevant article by Charles Williams)

Return cells content from range

Yesterday I learned here how to copy a row to a second sheet.
Sub maJolieProcedure(Texte As String)
With Worksheets("employes").Range("A:A")
Set c = .Find(what:=Texte)
If Not c Is Nothing Then
firstAddress = c.Row
Worksheets("employes").Rows(firstAddress).Copy _
Destination:=Worksheets("rapport").Range("A1")
MsgBox "Ok"
Else
MsgBox "Nok"
End If
End With
End Sub
To respect the formatting of the second sheet, I want to copy and paste the contents of each cell one by one.
I can identify the line number. However, I can't figure out how the Range object can return each cell one by one. For example, C3 content if Rows = 3.
Thanks a lot.
If you don't want to paste the formatting from one range to another paste values only.
Worksheets("employes").Rows(firstAddress).Copy
Worksheets("rapport").Range("A1").PasteSpecial xlValues
That's the same for all ranges, whether 1 cell or a million. The Copy process copies the subject to memory as one block. Any parsing must be done before the Copy instruction. An alternative is to read a range into an array.
Dim Arr As Variant
Arr = Worksheets("employes").Rows(firstAddress).Value
This will create a 3D array of 1 row and about 16000 columns. You might decide to limit your enthusiasm to only what you need.
With Worksheets("employees")
Arr = .Range(.Cells(firstAddress, 1), .Cells(firstAddress, .Columns.Count).End)xlToLeft)).Value
End With
Pay attention to the leading periods within the With statement. Each such period stands for the object mentioned in the With statement.
If your goal is to respect the formating of the second sheet, you don't need to loose time copying cell by cell.
It is more effective to do a paste special, like you do with the mouse:
Range("A1").Copy
Range("B1").PasteSpecial Paste:=xlPasteValues
works very well also with bigger ranges if you need:
Range("A1:A12").Copy
Range("B1:B12").PasteSpecial Paste:=xlPasteValues
or even
Range("A1:A12").Copy
Range("D3").PasteSpecial Paste:=xlPasteValues
If your goal is to really access all cell of a range individually , you just iterate on the range. For example:
For Each cell In Range("A1:A12")
cell.Value = cell.Value + 2
Next cell

Excel - Copy columns to rows

I have 3 columns in a sheet in excel as below
I need the output in the below format on a SEPARATE SHEET
I'm fine with either VB script or using just excel features. Could I please get some help?
Try this macro. Place the macro in a regular code module (Insert > Module). Adjust the ranges to suit your situation.
Sub rearrange()
Dim cel As Range, tgt As Range
Set cel = ActiveSheet.Range("A1")
Set tgt = ActiveSheet.Range("D1")
Do While Len(cel) > 0
tgt = cel
tgt.Offset(1, 0) = cel.Offset(0, 1) & cel.Offset(0, 2)
Set cel = cel.Offset(1, 0)
Set tgt = tgt.Offset(2, 0)
Loop
ActiveSheet.Range("A:C").Delete
End Sub
If you're not going to do this on a regular basis, here's a simple solution.
I don't have access to MS-Excel so I cannot give you the exact answer. But I hope this helps.
Steps:
Add a new column with the concatenate function to the right of the table for example, to merge cells b1 and c1, use =Concatenate(b1,c1) and keep this result in cell D1. Do a copy-paste of the function for the other rows as well.
Copy your selection to a new worksheet where you want the result.
Use paste special to only paste the values of copied cells without forumulas. This ensures that it won't reference the original cells or change relatively.
Use the transpose function to change the resulting contents like your final output while pasting the data. Similar one here.
If you need to do this regularly, this method is not suitable. You'll be better off with a VBA script. But it's been a very long time since I worked on Excel so I cannot help you there.

Copying an array to a filtered range gives irrational results

Copying the values of a filtered range to an array seems to work without a problem: the array then contains values from both filtered and unfiltered cells. However, when I copy the array's contents back to the filtered range, the results are incomprehensible to me.
Here's my code:
Sub test()
Dim rangecopy() As Variant
rangecopy() = Range(Cells(2, 1), Cells(14, 3)).Value
For c = LBound(rangecopy, 1) To UBound(rangecopy, 1)
rangecopy(c, 1) = c
rangecopy(c, 2) = c * 10
rangecopy(c, 3) = c * 100
Next
Range(Cells(2, 1), Cells(14, 3)).Value = rangecopy()
End Sub
It is supposed to give the following result. Here, the range was unfiltered when the macro copied the array to it.
If the range is filtered by column D ("NO" is filtered out), the result looks like this:
First, the filtered cells aren't updated. Then, most cells from column B get values from the array's first column (4, 5, 6), while a few others get values from the array's second column correctly (10). The last two rows are filled with #N/A error. Is this supposed to work that way? I'm using Office 2010.
I really hope someone with knowledge of the internal workings of VBA can provide more insight on your question. I can share the following:
First, it is working as intended. However, I don't know why this is the design, nor what exactly is happening in the assignment process.
There are many cases that create a similar issue. For instance, if you have the filter on (some rows are hidden) and try to fill (drag) a formula down, you will see similar results, in that hidden rows aren't populated, but they do affect the (relative) references in the formula. On the other hand, if you manually copy and paste into a filtered range, the data is pasted into the hidden rows (as you intend).
It seems that any range referenced that is part of the Autofilter range is actually non-contiguous*. Using Range.Address does not always reveal this, nor does looping through Range.Areas. If we modify your example, we can see where the "real" error is:
Dim r1 as range
Dim r2 as range
Set r1 = Sheet1.Range("A1:B5") 'some numbers in a range
Set r2 = Sheet2.Range("A2:B6") 'same-size range underneath a filtered header
r1.Copy Destination:=r2
It works when all the rows are visible. When the filter on Sheet2 creates hidden rows, the result is "Run-time error '1004': ...the Copy area and the paste area are not the same size and shape." On the other hand, using the "manual" / clipboard method works for hidden rows:
r1.Copy
r2.PasteSpecial (xlPasteValues)
Since assigning an array to a range bypasses the clipboard (as in the 1st block), we ought to receive an error (instead you just end up with erroneous results).
The only solutions I'm aware of are to either loop through the range and assign a value to each cell:
For i = 1 to LastRow
For j = 1 to LastCol
Sheet1.Cells(i,j).Value = myArr(i,j)
Next
Next
OR (better) remove the Autofilter, assign the array to the range, then reapply the filter.
*technically it's contiguous, so it may be better to say that the range is composed of several ranges/areas, although using .Address doesn't indicate this and there is only one area when you try to loop through Range.Areas

Using the left-function [VBA]

I'm trying to create a simple macro that copys the two first numbers in each cell in a column and printing them in a different column. This is in a excel-document with more than 1 worksheet. I've tried for a while to write the code, but with no prior experience, I have a hard time figuring out what to do. I know the "left()"-function is able to do this, but I don't know how I define which column to draw data from and to which column it will be printed. Any help will be greatly appreciated.
With no prior experience to writing VBA code, I am going to reccommend you stick to the formula method of doing. Honestly, even if you were familiar with VBA, you might still opt to use the formula.
A formula is put into the actual cell you want the value to be copied.
=LEFT(sourceCell, #of characters you want)
This is how it would look:
=LEFT(Sheet1!A1, 2)
Think of it as saying "this cell shall equal the first n characters in cell OO, starting from the left".
Once you are done with your formula, if you don't need it to be binded to the source anymore (if the sourceCell changes, so does the cell with the LEFT formula), you can highlight the cells, Ctrl + C to copy, then right-click and select Paste Special. Then select VALUE and hit OK and now the cells are hard-coded with the value they were showing, as if you typed it yourself.
Once you master using formulas, the next step is VBA. Don't go confusing yourself by jumping into VBA and writing code for ranges, etc. if you aren't comfortable with using =LEFT yet. One step at a time, and you'll be a pro before you know it. :)
Here is a quick sample sub to get you started:
Public Sub LeftSub()
Dim SourceRange As Range, DestinationRange As Range, i As Integer
'Define our source range as A1:A10 of Sheet1
Set SourceRange = Sheet1.Range("A1:A10")
'Define our target range where we will print.
'Note that this is expected to be of same shape as source
Set DestinationRange = Sheet1.Range("B1:B10")
'Iterate through each source cell and print left 2 bits in target cell
For i = 1 To SourceRange.Count
DestinationRange(i, 1).Value = Left(SourceRange(i, 1).Value, 2)
Next i
End Sub
How about
Sub foo()
Dim cell As Range
Dim sourceRange As Range
'//define the source column - looks for contiguous downward data from A1;
Set sourceRange = Range(Sheets("Sheet1").Range("A1"), Selection.End(xlDown))
'//iterate each cell
For Each cell In sourceRange
If IsEmpty(cell.Value) Then Exit For
'//example to place the value in corresponding row of column B in sheet 2
Sheets("Sheet2").Range("B" & cell.Row).Value = Left$(cell.Value,2)
Next
End Sub
Or an equivalent formula (in the destination cell)
=LEFT(Sheet1!A1,2)

Resources