B4J Tutorial [B4X] Custom Views with Enhanced Designer Support

Status
Not open for further replies.
SS-2016-01-14_16.16.18.png


Custom views are implemented as B4X classes or in a library.
A custom view includes a set of properties that can be set from the visual designer.
Note that the visual designer will show a box instead of the actual view.

Using custom views is very simple. You need to add the class or library to the project and then you can add the custom view in the same way you add other views:

SS-2016-01-18_14.39.00.png


This tutorial will explain how to implement custom views.

B4X Class

Add a custom view class:

SS-2016-01-18_14.43.10.png


Note that you can also add a regular class. The only difference is in the code template.

The template in B4J is:
B4X:
#Event: ExampleEvent (Value As Int)
#DesignerProperty: Key: BooleanExample, DisplayName: Boolean Example, FieldType: Boolean, DefaultValue: True, Description: Example of a boolean property.
#DesignerProperty: Key: IntExample, DisplayName: Int Example, FieldType: Int, DefaultValue: 10, MinRange: 0, MaxRange: 100, Description: Note that MinRange and MaxRange are optional.
#DesignerProperty: Key: StringWithListExample, DisplayName: String With List, FieldType: String, DefaultValue: Sunday, List: Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday
#DesignerProperty: Key: StringExample, DisplayName: String Example, FieldType: String, DefaultValue: Text
#DesignerProperty: Key: ColorExample, DisplayName: Color Example, FieldType: Color, DefaultValue: 0xFFCFDCDC, Description: You can use the built-in color picker to find the color values.
Sub Class_Globals
   Private fx As JFX
   Private EventName As String 'ignore
   Private CallBack As Object 'ignore
End Sub

Public Sub Initialize (vCallback As Object, vEventName As String)
   EventName = vEventName
   CallBack = vCallback
End Sub

Public Sub DesignerCreateView (Base As Pane, Lbl As Label, Props As Map)

End Sub

Private Sub Base_Resize (Width As Double, Height As Double)

End Sub

Initialize, DesignerCreateView and Base_Resize signatures must be as in the code above.

At runtime the Initialize sub will be called followed by DesignerCreateView. Most of the work is done in DesignerCreateView.

The events and the designer properties are declared in the attributes.

When you create a custom view a pane or panel (with the event name set to Base) is added to the views tree. In most cases you will add the other views to the base panel and resize the views in Base_Resize event. This is true for B4J and B4i. In B4A there is no resize event as the activity is recreated instead.

The passed label includes the text related settings. It is not added to the views tree. You can either ignore it or use its text properties.
The Props map holds the custom properties. You should get the properties values and apply them to the custom view.

Designer Properties

Each property is made of several fields, the following fields are required:
Key - The property key. This will be used to get the value from the Props map.
DisplayName - The property name in the properties grid.
FieldType - One of the following values (case insensitive): String, Int, Float, Boolean or Color.
DefaultValue - The default value.

Optional fields:
Description - Will be displayed at the bottom of the properties grid when the property is selected.
MinRange / MaxRange - Minimum and maximum numeric values allowed.
List - A pipe (|) separated list of items from which the developer can choose (should be used with string fields).

You shouldn't use commas in any of the above fields.

See the attached project for several B4J examples.


Java Custom Views

Creating a custom view in Java is similar to the above. The class needs to implement the DesignerCustomView interface.
The interface declares two methods:
B4X:
@Override
   public void DesignerCreateView(final ConcretePaneWrapper base, LabelWrapper label,
       Map args) {
     base.AddNode(getObject(), 0, 0, base.getWidth(), base.getHeight());
     new PaneWrapper.ResizeEventManager(base.getObject(), null, new Runnable() {

       @Override
       public void run() {
         SetLayoutAnimated(0, 0, 0, base.getWidth(), base.getHeight());
       }
     });
     getItems().AddAll(Common.ArrayToList(((String)args.Get("Items")).split("\\|")));
     getObject().setStyle(label.getStyle()); //set the font style


   }
   @Hide
   @Override
   public void _initialize(BA ba, Object arg1, String EventName) {
     innerInitialize(ba, EventName.toLowerCase(BA.cul), false);
   }
These methods are similar to the B4X methods discussed above. Note the usage of ResizeEventManager to resize the custom view together with the base panel (B4J only).

Properties are declared as annotations, for example:
B4X:
@DesignerProperties(values={
     @Property(key="MinValue", displayName="Min Value", fieldType="float", defaultValue="0", minRange="0"),
     @Property(key="MaxValue", displayName="Max Value", fieldType="float", defaultValue="100"),
     @Property(key="LowValue", displayName="Low Value", fieldType="float", defaultValue="0", minRange="0",
       description="Current lower value."),
     @Property(key="HighValue", displayName="High Value", fieldType="float", defaultValue="100",
       description="Current lower value."),
     @Property(key="ShowTickLabels", displayName="Show Tick Labels", fieldType="boolean", defaultValue="false"),
     @Property(key="ShowTickMarks", displayName="Show Tick Marks", fieldType="boolean", defaultValue="false"),
     @Property(key="SnapToTicks", displayName="Snap To Ticks", fieldType="boolean", defaultValue="false"),
     @Property(key="Orientation", displayName="Orientation", fieldType="string", defaultValue="Horizontal",
         list="Horizontal|Vertical"),

})

