Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/vba/15.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Events 在VBA中运行宏之前,检查工作表是否已更新_Events_Vba_Excel - Fatal编程技术网

Events 在VBA中运行宏之前,检查工作表是否已更新

Events 在VBA中运行宏之前,检查工作表是否已更新,events,vba,excel,Events,Vba,Excel,我正在写一个宏,其中有一个中央输入表-让我们称之为“主输入表”,其中用户输入相关变量。在“主输入表”中有一些输入,如“是否有更多输入表?”-当“是”时,将显示与输入相对应的工作表(之前已隐藏)-我们将其称为“关联输入表”。现在,我想确保用户在运行宏之前更新“关联的输入表”。是否有一种方法可以做到这一点-使用VBA提供的事件处理程序或使用任何其他方法?有一个工作表更改事件可能会执行您想要的操作: Private Sub Worksheet_Change(ByVal Target As Range)

我正在写一个宏,其中有一个中央输入表-让我们称之为“主输入表”,其中用户输入相关变量。在“主输入表”中有一些输入,如“是否有更多输入表?”-当“是”时,将显示与输入相对应的工作表(之前已隐藏)-我们将其称为“关联输入表”。现在,我想确保用户在运行宏之前更新“关联的输入表”。是否有一种方法可以做到这一点-使用VBA提供的事件处理程序或使用任何其他方法?

有一个工作表更改事件可能会执行您想要的操作:

Private Sub Worksheet_Change(ByVal Target As Range)

End Sub
将其放入“主信息表”的代码中,它将在每次更改该表时运行

但是,如果您不希望在每次更新工作表时都运行电子表格,而只希望检查它是否已更新。。。您可以这样创建一个全局变量(声明必须放在标准模块中:

Global MainSheetHasChanged as Boolean
然后,您只需将这行代码放在工作表\u changed宏中:

Private Sub Worksheet_Change(ByVal Target As Range)
    MainSheetHasChanged = True
End Sub

只要确保在运行其他宏后始终将变量设置回false。这就是您要寻找的吗?

工作表更改事件过程可能是一种方法,除非您在工作表的其他地方发生了许多更改

在这一点上,您的问题可以重新表述:“自从上次检查以来,我的范围有变化吗?”

抓取范围的副本并将其存储在某处,然后逐个单元格地对照缓存的副本检查当前范围,这是一种蛮力方法:如果只执行一次就可以了,但是如果重复执行,则存储哈希(由某种校验和函数生成的短代码或数字)更有效

校验和算法各不相同。Adler32简单而快速,但在比较(例如)时,它的性能很差-您会遇到“哈希冲突”或无法为不同的数据输入返回不同的哈希值一对由6-10个字母组成的单字。但是,当要求它检测一列24个8个字母的单词或几千个日期和数字的表的变化时,它的性能确实非常好

查找其他哈希-并保持最新:您的电脑将有多个包含诸如MD5和sha1之类哈希的库,这些库的性能应该比VBA中的手动哈希更好

下面是一些使用Adler-32校验和的演示代码。请阅读代码注释,其中有一些内容是您需要了解的,以便使其适应您的项目:

公共函数RangeHasChanged()为布尔值

'使用下面的Checksum()函数的演示函数

对于更高级的用户,我在网站上有一个“关注范围”课程: "

作者:奈杰尔·赫弗南,2006年5月

'请注意,此代码位于公共域中。请用 '作者的姓名,如果需要,请将其与任何专有代码分离 “主张该专有代码的所有权和商业机密性

“编码注释:

'预计此函数将保存在宿主工作表的 '模块,并重命名以指示正在监视的范围或表。它是 '使用命名范围而不是硬编码地址是个好主意

'您也可以选择将“1到255”编辑为您范围的宽度

'初始化静态值,以便第一次检查VBA会话 '不会自动注册'更改',留作 '读者:但是在打开工作簿时调用该函数就足够了

'这是为了在VBA中使用,而不是在工作表上使用。请使用 '设置'Option Private Module'以对函数向导隐藏此选项

将rngData设置为Excel.Range 将数据作为变量

暗长的 静态的,长期存在的

'请注意,我们捕获阵列中的整个范围,然后处理阵列: 这是对工作表的一次“点击”(任何交互中的缓慢操作) '使用工作表数据),并在VBA中进行所有后续处理

Set rngData=ThisWorkbook.Names(“DataEntryMain”).refrestorRange arrData=rngData.Value2

RangeHasChanged=False

lngChecksum=校验和(arrData)

