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: 2,144
  • B4J_Example.zip
    8.9 KB · Views: 2,123
  • B4A_ViewsEx_Src.zip
    2.5 KB · Views: 2,504
  • B4i_iUI8.zip
    44.2 KB · Views: 1,445
  • B4J_ControlsFX_SRC.zip
    16.2 KB · Views: 1,809
Last edited:

Cableguy

Expert
Licensed User
Longtime User
Thank YOU sir!
 

Daestrum

Expert
Licensed User
Longtime User
Glad you added the DesignerProperties explanation for java, I couldn't get my head round the B4X explanation.
 

rwblinn

Well-Known Member
Licensed User
Longtime User
Question
How to access the Base Pane Properties, like Top, Left, Height, Width, Background, Border from Code after the Custom View has been added to a Form via the Visual Designer?

Example:
MyCustomView.Top = 100
Etc.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
You can get the position and size properties from the Base object directly:
Base.Top, Base.Left... (better to do it in Base_Resize event).

In most cases you don't need to get the background and the border as they will already be set.

Example of getting the background color (as a hex string): CSSUtils.GetProperty(Base, "-fx-background-color")
 

Hugh Thomas

Member
Licensed User
Longtime User
If I implement a custom view as a class in B4A, is it possible to add it to a parent view programmatically, eg:

B4X:
    Dim vw As clsMyCustView
vw.Initialize(Me, "vw_MyEvent")
Activity.AddView(vw, 0, 0, 100%x, 100%y)

The compile fails with "inconvertable types" on the call to Activity.AddView.

Hugh
 

Hugh Thomas

Member
Licensed User
Longtime User
Thanks, I expected as much, but thought I'd ask as I'd like to make my view behave as much as possible like the standard views.

On a related matter, is there any way for a custom view class to find out when the base panel is added to a parent view (so that it knows it's dimensions are now available)? I though the Base_Resize event would do it, but that's not available on B4A.

Thanks,

Hugh
 

Hugh Thomas

Member
Licensed User
Longtime User
Ignore that last question, I think I figured it out by myself.

I've been thinking that defining a Custom View class meant that the class would be treat as a View by B4A. In fact the only thing special about a Custom View class is that the Designer understands that it's a view, and will let you set it up in a layout. As far as the rest of B4A is concerned it's just another class that happens to have views in it, so it won't get resize events etc.

Hugh
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
In fact the only thing special about a Custom View class is that the Designer understands that it's a view, and will let you set it up in a layout

It is more than this. The designer will also apply the anchors and designer script to the base panel. This is similar to how native views work.
 

Hugh Thomas

Member
Licensed User
Longtime User
OK. So the designing provides all this added value for the custom view.

Unfortunately I build up my GUI programmatically so I missing out on this assistance from the Designer, and will have to expose the base panel, and provide other methods to handle resizing etc.

I stopped using the designer because I couldn't get the behavior I wanted, but now that I've got more experience with B4A I'll reconsider for my next project.

Thanks,

Hugh
 

Hugh Thomas

Member
Licensed User
Longtime User
Commas in the #DesignerProperty Description field terminate the description. Is there a way of quoting the description, or escaping the commas, so that I can have commas in the description? I've tried putting the whole description in quotes but that didn't work.

Hugh
 

Cableguy

Expert
Licensed User
Longtime User
try the QUOTE keyword, it was ment for such cases
 
Status
Not open for further replies.
Top