You need to use BADoclet v1.05+ (v1.05 is attached) to generate the XML file.
See the attached ControlsFX source code as an example.

Tips

- In addition to the defined properties the Props map will include the parent form (key=Form) or activity (key=activity) or page (key=page).
- The keys in the Props map are case sensitive.
- If you add a new property to an existing class or library (that was already implemented as a custom view) then you should use Props.GetDefault if you want to maintain backwards compatibility.
- CSSUtils class is very useful for style manipulation: https://www.b4x.com/android/forum/threads/61824/#content
 

Attachments

  • BADoclet.zip
    9.4 KB · Views: 1,483
  • B4J_Example.zip
    8.9 KB · Views: 1,502
  • B4A_ViewsEx_Src.zip
    2.5 KB · Views: 1,831
  • B4i_iUI8.zip
    44.2 KB · Views: 916
  • B4J_ControlsFX_SRC.zip
    16.2 KB · Views: 1,177
Last edited:

LucaMs

Expert
Licensed User
Wonderful (perhaps I have already commented this thread, I have to look at :)).

I would add these lines to the template:
B4X:
Public Sub setTag(Tag As String)
    mBase.Tag = Tag
End Sub
Public Sub getTag As String
    Return mBase.Tag
End Sub

and perhaps other common properties, like:
B4X:
#Region  + PROPERTIES

Public Sub setTag(Tag As String)
    mBase.Tag = Tag
End Sub
Public Sub getTag As String
    Return mBase.Tag
End Sub

Public Sub setVisible(Visible As Boolean)
   mBase.Visible = Visible
End Sub
Public Sub getVisible As Boolean
   Return mBase.Visible
End Sub

Public Sub setEnabled(Enabled As Boolean)
   mBase.Enabled = Enabled
   For Each v As View In mBase.GetAllViewsRecursive
     v.Enabled = Enabled
   Next
End Sub
Public Sub getEnabled As Boolean
   Return mBase.Enabled
End Sub

#End Region

setEnabled should be different in B4J, of course (I think in B4I too, I don't know)


P.S. I have also a panel on my custom view, same size of Base panel. I use it to change the "disabled look" of my custom view, setting its color to ARGB(160, 0, 0, 0) and to raise the view Click (the view is enabled and then the panel color is Transparent).
Perhaps also these "features" should be in all custom views as default.

Uhm... I'm thinking I should post my template (a Forum for members' templates? Or should we use the Snippets Forum?)
 
Last edited:

Hugh Thomas

Member
Licensed User
Is it possible to change the layout values (left, top, width, height) of the Base panel in B4i? I'm working on a CustomView that adjusts the base panel layout values depending on the value of the customview's designer properties. This works in B4A, but in B4i changing base panel layout values has no effect.

Hugh
 

Mashiane

Expert
Licensed User
Thanks for Clarity ... as a goodie another small Custom View attached: ButtonTextField.
View attachment 40756
I would like to add the ButtonTextField control in code and set properties like Tag, Width and also return these. Have done a little adjustment to it for those properties. How do I fix it so that I can add it to a control via code e.g. initialize etc?

Thanks
 

Cableguy

Expert
Licensed User
you need to create an "AddToParent" method, and re-create the view in it by adding your custom view to the passed parent
 

klaus

Expert
Licensed User
You need to add a routine in the class to add the CustomView to a parent.

Have a look HERE, in the B4A forum, it was a similar question.

You may have a look at chapter 12.4 Custom views in the B4A User's Gude.
And especially chapter 12.4.2 Adding a custom view by code.
There you find an example of a CustomView which can also be added in the code.

The CustomView principles are the same for B4A, B4i and B4J.
 

rwblinn

Well-Known Member
Licensed User
Hi,

thanks for the additional information .. still learning how to create B4J custom views properly.
Based on B4A user guide, updated & tested attached example LabeledTextField with a second label created via code.
In the Custom View class added
B4X:
Public Sub AddToParent(Parent As Pane, Left As Int, Top As Int, Width As Int, Height As Int)
which includes creating of the used views label and textfield. The properties of those are set in the main module.

Let me know if this was done correctly.
 

Attachments

  • B4JHowToCustomViewLabeledTextField.zip
    4.2 KB · Views: 310

klaus

Expert
Licensed User
Yes it's the right way.

One comment:
You use this routine:
Public Sub SetLabelText(v As String)
lblField.Text = v
End Sub

And in the calling module:
LabeledTextField2.SetLabelText("Enter 2:")

You may use this routine:
Public Sub setLabelText(LabelText As String)
lblField.Text = LabelText
End Sub

And in the calling module:
LabeledTextField2.LabelText = "Enter 2:"

When using SetLabelText it is considered as a routine, S upper case.
When using setLabelText it is considered as a property, s lower case.

The same with GetXXX and getXXX.

Attached a modified version with some other suggestions.
 

Attachments

  • B4JHowToCustomViewLabeledTextField_New.zip
    3.8 KB · Views: 332

rwblinn

Well-Known Member
Licensed User
Thanks a lot for the additional modifications - very well explained. Will update the B4JHowTo Examples for Custom Views accordingly = done.

Been of great help - Appreciated.
 
Last edited:
Status
Not open for further replies.
Top