【问题标题】:VBA (Excel) Dictionary on Mac?Mac上的VBA(Excel)字典?
【发布时间】:2013-11-08 21:58:14
【问题描述】:

我有一个 Excel VBA 项目,它大量使用 Windows 脚本 Dictionary 对象。我最近有一个用户尝试在 Mac 上使用它并收到以下错误:

Compile Error: Can't find project or library

这是使用Tools > References > Microsoft Scripting Runtime 库的结果。

我的问题是,有没有办法在 Mac 上完成这项工作

以下是我认为可能的解决方案的 3 种情况:

  1. 使用允许在 Mac 上使用字典的 Mac 插件(如果存在我最喜欢的选项
  2. 像下面这样做一些变量切换:

    isMac = CheckIfMac
    If isMac Then
        ' Change dictionary variable to some other data type that is Mac friendly and provides the same functionality 
    End If
    
  3. 编写 2 个完全独立的例程来做同样的事情(请不要让这成为需要发生的事情):

    isMac = CheckIfMac
    If isMac Then
        DoTheMacRoutine
    Else
       DoTheWindowsRoutine
    End If
    

【问题讨论】:

  • 如果您需要跨平台,可以将 Scripting.Dictionary 替换为自定义类:例如 sysmod.wordpress.com/2011/11/02/…
  • 把它作为一个答案,我会接受它——这个例子就像一个魅力。可能应该复制/粘贴一些(如果不是全部)实际代码(有 2 个文件,不要太大),以防链接中断,以便查看者仍然可以使用它。

标签: excel vba macos dictionary


【解决方案1】:

从 cmets 中提取答案以防止链接失效。

Patrick O'Beirne @ sysmod 写了一个类集来解决这个问题。

请务必访问 Patirk 的博客以表示感谢!也有可能他有更新的版本。

将其保存为名为 KeyValuePair.cls 的纯文本文件并导入 Excel
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "KeyValuePair"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
'Unrestricted class just to hold pairs of values together and permit Dictionary object updating
Public Key As String
Public value As Variant

将其保存为名为 Dictionary.cls 的纯文本文件并导入 excel
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Dictionary"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

'Collection methods: Add, Count, Item, Remove
'Dictionary : .Add(Key as string, Item as variant), .CompareMode, .Count, .Exists(Key); _
   .Item(Key) - writeable, .Items, .Keys, .Remove(Key), .RemoveAll
'plus KeyValuePairs collection, KeyValuePair(Index as long), Tag as variant
' 25-11-2011 KeyValuePair helper object

Public KeyValuePairs As Collection ' open access but allows iteration
Public Tag As Variant            ' read/write unrestricted

Private Sub Class_Initialize()
   Set KeyValuePairs = New Collection
End Sub

Private Sub Class_Terminate()
   Set KeyValuePairs = Nothing
End Sub

' in Scripting.Dictionary this is writeable, here we have only vbtextCompare because we are using a Collection
Public Property Get CompareMode() As VbCompareMethod
   CompareMode = vbTextCompare   '=1; vbBinaryCompare=0
End Property

Public Property Let Item(Key As String, Item As Variant)    ' dic.Item(Key) = value ' update a scalar value for an existing key
   Let KeyValuePairs.Item(Key).value = Item
End Property

Public Property Set Item(Key As String, Item As Variant)    ' Set dic.Item(Key) = value ' update an object value for an existing key
   Set KeyValuePairs.Item(Key).value = Item
End Property

Public Property Get Item(Key As String) As Variant
   AssignVariable Item, KeyValuePairs.Item(Key).value
End Property

' Collection parameter order is Add(Item,Key); Dictionary is Add(Key,Item) so always used named arguments
Public Sub Add(Key As String, Item As Variant)
   Dim oKVP As KeyValuePair
   Set oKVP = New KeyValuePair
   oKVP.Key = Key
   If IsObject(Item) Then
      Set oKVP.value = Item
   Else
      Let oKVP.value = Item
   End If
   KeyValuePairs.Add Item:=oKVP, Key:=Key
End Sub

Public Property Get Exists(Key As String) As Boolean
   On Error Resume Next
   Exists = TypeName(KeyValuePairs.Item(Key)) > ""  ' we can have blank key, empty item
End Property

Public Sub Remove(Key As String)
   'show error if not there rather than On Error Resume Next
   KeyValuePairs.Remove Key
End Sub

Public Sub RemoveAll()
   Set KeyValuePairs = Nothing
   Set KeyValuePairs = New Collection
End Sub

Public Property Get Count() As Long
   Count = KeyValuePairs.Count
End Property

Public Property Get Items() As Variant     ' for compatibility with Scripting.Dictionary
Dim vlist As Variant, i As Long
If Me.Count > 0 Then
   ReDim vlist(0 To Me.Count - 1) ' to get a 0-based array same as scripting.dictionary
   For i = LBound(vlist) To UBound(vlist)
      AssignVariable vlist(i), KeyValuePairs.Item(1 + i).value ' could be scalar or array or object
   Next i
   Items = vlist
End If
End Property

Public Property Get Keys() As String()
Dim vlist() As String, i As Long
If Me.Count > 0 Then
   ReDim vlist(0 To Me.Count - 1)
   For i = LBound(vlist) To UBound(vlist)
      vlist(i) = KeyValuePairs.Item(1 + i).Key   '
   Next i
   Keys = vlist
End If
End Property

Public Property Get KeyValuePair(Index As Long) As Variant  ' returns KeyValuePair object
    Set KeyValuePair = KeyValuePairs.Item(1 + Index)            ' collections are 1-based
End Property

Private Sub AssignVariable(variable As Variant, value As Variant)
   If IsObject(value) Then
      Set variable = value
   Else
      Let variable = value
   End If
End Sub

Public Sub DebugPrint()
   Dim lItem As Long, lIndex As Long, vItem As Variant, oKVP As KeyValuePair
   lItem = 0
   For Each oKVP In KeyValuePairs
      lItem = lItem + 1
      Debug.Print lItem; oKVP.Key; " "; TypeName(oKVP.value);
      If InStr(1, TypeName(oKVP.value), "()") > 0 Then
         vItem = oKVP.value
         Debug.Print "("; CStr(LBound(vItem)); " to "; CStr(UBound(vItem)); ")";
         For lIndex = LBound(vItem) To UBound(vItem)
            Debug.Print " (" & CStr(lIndex) & ")"; TypeName(vItem(lIndex)); "="; vItem(lIndex);
         Next
         Debug.Print
      Else
         Debug.Print "="; oKVP.value
      End If
   Next
End Sub

'NB VBA Collection object index is 1-based, scripting.dictionary items array is 0-based
'cf Scripting.Dictionary Methods s.Add(Key, Item), s.CompareMode, s.Count, s.Exists(Key); _
   s.Item(Key) - updateable, s.Items, s.Key(Key), s.Keys, s.Remove(Key), s.RemoveAll
'Scripting.Dictionary has no index number; you can index the 0-based variant array of Items returned
'  unlike Collections which can be indexed starting at 1
'Efficient iteration is For Each varPair in thisdic.KeyValuePairs
'Another difference I introduce is that in a scripting.dictionary, the doc says
'  If key is not found when changing an item, a new key is created with the specified newitem.
'  If key is not found when attempting to return an existing item, a new key is created and its corresponding item is left empty.
'but I want to raise an error when addressing a key that does not exist
'similarly, the scripting.dictionary will create separate integer and string keys for eg 2

【讨论】:

  • 我讨厌接受@Tim Williams 最初给出的答案,但我一直在等待他将这个评论作为答案将近一年。感谢您发布解决方案和提取实际代码。
  • 谢谢,我讨厌链接失效。这个问题正是我需要问的。不得不挖掘 cmets 令人失望。我希望这可以节省其他人一些时间。
  • Patirk 的实现不适用于 Mac 上的 MS Office 2016。我使用了另一种实现。请在我的回答中查看。
  • 如何使用它?谢谢...任何方法都会很棒
【解决方案2】:

Patirk 的实现不适用于 Mac 上的 MS Office 2016。我利用了 Tim Hall 的实现。 这是链接:https://github.com/VBA-tools/VBA-Dictionary

截至 2017 年 9 月,将 cls 文件导入 Excel 也无法在 Mac 上的 MS Office 2016 中工作。所以我必须创建一个类模块并在该模块中手动复制和粘贴 Dictionary.cls 的内容,同时删除来自 Dictionary.cls 的元信息,例如 VERSION 1.0 CLASSBEGINENDAttribute

【讨论】:

    【解决方案3】:

    我终于更新了 Excel 2016 for Mac 的文件。 http://www.sysmod.com/Dictionary.zip (字典中的大写D)

    解压并导入类文件(在 Excel 2016 for Mac 16.13 Build 424 中测试,2018 年 4 月 27 日)

    我给 MS 的错误报告位于 answers.microsoft.com Excel 16.13 for Mac User Defined Class passed as parameter all properties are Null

    如果我错过了其他任何事情,请告诉我! 祝你好运,

    帕特里克·奥贝恩

    【讨论】:

    • 链接失效
    • Chrome 抱怨提供的链接下载不安全。该站点的证书由 odin.com 颁发,不受 chrome 信任。忽略后,下载似乎没问题。
    • 是的,它只是一个 http 站点
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-19
    • 2020-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多