VBA 7.1通过循环遍历集合来设置多个类属性

时间:2017-06-29 16:18:48

标签: vba loops collections user-defined-types

自学成才的VBA noob。如果我违反礼仪或者问别人已经知道的事我很抱歉。而且,如果我做的事情看起来很疯狂,那是因为这是我能想到或实际工作的唯一方式。在我的工作中有一个部门可以将我的临时代码变成体面的东西,但我必须首先给他们一个可行的模型。

我有两个本机VBA程序。一个是终端模拟器,我用它来刮取大型机数据并构造一个自定义类对象,然后打算将它传递给MS Excel进行数字运算。我坚持使用VBA,直到我能说服IT人员我值得使用Visual Studio许可证和脚本访问权限。此外,我必须在内存中传递该类,而不是在程序崩溃时传递电子表格;允许丢失的文件中没有松散,易于恢复的数据。

数据是最多包含99行的发票,每行可以对项目或服务收费。发票是自定义发票类,每行是包含在一组行中的自定义行类。我已经构建了所有内容并且正在工作,但我一直试图将线对象设置为其发票行属性。具有这种效果的东西:

For x = 1 To intLines
    Invoice.Linex = cLines.Item(x)
Next x

希望在Excel中我可以使用这样的发票:

currTotalChrg = Invoice.Line01.Charge + Invoice.Line02.Charge

我查看了CallByName函数但无法使其工作,并且无法找到一个在线示例来向我展示如何正确设置它。没有它,我不知道如何制作我所见过的其他人称之为包装器来构造和执行这些线。如果必须,我可以构建一个SelectCasenstein来完成这项工作,但必须有一个更好的方法。由于我不能发布代码(专有问题和政府法规),我的答案含糊不清;如果指向正确的方向,我可以弄清楚螺母和螺栓。

Thanx的时间和帮助!

2 个答案:

答案 0 :(得分:3)

