【问题标题】:Returns dictionary (associated array) of sub-folders and the amount of email contained within each subfolder within main folder返回子文件夹的字典(关联数组)和主文件夹中每个子文件夹中包含的电子邮件数量
【发布时间】:2019-04-11 14:59:40
【问题描述】:

我提供了一个解决方案来单击一个文件夹并返回该文件夹中包含的项目数。

现在,他们询问是否可以保留该退货,并按点击的主文件夹中的子文件夹进行细分。

例子:

INBOX 有 3 个子文件夹:Folder1、Folder2、Folder3

INBOX 包含 3 封电子邮件,其中一封来自每个子文件夹。 因此: 收件箱总数:3
文件夹 1 总计:1
文件夹 2 总数:1
文件夹 3 总数:1

我创建了一个循环,将主文件夹中包含的所有子文件夹放入一个数组中。

我的下一个想法是将其转换为字典,在其中我将包含的项目预设为 0。然后在使用循环形成字典时,我正在使用当前用于检查某些内容是否在日期范围内以查看什么它所属的“文件夹”并将我在字典(关联数组)中预设为零的值加一,与“匹配”一样多次

以下是我尝试过的:

Sub Countemailsperday()
Dim objOutlook As Object, objnSpace As Object, objFolder As MAPIFolder
Dim EmailCount As Integer
Dim ODate As String
Dim ODate2 As String
Dim dict As Dictionary
Set dict = New Dictionary
Dim coll As New Collection
Dim oDict As Object

Set oDict = CreateObject("Scripting.Dictionary")
' Dim Dict As Scripting.Dictionary

ODate = InputBox("Start Date? (format YYYY-MM-DD")
ODate2 = InputBox("End Date? (format YYYY-MM-DD")
Set objOutlook = CreateObject("Outlook.Application")
Set objnSpace = objOutlook.GetNamespace("MAPI")
On Error Resume Next
Set objFolder = Application.ActiveExplorer.CurrentFolder
If Err.Number <> 0 Then
    Err.Clear
    MsgBox "No such folder."
    Exit Sub
End If
EmailCount = objFolder.Items.Count
MsgBox "Number of emails in the folder: " & EmailCount, , "email count"
Dim ssitem As MailItem
Dim dateStr As String
Dim numholder As Integer
Dim myItems As Outlook.Items
'Dim dict As Object
Dim msg As String
Dim oParentFolder As MAPIFolder
Dim i As Integer
Dim iElement As Integer
Dim sArray() As String
Dim ArrayLen As Integer
Dim Subtractor As Integer
Dim str As String
ReDim sArray(0) As String
Set oParentFolder = objFolder
Set myItems = objFolder.Items
'Set Dict = New Scripting.Dictionary
If oParentFolder.Folders.Count Then
    For i = 1 To oParentFolder.Folders.Count
        If Trim(oParentFolder.Folders(i).Name) <> "" Then
            iElement = IIf(sArray(0) = "", 0, UBound(sArray) + 1)
            ReDim Preserve sArray(iElement) As String
            sArray(iElement) = oParentFolder.Folders(i).Name
        End If
    Next i
Else
    sArray(0) = oParentFolder.Name

End If

ArrayLen = UBound(sArray) - LBound(sArray) + 1
'MsgBox "thingy thing"
'MsgBox "thing" & sArray(1) ' This is how to iterate through the Dictionary
myItems.SetColumns ("ReceivedTime")
' Determine date of each message:
 ' MsgBox DateValue(ODate)
For Subtractor = 0 To (ArrayLen - 1)
    If oDict.Exists(sArray(Subtractor)) Then
        oDict(sArray(Subtractor)).Add

With dict
    For Subtractor = 0 To (ArrayLen - 1)
        If ArrayLen = 1 Then
            .Add Key = objFolder.Name, Item = 0
        Else
            If Subtractor = 0 Then
                .Add Key = CStr(sArray(Subtractor)), Item = 0
            Else

            End If
            str = CStr(sArray(Subtractor))
        End If
    Next Subtractor
End With
MsgBox str
If dict.Exists(str) Then
    Debug.Print (dict(str))
Else
    Debug.Print ("Not Found")
End If
MsgBox dict(str)
numholder = 0
'For Each
For Each myItem In myItems
    dateStr = GetDate(myItem.ReceivedTime)
     ' MsgBox DateValue(dateStr)
    If DateValue(dateStr) >= DateValue(ODate) And DateValue(dateStr) <= DateValue(ODate2) Then
        If Not dict.Exists(dateStr) Then
            dict(dateStr) = 0
            numholder = numholder
        End If
        dict(dateStr) = CLng(dict(dateStr)) + 1
        numholder = numholder + 1
    End If
