Android Tutorial Classes tutorial

Basic4android v2.00 adds support for classes modules.

Classes definition from Wikipedia:

Example:
B4X:
'Class Person module
Sub Class_Globals
   Private FirstName, LastName As String
   Private BirthDate As Long
End Sub

Sub Initialize (aFirstName As String, aLastName As String, aBirthDate As Long)
   FirstName = aFirstName
   LastName = aLastName
   BirthDate = aBirthDate
End Sub

Public Sub GetName As String
   Return FirstName & " " & LastName
End Sub

Public Sub GetCurrentAge As Int
   Return GetAgeAt(DateTime.Now)
End Sub

Public Sub GetAgeAt(Date As Long) As Int
   Dim diff As Long
   diff = Date - BirthDate
   Return Floor(diff / DateTime.TicksPerDay / 365)
End Sub

'Main module
...
Dim p As Person
p.Initialize("John", "Doe", DateTime.DateParse("05/12/1970"))
Log(p.GetCurrentAge)

I will start by explaining the differences between classes, code modules and types.

Similar to types, classes are templates. From this template you can instantiate any number of objects.
The type fields are similar to the classes global variables. However unlike types which only define the data structure, classes also define the behavior. The behavior is defined in the classes subs.

Unlike classes which are a template for objects, code modules are collections of subs. Another important difference between code modules and classes is that code modules always run in the context of the calling sub (the activity or service that called the sub). The code module doesn't hold a reference to any context. For that reason it is impossible to handle events or use CallSub with code modules.
Classes store a reference to the context of the activity or service module that called the Initialize sub. This means that classes objects share the same life cycle as the service or activity that initialized them.

Code modules are somewhat similar to singleton or static classes.

Adding a class module
Adding a new or existing class module is done by choosing Project -> Add New Module -> Class module or Add Existing module.
Like other modules, classes are saved as files with bas extension.

Classes structure
Classes must have the following two subs:

Class_Globals - This sub is similar to the activity Globals sub. These variables will be the class global variables (sometimes referred to instance variables or instance members).

Initialize - A class object should be initialized before you can call any other sub. Initializing an object is done by calling the Initialize sub. When you call Initialize you set the object's context (the parent activity or service).
Note that you can modify this sub signature and add arguments as needed.

In the above code we created a class named Person and later instantiate an object of this type:
B4X:
Dim p As Person
p.Initialize("John", "Doe", DateTime.DateParse("05/12/1970"))
Log(p.GetCurrentAge)

Calling initialize is not required if the object itself was already initialized:
B4X:
Dim p2 As Person
p2 = p 'both variables now point to the same Person object.
Log(p2.GetCurrentAge)

Polymorphism
Polymorphism allows you to treat different types of objects that adhere to the same interface in the same way.
Basic4android polymorphism is similar to the Duck typing concept.

As an example we will create two classes named: Square and Circle.
Each class has a sub named Draw that draws the object to a canvas:
B4X:
'Class Square module
Sub Class_Globals
   Private mx, my, mLength As Int
End Sub

'Initializes the object. You can add parameters to this method if needed.
Sub Initialize (x As Int, y As Int, length As Int)
   mx = x
   my = y
   mLength = length
End Sub

Sub Draw(c As Canvas)
   Dim r As Rect
   r.Initialize(mx, my, mx + mLength, my + mLength)
   c.DrawRect(r, Colors.White, False, 1dip)
End Sub
B4X:
'Class Circle module
Sub Class_Globals
   Private mx, my, mRadius As Int
End Sub

'Initializes the object. You can add parameters to this method if needed.
Sub Initialize (x As Int, y As Int, radius As Int)
   mx = x
   my = y
   mRadius = radius
End Sub

Sub Draw(cvs As Canvas)
   cvs.DrawCircle(mx, my, mRadius, Colors.Yellow, False, 1dip)
End Sub

In the main module we create a list with Squares and Circles. We then go over the list and draw all the objects:
B4X:
Sub Process_Globals
   Dim shapes As List
End Sub

Sub Globals
   Dim cvs As Canvas  
End Sub

