Excel 关闭第二个工作簿时丢失数据

Excel loses data when second workbook is closed

编辑问题底部

我的代码中有一个函数,它获取样本字典,从另一个工作簿中填充数据,并 returns 填充字典。一切正常,但是一旦我将文件的打开方式更改为只读,我就遇到了问题。

这是代码(已简化以删除冗余部分):

Function get_samples_data(ByVal instructions As Scripting.Dictionary, ByRef patients_data As Scripting.Dictionary) As Scripting.Dictionary
    'takes a dictionary of samples and fills their data from the file <0 GL all RL>
    Dim wb          As Workbook
    Dim ws          As Worksheet
    Dim data_start  As Long
    Dim data_end    As Long
    Dim rw          As Range
    Dim rw_nr       As String
    
    'open <GP all> in ReadOnly mode based on path and filename in specific cells
    Application.ScreenUpdating = False
    Set wb = Workbooks.Open(ThisWorkbook.Sheets(1).Cells(13, 2).Value2 & ThisWorkbook.Sheets(1).Cells(13, 1).Value2, False, True)
    Set ws = wb.Worksheets("ALL")
    
    'get row nr. of the first and the last sample to export
    data_start = ws.Columns("A:A").Find(what:=instructions("from_sample"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows).Row
    data_end = ws.Columns("A:A").Find(what:=instructions("to_sample"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows).Row
        
    'main loop
    For i = data_start To data_end
        Set rw = ws.Rows(i)
        rw_nr = rw.Cells(1, 1).Value
        If rw.Cells(1, 11).Value = instructions("group") Then
            If patients_data.Exists(rw_nr) Then
                Set patients_data(rw_nr) = fetch_sample_data(rw, patients_data(rw_nr))
            End If
        End If
    Next

    'close <GP all> without saving
    wb.Close (False)

    Set get_samples_data = patients_data
End Function

当我调试时,我注意到,调用 wb.Close(False) 时数据丢失了。在那之前数据是完整的,但是一旦关闭源工作簿,数据(它是一个范围对象)就会变成空白。未设置为空,当在源工作簿中找不到数据时会发生这种情况,但在调试器中可以看到范围对象的所有属性,但都具有值。

在我将打开模式更改为只读之前,一切正常并且数据保留在那里。 我错过了什么?为什么存储在不同变量中的数据丢失了?

编辑: 获取示例数据确实 return 是一个范围对象。

Private Function fetch_sample_data(ByVal rw As Range, ByRef sm As sample) As sample
    Dim data As Range
    
    Set data = Range(rw.Cells(1, 19), rw.Cells(1, 63))
    Set sm.data = data
    Set fetch_sample_data = sm
    
End Function

我尝试更改关闭顺序并设置 return 值,但错误仍然存​​在。

那么,Range 对象是否始终只是对作品中某个范围的引用sheet?如果我希望保留数据,是否需要将所有相关的 Range 对象更改为数组?或者有没有办法创建一个独立于工作簿的 Range 对象(我不想将范围复制到带有宏的主工作簿中的任何 sheet 中)?

下面是主子,正如@Pᴇʜ 要求的那样。剩下的功能我就不加了,因为整个代码分散在1个表单,2个模块和14个类(很多携带长方法)。 两个注释打开命令是那些导致一切正常工作的命令。关闭命令位于 main sub 的末尾,因此关于 @Pᴇʜ 的评论,如果范围对象始终只是对实际单元格范围的引用,则它们在整个程序期间都可用。

Sub RL_creator_GP_main()
    Dim instructions    As New Scripting.Dictionary
    Dim samples         As Scripting.Dictionary
    Dim result_list     As Variant
    Dim rep             As cReport
    Dim scribe          As New descriptor
    
    Application.ScreenUpdating = False
    
    'get instructions from inputboxes (group, from sample, to sample)
    Set instructions = procedures.input_instructions()
    If instructions.Exists("terminated") Then
        Exit Sub
    End If
    
    'get <GP all> and <RL headers> ready
    'Call procedures.prepare_file("GP all.xlsx", pth:=ThisWorkbook.Sheets(1).Cells(12, 2).Value)
    'Call procedures.prepare_file("RL headers.xlsx", pth:=ThisWorkbook.Sheets(1).Cells(13, 2).Value)
    
    'get patients data from <RL headers>, closes the file afterwards
    Set samples = procedures.get_patients_data(instructions)
    
    'get patients data from <GP all>, closes the file afterwards
    Set samples = procedures.get_samples_data(instructions, samples)

因为 samples 已提交 ByRefget_samples_data 你不需要 return 它:

Sub RL_creator_GP_main()
    'your code here …

    'get patients data from <RL headers>, closes the file afterwards
    Set samples = procedures.get_patients_data(instructions)
    
    'get patients data from <GP all>, closes the file afterwards
    procedures.get_samples_data instructions, samples 'this call will change the original samples because it is ByRef!

fetch_sample_data 中,您向字典中添加了一个范围。但是 Range 对象只是对工作表的引用,本身不包含数据。因此,与其将范围转换为数组以添加实际数据,而不仅仅是引用:

Private Sub fetch_sample_data(ByVal rw As Range, ByRef sm As sample)
    Dim data() As Variant
    data = Range(rw.Cells(1, 19), rw.Cells(1, 63)).Value
    Set sm.data = data
    'again you don't need a function to return the sample as it is ByRef 
End Sub

最后 get_samples_data 应该是一个 sub 而不是函数。并将 fetch_sample_data 作为 fetch_sample_data rw, patients_data(rw_nr)

的子调用
Sub get_samples_data(ByVal instructions As Scripting.Dictionary, ByRef patients_data As Scripting.Dictionary)
    'takes a dictionary of samples and fills their data from the file <0 GL all RL>
    Dim wb          As Workbook
    Dim ws          As Worksheet
    Dim data_start  As Long
    Dim data_end    As Long
    Dim rw          As Range
    Dim rw_nr       As String
    
    'open <GP all> in ReadOnly mode based on path and filename in specific cells
    Application.ScreenUpdating = False
    Set wb = Workbooks.Open(ThisWorkbook.Sheets(1).Cells(13, 2).Value2 & ThisWorkbook.Sheets(1).Cells(13, 1).Value2, False, True)
    Set ws = wb.Worksheets("ALL")
    
    'get row nr. of the first and the last sample to export
    data_start = ws.Columns("A:A").Find(what:=instructions("from_sample"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows).Row
    data_end = ws.Columns("A:A").Find(what:=instructions("to_sample"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows).Row
        
    'main loop
    For i = data_start To data_end
        Set rw = ws.Rows(i)
        rw_nr = rw.Cells(1, 1).Value
        If rw.Cells(1, 11).Value = instructions("group") Then
            If patients_data.Exists(rw_nr) Then
                fetch_sample_data rw, patients_data(rw_nr)
            End If
        End If
    Next

    'close <GP all> without saving
    wb.Close (False)
End Sub

背景说明

调用函数和子函数:
首先不需要 Call 语句。函数中的参数总是在括号中,函数用于return一个值。

Result = MyFunction(Param1, Param2) ' functions return a result and parameters are in parentesis

MySub Param1, Param2 ' subs don't return a result and don't use parentesis

Call MySub(Param1, Param2) ' But with the Call statement they need parentesis

ByRef 做什么:
如果您声明一个参数 ByRef ,这意味着您不会将数据提交给子程序,而只是对内存中该数据的引用(通过引用)。因此,如果您有以下子项:

Sub MySub(ByVal Param1, ByRef Param2)
    Param1 = 1
    Param2 = 2
End Sub

并像

一样使用它
Sub Example()
    Dim Var1 As Long: Var1 = 10
    Dim Var2 As Long: Var2 = 20

    MySub Var1, Var2 'note Var2 is submitted ByRef!

    Debug.Print Var1, Var2 'returns 10,  2 the value in Var2 got changed by MySub without returning anything
End Sub

因此,当您通过引用提交变量时,这意味着 MySub 在执行 Param2 = 2 时更改了 Var2 中的值,因为 Param2Var2 引用了内存中相同 space。如果您提交 ByVal(按值),您实际上会在内存中复制数据,并且 Param1Var1 引用内存中的不同位置。

这就是为什么你不需要函数来 return 如果你提交它 ByRef 你已经改变了内存中的数据。

因此在您的代码中,如果您声明 Sub get_samples_data(ByVal instructions As Scripting.Dictionary, ByRef patients_data As Scripting.Dictionary) 然后像 procedures.get_samples_data instructions, samples 那样调用它,会使 patients_datasamples 指向内存中相同的 space。因此,因为数据在内存中只有一次,并且只有 2 个链接指向它们,所以在其中一个链接中所做的任何更改实际上都会编辑内存中完全相同的数据。因此你不需要 return 数据。