I have a userform which assembles itself at runtime, by looking in a folder and extracting all the pictures from it into image-controls on my form. What makes the process a little more complex is that I'm also using the image-controls' events to run some code.
As a simplified example - I have a form which creates a picture at runtime, the picture has an on-click event to clear its contents. To do this I have a custom class to represent the image object
In a blank userform called "imgForm"
Dim oneImg As New clsImg 'our custom class
Private Sub UserForm_Initialize()
Set oneImg.myPic = Me.Controls.Add("Forms.Image.1") 'set some property of the class
oneImg.Init 'run some setup macro of the class
End Sub
In a class module called "clsImg"
Public WithEvents myPic As MSForms.Image
Public Sub Init() 'can't put in Class_Initialise as it is called before the set statement - so myPic is still empty at that point
myPic.Picture = LoadPicture(path/image)
End Sub
Public Sub myPic_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
onePic.Picture = Nothing
End Sub
The problem is, this doesn't display the changes, and I realised I needed a imgForm.Repaint in there somewhere - the question is, where?
Attempts
First option is to put it in the Init() sub of clsImg. (ie. have a line imgForm.Repaint at the end of the click event) That works, but not ideal as the class can then only be used with the userform of the correct name.
A 2nd idea was to pass the userform as an argument to Init()
Public Sub Init(uf As UserForm) 'can't put in Class_Initialise as it is called before the set statement - so myPic is still empty at that point
myPic.Picture = LoadPicture(path/image)
uf.Repaint
End Sub
And called with
oneImg.Init Me
That works too, but would mean that wherever I require a repaint, I would have to pass the parameter which is also not ideal - the code is in reality a lot more complex than is shown here, so I don't want to have to add in this extra parameter unless necessary
The third option which I'm currently using is to pass the userform object to the class and save it there.
So with a Public myForm As UserForm at the top of my class module, I can pass the userform with the Init(uf As UserForm) and have a
Set myForm = uf 'Works with a private "myForm"/ class Property
Or I can set it directly from the userform code with a
Set clsImg.myForm = Me 'only if "myForm" is Public
But what does this do for memory - does saving the userform as a variable in my class take up a lot of memory? Bear in mind that in my real code I declare an array of clsImgs that can be of the order of >100 instances so I don't really want to be making copies of the UF in each class if that's what this method does. Also, it's ugly
What I really want...
... is a way of telling the userform that it needs to repaint, rather than directly repainting from within the class. To me this says I need an event to occur in my class, which the userform hears with some custom event handler. Exactly how Worksheet_Change works, the sheet object raises a change event, the sheet class code handles it.
Is such a thing possible (I suppose I would have to declare clsImg WithEvents - can you do that for an array?), or is there a better alternative. I'm looking for a method which does not impede performance with a large number of classes declared, as well as one which is portable and easily readable. This is my first use of Classes so I may be missing something really obvious!
Since good practice is that classes are self-contained (as you obviously know) the clsImg should indeed not have to be aware of the UserForm and thus shouldn't tell the UserForm to repaint.
What this calls for, is indeed that the clsImg raises an event that the UserForm hooks into, so it repaints based on that event, or, in your own words: "a way of telling the userform that it needs to repaint."
I replicated your Custom Class (clsImg) as follows (wanted to use a proper Setter / Getter, functionality doesn't really change)
clsImg Code:
Private WithEvents myPic As MSForms.Image 'Because we need the click event.
Public Event NeedToRepaint() 'Because we need to raise an event that the UserForm can hook into.
Public Property Let picture(value As MSForms.Image)
Set myPic = value
End Property
Public Property Get picture() As MSForms.Image
Set picture = myPic
End Property
Public Sub myPic_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
myPic.picture = Nothing
RaiseEvent NeedToRepaint
End Sub
Next, in the UserForm we hook into this NeedToRepaint Event that's raised during the Event Handler of the MouseDown of the picture.
UserForm1 Code:
Private WithEvents oneImg As clsImg 'Our custom class
Private Sub oneImg_NeedToRepaint() 'Handling the event of our custom class
Me.Repaint
End Sub
Private Sub UserForm_Initialize()
Dim tmpCtrl As MSForms.Image
Set oneImg = New clsImg
Set tmpCtrl = Me.Controls.Add("Forms.Image.1")
tmpCtrl.picture = LoadPicture("C:\Path\image.jpg")
oneImg.picture = tmpCtrl
End Sub
The second part of your question is whether you can use this in an array.
The short answer is "no" - Each object would have to have it's own Event Handler. However, there are ways to work around this limitation by using a Collection or some similar approach. Still, this wrapper will have to be "UserForm aware" since that's where you'll be repainting. The approach would be something like in this article
EDIT: A solution / workaround for not being able to use an Array:
Since I really liked this question - Here's another approach.
We can apply somewhat of a PubSub pattern as follows:
I did a quick build for CommandButtons, but no reason that it can not be made for other classes of course.
Publisher class:
Public Event ButtonClicked(value As cButton)
Public Sub RegisterButtonClickEvent(value As cButton)
RaiseEvent ButtonClicked(value)
End Sub
'Add any other events + RegisterSubs.
In a regular class, I setup a factory routine to keep this specific Publisher a singleton (as in: It will always be the very same in memory object that you're pointing at):
Private pub As Publisher
Public Function GetPublisher() As Publisher
If pub Is Nothing Then
Set pub = New Publisher
End If
Set GetPublisher = pub
End Function
Next, we have the UserForm (I just made one with 4 buttons) and the button class to utilize this Publisher. The Userform will just subscribe to the event it raises:
Userform code:
Private WithEvents pPub As Publisher 'Use the Publishers events.
Private button() As cButton 'Custom button array
Private Sub pPub_ButtonClicked(value As cButton) 'Hook into Published event.
MsgBox value.button.Caption
End Sub
Private Sub UserForm_Initialize()
Set pPub = GetPublisher 'Private publisher for getting it's event. Will be always the same object as long as you use "GetPublisher"
Dim i As Integer
Dim btn As MSForms.CommandButton
'Create an array of the buttons:
i = -1
For Each btn In Me.Controls
i = i + 1
ReDim Preserve button(0 To i)
Set button(i) = New cButton
button(i).button = btn
Next btn
End Sub
Last we have the cButton class, that centralizes the button events (through the array). Instead of handling each event individually, we just tell the publisher that an Event has been raised.:
Private WithEvents btn As MSForms.CommandButton
Private pPub As Publisher
Public Event btnClicked()
Private Sub btn_Click()
pPub.RegisterButtonClickEvent Me 'Pass the events to the publisher.
End Sub
Public Property Let button(value As MSForms.CommandButton)
Set btn = value
End Property
Public Property Get button() As MSForms.CommandButton
Set button = btn
End Property
Private Sub Class_Initialize()
Set pPub = GetPublisher
End Sub
With this approach we have one "Publisher" that can handle any event from specific classes that register the right event with it. You could also add image events, workbook events, etc.
The publisher itself raises the events we need based on what gets passed to it.
This way the UserForm can be agnostic of the button class and vice versa.
Based on what is supported in VBA, I'm quite confident this is the cleanest approach for your scenario. If anyone has a better idea, I'd love to see another answer.
I did the following, If you pass the control as a control, you can use the parent.
In my form
Public c As Collection
Private Sub UserForm_Initialize()
Dim ctl As Control
Dim cls As clsCustomImage
Set c = New Collection
For Each ctl In Me.Controls
If TypeName(ctl) = "Image" Then
Set cls = New clsCustomImage
cls.init ctl
c.Add cls, CStr(c.Count)
End If
Next ctl
End Sub
and in my class, clsCustomImage
Private WithEvents i As MSForms.Image
Private frm As UserForm
public event evtRepaint
Public Sub init(c As control)
Set frm = c.parent
Set i = c
End Sub
Private Sub Class_Initialize()
End Sub
Private Sub Class_Terminate()
Set frm = Nothing
Set i = Nothing
End Sub
'
Private Sub i_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
i.Picture = Nothing
frm.Repaint
raiseevent evtRepaint
End Sub
EDIT
To have a single handler, you'd need to look at something along these lines, in a class called say clsHoldAndHandle
Private c As Collection
Private f As UserForm
Private WithEvents cls As clsCustomImage
Public Sub AddControl(ctl As Control)
Set cls = new clsCustomImage
If f Is Nothing Then Set f = ctl.Parent
cls.init ctl
c.Add cls, CStr(c.Count)
End Sub
Private Sub Class_Initialize()
Set c = New Collection
End Sub
Private Sub cls_evtRepaint()
f.Repaint
End Sub
Related
tl;dr Is there a way to enable events for built-in objects without coupling the event to the original object's parent, assuming the event interacts with the parent?
Disclaimer 1: I don't have access to MS Office on my home machine and therefore type all code from memory. I'm sorry if something's incorrect.
Disclaimer 2: This post is incredibly lengthy because I've been trying to figure out how to do this process for several years but never quite hit the correct Google terms to figure it out. I do a lot of explaining in the hopes that it might help someone with the same issues.
The Original Problem
I've had this longstanding issue of having Userforms with near-identical event handling but no way to compact the code into a generic solution. For example, let's say I have a Userform with a bunch of Command Buttons that all do the same thing when clicked. Traditionally, you would have to include something like the following in Userform1
Private Sub CommandButton1_Click()
Me.DoSomething CommandButton1.Name
End Sub
Private Sub CommandButton2_Click()
Me.DoSomething CommandButton2.Name
End Sub
'...a bunch more of these...'
Private Sub CommandButtonN_Click()
Me.DoSomething CommandButtonN.Name
End Sub
This is annoying to setup and hurts readability for a large number of buttons.
The Naive Solution
I recently discovered that wrapper classes can be utilized to make a generic WithEvents handler for built-in objects. Applying this to our previous example, we create an EventCommandButton.cls Class with the following code
Private WithEvents mCommandButton as MSForms.CommandButton
Private Sub mCommandButton_Click()
mCommandButton.Parent.DoSomething(mCommandButton.Name)
End Sub
Property Get CommandButton() as MSForms.CommandButton
Set CommandButton = mCommandButton
End Property
Property Set CommandButton(cmdBtn as MSForms.CommandButton)
Set mCommandButton = cmdBtn
End Property
And Userform1 turns into
Private EventCommandButtons() as New EventCommandButton
Private Sub Userform1_Initialize()
For Each ctl in Me.Controls
If TypeName(ctl) = "CommandButton" Then
i = i + 1
ReDim Preserve EventCommandButtons(1 to i)
Set EventCommandButtons(i).CommandButton = ctl
End If
Next
End Sub
This approach saves space and looks comparatively nice, but it presents (at least) 2 major issues:
All of Userform1's control events are no longer housed in its own code
Our EventCommandButton requires a specific procedure (DoSomething(str)) to exist in its parent or else we'll get an error.
A Slight Refinement
The solution I'm currently implementing is to take a more intuitive approach that returns control of the event handling back to where you'd expect it to be. In EventCommandButton.cls we add a new property to specify where we expect to find the return code:
Private mCommandButton as MSForms.CommandButton
Private mCallback as Object
Private Sub mCommandButton_Click()
'Some error handling should be here to check that mCallback is set
mCallback.EventCommandButton_Click(mCommandButton)
End Sub
Property Get Callback() as Object
Set Callback = mCallback
End Property
Property Set Callback(ParentObject as Object)
'Let's not assume it's always the .Parent
Set mCallback = ParentObject
End Property
Property Get CommandButton() as MSForms.CommandButton
Set CommandButton = mCommandButton
End Property
Property Set CommandButton(cmdBtn as MSForms.CommandButton)
Set mCommandButton = cmdBtn
End Property
And in Userform1
Private EventCommandButtons() as New EventCommandButton
Public Sub EventCommandButton_Click(cmdBtn as MSForms.CommandButton)
Me.DoSomething cmdBtn.name
End Sub
Private Sub Userform1_Initialize()
For Each ctl in Me.Controls
If TypeName(ctl) = "CommandButton" Then
i = i + 1
ReDim Preserve EventCommandButtons(1 to i)
Set EventCommandButtons(i).CommandButton = ctl
Set EventCommandButtons(i).Callback = Me 'Set new property
End If
Next
End Sub
This approach feels close to the intuitive solution of the original problem (with some extra steps involved) and resolves issue #1 from the previous, but we still have issues:
There's still coupling between the Class and Userform, now requiring that each parent object must have corresponding pseudo-event procedures of the form Public Sub [ClassName]_[EventName]([OriginalObject], Optional [EventParams]), which isn't intuitive and looks weird amongst the sea of Private Event Subs.
The coupling now depends on the class name, which may not always be ideal. Renaming the class will require editing the events to reflect that.
For the wrapper to be "complete", it must include all events and error handling to ignore the ones that aren't setup on the Parent side. At some point I'd think having all these On Error GoTo EoF statements in each class instance will have a performance impact.
The Question
Is there a way that this process can be further improved to reduce the coupling between (in this case) the Class and Form code? With VBIDE we could detect the classname and generate the pseudo-events, but without VBIDE access it seems like it requires some upkeeping and instruction to properly utilize the class.
In Python (and I'm sure other languages), you could just pass a reference to a function to direct the event returns; however, VBA doesn't seem to support this.
If you can pass the method name from the parent as a string you could use something like CallByName mCallback, vbMethod, mProcName, mCommandButton from within the class instance, to call the method mProcName on the parent, passing the clicked-on button.
For example:
Event class (properties changed to public fields for brevity)
Option Explicit
Public WithEvents mCommandButton As MSForms.CommandButton
Public mCallback As Object '<< object on which the callback method is to be called
Public mProcName As String '<< name of the callback method
Private Sub mCommandButton_Click()
CallByName mCallback, mProcName, VbMethod, mCommandButton
End Sub
Form code:
Private EventCommandButtons As Collection
Public Sub ButtonClick(cmdBtn As MSForms.CommandButton)
MsgBox "clicked on button " & cmdBtn.Caption
End Sub
Private Sub Userform_Initialize()
Dim ctl As Object
Set EventCommandButtons = New Collection
For Each ctl In Me.Controls
If TypeName(ctl) = "CommandButton" Then
EventCommandButtons.Add NewClickHandler(ctl)
End If
Next
End Sub
Function NewClickHandler(btn As Object) As EventCommandButton
Set NewClickHandler = New EventCommandButton
Set NewClickHandler.mCommandButton = btn
Set NewClickHandler.mCallback = Me
NewClickHandler.mProcName = "ButtonClick"
End Function
I've been building testable MVC logic for my Access database using RubberDuck's answer to Best way to test a MS Access Application? but I'm stuck with the custom event handling. I can't figure out why the OnCreate event isn't firing.
Form_CreateStudents:
Option Compare Database
Private ctrl As ctrCreateStudent
Public Event OnCreate()
Private Sub btnCreate_Click()
Set ctrl = New ctrCreateStudent
ctrl.Run
RaiseEvent OnCreate
End Sub
Class module ctrCreateStudent:
Private WithEvents frm As [Form_Create Students]
Public Sub Run()
MsgBox "run called"
Set frm = New [Form_Create Students]
End Sub
Public Sub frm_OnCreate()
MsgBox "frm_oncreate event called"
End Sub
Run is being called, but frm_OnCreate is just ignored. I'm relatively new to VBA, what am I missing here?
Quite simple:
frm is a New [Form_Create Students], not the one calling it.
This new form doesn't raise the OnCreate event. In fact, this new form is not even visible, because you haven't set frm.Visible = True
If you want to set it to the form that just called Run, pass it:
On the form:
Private ctrl As ctrCreateStudent
Public Event OnCreate()
Private Sub btnCreate_Click()
Set ctrl = New ctrCreateStudent
ctrl.Run Me
RaiseEvent OnCreate
End Sub
On the class:
Private WithEvents frm As [Form_Create Students]
Public Sub Run(parentForm As [Form_Create Students])
MsgBox "run called"
Set frm = parentForm
End Sub
Public Sub frm_OnCreate()
MsgBox "frm_oncreate event called"
End Sub
A strong warning, though: this code contains a reference loop, and thus a memory leak.
The form has a reference to the class, and the class has a reference to the form, so neither will ever get destroyed. Every time you close and open the form, a new form and class object will get created, and none of them will ever get destroyed.
When closing the form, it turns invisible and looks gone, but it's still there and using memory.
There are many ways to work around this, but an easy one is:
In the class:
Public Sub frm_Close()
Set frm = Nothing 'Release form object, break reference loop
End Sub
And make sure the Form's On Close property is set to "[Event Procedure]" so the close event gets raised.
Is there any way to update a ListBox on a UserForm outside of the Userform_Initialize sub?
Why?
I am building a blackjack game and using listboxes to tell the user what cards they have/ the dealer has. I was hoping to use a simple sub (ShowCards) to add items to the listboxes but I've run into problems:
The Play button calls the PlayBlackjack sub which sits in a normal module
Option Explicit
Dim cards As New Collection
Sub ShowGame()
UFDisplay.Show
End Sub
Sub PlayBlackjack()
'fill the cards collection with 5 shuffled decks
PrepareCards
Dim i As Integer
Dim userHand As New Collection
Dim dealerHand As New Collection
'deal cards (removing the dealt cards from the cards collection)
For i = 1 To 2
DealCard cards, userHand
DealCard cards, dealerHand
Next i
ShowCards userHand, UFDisplay.UserHandList <-- ERROR HERE (Type mismatch)
'more code to follow
End Sub
Private Sub ShowCards(hand As Collection, list As ListBox)
Dim i As Integer
For i = 1 To hand.Count
list.AddItem hand(i).CardName
Next i
End Sub
Let me know if you think you need any more of the code. hand is a collection of card classes where .CardName returns something like 3 of Hearts
Everything I read seems to tell me that the userform is static after being initialized so I would need to refresh it in some way after adding the new items. I tried Userform.Repaint with no luck.
So if there really is no other way, should I declare userHand and dealerHand as global variables, update them and call Useform_Initialize to take the updated values and show them to the user? Given the nature of the game being that multiple more cards could be dealt for both players it doesn't seem sensible to be re-initializing the userform multiple times each game.
All suggestions welcome. If you think I should have done it completely differently I'd still be keen to hear (but not so interested in worksheet solutions)
Update #1
For clarity, ShowGame is called by a button on the workshet, then PlayBlackjack is called from the Play button on the Userform (nothing else in the userform code)
Ah, I see. You cannot declare the listBox parameter as ListBox. The latter is reserved for Activex controls, not VBA controls. Change the signature of your ShowCardssub into this:
Private Sub ShowCards(hand As Collection, list As Control) '<~~ or MSForms.ListBox, or simply as Object...
You could use a class for the hand also, and set the class listbox to be the form listbox, for example, the class clsHand
Public colHand As collection
Public lstToUpdate As MSForms.ListBox
Private Sub Class_Initialize()
Set colHand = New collection
End Sub
Friend Function AddCard(card As clsCard)
colHand.Add card, CStr(colHand.Count)
If Not lstToUpdate Is Nothing Then
lstToUpdate.AddItem card.strCardName
End If
End Function
It's use in a form
Private clsPlayerHand As clsHand
Private Sub UserForm_Initialize()
Set clsPlayerHand = New clsHand
Set clsPlayerHand.lstToUpdate = Me.ListBox1
End Sub
Private Sub CommandButton1_Click()
Dim clsC As New clsCard
clsC.strCardName = "one"
clsPlayerHand.AddCard clsC
End Sub
EDIT: Recommendation,
Use the numeric and the suit name for your cards, then you can do the following, say enabling the split button, incidentally, you'd use an array of hands then arrHands(x) ...
Public colHand As collection
Public lstToUpdate As MSForms.ListBox
Public cmdSplitButton As MSForms.CommandButton
Private Sub Class_Initialize()
Set colHand = New collection
End Sub
Friend Function AddCard(card As clsCard)
colHand.Add card, CStr(colHand.Count)
If Not lstToUpdate Is Nothing Then
lstToUpdate.AddItem card.CardName
End If
If Not cmdSplitButton Is Nothing Then
If colHand.Count = 2 Then _
cmdSplitButton.Enabled = colHand(1).NumericPart = colHand(2).NumericPart
End If
End Function
Look at using classes to their full potential and look at events also, to react to certain things.
I have the following problem:
Private Sub TextBox1_Change()
Control (this) <<<<----- this is Empty
End Sub
Private Sub TextBox2_Change()
Control (this) <<<<----- this is Empty
End Sub
Private Sub TextBox3_Change()
Control (this) <<<<----- this is Empty
End Sub
Public Sub Control(asdf As MSForms.TextBox)
asdf.Font.Size = 11
asdf.Font.Bold = True
End Sub
The compiler says that 'this' is empty. What should I put there to recognize the TextBox?
Thx
When you use parentheses in your code you're evaluating Me.TextBox1, which ends up passing a String to Control. If you drop the parens it will work.
Private Sub TextBox1_Change()
Control Me.TextBox1 'without the parens
End Sub
Typically you don't use () when calling a Sub, unless you're using Call
If you end up having a lot of textboxes, it may be easier to customize the event handler in a custom class.
In a standard module, put a globally scoped collection variable
Public gcolTextboxes As Collection
Create a custom class module called CTbxEvents
Private WithEvents mtb As MSForms.TextBox
Public Property Get tb() As MSForms.TextBox
Set tb = mtb
End Property
Public Property Set tb(otb As MSForms.TextBox)
Set mtb = otb
End Property
Private Sub mtb_Change()
tb.Font.Size = 11
tb.Font.Bold = True
End Sub
Finally, in ThisDocument, load up all the textboxes when the document opens.
Private Sub Document_Open()
Dim f As Field
Dim clsTbxEvents As CTbxEvents
'Globally scoped collection to hold the classes
Set gcolTextboxes = New Collection
'Loop throught the fields
For Each f In Me.Fields
'Only fields that are activex controls
If f.Type = wdFieldOCX Then
'only activex controsl that are textboxes
If TypeOf f.OLEFormat.Object Is MSForms.TextBox Then
'create a new class, add the textbox, add to collection
Set clsTbxEvents = New CTbxEvents
Set clsTbxEvents.tb = f.OLEFormat.Object
gcolTextboxes.Add clsTbxEvents
End If
End If
Next f
End Sub
Now any textbox you add will use the same event handler and you don't keep having to call a separate sub. If you truly only have three textboxes, it's probably overkill.
The this keyword is specific to C++/C#/Java, the corresponding keyword for VB/VB.NET is Me
I have userform which collects some user input. Now what I'm trying to do, is to declare some event to throw from userform when OK button is clicked. I'm new to vba so I don't know how to do it. Any code or link to tutorial would be greatly appreciated.
Load UserForm1
UserForm1.Show
//here I want to capture UserForm1 OK button's click event and read the data
In child-form declare event and raise it at the certain moment:
Public Event clickOnChild(ByVal inputText As String)
RaiseEvent clickOnChild(Me.TextBox1.Value)
In a custom class module, worksheet class module or other user form you can catch the event. However you can't catch event in standard module because WithEvents variable are valid in object module only. To catch your event in e.g. other user form declare WithEvents variable of type childUserForm and
add event-handler where the event will be catched and handled:
Private WithEvents childForm As childUserForm
Private Sub childForm_clickOnChild(ByVal inputText As String)
Complete example:
Child user form:
Option Explicit
Public Event clickOnChild(ByVal inputText As String)
Private Sub CommandButton1_Click()
RaiseEvent clickOnChild(Me.TextBox1.Value)
End Sub
Parent user form:
Option Explicit
Private WithEvents childForm As childUserForm
Private Sub CommandButton1_Click()
childForm.Show
End Sub
Private Sub childForm_clickOnChild(ByVal inputText As String)
MsgBox "Input in child form was: " & inputText
End Sub
Private Sub UserForm_Initialize()
Set childForm = New childUserForm
End Sub
As I said in a comment, I don't think what you want to do is possible, but I thought of the following workarounds:
If your user input is very simple, like just entering a string, a messagbox could work:
Dim sUserInput As Variant
sUserInput = InputBox("Please enter something useful.", "Title", "Default")
Debug.Print "sUserInput=" & sUserInput
If you need the form to capture user input, making it modal and then exposing a value through a public method might work.
In the form:
Option Explicit
Private msFormString As String
Private Sub CommandButton1_Click()
msFormString = "Someone clicked on Button 1!"
'***** Note: if you use Unload Me, the string
'***** is unloaded with the form...
Me.Hide
End Sub
Public Function GetFormString() As String
GetFormString = msFormString
End Function
The calling code:
Load UserForm1
Call UserForm1.Show(vbModal)
Debug.Print "Value from UserForm1: " & UserForm1.GetFormString
Note: The function could return an object, class or array if you need to pass more data back.