Sub Activity_Create(FirstTime As Boolean)
   cvs.Initialize(Activity)
   Dim sq1, sq2 As Square
   Dim circle1 As Circle
   sq1.Initialize(100dip, 100dip, 50dip)
   sq2.Initialize(2dip, 2dip, 100dip)
   circle1.Initialize(50%x, 50%y, 100dip)
   shapes.Initialize
   shapes.Add(sq1)
   shapes.Add(sq2)
   shapes.Add(circle1)
   DrawAllShapes
End Sub

Sub DrawAllShapes
   For i = 0 To shapes.Size - 1
      CallSub2(shapes.Get(i), "Draw", cvs) 'Call Draw of each object
   Next
   Activity.Invalidate
End Sub
(the example code is attached)

As you can see, we do not know the specific type of each object in the list. We just assume that it has a Draw method that expects a single Canvas argument. Later we can easily add more types of shapes.
You can use the SubExists keyword to check whether an object includes a specific sub.

You can also use the Is keyword to check if an object is of a specific type.

Self reference
The Me keyword returns a reference to the current object. 'Me' keyword can only be used inside a class module.
Consider the above example. We could have passed the shapes list to the Initialize sub and then add each object to the list from the Initialize sub:
B4X:
Sub Initialize (Shapes As List, x As Int, y As Int, radius As Int)
   mx = x
   my = y
   mRadius = radius
   Shapes.Add(Me) 'Me is used to add this object to the list
End Sub

Activity object
This point is related to the activities special life cycle. Make sure to first read the activities and processes life-cycle tutorial.

Android UI elements hold a reference to the parent activity. As the OS is allowed to kill background activities in order to free memory, UI elements cannot be declared as process global variables (these variables live as long as the process lives). Such elements are named Activity objects. The same is true for custom classes. If one or more of the class global variables is of a UI type (or any activity object type) then the class will be treated as an "activity object". The meaning is that instances of this class cannot be declared as process global variables.

Properties
Starting from B4A v2.70, classes support properties. Properties syntax can be considered a syntactic sugar.
Properties combine two methods into a single "field" like member.
For example the two following methods:
B4X:
'Gets or sets the text
Sub getText As String
   Return btn.Text
End Sub

Sub setText(t As String)
   btn.Text = t
End Sub
Are merged automatically into a single property:



The property can be treated like any other field:
B4X:
Dim c1 As SomeClass
c1.Text = "abc"
Log(c1.Text)

The rules for properties:
- Only relevant for classes.
- One or two subs with the format get<prop> / set<prop>. Note that get / set must be lower case.
- A property can be read-only (only get), write-only (only set) or both.
- The two subs types (parameter in the set sub and return type in the get sub) must be the same.
- Within the class you should call the methods directly. The property will not appear.
- The property cannot have the same name as a global variable.

Related links:
Built-in documentation
Variables & Objects
Variables & Subs visibility
 

Attachments

  • Draw.zip
    7.1 KB · Views: 2,463
Last edited:

Baltazar

Member
Licensed User
Longtime User
Hi everyone,
I followed the tutorial on classes but can't find the way to directly derive UI classes. I mean , there is no way to declare a class to be a descendant of another class because in the process of creating it, there's no way to declare the parent class too.
To make my inquiry clear , here's an example :
B4X:
'Class module
Sub Class_Globals
   Private pnl As Panel
   Public cols,rows As Int
   Private Col, Row As Int
End Sub

'Initializes the object.
'iCols = initial number of columns
'iRows =  initial number of rows
Public Sub Initialize ( ObjName As String,iCols As Int,iRows As Int )
    Dim itrx,itry As Int
    cols = iCols
    rows = iRows
    pnl.Initialize("")
    pnl.Color = Colors.white
End Sub

Sub getPanel() As Panel
  
    Return  pnl
End Sub
B4X:
Sub Process_Globals
    

End Sub

Sub Globals
  
     Dim grd As ImageGrid
End Sub

Sub Activity_Create(FirstTime As Boolean)
 
    grd.initialize("myGrid",12,6)
    Activity.AddView(grd.Panel,0,0,100%x,20%y)
End Sub
My point in the example is that, instead of adding my class to activity using AddView, I cannot simply do that because my class is not a descendant of View. Of course, accessing its public member which is a descendant of View would perfectly work just as in the example. Am I missing something? Or Is it just a language thing?
 