Next myItem
' Output counts per day:
msg = ""
For Each o In dict.Keys
    msg = msg & o & ": " & dict(o) & " items" & vbCrLf
Next
If msg = "" Then
    MsgBox "There are no emails during this time range"
End If
If msg <> "" Then
    MsgBox "Number of emails during date range: " & numholder
    MsgBox msg
End If
Set objFolder = Nothing
Set objnSpace = Nothing
Set objOutlook = Nothing
End Sub

Function GetDate(dt As Date) As Date
GetDate = Year(dt) & "-" & Month(dt) & "-" & Day(dt)
End Function

我要完成以下工作:

收件箱总数:3
文件夹 1 总计:1
文件夹 2 总数:1
文件夹 3 总数:1

以及处理单击的文件夹不包含子文件夹的情况。

【问题讨论】:

  • 请详细说明:在起作用吗?
  • 嗨@peakpeak,我最初尝试使用 Dictionary 实例将 sArray 转换为字典(然后我发现还有一个 scripting.dictionary 并查看它是否具有不同的属性)- - 它不会像我预期的那样附加到字典中,而是会删除以前包含的内容,并用 With Dict 语句替换“附加”到字典中的内容。这就是我现在卡住的地方。我也很好奇这是否是最好的方法,或者是否有更好的方法,但根据当前的思路,这是目前的卡住。
  • 更好是一把多刃剑。为了什么?您需要定义在没有“更好”方法的情况下要完成的工作。那么变量太多
  • @peakpeak,我需要能够在单击 Outlook 应用程序上的文件夹时返回某个日期范围内的电子邮件数量(我的代码确实正确处理了日期范围部分以及主要文件夹部分)按主文件夹和子文件夹分组。
  • 好吧,我再说一遍:什么不起作用?

标签: vba loops dictionary outlook


【解决方案1】:

我不明白你的代码。你做事,但不解释它们对你的目标有何贡献。有看起来不相关的日期处理代码。如果一个人可以编写代码并且不需要再次查看它,那么缺少 cmets 就可以了。但通常,在六个月、十二个月或二十个月之后,例行程序需要一些注意。可能存在未正确处理的边缘条件,或者需求已更改。维护记录不佳的代码是一场噩梦。

此答案底部的代码是我几年前编写的例程的简化版本。它并没有完全按照您的要求执行,也没有使用您要求的技术。也许我的代码会被接受。如果没有,我相信我已经提供了足够的解释,让您可以根据您的要求修改我的代码。

首先介绍一下我使用过的技术。正如 peakpeak 所暗示的,我的代码使用递归。我还使用了集合而不是字典。这些技术没有记录在代码中,因为它们是 VBA 的标准功能,我没有在我的代码中记录 VBA。

我不使用字典。集合提供了我曾经需要的所有功能。据我了解,字典与集合有很多共同之处,并且具有集合所缺乏的一些功能。但对我来说更重要的是,它们缺乏一些我认为必不可少的集合功能。

你指定一个集合:

   Dim Coll As New Collection
or
   Dim Coll As Collection
   Set Coll = New Collection

Coll.Add X 将在 Coll 的末尾创建一个包含 X 的新条目。您可以在现有条目的中间添加新条目,也可以删除现有条目,但我在下面的代码中不使用此功能。

Coll.Add X 中,X 几乎可以是任何东西。它可以是一个简单的值,例如字符串、长整数或布尔值。它可以是数组或类的实例。它不能是用户类型的实例。您不能修改集合中的条目。如果您需要修改条目,您必须删除现有条目并将修改后的版本添加到相同的位置。

由于集合中的条目可以是任何东西,因此您需要小心。如果变量 I 是 Long:

I = I + Coll(5)

如果 Coll(5) 是一个字符串或任何其他不能添加到 Long 的东西,将给出运行时错误。

如果您将数组添加到集合中,读取它的语法可能不是很明显。考虑:

Coll.Add VBA.Array(Fldr.Name, Level, NumEmails)

假设上面的Add已经在Coll中创建了第三个条目;即 Coll(3)。那么:

  • Coll(3)(0)FldrName
  • Coll(3)(1)Level
  • Coll(3)(2)NumEmails

请注意,我使用VBA.Array 而不是Array,因为ArrayOption Base 语句的影响。通过使用VBA.Array,我知道下限将始终为零。

仔细想想,也许这种语法并不奇怪。如果我声明Dim Arr(0 To 5) As Long,我写Arr(0) 来访问Arr 的元素0。我的Coll(3) 是一个数组,所以我写Coll(3)(0) 来访问Coll(3) 的元素0。