删除ARR数据

'当文件打开时,无论何时 '重新初始化VBA项目,清除所有变量。 '这些事件均不应报告为'变更'

如果lngChecksum和lngChecksum都存在,那么 RangeHasChanged=True 如果结束

lngExisting=lngChecksum

端函数

我可以发誓我在几年前就在这里发布了这篇文章,但这里有一个32位VBA中Adler-32的实现。

其中有一个可怕的漏洞:Adler-32返回一个32位整数,而VBA Long是一个带符号的整数,范围为±(2^31)-1,所以我在+2^31实现了溢出的“环绕”,在-2^31+1重新启动。并且做了一些我真的,真的不应该用浮点变量做的事情。最终,每个人,每个地方,都会有64位的Office,这将是一种奇怪和不必要的…对吗?

当然,真正的问题是:为什么要麻烦?

它归结为检查更改的常见问题:如果您不想使用“on change”事件,或者您在VBA中直接处理数据,那么大型数据集需要比逐项暴力方法更好的方法。至少,如果您不止一次地这样做:将每个项滚动到您的表中的成本已经降低h总是比成本高一个比一个。。。

…如果您正在从MySQL或某个web API库导入快速哈希算法(如果可以获得公开的函数,请尝试使用MDA5),这仍然是正确的,除非您可以找到直接读取VBA变量数组的内容并释放VBA线程o

Public Function RangeHasChanged() As Boolean

' Demonstration function for use of the Checksum() function below.

' For more advanced users, I have a 'Watched Range' class on the website: ' http://excellerando.blogspot.com

' Author: Nigel Heffernan, May 2006 http://excellerando.blogspot.com

' Please note that this code is in the public domain. Mark it clearly, with ' the author's name, and segregate it from any proprietary code if you need ' to assert ownership & commercial confidentiality on that proprietary code

' Coding Notes:

' It is expected that this function will be saved in the host worksheet's ' module and renamed to indicate the range or table being monitored. It's a ' good idea to use a named range rather than a hardcoded address.

' You might also choose to edit the '1 To 255' to the width of your range.

' Initialising the static values so that the first check in your VBA session ' does not automatically register a 'change' is left as an exercise for the ' reader: but calling the function on opening the workbook works well enough

' This is intended for use in VBA, not for use on the worksheet. Use the ' setting 'Option Private Module' to hide this from the function wizard.

Dim rngData As Excel.Range Dim arrData As Variant

Dim lngChecksum As Long Static lngExisting As Long