Baltazar

Member
Licensed User
Longtime User
Thanks Erel. I want to do everything programmatically. I think I can manage with just using a View type class member.
 

notedop

Member
Licensed User
Longtime User

You must first call Initialize method. You can then call other methods that will do the "actual" initialization.

So, if I would have a class with 2 different initialize methods, I will always first need to call .initialize before doing anything else?

B4X:
'Class module
Sub Class_Globals
    Dim somevariable As String
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
'no code here
End Sub

Public Sub InitializeWithFile(File As String)
'do stuff with the file
End Sub

Public Sub initilizeWithBla(File As String, bla As Bitmap)
'do stuff with blabla
End Sub

Example 1
B4X:
Dim cc as CustomClass
'first call initialize
cc.initialize
'then do the actual initialisation
cc.initializeWithFile("blablabla")

Example 2
B4X:
Dim cc as CustomClass
'first call initialize
cc.initialize
'then do the actual initialisation
cc.initializeWithBla("blablabla", somemorebla)


I've tried to use callsub(me, "Initialize") in the InitializeWithFile and InitializeWithBla sub hoping that I would not need to call .Initialize first.
Unfortuantely does not seem to work...
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
You can add parameters to Initialize, so you could either move the code from your InitializeWithFile into there & add the file name as a parameter, or you could add the file name as a parameter & then call InitializeWithFile from Initialize:

B4X:
Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(FileName as String)
'Do stuff with the file
End Sub

OR

B4X:
Public Sub Initialize(FileName as String)
    InitializeWithFile(FileName)
End Sub

Private Sub InitializeWithFile(FileName As String)
'do stuff with the file
End Sub
 

LucaMs

Expert
Licensed User
Longtime User
Thanks to a member's observation (unfortunately at this moment I can not find the thread) we have "discovered" that the initialization of a class is implied, it is made "in" the declaration of the object.

This means that you can write in a class:
B4X:
Public Sub Initialize
end Sub

Public Sub Initialize2 (x As Int, y As Int)
end Sub

Public Sub Initialize3 (Name As String)
end Sub



Despite this, you could also do this:
B4X:
Public Sub Initialize (Parms() As Object)
end Sub

and fulfill some choices based on the number and type of objects in the array.


