使用VBA

时间:2016-02-24 10:39:44

标签: excel vba excel-vba

(编辑:为解决方案增加了2个限制)

我有一个表(明智的listobject),我需要复制某些行。出于效率原因,我正在使用SourceListRow.Range.Value2 = DestListRow.Range.Value2(一次复制整个范围)。复制的单元格范围之后的所有公式列都会自动完全自动扩展到新行并对复制的数据进行操作。我在Windows上使用Excel 2010。

然而,尽管我已经使用了这种代码,但是我现在只是在使用Range.Value / Range.Value2时偶然发现了一个奇怪的现象:如果为它分配一个空字符串,那么最后一个单元格value不是空字符串,但它将为Empty。即:数据不会被忠实地复制,并且副本可能与源不同,特别是如果连续的公式使用ISBLANK等。因此,在处理副本与源文件时,相同的公式会有不同的结果。

见下面的测试代码。打开一个新的空白Excel工作簿,转到VBA,添加一个新模块并添加以下代码:

Sub Test()
  ActiveSheet.Range("a1").Formula = "="""""
  ActiveSheet.Range("b1").Formula = "=isblank(a1)"
  ActiveSheet.Range("c1").Value2 = TypeName(ActiveSheet.Range("a1").Value2)

  ActiveSheet.Range("a2").Value2 = ActiveSheet.Range("a1").Value2
  ActiveSheet.Range("b2").Formula = "=isblank(a2)"
  ActiveSheet.Range("c2").Value2 = TypeName(ActiveSheet.Range("a2").Value2)

  ActiveSheet.Range("a3").Value2 = ""
  ActiveSheet.Range("b3").Formula = "=isblank(a3)"
  ActiveSheet.Range("c3").Value2 = TypeName(ActiveSheet.Range("a3").Value2)

  ActiveSheet.Range("a4").Formula = ActiveSheet.Range("a1").Formula
  ActiveSheet.Range("b4").Formula = "=isblank(a4)"
  ActiveSheet.Range("c4").Value2 = TypeName(ActiveSheet.Range("a4").Value2)

  Call ActiveSheet.Range("a1").Copy
  Call ActiveSheet.Range("a5").PasteSpecial(xlPasteValues)
  ActiveSheet.Range("b5").Formula = "=isblank(a5)"
  ActiveSheet.Range("c5").Value2 = TypeName(ActiveSheet.Range("a5").Value2)
End Sub

然后运行它并查看表单;

  • B1告诉FALSE(因为它应该 - 单元格不为空),C1告诉“String”(单元格值确实是一个空字符串);
  • B2告诉TRUE,即使我们只是按原样复制A1的值; C2告诉“Empty”,而它应该告诉“String”,如果值被忠实地复制了;
  • 作为测试,B3告诉TRUE,即使我们只是将其显式设置为空字符串; C3再次告诉“Empty”确认数据重整;
  • 将Range.Formula分配给OtherRange.Formula工作(字符串数据类型保留在B4中),但我不想复制公式,我只想复制值!
  • 作为测试我模仿了GUI在A5中会产生什么,果然,Copy / Paste-As-Values确实保留了is-an-empty-string状态......

怎么办?

  • 使用Range.Copy / Range.PasteSpecial(xlPasteValues)是不可接受的,性能方面和剪贴板方式;
  • using.Formula也不是一个选项,因为我想复制值;
  • 我可以迭代数组中的所有值,测试它们是否为空字符串,然后将这些单元格的公式设置为=“”,但我想一次复制整个多单元格范围以获得效率。 ..
  • 我不能使用自动过滤器,因为我的数据存在于一个表(a.k.a. ListObject)中;我可以使用桌子自己的自动过滤器,但人们可能已经使用过它,所以我不得不在之后恢复它,这对于过多的垃圾来说。
  • 使用Range.Find和Range.Replace也不是一个选项,因为它改变了用户的查找/替换对话框设置。

所有想法都赞赏!

1 个答案:

答案 0 :(得分:0)

SpecialCells(xlCellTypeBlank)和AutoFilter以不同方式处理空字符串。您的源数据是否可过滤?如果是这样,您是否可以使用占位符值,然后在复制后将单元格更改为.Formula = "="""""

我拿了你的示例代码并生成了工作簿,然后在它上面添加了一行用于过滤器头。下面的代码会将所有空字符串更改为" ChangeMe&#34 ;;然后,您可以复制值并替换" ChangeMe"的所有实例。在目的地中使用" ="""""使用Range.Replace。

Sub Test2()

ActiveSheet.Range("A2:A6").SpecialCells(xlCellTypeBlanks).Interior.Color = 255 'Just to prove that xlCellTypeBlanks only selects actual blanks

ActiveSheet.Range("A1:A6").AutoFilter Field:=1, Criteria1:="=" 'Shows both actual blanks and ZLS/null strings
ActiveSheet.Range("A1:A6").SpecialCells(xlCellTypeBlanks).EntireRow.Hidden = True 'Be able to use SpecialCells(xlCellTypeVisible) to reference all ZLS
ActiveSheet.Range("A2:A6").SpecialCells(xlCellTypeVisible).Value2 = "ChangeMe"
'Now unfilter and unhide your data, copy using .Value2, then .Replace "ChangeMe" with "="""""

End Sub

我认为这可以完成你想要做的事情 - 让我知道这是否是解决问题的正确方向。

编辑:误解原始问题。旧答案下方不正确。

虽然效率不如原始代码,但使用.AutoFilter比循环遍历许多单元格要有效得多。此外,.AutoFilter将零长度字符串和空单元格视为相同。

这样的事情:

Sub CopyAndClearFakeBlanks()

Dim WSSource As Worksheet
Dim WSDest As Worksheet
Dim LRow As Long
Dim LPasteRow As Long

Set WSSource = Sheets("Source Data")
Set WSDest = Sheets("Paste Here")

LRow = WSSource.Range("A:A").Find(what:="*", searchdirection:=xlPrevious).Row 'Note that this will ignore blanks, so you may want to use a different method for finding your last row. Depends on how your users would need the data if there are trailing blanks.

On Error Resume Next
LPasteRow = 2 'Always need at least one row before the data for filtering properly; you can delete after if necessary
LPasteRow = WSDest.Range("A:A").Find(what:="*", searchdirection:=xlPrevious).Row + 1
WSDest.AutoFilterMode = False
On Error GoTo 0 'ofc use proper error handling in your actual code

WSDest.Range("A" & LPasteRow & ":A" & LPasteRow + LRow - 1).Value2 = WSSource.Range("A1:A" & LRow).Value2
WSDest.Range("A" & LPasteRow - 1 & ":A" & LPasteRow + LRow).AutoFilter field:=1, Criteria1:="=" 'Show blank or ZLS only

On Error Resume Next
WSDest.Range("A" & LPasteRow & ":A" & LPasteRow + LRow).SpecialCells(xlCellTypeVisible).Clear 'Turn them into true blanks
WSDest.ShowAllData
WSDest.AutoFilterMode = False
On Error GoTo 0

Set WSDest = Nothing
Set WSSource = Nothing

End Sub