Android Question Select a class at runtime

max123

Well-Known Member
Licensed User
Longtime User
Hi all,

in my project I've 2 classes I wrote. These classes are substantilally viewers tha show a scrollable Canvas inside a big ScrollView2D panel (many thanks to @Informatix for his good ScrollView2D library). This way I've a big 2D scrollable canvas. It is used to simulate a 3D Printer or CNC machine.

Both classes have same methods with same arguments, the only difference of both classes are that the first one show a 2D view, the second one show a static 3D view I've created on a canvas using math calculations.

In my project currently I can switch from 2D view and 3D view just by declaring the right class I want as global variable (in Globals sub) this way:
B4X:
' Uncomment just one
'Private Viewer As GCodeViewer2D
Private Viewer As GCodeViewer3D

This works, but now I want to add in the app settings the ability to switch from 2D to 3D view, so can be selected from end user, then when app restarts, it read from settings what view to use and use it.

What I need is the ability to declare a right class at runtime, but it need to be globally scoped because the app use it in many functions. Something like this:
B4X:
    If Settings.UseViewer2D Then
        Dim Viewer As GCodeViewer2D  ' Must be a global variable
    Else If Settings.UseViewer3D Then
        Dim Viewer As GCodeViewer3D  ' Must be a global variable
    End If
 
    ......
 
    ' Use the right viewer class 2D or 3D
    ' NOTE: both classes have same methods and any sub the same arguments
    Viewer.Initialize(pnlViewer, 1.0, Settings.Antialiasing)
    Viewer.Show(5dip, 5dip, 60%x, 70.7%y)
    Viewer.SetDrawingPlaneSize(Settings.X_MAX_POS, Settings.Y_MAX_POS)  ' Setup drawing plane for a machine.
    '    Viewer.Resize(5dip, 5dip, 60%x, 70.7%y) ' Resize the viewer pamel
     
    If Not (Settings.RoundPlane) Then
        Viewer.setStartPoint(0, 0, 0) ' Set start point to X0 Y0 Z0
        Log("Viewer StartPoint: 0,0,0")
    Else
        Viewer.setStartPoint(Settings.X_MAX_POS*0.5, Settings.Y_MAX_POS*0.5, 0) ' Set start point to X0 Y0 Z0
        Log("Viewer StartPoint: " & (Settings.X_MAX_POS*0.5) & "," & (Settings.Y_MAX_POS*0.5) & ", 0")
    End If

    ......

I've even tried to manage both classes as Object but without success.

Is that possible? If yes, what is the best approach to do it?

Many thanks
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Many thanks to all, today I will do some tests outside my app and post results.
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
I think you should also be able to put the objects in an array and just use an index, no conditionals required. Maybe?
He doesn't have As(Type) available to him and it would have to be an array of Objects which he would need to assign the accessed one to a variable of the correct viewer type to access it's methods which would need a test of some sort. So no! I think a simple conditional will be faster.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Hi @agraham

this worked, so as you said I've created a third class that is a sort of wrapper that handle both my two classes.

I do not know if it is the right way, in the third class I initialize only one class this way:

Third Class:
Sub Class_Globals
    Private cA As ClassA
    Private cB As ClassB
 
    Private mUseClassB As Boolean
End Sub

'Inizializza l'oggetto. Puoi aggiungere parametri a questo metodo,se necessario.
Public Sub Initialize(UseClassB As Boolean)
    mUseClassB = UseClassB
    If mUseClassB Then cB.Initialize Else cA.Initialize
End Sub

Public Sub LogClassName
    If mUseClassB Then cB.LogClassName Else cA.LogClassName
End Sub

Public Sub LogTime(YourName As String)
    If mUseClassB Then cB.LogTime(YourName) Else cA.LogTime(YourName)
End Sub

Public Sub LogMessage(YourName As String, Msg As String)
    If mUseClassB Then cB.LogMessage(YourName, Msg) Else cA.LogMessage(YourName, Msg)
End Sub
Now I need to test performances. I will post results.

Before I implement it in my app I need to ensure there is no best way to do this, maybe @Erel have some suggestions?

Many thanks to all ;)
 

Attachments

  • SelectClass.zip
    8 KB · Views: 186
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Sorry, I missed to attach a zip file... Now I attached it....
Please can you see if it is ok?

Many thanks
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Can this be added to B4X Manual Guides ?
 
Upvote 0

cklester