递归是例程调用自身的地方。这种技术非常适合处理树状结构。有些技术速度更快,而且不会占用大量内存,但这些其他技术都不是那么简单易用。

假设要处理的文件夹层次结构是:

FolderA
  FolderB
    FolderC
    FolderD
      FolderE
  FolderF 
  FolderG

我的例程是NumEmailsByFolder,并且有参数:

  1. 对顶级文件夹的引用
  2. 长级
  3. 对集合 FldrDtls 的引用

您的要求中未提及级别,但如果没有级别,您将无法判断 FolderF 在 FolderA 内。我倾向于将顶层视为 0 级,但您可以使用任何您认为方便的值。

外部例程创建一个空集合,我的例程调用 FldrDtls,然后调用:

NumEmailsByFolder([FolderA], 0, [FldrDtls]) 

其中 [X] 表示对对象 X 的引用。

NumEmailsByFolder 计算 FolderA 中的电子邮件数量,向 FldrDtls 添加一个名为“FolderA”、级别 0 和电子邮件计数的条目。然后,它会调用具有级别 1 的 FolderB、FolderF 和 FolderG 自身。这使得代码非常简单。递归的秘密在于解释器执行所有不同调用的顺序:

Calls in sequence executed                      Entry added to FldrDtls
NumEmailsByFolder([FolderA], 0, [FldrDtls])     FolderA     0  Count
NumEmailsByFolder([FolderB], 1, [FldrDtls])     FolderB     1  Count
NumEmailsByFolder([FolderC], 2, [FldrDtls])     FolderC     2  Count
NumEmailsByFolder([FolderD], 2, [FldrDtls])     FolderD     2  Count
NumEmailsByFolder([FolderE], 2, [FldrDtls])     FolderE     3  Count
NumEmailsByFolder([FolderF], 1, [FldrDtls])     FolderF     1  Count
NumEmailsByFolder([FolderG], 1, [FldrDtls])     FolderG     1  Count

FldrDtls 中的条目按所需顺序排列,子文件夹位于其父文件夹之后。我的示例层次结构中只有四个级别,但相同的代码将处理 10 或 100 个级别,其中所有困难的东西都由解释器处理。

大多数人一开始似乎觉得递归很难理解;很多年前我在大学里教它的时候我确实做到了。然后突然间你看到了光明,你不再明白为什么你觉得这很困难。我把它比作学开车。在第一节课结束时,您知道您将永远无法转动方向盘、踩下一个或多个踏板、移动变速杆、照镜子和使用指示器,同时试图避开其他道路用户同时。但在几节课之后,你可以做所有这些,甚至更多。

我的日常是:

Sub NumEmailsByFolder(ByRef FldrPrnt As Folder, ByVal Level As Long, _
                      ByRef FldrDtls As Collection)

  ' Adds an entry to FldrDtls for FldrPrnt.
  ' Calls itself for each immediate subfolder of FldrPrnt.

  ' Each entry in FldrDtls is an zero-based array containing:
  '  * (0) Folder name
  '  * (1) Level of folder within hierarchy.  The level of the first (top)
  '        folder is as specified in the call.  Each level down is one more.
  '  * (2) Number of emails in folder. Note: this value does not include
  '        any emails in any subfolders

  ' The external routine that calls this routine will set the parameters:
  '  * FldrPrnt can be a Store or a MAPIFolder at any level with the
  '    folder hierarchy.
  '  * Level might typically be set to zero or one but the initial value
  '    is unimportant to this routine.
  '  * FldrDtls would normally be an empty collection.  This is not checked
  '    so FldrDtls may contain existing entries if this is convenient for
  '    the calling routine.

  ' On return to the external routine, the entries in FldrDtls might be:
  '    Inbox        0     10
  '    SubFldr1     1      5
  '    SubSubFldr1  2      3
  '    SubSubFldr2  2      4
  '    SubFldr2     1      9

  Dim ErrNum As Long
  Dim InxI As Long
  Dim InxS As Long
  Dim ItemsCrnt As Items
  Dim SubFldrsCrnt As Folders
  Dim NumMailItems As Long

  With FldrPrnt

    'Count MailItems, if any
    Err.Clear
    NumMailItems = 0

    ' In the past, I have had code crash when I attempted to access the
    ' Items of a folder but I have had no such error recently. This could
    ' be because I am now retired and my employer's Outlook installation
    ' had folders without items.  Alternatively, it could be because
    ' Outlook 2016 is more robust than Outlook 2003. I use On Error to
    ' ensure any such error does not crash my routine.

    On Error Resume Next
    Set ItemsCrnt = FldrPrnt.Items
    ErrNum = Err.Number
    On Error GoTo 0
    If ErrNum = 0 Then
      ' Only attempt to count MailItems within FldrPrnt if attempting to
      ' access its Items does not give an error.
      For InxI = 1 To ItemsCrnt.Count
        If ItemsCrnt(InxI).Class = olMail Then
          NumMailItems = NumMailItems + 1
        End If
      Next
    End If

    FldrDtls.Add VBA.Array(FldrPrnt.Name, Level, NumMailItems)

    Set SubFldrsCrnt = FldrPrnt.Folders

    ' See above for explanation of On Error

    ErrNum = Err.Number
    On Error GoTo 0
    If ErrNum = 0 Then
      ' Only attempt to count MailItems within FldrPrnt if attempting to
      ' access its Folders does not give an error.
      For InxS = 1 To SubFldrsCrnt.Count
        Call NumEmailsByFolder(SubFldrsCrnt(InxS), Level + 1, FldrDtls)
      Next
    End If

  End With