似乎你想要一个 try { LyncClient lyncClient = LyncClient.GetClient(); Contact contact; List<object> endPoints = new List<object>(); Dictionary<string, string> phoneNumbers = new Dictionary<string, string>(); contact = lyncClient.ContactManager.GetContactByUri("sip:myusername@domain.com"); //PASS THE SIP ADDRESS HERE var telephoneNumber = (List<object>)contact.GetContactInformation(ContactInformationType.ContactEndpoints); //var contactName = contact.GetContactInformation(ContactInformationType.DisplayName).ToString(); //var availability = contact.GetContactInformation(ContactInformationType.Activity).ToString(); //foreach (object endPoint in telephoneNumber) //{ //Console.WriteLine(((ContactEndpoint)endPoint).DisplayName + " " + ((ContactEndpoint)endPoint).Type.ToString()); //} endPoints = telephoneNumber.Where<object>(N => ((ContactEndpoint)N).Type == ContactEndpointType.HomePhone || ((ContactEndpoint)N).Type == ContactEndpointType.MobilePhone || ((ContactEndpoint)N).Type == ContactEndpointType.OtherPhone || ((ContactEndpoint)N).Type == ContactEndpointType.WorkPhone).ToList<object>(); foreach (var endPoint in endPoints) { //Console.WriteLine(((ContactEndpoint)test).DisplayName.ToString()); string numberType = Regex.Replace(((ContactEndpoint)endPoint).Type.ToString(), @"Phone", ""); //string number = Regex.Replace(((ContactEndpoint)endPoint).DisplayName.ToString(), @"[^0-9]", ""); string number = ""; //Numbers only with dashes if (Regex.IsMatch(((ContactEndpoint)endPoint).DisplayName.ToString(), @"^\d{3}-\d{3}-\d{4}$")) { number = ((ContactEndpoint)endPoint).DisplayName.ToString(); try { phoneNumbers.Add(numberType, number); } catch { } } //Console.WriteLine(numberType + " " + number); } foreach (var entry in phoneNumbers) { //entry.Key is the PhoneType //entry.Value is the Phone Number } } catch (Exception ex) { MessageBox.Show("An error occurred: " + ex.Message); } 集合类,它包含Invoice个对象并公开InvoiceLineItem属性。

您无法直接在VBE中编辑模块/成员属性,但如果您希望能够通过良好的TotalAmount循环迭代发票的订单项,那么您将拥有找到一种方法。一种方法是导出类并在您喜欢的文本编辑器中编辑它以添加属性,保存它,然后将其重新导入到您的VBA项目中。 Rubberduck的下一个版本将允许您使用&#34;注释&#34; (魔术评论),我也在这里包括:

For Each

你可以在课外实现总和,但IMO将是特征羡慕;发票希望能够告诉您其总金额&amp;量。

所以我会为此公开属性:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Invoice"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Public Const MAX_LINE_ITEMS As Long = 99

Private Type TInvoice
    InvoiceNumber As String
    InvoiceDate As Date
    'other members...
    LineItems As Collection
End Type

Private this As TInvoice

Private Sub Class_Initialize()
    this.LineItems = New Collection
End Sub

'@Description("Adds an InvoiceLineItem to this invoice. Raises an error if maximum capacity is reached.")
Public Sub AddLineItem(ByVal lineItem As InvoiceLineItem)
Attribute AddLineItem.VB_Description = "Adds an InvoiceLineItem to this invoice."
    If this.LineItems.Count = MAX_LINE_ITEMS Then
        Err.Raise 5, TypeName(Me), "This invoice already contains " & MAX_LINE_ITEMS & " items."
    End If

    this.LineItems.Add lineItem
End Sub

'@Description("Gets the line item at the specified index.")
'@DefaultMember
Public Property Get Item(ByVal index As Long) As InvoiceLineItem
Attribute Item.VB_Description = "Gets the line item at the specified index."
Attribute Item.VB_UserMemId = 0
    Set Item = this.LineItems(index)
End Property

'@Description("Gets an enumerator that iterates through line items.")
'@Enumerator
Public Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_Description = "Gets an enumerator that iterates through line items."
Attribute NewEnum.VB_UserMemId = -4
    Set NewEnum = this.LineItems.[_NewEnum]
End Property

'...other members...

然后你也可以......

'@Description("Gets the total amount for all line items.")
Public Property Get TotalAmount() As Double
    Dim result As Double
    Dim lineItem As InvoiceLineItem
    For Each lineItem In this.LineItems
        result = result + lineItem.Amount
    Next
    TotalAmount = result
End Property

'@Description("Gets the total quantity for all line items.")
Public Property Get TotalQuantity() As Double
    Dim result As Double
    Dim lineItem As InvoiceLineItem
    For Each lineItem In this.LineItems
        result = result + lineItem.Quantity
    Next
    TotalQuantity = result
End Property

从您的帖子和问题的性质来看,我怀疑您的类有什么,99个属性,发票上每行一个?

  

我坚持使用VBA,直到我能说服IT人员说我值得拥有Visual Studio许可和脚本访问权限。

VBA就像面向对象一样,是一种语言,与其他任何&#34;真正的&#34;您可以使用Visual Studio的语言。上面的解决方案与我在C#或VB.NET中实现它的方式非常相似。如果您的VBA类有每个可能的发票行的成员,那么您的思考是错误的 - 而不是您正在使用的语言。

出于错误的原因停止讨厌VBA。编辑很糟糕,克服它。

答案 1 :(得分:0)

我给你一个部分答案:不完全是你要求的,但它会显示一个可以做到的语法。

我有一个&#39;总计&#39; class - 字典的简单包装器 - 允许您指定命名字段并开始添加值。这是微不足道的,做到这一点并没有太大的收获......但请耐心等待:

Dim LoanTotals As clsTotals
Set LoanTotals = New clsTotals
For Each Field In LoanFileReader.Fields LoanTotals.CreateField Field.Name Next Field
For Each LineItem In LoanFileReader LoanTotals.Add "LoanAmount", LineItem!LoanAmount LoanTotals.Add "OutstandingBalance", LineItem!OutstandingBalance LoanTotals.Add "Collateral", LineItem!Collateral Next LineItem
课堂上的实施细节并不十分有趣 - 你可以知道这一切都以Debug.Print LoanTotals.Total("LoanAmount")

结尾

...但是,如果我实施了这个怎么办?

Dim LoanTotals As clsTotals
Set LoanTotals = New clsTotals
For Each Field In LoanFileReader.Fields LoanTotals.CreateCommand Field.Name, Field.MainframeCommand Next Field

......使用这样的内部实现:

Public Sub ExecuteCommand(CommandName, ParamArray() Args())
' Wrapper for objMainService, ends a command to the COM interface of the Mainframe client
CallByName objMainService, CommandName, vbMethod, Args
End Sub
换句话说,您可以连接Shell命令来执行这些大型机功能。

...现在你已经填充了一个VB类,它封装了运行时提供的一组函数的原始API。

正如我所说:它不是完全您想要的东西,但它可能让您更接近您需要的解决方案。

为了完整起见,这里是&#39; Totals&#39;的代码。类:

用于聚合运行时指定的命名字段的总计的VBA类:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "clsTotals"
Attribute VB_Description = "Simple 'Totals' class based on a Scripting.Dictionary object"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
' Simple 'Totals' class based on a Scripting.Dictionary object
' Nigel Heffernan, Excellerando.Blogspot.com April 2009
' As it's based on a dictionary, the 'Add' and 'Reset' methods ' support implicit key creation: if you use a new name (or you ' mistype an existing name) a new Totals field will be created