Well-Known Member
Licensed User
He doesn't have As(Type) available to him and it would have to be an array of Objects which he would need to assign the accessed one to a variable of the correct viewer type to access it's methods which would need a test of some sort. So no! I think a simple conditional will be faster.
What about using duck typing and CallSub()? If each object has the same methods...? I'm just thinking there's got to be a way to extend his algorithm easily, in case he needs more classes in the future (but also as an exercise for clarity for me). ? I'm always thinking about the generic case.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
What about using duck typing and CallSub()? If each object has the same methods...? I'm just thinking there's got to be a way to extend his algorithm easily, in case he needs more classes in the future (but also as an exercise for clarity for me). ? I'm always thinking about the generic case.
Thanks @cklester I've tried agraham's method and work well at same speed (near). I can now switch from one class to another in the main by just use a boolean variable that can be stored in the code or in the app settings.

The third class, a sort of wrapper have an If on every function, the syntax sound like a horror but it works.

If this work with two classes I suppose it even work with more than two using a Select instead of If stantment.

I still test it and had other problems with canvas, next days I will post here results.
 
Last edited:
Upvote 0

agraham

Expert
Licensed User
Longtime User
What about using duck typing and CallSub()? If each object has the same methods...
Yes but CallSub() is quite slow as, compared to a direct call, it has to look up the method to call in a hashtable and invoke it by reflection. It's not a huge overhead, particularly if the called Sub does significant processing, but if called repeatedly for a trivial Sub, say in a loop, it could impact performance.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Related example: https://www.b4x.com/android/forum/t...orting-with-b4xcomparatorsort.139006/#content

Note that there is an important compiler optimization for CallSub that is applied when:
1. The target module is a class.
2. The method name is a literal string.
3. In B4A / B4J.
4. In release mode.