End Sub

我希望你同意这段代码很简单,考虑到它可以实现什么。如果我认为删除错误处理代码是安全的,那么例程会更小。

为演示如何调用此例程,添加以下代码:

Option Explicit
Sub TestNumEmailsByFolder()

  Dim FldrDtls As Collection
  Dim Fldr1 As Folder
  Dim Fldr2 As Folder
  Dim Fldr3 As Folder
  Dim FldrCrnt As Folder
  Dim FldrInx As Variant
  Dim InxF As Long

  Set Fldr1 = Session.Folders("johndoe@acme.com").Folders("Inbox").Folders("Test")
  Set Fldr2 = Session.Folders("johndoe@acme.com").Folders("Inbox")
  Set Fldr3 = Session.Folders("johndoe@acme.com")

  For Each FldrInx In Array(Fldr1, Fldr2, Fldr3)
    Set FldrCrnt = FldrInx
    Set FldrDtls = New Collection
    Call NumEmailsByFolder(FldrCrnt, 0, FldrDtls)
    Debug.Print "Emails"
    For InxF = 1 To FldrDtls.Count
      Debug.Print PadL(FldrDtls(InxF)(2), 5) & _
                  Space(1 + FldrDtls(InxF)(1) * 2) & FldrDtls(InxF)(0)
    Next
  Next

End Sub
Public Function PadL(ByVal Str As String, ByVal PadLen As Long, _
                     Optional ByVal PadChr As String = " ") As String

  ' Pad Str with leading PadChr to give a total length of PadLen
  ' If the length of Str exceeds PadLen, Str will not be truncated

  '   Sep15 Coded
  ' 20Dec15 Added code so overlength strings are not truncated
  ' 10Jun16 Added PadChr so could pad with characters other than space

  If Len(Str) >= PadLen Then
    ' Do not truncate over length strings
    PadL = Str
  Else
    PadL = Right$(String(PadLen, PadChr) & Str, PadLen)
  End If

End Function

修改Set Fldr1Set Fldr2Set Fldr3 语句以引用系统上的文件夹。我从层次结构底部的文件夹开始,然后是中间的文件夹,然后是顶部的文件夹。我建议您选择一组类似的文件夹。研究立即窗口的输出并考虑如何创建列表序列。

这是你想要的例程吗?

它使用集合而不是字典?这有关系吗?如果我对字典的理解是正确的,那么字典就不合适了。

您使用数组和ReDim Preserve。当您不知道需要多少条目时,集合是一个不错的选择。 ReDim Preserve 在时间和内存方面是一个昂贵的命令。解释器必须为扩大的数组找到一个足够大的新内存块。它必须将旧数组中的值复制到新数组中并初始化新元素。最后,它必须释放旧数组以进行垃圾回收。如果我需要将最终结果放在数组中,那么对于这种类型的问题,我通常在集合中构建列表,根据集合的大小调整数组的大小,然后将数据从集合复制到数组。

针对某个文件夹的电子邮件计数不包括其子文件夹中的电子邮件。这似乎是一个要求。您不能修改集合中的条目,因此,如果这是必需的,我会将其作为转换为数组的一部分来处理。

子文件夹未按字母顺序列出。我从未正确调查过,但我怀疑子文件夹按创建的顺序列出。如果这不令人满意,您将需要排序。有几种可能的方法。鉴于每个文件夹通常会有少量子文件夹,我怀疑最简单的方法是最好的。如果您需要更强大的功能,我有一个快速排序的实现,它使用索引来避免对源列表进行排序。

【讨论】:

  • 托尼,我想这可能足以让我想出一个解决方案。如果是这样,我会以干净且有据可查的格式发布最终结果。
猜你喜欢
  • 1970-01-01
  • 2015-09-14
  • 2022-01-10
  • 2012-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多