' Coding Notes:
' This is a wrapper class: 'Implements' is not appropriate, as ' we are not reimplementing the class. Or not very much. Think ' of it as the 'syntactic sugar' alternative to prefixing all ' our method calls in the extended class with 'Dictionary_'.
Private m_dict As Scripting.Dictionary Attribute m_dict.VB_MemberFlags = 40 Attribute m_dict.VB_VarDescription = "(Internal variable)"
Public Property Get Sum(FieldName As String) As Double Attribute Sum.VB_Description = "Returns the current sum of the specified field." Attribute Sum.VB_UserMemId = 0 ' Returns the current sum of the specified field
Sum = m_dict(FieldName)
End Property

Public Sub CreateField(FieldName As String) Attribute CreateField.VB_Description = "Explicitly create a new named field" ' Explicitly create a new named field
If m_dict.Exists(FieldName) Then Err.Raise 1004, "Totals.CreateField", "There is already a field named '" & FieldName & "' in this 'Totals' object." Else m_dict.Add FieldName, 0# End If
End Sub

Public Sub Add(FieldName As String, Value As Double) Attribute Add.VB_Description = "Add a numeric amount to the field's running total \r\n Watch out for implicit field creation." ' Add a numeric amount to the field's running total ' Watch out for implicit field creation.
m_dict(FieldName) = m_dict(FieldName) + Value
End Sub

Public Sub Reset(FieldName As String) Attribute FieldName.VB_Description = "Reset a named field's total to zero \r\n Watch out for implicit key creation" ' Reset a named field's total to zero ' Watch out for implicit key creation
m_dict(FieldName) = 0#
End Sub

Public Sub ResetAll() Attribute ResetAll.VB_Description = "Clear all the totals" ' Clear all the totals
m_dict.RemoveAll Set m_dict = Nothing
End Sub

Public Property Get Fields() As Variant Attribute Fields.VB_Description = "Return a zero-based vector array of the field names" 'Return a zero-based vector array of the field names
Fields = m_dict.Keys
End Property
Public Property Get Values() As Variant Attribute Values.VB_Description = "Return a zero-based vector array of the current totals" 'Return a zero-based vector array of the current totals
Fields = m_dict.Items
End Property

Public Property Get Count() As Long Attribute Count.VB_Description = "Return the number of fields" 'Return the number of fields
Count= m_dict.Count
End Property
Public Property Get Exists(FieldName As String) As Boolean Attribute Count.VB_Description = "Return a zero-based vector array of the field names" 'Return True if a named field exists in this instance of clsTotals
Exists = m_dict.Exists(FieldName)
End Property

Private Sub Class_Initialize()
Set m_dict = New Scripting.Dictionary m_dict.CompareMode = TextCompare

End Sub

Private Sub Class_Terminate()
m_dict.RemoveAll Set m_dict = Nothing
End Sub
如果您无法将属性语句导入项目,请将其注释掉。