When it is applied the CallSub call happens without reflection and it is significantly faster.
(you can check B4XComparatorSort code to see a hack I've used to implement a similar optimization in B4i)
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Hi @agraham and all users,

I can confirm that your suggestion to select a Class at runtime worked.

This worked because my app do some other things in background, but if we search performances I think this is not the right way, even need to instantiate all classes even if not used, this is not a good thing because unused classes use memory but just one is used.
Maybe can be used a generally Object to assign a Class, and then select once at runtime just when initialized, but not sure for this, I need to investigate.

I've used the IF stantment but using more classes a SELECT stantment can be used .

Anyways as an example for users I've attached my wrapper class.

Many thanks for your advice which I really appreciated.

Massimo

IMPORTANT NOTE: All classes must have the same methods and with the same arguments as my example code.

B4X:
Sub Class_Globals
    Private Viewer2D As GCodeViewer2D
    Private Viewer3D As GCodeViewer3D
 
    Private mUse3D As Boolean
End Sub

'Inizializes the gcode viewer object to the given Pane with a selected Zoom factor for grid.
'Zoom factor 1.0 is a non scaled grid.
Public Sub Initialize (Use3D As Boolean, Pnl As Panel, Zoom As Float, Antialiasing As Boolean)
    mUse3D = Use3D
    If mUse3D Then
        Viewer3D.Initialize(Pnl, Zoom, Antialiasing)
    Else
        Viewer2D.Initialize(Pnl, Zoom, Antialiasing)
    End If
End Sub

'Show the gcode viewer.
Public Sub Show(X As Int, Y As Int, Width As Int, Height As Int, InnerPaneWidth As Int, InnerPaneHeight As Int)
    If mUse3D Then
        Viewer3D.Show(X, Y, Width, Height, InnerPaneWidth, InnerPaneHeight)
    Else
        Viewer2D.Show(X, Y, Width, Height, InnerPaneWidth, InnerPaneHeight)
    End If
End Sub

'Resize the gcode viewer panel.
Public Sub Resize (X As Float, Y As Float, Width As Float, Height As Float)
    If mUse3D Then
        Viewer3D.Resize(X, Y, Width, Height)
    Else
        Viewer2D.Resize(X, Y, Width, Height)
    End If
End Sub

'Clear the viewer with specified color.
Public Sub Clear (Color As Int)
    If mUse3D Then
        Viewer3D.Clear(Color)
    Else
        Viewer2D.Clear(Color)
    End If
End Sub

'Fill the working plane with a specified color.
Public Sub FillPlane(Color As Long)
    If mUse3D Then
        Viewer3D.FillPlane(Color)
    Else
        Viewer2D.FillPlane(Color)
    End If
End Sub

'Sets the current drawing plane size of CNC machine expressed in Units (Millimeters or Inches).
'
'Example:
'<code>
'Dim Viewer As GCodeViewer3D
'Viewer.SetDrawingPlaneSize(200,200)   'Setup drawings for a machine that has plane 200x200 millimeters.  </code>
Public Sub SetDrawingPlaneSize(PlaneWidth As Float, PlaneHeight As Float)
    If mUse3D Then
        Viewer3D.SetDrawingPlaneSize(PlaneWidth, PlaneHeight)
    Else
        Viewer2D.SetDrawingPlaneSize(PlaneWidth, PlaneHeight)
    End If
End Sub

'Draw a grid based on CNC working plane dimensions (Units) you previousely setup with SetDrawingPlaneSize method.
'You can set Canvas background color, grid color and line width.
'
'The grid has separate settings for MinorTicks (millimeters), MediumTicks (centimeters) and MajorTicks (every 5 centimeters)
'
'Example:
'<code>Viewer.DrawGrid(Colors.Black, Colors.ARGB(100,255,255,255), Colors.ARGB(100,255,255,255), Colors.ARGB(100,255,255,255), .2, .4, .8)</code>
'
'Parameters:
'BackColor:  the viewer background color (NOTE: this is not a grid background only but full viewer background)
'MinorTicksColor:  the color of MinorTicks lines
'MediumTicksColor:  the color of MediumTicks lines
'MajorTicksColor:  the color of MajorTicks lines
'MinorTicksLineWidth:  the width of MinorTicks lines
'MediumTicksLineWidth:  the width of MediumTicks lines
'MajorTicksLineWidth:  the width of MajorTicks lines
Public Sub DrawPlaneGrid (BackColor As Int, MinorTicksColor As Int, MediumTicksColor As Int, MajorTicksColor As Int, MinorTicksLineWidth As Float, MediumTicksLineWidth As Float, MajorTicksLineWidth As Float)
    If mUse3D Then
        Viewer3D.DrawPlaneGrid(BackColor, MinorTicksColor, MediumTicksColor, MajorTicksColor, MinorTicksLineWidth, MediumTicksLineWidth, MajorTicksLineWidth)
    Else
        Viewer2D.DrawPlaneGrid(BackColor, MinorTicksColor, MediumTicksColor, MajorTicksColor, MinorTicksLineWidth, MediumTicksLineWidth, MajorTicksLineWidth)
    End If
End Sub

'Draw a plane border (squared or rounded) with the specified color and line width.
'Use SetStartPoint method to set a start position that by default is X = 0 , Y = 0, Z = 0.
Public Sub DrawPlaneBorders(RoundPlane As Boolean, Color As Int, LineWidth As Float)
    If mUse3D Then
        Viewer3D.DrawPlaneBorders(RoundPlane, Color, LineWidth)
    Else
        Viewer2D.DrawPlaneBorders(RoundPlane, Color, LineWidth)
    End If
End Sub

'Sets first drawing start point position from origin.
'By default X = 0, Y = 0, Z = 0 and assumed as CNC Home position.
'
'Z not used on 2D class (see 3D class), you can pass any numeric value like 0, the library just ignore it.
Public Sub SetStartPoint(X As Float, Y As Float, Z As Float)
    If mUse3D Then
        Viewer3D.SetStartPoint(X, Y, Z)
    Else
        Viewer2D.SetStartPoint(X, Y, Z)
    End If
End Sub

'Force the viewer redraw a full view.
Public Sub Redraw
    If    mUse3D Then Viewer3D.Redraw Else Viewer2D.Redraw
End Sub

Public Sub ResetRedrawingArea
    If mUse3D Then
        Viewer3D.ResetRedrawingArea
    Else
        Viewer2D.ResetRedrawingArea
    End If
End Sub

'Force the viewer redraw the working area rectangle.
Public Sub RedrawWorkingArea
    If mUse3D Then
        Viewer3D.RedrawWorkingArea
    Else
        Viewer2D.RedrawWorkingArea
    End If
End Sub

' PROPERTIES

'Gets current X Axis position.
Public Sub CurrentX As Float
    If mUse3D Then
        Return Viewer3D.CurrentX
    Else
        Return Viewer2D.CurrentX
    End If
End Sub

'Gets current Y Axis position.
Public Sub CurrentY As Float
    If mUse3D Then
        Return Viewer3D.CurrentY
    Else
        Return Viewer2D.CurrentY
    End If
End Sub

'Gets current Z Axis position.
Public Sub CurrentZ As Float
    If mUse3D Then
        Return Viewer3D.CurrentZ
    Else
        Return Viewer2D.CurrentZ
    End If
End Sub

'Gets or sets the current viewer zoom factor.
Public Sub setZoom(Zoom As Float)
    If mUse3D Then Viewer3D.Zoom = Zoom Else Viewer2D.Zoom = Zoom
End Sub
Public Sub getZoom As Float
    If mUse3D Then
        Return Viewer3D.Zoom
    Else
        Return Viewer2D.Zoom
    End If
End Sub

'Get round plane property.
'You can set it with DrawPlaneBorders method
Public Sub getRoundPlane As Boolean
    If mUse3D Then
        Return Viewer3D.RoundPlane
    Else
        Return Viewer2D.RoundPlane
    End If
End Sub

' VIEWS

'Returns a reference of viewer canvas object.
'You can use this to change any properties you like, add a style etc...
Public Sub getCanvas As Canvas
    If mUse3D Then
        Return Viewer3D.Canvas
    Else
        Return Viewer2D.Canvas
    End If
End Sub

'Returns a reference of viewer panel object.
'You can use this to change any properties you like, add a style etc...
Public Sub getPanel As Panel
    If mUse3D Then
        Return Viewer3D.Panel
    Else
        Return Viewer2D.Panel
    End If
End Sub

'Returns a reference of viewer ScrollPane object.
'You can use this to get InnerNode instance or change any properties you like, add a style etc...
Public Sub getScrollPane As ScrollView2D
    If mUse3D Then
        Return Viewer3D.ScrollPane
    Else
        Return Viewer2D.ScrollPane
    End If
End Sub

' //////////// DRAWING FUNCTIONS ////////////

'Draw a point in the specified position, color and line with.
'
'Z not used on 2D class (see 3D class), you can pass any numeric value like 0, the library just ignore it.
Public Sub DrawPoint(X As Float, Y As Float, Z As Float, Color As Int, PointWidth As Float)
    If mUse3D Then
        Viewer3D.DrawPoint(X, Y, Z, Color, PointWidth)
    Else
        Viewer2D.DrawPoint(X, Y, Z, Color, PointWidth)
    End If
End Sub

'Draw a line from X1,Y1 to X2,Y2 position with the specified color and width.
'
'Z1 and Z2 not used on 2D class (see 3D class), you can pass any numeric value like 0, the library just ignore it.
Public Sub DrawLine(X1 As Float, Y1 As Float, Z1 As Float, X2 As Float, Y2 As Float, Z2 As Float, Color As Int, LineWidth As Float, StartCup As Boolean, EndCup As Boolean)
    If mUse3D Then
        Viewer3D.DrawLine(X1, Y1, Z1, X2, Y2, Z2, Color, LineWidth, StartCup, EndCup)
    Else
        Viewer2D.DrawLine(X1, Y1, Z1, X2, Y2, Z2, Color, LineWidth, StartCup, EndCup)
    End If
End Sub

'Draw a line from last X,Y,Z position to the new X,Y,Z position with the specified color and width.
'Use SetStartPoint method to set a first start position that by default is X=0 Y=0 Z=0 (Home).
'
'Z not used on 2D class (see 3D class), you can pass any numeric value like 0, the library just ignore it.
Public Sub DrawLineTo(X As Float, Y As Float, Z As Float, Color As Int, LineWidth As Float, StartCup As Boolean, EndCup As Boolean)
    If mUse3D Then
        Viewer3D.DrawLineTo(X, Y, Z, Color, LineWidth, StartCup, EndCup)
    Else
        Viewer2D.DrawLineTo(X, Y, Z, Color, LineWidth, StartCup, EndCup)
    End If
End Sub

'Draw a circle in the specified position, radius, color, fill and stroke width.
'
'Z not used on 2D class (see 3D class), you can pass any numeric value like 0, the library just ignore it.
Public Sub DrawCircle(X As Float, Y As Float, Z As Float, Radius As Float, Color As Int, Filled As Boolean, StrokeWidth As Float)
    If mUse3D Then
        Viewer3D.DrawCircle(X, Y, Z, Radius, Color, Filled, StrokeWidth)
    Else
        Viewer2D.DrawCircle(X, Y, Z, Radius, Color, Filled, StrokeWidth)
    End If
End Sub

'Draw a text (2D) with the specified properties.
Public Sub DrawText(Text As String, X As Float, Y As Float, Typ As Typeface, Size As Float, Color As Int, Alignment As String)
    If mUse3D Then
        Viewer3D.DrawText(Text, X, Y, Typ, Size, Color, Alignment)
    Else
        Viewer2D.DrawText(Text, X, Y, Typ, Size, Color, Alignment)
    End If
End Sub

Public Sub TakeSnapshot
    If mUse3D Then    Viewer3D.TakeSnapshot Else Viewer2D.TakeSnapshot
End Sub
 
Last edited:
Upvote 0
Top