' Note that we capture the entire range in an Array, then work on the array: ' this is a single 'hit' to the sheet (the slow operation in any interaction ' with worksheet data) with all subsequent processing in VBA.

Set rngData = ThisWorkbook.Names("DataEntryMain").RefersToRange arrData = rngData.Value2

RangeHasChanged = False

lngChecksum = CheckSum(arrData)

Erase arrData

' lngExisting is zero when the file opens, and whenever the ' VBA project is reinitialised, clearing all the variables. ' Neither of these events should be reported as a 'change'.

if lngExisting <> lngChecksum AND lngExisting <> 0 Then RangeHasChanged = True End If

lngExisting = lngChecksum

End Function

I could've sworn I posted this here, years ago, but here's an implementation of Adler-32 in 32-bit VBA.

There's a horrible hack in it: Adler-32 returns a 32-bit integer, and the VBA Long is a signed integer with a range ± (2^31) -1, so I've implemented a 'wrap around' of the overflow at +2^31, restarting at -2^31 +1. And done something I really, really shouldn't have done with a floating-point variable. Eventually everyone, everywhere, will have 64-bit Office and this'll be kind of quaint and unnecessary... Right?

Of course, the real question is: why bother?

It boils down to the common question of checking for changes: if you don't want to use the 'on change' event, or you're dealing with data directly in VBA before it hits the sheet, large data sets need something better than an item-by-item brute force approach. At least, if you're doing it more than once: the cost of rolling each item into your hash is always more than the cost of the one-by-one comparison...

...And that's still true if you're importing a fast hashing algorithm from MySQL or one of the web API libraries (try MDA5, if you can get at an exposed function), unless you can find something that reads VBA variant arrays directly and relieve your VBA thread of the task of enumerating the list values into the imported function.

Meanwhile, here's a hash algorithm that's within reach of VBA: Adler32. The details are in Wikipedia’s article on Adler32: http://en.wikipedia.org/wiki/Adler-32 and an hour's testing will teach you some lessons about hashing:
  1. 'Hash collisions' (differing data sets returning the same hash code) are more common than you expected, especially with data containing repeated patterns (like dates);>
  2. Choice of hashing algorithm is important;
  3. ...And that choice is more of an art than a science;
  4. Admitting that you really shouldn't have bothered and resorting to brute force is often the better part of valour.


Adler-32 is actually more useful as a tool to teach those lessons, than as a workaday checksum. It's great for detecting changes in lists of more than 100 distinct items; it's tolerable, on a list of 24 randomly-generated 8-letter words (hash collisions at 1 in 1800 attempts) and it starts giving you single-digit percentage occurrences of the hash collision error in a list of 50 not-so-distinct option maturities, where the differences are mostly in the last 10 chars and those ten chars are recurring 3-month maturity dates.

By the time you're comparing pairs of 6-letter strings, more than 10% of your changes will be missed by the checksum in a non-random data set. And then you realise that might as well be using string comparison for that kind of trivial computation anyway.

So the answer is always: test it.

Meanwhile, here's the algorithm, horrible hacks and all:

Public Function CheckSum(ByRef ColArray As Variant) As Long Application.Volatile False

' Returns an Adler32 checksum of all the numeric and text values in a column

' Capture data from cells as myRange.Value2 and use a 32-bit checksum to see ' if any value in the range subsequently changes. You can run this on multi- ' column ranges, but it's MUCH faster to run this separately for each column ' ' Note that the VBA Long Integer data type is not a 32-bit integer, it's a ' signed integer with a range of± (2^31) -1. So our return value is signed ' and return values exceeding +2^31 -1 'wraparound' and restart at -2^31 +1.

' Coding Notes:

' This is intended for use in VBA, and not for use on the worksheet. Use the ' setting'Option Private Module' to hide CheckSum from the function wizard

' Author: Nigel Heffernan, May 2006http://excellerando.blogspot.com ' Acknowledgements and thanks to Paul Crowley, who recommended Adler-32

' Please note that this code is in the public domain. Mark it clearly, with ' the author's name, and segregate it from any proprietary code if you need ' to assert ownership & commercial confidentiality on your proprietary code

Const LONG_LIMIT As Long = (2 ^ 31) - 1 Const MOD_ADLER As Long = 65521

Dim a As Long Dim b As Long

Dim i As Long Dim j As Long Dim k As Long

Dim arrByte() As Byte

Dim dblOverflow As Double

If TypeName(ColArray) = "Range" Then ColArray = ColArray.Value2 End If

If IsEmpty(ColArray) Then CheckSum = 0 Exit Function End If

If (VarType(ColArray) And vbArray) = 0 Then ' single-cell range, or a scalar data type ReDim arrData(0 To 0, 0 To 0) arrData(0, 0) = CStr(ColArray) Else arrData = ColArray End If

a = 1 b = 0

For j = LBound(arrData, 2) To UBound(arrData, 2) For i = LBound(arrData, 1) To UBound(arrData, 1) ' VBA Strings are byte arrays: arrByte(n) is faster than Mid$(s, n) arrByte = CStr(arrData(i, j))' Is this type conversion efficient? For k = LBound(arrByte) To UBound(arrByte) a = (a + arrByte(k)) Mod MOD_ADLER b = (b + a) Mod MOD_ADLER Next k ' Terminating each item with a 'vTab' char constructs a better hash ' than vbNullString which, being equal to zero, adds no information ' to the hash and therefore permits the clash ABCD+EFGH = ABC+DEFGH ' However, we wish to avoid inefficient string concatenation, so we ' roll the terminating character's bytecode directly into the hash: a = (a + 11) Mod MOD_ADLER' vbVerticalTab = Chr(11) b = (b + a) Mod MOD_ADLER Next i ' Roll the column into the hash with a terminating horizontal tab char: a = (a + 9) Mod MOD_ADLER ' Horizontal Tab = Chr(9) b = (b + a) Mod MOD_ADLER

Next j

' Using a float in an integer calculation? We can get away with it, because ' the float error for a VBA double is < ±0.5 with numbers smaller than 2^32

dblOverflow = (1# * b * MOD_ADLER) + a

If dblOverflow > LONG_LIMIT Then' wraparound 2^31 to 1-(2^31) Do Until dblOverflow < LONG_LIMIT dblOverflow = dblOverflow - LONG_LIMIT Loop CheckSum = 1 + dblOverflow - LONG_LIMIT Else CheckSum = b * MOD_ADLER + a End If

End Function