[P.S.
 
Last edited:

notedop

Member
Licensed User
Longtime User
You can add parameters to Initialize, so you could either move the code from your InitializeWithFile into there & add the file name as a parameter, or you could add the file name as a parameter & then call InitializeWithFile from
I;m aware I can add parameters to the Initilize method. I'm looking for different methods to initilize 1 object. To goal is each method can be initiliazed by different parameters.

Below might be interesting. So I can write multiple initilize subs, with each different parameters. Based on the parametersit will know which initialize sub to load?

Edit: nope, does not work.

 

notedop

Member
Licensed User
Longtime User
Problem is that the Class Globals do not get loaded when using Initialize2 or Initialize3
See attached example. The toastmessage should Always show "This is a test variable"
But it doesn't.

MAIN
B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.

End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
   
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")
   
    'Dim standard As Test
    Dim two As Test
    Dim three As Test
   
    'standard.Initialize
    two.Initialize2("Good")
    three.Initialize3(True)

End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

B4X:
'Class module
Sub Class_Globals
    Dim var As String = "This is a test variable"
End Sub

'Initializes the object. You can add parameters to this method if needed.
'Public Sub Initialize
'    ToastMessageShow("Initialize "& var, True)
'End Sub

Public Sub Initialize2(text As String)
    ToastMessageShow("Initialize2 " & text &" And " & var, True)
End Sub

Public Sub Initialize3(Text As Boolean)
    ToastMessageShow("Initialize3 " & var, True)
End Sub
 

Attachments

  • test.zip
    6.5 KB · Views: 465

LucaMs

Expert
Licensed User
Longtime User
B4X:
Sub Class_Globals
    Dim mVar As String
End Sub

PublicSub Initialize2(text AsString)
    mVar = "This is a test variable"
    ToastMessageShow("Initialize2 " & text &" And " & mVar, True)
End Sub

PublicSub Initialize3(Text AsBoolean)
    mVar = "This is a test variable"
    ToastMessageShow("Initialize3 " & mVar, True)
End Sub



(I like "m" as prefix for module variables )
 

notedop

Member
Licensed User
Longtime User
yeah, i was doing another test and I was expecting and error on below, but it didn't give an error.
Meaning it did declare the Var as String.

B4X:
Public Sub Initialize2(text As String)
    Log("text: " & text)
    var = text
    Log("Initialize2 " & var)
End Sub

So when using this method I cannot assign a value in the class globals at the moment of declaring, I should assign them in the initialisation.

Good to know.
But I still can't get this to work with my class existing class.... It's giving a nullpointer exception if I do not call Initialize prior to calling Initialize3 which i'm now doing as a workaround. If I would remove the Initialize method from the class and do not call this method it gives me the nullpointer exception.... attached the project. Classmodule Test
2 times used as Tester and Bullet in the main class.

Note: attached project is a messy result of my learning curve with LibGDX

B4X:
'Class module
Sub Class_Globals


    Private IsPlayer As Boolean = False

    Private MAXIMUM_VELOCITY As Byte
    Private ACCELERATION As Float
    Private DAMPING As Float
    Private ac_x As Float
    Private ac_y As Float

    Private Batch As lgSpriteBatch
    Private Bodie As lgBox2DBody
    Private Textures() As lgTexture
    Private Sprites() As lgSprite

    Private Conversion As lgMathUtils
    Private Loader As lgBox2DBodyEditorLoader

    Private fd As lgBox2DFixtureDef

    Private StateTime As Float
    Private TimePerFrame As Float
    Private CurrentFrame As Byte
    Private TotalFrames As Byte
    Private l As List

    Private DeathZone As Byte

End Sub

'Please use InitializeStatic or InitializeWithDefination after initializing this object.
'Public Sub Initialize()
'
'End Sub

'First call empty Initialize before using this sub
'World To add the Box2D objects To.
'JSon as string for the location of the Json file containing all polygons
'Use to initialize with pre-set BodyDefinition and FixtureDefinition.

Public Sub Initialize2(world As lgBox2DWorld, JSon As String)

    Dim Loader As lgBox2DBodyEditorLoader

    TimePerFrame = 0.1
    StateTime = 0
    DeathZone = 2

    MAXIMUM_VELOCITY  = 3
    ACCELERATION = 0.2
    DAMPING = 0.05

     Batch.Initialize
    Loader.InitializeWithFile(JSon)
    Dim l As List = Loader.SortedList
    Log(l)
    'set the array size based on the Json file.
    Dim Textures(l.Size) As lgTexture
    Dim Sprites(l.Size) As lgSprite
    TotalFrames = l.Size
    Log("Initial after: " & JSon)

    '1. Create a BodyDef, as usual:
    Dim bd As lgBox2DBodyDef
    bd.Position.set(50%x/Main.P2M, 50%y/Main.P2M)
    bd.gravityScale = 0
    bd.Type = world.BODYTYPE_Dynamic

    'do not rotate the body.
    bd.fixedRotation = True

    '2. Create a FixtureDef, as usual:

    fd.Density = 0
    fd.Friction = 0.5
    fd.Restitution = 0.2

    '3. Create a Body, as usual:
    Bodie = world.CreateBody(bd)


    '5. Load the associated image:
    '    First load the images as we need it's width to set the scaling in the bodyfixture.

    For i = 0 To L.Size -1
        Textures(i).Initialize("actors/" & Loader.GetImagePath(L.get(i)))
        Sprites(i).InitializeWithTexture(Textures(i))
        Sprites(i).SetSize(Sprites(i).Width/Main.P2M, (Sprites(i).Width/Main.P2M) * (Sprites(i).Height / Sprites(i).Width) )
        Dim Origin As lgMathVector2
        Origin = Loader.GetOrigin(L.get(i), Sprites(i).Width)
        Sprites(i).SetOrigin(Origin.X, Origin.Y)
    Next

    '4. Create the body fixture automatically by using the Loader, only first occurance.
    Loader.AttachFixture(Bodie, L.get(0), fd, Sprites(0).Width)



    'Log ("List: " & L)

End Sub


'First call empty Initialize before using this sub
'Same as initialize however now with custom bodydefinition and fixturedefinition.
'Returns the body.
Public Sub Initialize3(world As lgBox2DWorld, JSon As String, bd As lgBox2DBodyDef, FixtureDef As lgBox2DFixtureDef, IssPlayer As Boolean)

    Dim Loader As lgBox2DBodyEditorLoader

    TimePerFrame = 0.1
    StateTime = 0
    DeathZone = 2

    MAXIMUM_VELOCITY  = 3
    ACCELERATION = 0.2
    DAMPING = 0.05

    Batch.Initialize
    Loader.InitializeWithFile(JSon)
    Dim l As List = Loader.SortedList
    Log(l)

    'set the array size based on the Json file.
    Dim Textures(l.Size) As lgTexture
    Dim Sprites(l.Size) As lgSprite
    TotalFrames = l.Size
     Log("Initial after: " & JSon)
 
    'Set player boolean
    IsPlayer = IssPlayer

    '3. Create a Body, as usual:
    Bodie = world.CreateBody(bd)

    '5. Load the associated image:
    '    First load the images as we need it's width to set the scaling in the bodyfixture.

    For i = 0 To L.Size -1
        Textures(i).Initialize("actors/" & Loader.GetImagePath(L.get(i)))
        Sprites(i).InitializeWithTexture(Textures(i))
        Sprites(i).SetSize(Sprites(i).Width/Main.P2M, (Sprites(i).Width/Main.P2M) * (Sprites(i).Height / Sprites(i).Width) )
        Dim Origin As lgMathVector2
        Origin = Loader.GetOrigin(L.get(i), Sprites(i).Width)
        Sprites(i).SetOrigin(Origin.X, Origin.Y)
    Next

    '4. Create the body fixture automatically by using the Loader, only first occurance.
    Loader.AttachFixture(Bodie, L.get(0), FixtureDef, Sprites(0).Width)

    'Log ("List: " & L)

End Sub


'Pass the camera to set the projection matrix.
'DeltaTime is used to match the game speed
'Ac_x1,Ac_y1 is the acceleration. pass Null if not available.
Sub draw(camera As lgOrthographicCamera,DeltaTime As Float, ac_x1 As Float, ac_y1 As Float)

    'get accelator movement for player movement.
    ac_x = ac_x1
    ac_y = ac_y1

    'deltatime is the time spent since last draw.
    'first define which frame we should show

    StateTime = StateTime + DeltaTime

    If StateTime > TimePerFrame Then
        If CurrentFrame < TotalFrames-1  Then
            CurrentFrame = CurrentFrame +1
            StateTime = 0
        Else
            CurrentFrame = 0
            StateTime = 0
        End If
    End If

    'destroy the fixtures belonging to the previous frame.
    Dim arr As lgArray
    Bodie.GetFixtureList(arr)
    'Log("Fixturelist: " & arr.Size)


    For Each f As lgBox2DFixture In arr.toList
        Bodie.destroyFixture(f)
    Next

    'attach new fixture based on current frame.
    Loader.AttachFixture(Bodie, L.get(CurrentFrame), fd, Sprites(CurrentFrame).Width)

    'update positions.
    update

    Batch.ProjectionMatrix = camera.Combined
    Batch.Begin
        Sprites(CurrentFrame).draw(Batch)
    Batch.End

End Sub

Private Sub update()

'set the sprite at the bodies position.
For i = 0 To Sprites.Length - 1
    Sprites(i).X = Bodie.Position.X - Sprites(i).OriginX
    Sprites(i).Y = Bodie.Position.Y - Sprites(i).OriginY
    Sprites(i).Rotation = Bodie.Angle * Conversion.radiansToDegrees
Next


'============================================
'============Write code for movement=========
'============================================
'Handle the player movement based on accelerator
'Only if object is player

If IsPlayer Then

    If ac_x> (Main.Accel_Base.x +DeathZone) Then        'check if outside deathzone

        'Log("Tilting top - up")
        If (Bodie.LinearVelocity.y > (MAXIMUM_VELOCITY*-1)) OR (Bodie.LinearVelocity.y > 0) Then
            Bodie.applyLinearImpulse2(0,ACCELERATION*-1, Bodie.WorldCenter.x,Bodie.WorldCenter.y, True)
        End If
    

    Else If ac_x < (Main.Accel_Base.x -DeathZone) Then'check if outside deathzone
        'Log("Tilting bottom - up")
        If (Bodie.LinearVelocity.y < MAXIMUM_VELOCITY) OR (Bodie.LinearVelocity.y < 0) Then
            Bodie.applyLinearImpulse2(0,ACCELERATION, Bodie.WorldCenter.x,Bodie.WorldCenter.y, True)
        End If
    Else'within deathzone; stabilize player
    'Log("Deathzone: y velocity=" & Bodie.LinearVelocity.y )
        If Bodie.LinearVelocity.y > DAMPING Then
            Bodie.applyLinearImpulse2(0,-DAMPING, Bodie.WorldCenter.x,Bodie.WorldCenter.y, True)
        Else If Bodie.LinearVelocity.y < -DAMPING Then
            Bodie.applyLinearImpulse2(0,DAMPING, Bodie.WorldCenter.x,Bodie.WorldCenter.y, True)
        End If
    End If
End If

'====================================================
'==============Constant movement over X==============
'====================================================
If Bodie.LinearVelocity.x < MAXIMUM_VELOCITY Then

    If Bodie.isBullet Then
        Bodie.setLinearVelocity2(MAXIMUM_VELOCITY, 0)
    Else
        Bodie.applyLinearImpulse2(ACCELERATION,0, Bodie.WorldCenter.x,Bodie.WorldCenter.y, True)
    End If

End If

End Sub

'Returns position as lgMathVector2
Public Sub getPosition As lgMathVector2
    Return Bodie.Position
End Sub


Public Sub setPosition(V As lgMathVector2)
Bodie.setTransform(V, 0)
Return True
End Sub

Public Sub getX As Float

Return Bodie.Position.x

End Sub

Public Sub getY As Float

Return Bodie.Position.y

End Sub

Public Sub setX(x As Float)

Bodie.Position.x = x
Return True

End Sub

Public Sub setY(y As Float)

Bodie.Position.y =  y
Return True

End Sub

Public Sub getDeathZone As Byte

Return DeathZone

End Sub

Public Sub setDeathZone(i As Byte)

DeathZone = i
Return True

End Sub

Public Sub setMAXVELOCITY(i As Byte)

MAXIMUM_VELOCITY = i
Return True

End Sub
Public Sub getMAXVELOCITY As Byte

Return MAXIMUM_VELOCITY

End Sub

'gets width of current frame in pixels
Public Sub Width As Float
    Return Sprites(CurrentFrame).Width
End Sub

'gets height of current frame in pixels
Public Sub Height As Float
    Return Sprites(CurrentFrame).Height
End Sub

Public Sub Dispose

Batch.Dispose
For Each i As lgTexture In Textures
    Textures(i).Dispose
Next
Return True

End Sub
 
Last edited:

jonydoboi

Member
Licensed User
Longtime User
I have B4A version 2.30.
By using a class can I add a button and label and display them with code inside the class?
 

jonydoboi

Member
Licensed User
Longtime User
Wanted to write little sub programs like file explorer that fetch names and paths. ETC.
Never done classes before.
I noticed that AddView does not use dips in the parameters. Just ints.
Am looking at LimitBarDemo1.
 

shaffnert03

Member
Licensed User
Longtime User
Can you dynamically add properties to a class? As in, I'd like to programmatically create properties since I'm not sure how many of them I'll have in advance. Is this possible?

It appears there's a way to do this in VB.NET (https://msdn.microsoft.com/en-us/library/2sd82fz7.aspx or https://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject(v=vs.110).aspx) and it would be useful to me in a project I'm working on, but I've not been able to find a B4A way to do it. Thoughts anyone?

I should perhaps note too that there are other ways I can work around this if there's not a way; they just wouldn't be half so elegant so I wanted to make sure I hadn't missed a way to do this before I pursued them.
 
Last edited:
Cookies are required to use this site. You must accept them to continue using the site. Learn more…