B4J Tutorial [B4X] Custom Views with Enhanced Designer Support

Discussion in 'B4J Tutorials' started by Erel, Jan 18, 2016.

  1. Erel

    Erel Administrator Staff Member Licensed User

    [​IMG]

    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:

    [​IMG]

    This tutorial will explain how to implement custom views.

    B4X Class

    Add a custom view class:

    [​IMG]

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

    The template in B4J is:
    Code:
    #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:
    Code:
    @Override
       
    public void DesignerCreateView(final ConcretePaneWrapper base, LabelWrapper label,
           
    Map args) {
         base.AddNode(getObject(), 
    00, base.getWidth(), base.getHeight());
         new PaneWrapper.ResizeEventManager(base.getObject(), 
    null, new Runnable() {

           @Override
           
    public void run() {
             SetLayoutAnimated(
    000, 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:
    Code:
    @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).
    - 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
     

    Attached Files:

    Last edited: Mar 3, 2016
  2. DonManfred

    DonManfred Expert Licensed User

    I do compile my libs with SLC

    Will there be a new version of SLC in future?
     
  3. Erel

    Erel Administrator Staff Member Licensed User

    Yes. You just need to copy the two files to SLC folder (replacing the existing files).
     
    somed3v3loper and DonManfred like this.
  4. Cableguy

    Cableguy Expert Licensed User

    Thank YOU sir!
     
  5. rwblinn

    rwblinn Well-Known Member Licensed User

    Thanks for this functionality.

    Wrote a simple example LabeledTextField to test (using the B4J 4.20 Beta Version).
     

    Attached Files:

  6. Daestrum

    Daestrum Well-Known Member Licensed User

    Glad you added the DesignerProperties explanation for java, I couldn't get my head round the B4X explanation.
     
  7. rwblinn

    rwblinn Well-Known Member Licensed 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.
     
  8. Erel

    Erel Administrator Staff Member Licensed 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")
     
  9. rwblinn

    rwblinn Well-Known Member Licensed User

    Thanks for Clarity ... as a goodie another small Custom View attached: ButtonTextField.
    upload_2016-1-19_16-22-33.png
     

    Attached Files:

    Mashiane, Erel, DonManfred and 2 others like this.
  10. Hugh Thomas

    Hugh Thomas Member Licensed User

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

    Code:
    Dim vw As clsMyCustView
    vw.Initialize(Me, 
    "vw_MyEvent")
    Activity.AddView(vw, 00100%x100%y)
    The compile fails with "inconvertable types" on the call to Activity.AddView.

    Hugh
     
  11. Erel

    Erel Administrator Staff Member Licensed User

    The class instance itself is not a view, so you cannot add it directly.

    You can expose the base panel or the view itself and then add the view to the layout.
     
  12. Hugh Thomas

    Hugh Thomas Member Licensed 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
     
  13. Hugh Thomas

    Hugh Thomas Member Licensed 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
     
  14. Erel

    Erel Administrator Staff Member Licensed User

    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.
     
  15. Hugh Thomas

    Hugh Thomas Member Licensed 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
     
    micro likes this.
  16. JanPRO

    JanPRO Well-Known Member Licensed User

    Hi,
    can you add an example for B4i please?
     
    Last edited: Feb 12, 2016
  17. DonManfred

    DonManfred Expert Licensed User

  18. JanPRO

    JanPRO Well-Known Member Licensed User

    Your are right, but I was a bit imprecise: I mean an (code) example for a CustomView implemented in a library.
     
  19. Hugh Thomas

    Hugh Thomas Member Licensed 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
     
  20. Cableguy

    Cableguy Expert Licensed User

    try the QUOTE keyword, it was ment for such cases
     
Loading...