Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member
0 Kudos

This blog deals with creating custom Web DynPro controls for a Mobile 7.1 device in a disconnected scenario.

Overall, custom controls can be tricky to create and deploy initially. In addition, there are many steps, such as memory disposal, that you must take care of yourself. Custom controls add significant flexibility to your application, however, the standard SAP controls are easier and quicker to implement.

 

The following is largely based on documentation sent to me from SAP. I've gone through and modified it to add my own thoughts and experiences.

 

 

h2. Creating a simple SWT custom control


Overview


There are three pieces needed to integrate a custom into
WebDynpro Lite framework. The following classes must be made:



  • IUIElementFactory: To instantiate
    and configure the control during runtime.
  • UiElementMethodCallAdapter:
    For interaction with the WebDynpro Lite framework, e.g. for context
    binding


Requirements for native controls


There are no special requirements for native controls, especially because it
is possible to write all needed code to instantiate it inside an
IUIElementFactory. Every SWT control needs to be a subclass of org.eclipse.swt.widgets.Control. Of
course it must not contain the SWT main loop itself.

Custom controls can be simple widgets like buttons but also composite widgets
or even complete SWT applications. Be careful: Larger widgets are more
independent of WebDynpro Lite. This can be an advantage or a disadvantage.


Create the IUiElementFactory


Please refer to the Javadocs of UIElementFactory to see an
extensive description.

 

 public class SampleFlatButtonCustomWidgetFactory implements IUIElementFactory {

        /**
         * Sample Implementation for an IUIElementFactory. Please see implementation and comments below for details.
         */
        public UIElement createNewInstance(int type, String subType, UIContainer parent,
                Map style) {

            //create the native control, and (important) specify parent cotainer, also apply all style parameters
            Button button = new Button(getSwtCompositeForWdliteContainer(parent),
                    SWT.BORDER);

            //The custom control has to provide a
            //implementation of an UiElementMethodCallAdapter. It contains all code for interaction between the custom control and the WebDynpro Lite framework
            FlatButtonAdapter flatButtonAdapter = new FlatButtonAdapter(button);
            //Adds the UiGenericElement which the wdlite framework used for communication with the custom control.
            //This UiGenericElement needs a UiElementMethodCallAdapter as argument.
            UiGenericElement wdliteElement = new UiGenericElement(
                    flatButtonAdapter);
            flatButtonAdapter.setUiGenericElement(wdliteElement);

            //The GenericSwtControlWrapper is needed for the SWT abstraction of the wdlite framework. Main purpose
            //is to store the control and listen and forward to events.
            //Please see Javadocs of UiElementFactory to see how events work
            GenericSwtControlWrapper wrappedSwtControl = new GenericSwtControlWrapper(
                    parent, wdliteElement, button);
            wdliteElement.setUiWidgetAndParent(wrappedSwtControl, parent);

            return wdliteElement;
        }

        public UIElement createNewInstance(int type, String subType,
                UIContainer container, Map style, String glyph) {
            return createNewInstance(type, subType, container, style);
        }

        private Composite getSwtCompositeForWdliteContainer(UIContainer parent) {
            return SWTUtils.getSwtComposite(parent);
        }
    }

 


Create the UiElementMethodCallAdapter


Please refer to the Javadocs of UiElementMethodCallAdapter
to see an extensive description. 

 

/**
     *


     * This class is a sample adapter for usage with
     * {@link SampleFlatButtonCustomWidgetFactory}. It allows to set the button
     * texts.
     *


     *
     * @author D044859
     *
     /<br />    public class FlatButtonAdapter extends BaseWdliteUiElementMethodCallAdapter implements SelectionListener, IEventSource {<br /><br />        /*
         * The native widget, needed for interaction, registering events, ...
         /<br />        private final Button button;<br />        <br />        /*
         * Needed to forward events
         /<br />        private UiGenericElement wdliteUiGenericElement;<br /><br />        private IWDNode node;<br /><br />        private String buttonProb = "";<br />     <br />        /*
         * The constructor just stores the native control and add an event listener
         * @param b
         /<br />        public FlatButtonAdapter(Button b) {<br />            this.button = b;<br />            b.addSelectionListener(this);<br />        }<br /><br />        <br />        /*
         * Do sth when a property is changed. Proeprties can be manually bound context
         * attribute values or  text / value properties specified in the ui or in the wdDoModify() method
         */
        public void onPropertyChange(String name,Object value){           
            System.out.println("property changed: " + name +  ", value: " + value);
            if ("textProp".equals(name) && value != null){
                button.setText(value.toString());
                buttonProb = value.toString();
            }
        }

        //just a setter
        public void setUiGenericElement(UiGenericElement wdliteUiGenericElement) {
            this.wdliteUiGenericElement = wdliteUiGenericElement;
        }

        //just store the context node. Do anything you like with it...
        public void setContextNode(IWDNode node) {
            this.node = node;
        }

        //Translates a call from wdliteFramework to the native control
        //Please note: setText is just a typesafe convenient method of setProperty()
        public void setText(String text) {
            button.setText(text);

        }

        public void widgetDefaultSelected(SelectionEvent arg0) {
        }
       
        public void setWidth(int width) {
            System.out.println("width: " + width);
        }

     
    }

 


Context Mapping


The context mapping for custom controls works exactly the
same way as for "normal" WebDynpro Lite controls, so any
documentation for these apply also for custom control. In general each custom
control gets access to one complete com.sap.tc.mobile.wdlite.progmodel.core.context.ContextNode
which can contain any number of child nodes.

 

In other words, if the control needs access to several WebDynpro context nodes, than  those nodes must be child nodes of a single parent node. The custom control must be binded to the larger parent node.

 

Please note the following addition to the setNode method of the adapter

 

//just store the context node. Do anything you like with it...
    public void setContextNode(final IWDNode node) {
            final IContextNodeCursorListener listener = new IContextNodeCursorListener()   {
            public void listenContextNodeCursorChanged(ContextNode node) {
                TUsersNode inputNode = (TUsersNode) node;
                composite.setUserNode(inputNode);           
            }
        };
        ((ContextNode)node).addContextNodeElementListener(listener);
           
        DisposeListener disposeListener = new DisposeListener() {
            public void widgetDisposed(DisposeEvent arg0) {
                ((ContextNode)node).removeContextNodeElementListener(listener);
            }
        };
        composite.addDisposeListener(disposeListener);
   
    }

 


Listening and registering for Events


Implementing the event handling code of the custom control:

The custom control developer has to handle and listen to all events of the
custom control manually. This means writing the usual SWT code in the
implementation of UiElementMethodCallAdapter of the control. Events which needs
to be forwarded to the ui must be additionally wrapped into an UIEvent and forwarded
to the doHandleCustomControlEvent() method of the according UIGenericElement,
just take a look at the provided examples on howto doing this.

Note: Since only one event type (onAction) is supported, the custom control
developer has to add additional data so an application developer can
distinguish between multiple events of a custom control.



//SWT Event Listener: Translate the native SWT event into a wdlite event an forward it. Note you have to implement the empty IEventSource interface to do this.
public void widgetSelected(SelectionEvent arg0) {
        UIEvent wdliteUiEvent = new UIEvent(this, IEvent.EVENT_ONACTION);
//add any data to the event, can be accessed by application developer
wdliteUiEvent.setEventData("someEventData", "yourcustomData");
//allows to distinguish between multiple events of a custom control
wdliteUiEvent.setEventData("eventType", "btnClick");
wdliteUiGenericElement.doHandleCustomControlEvent(wdliteUiEvent);
}


 

 

h4. Don't forget to clean up!


Add dispose listeners to your composites where you dispose of all of your UI elements.

 

 


Adding a custom control into a wdlite application.


Following steps are necessary to do this:


Add the custom control to a view


All custom controls are represented by the Interactive Form
control. You can find it in the list of controls in the Integration folder. If
the Interactice Form is not present there you have to manually delete or rename
the ProjectProperties.wdproperties file from /src/packages folder. After this
you need to restart your ide or atleast close and reopen the current project.
After doing this many new controls (most of them can not be used) are present
in the control list.

The Interactive Form control is a placeholder for any custom control. It is
used because it is not possible to add new controls to WebDynpro. After adding
the Interactive Form to the view you can change the common properties in the
properties view. All properties which are specific to a custom control can not
be changed here. This must be done in code.

h4. Add events mapping to custom control (optional)

Select your custom
control in the Layout tab of your View and open its properties. Create an
action method for the onSubmit event (onCheck event can not be used). The
defined action listener will be called for all events of a custom control. You
just have to implement the needed code in this action listener method.

Note: There is only one action method available which must be used for all
events of a single custom control. 


Setting and adapting the custom control properties


All non standard properties of a custom control must be set
manually in the #wdDoModifyView(App1ViewView view, boolean firstTime) method of
the corresponding *View class. It is possible to use the
*ViewView#getViewItem(name) method or a typesafe one to access the single
ViewElement.

You have to do two things here: Setting the id of the customControl (1) to do
the association and set any number of properties (2).



//wdDoModifyView of an View: Allows to set the type of a custom control, forward properties and bind to context attributes
public void wdDoModifyView(App1ViewView view, boolean firstTime) {
//@@begin wdDoModifyView()
  //important: application will fail if not set. The custom control has to document the id to use.
idOfCustomControl  = "FlatButton";
view.getSomeCustomControlViewItem().setUIControlSubType(idOfCustomControl);
  //set a custom parameter which is forwarded to the custom control
  view.getSomeCustomControlViewItem().setPropertyValue("nameOfFancyHeadline", value)
 
  //bind to a context attribute: binds the attribute someAttribute of the node someNode to the custom control property someProperty
   view.getSomeCustomControlViewItem().bindProperty("someProperty",(ContextNode)someNode, "someAttribute");
 
//@@end
}


Declare the dependencies inside the application


Right now only the id(s) of the custom control(s) is associated
with elements in the application. To be able to instaniate the controls, the
container also needs the classnames of the factories which create the controls.
This information is specified in the file custom_controls.properties which must
be located in the root classpath of the application (e.g. placed inside the
/src folder). This file contains your custom control factories and a comma seperated list of control IDs recognized by that factory. Sample content:



Wdlite-custom-control-factory-class-1=com.sap.SampleFlatButtonCustomWidgetFactory

Wdlite-custom-control-factory-ids-1=FlatButton,SomeOtherControl



This will specify class com.sap.SampleFlatButtonCustomWidgetFactory is responsible for
creating the custom controls with ids "FlatButton" and "SomeOtherControl". You can
find detailed information in the Javadocs of
com.sap.tc.mobile.wdlite.customcontrols.detector.CustomControlsPropertiesLoader.


<!>

<!></p>


Packaging


Custom controls must be packaged either inside an
application or as individual component (preferred). For doing the first one it
sufficient to copy all the custom controls classes to the application and add
the needed dependencies. This approach is easy but has several disadvantages, mainly
the more difficult maintanance.

The 2nd way is slightly more complicated but allows one to easily reuse the custom
control into any number of applications. Following steps are needed:



  1. Creating a new java dc
  2. Add the code of the custom
    control to the java dc
  3. Declare all needed
    dependencies and ensure the DC compiles without errors: Tc/mobile/oca/api
    + tc/mobile/wdlite/customcontrols/api + tc/mobile/wrapper/eswt
Create a new assembly public part and ensure
     that the complete code is contained in this public part, e.g. by exporting
     the whole Java Package Tree.
Create a new Mobile
     Application as wrapper for the custom control. This is necessary since the
     custom control is no separate deployable unit, this will probably change
     in future (not in 2007) but for now this additional step is necessary. Add


     the previously created custom control java dc as dependency to this
     application component. Note:
     You have to do this inside the Development Component Perspective since it
     is not supported inside the Mobile Application View right now. This
     application component needs at least one public part (can be empty) to be
     able to be used as mcd dependency (else mcd dependency will not be
     created, verify this in the OCA perspective: Point to SomeComponent –and
     select “Deployment Descriptors” in the context menu)
You do not (this changed from previous description) have to add a
     dependency from the component of your wdlite application to the application component which wraps the
     custom control. This has to be done in the DC Perspective since the OCA
     designtime does not support this yet. Without doing this the custom
     control is not accessible for the application on runtime.
Add a manual dependency in the mcd.xml of the UI component to the
     application dc which wraps the custom control.


<mcd_dependencies> </mcd_dependencies>

  1. Please note also the
    custom_controls.properties exists only in the ui components root classpath
    and verify it is *not present on any
    other place in the classpath* (e.g. in a 2nd cc in the /lib folder) !!!
    If the custom_controls.properties is present more than one time, only the
    first found can be loaded. This can lead e.g. to the error message "Unknown
    UI Element type: -1" when starting the application. (See the faq
    below for additional notes about this error message).
  2. In case you have errors on
    application startup (mainly class not found exceptions) you can check
    manually the deployed files: Redeploy your ui component and your custom
    control app dc and open the runime container inbox folder. Check the
    custom control app dc contains all class files of the custom control (if
    no classes are present, the custom_control java dc does not contain an assembly
    public part), Check the ui component mcd.xml contains the dependency to
    the mcd.xml. If not present create it manually and check if this solves
    the problem in the first place. (This issue can be caused by a missing
    empty compilation public part in the custom control application dc)


 

h2. Tips and Tricks


*Data binding </p><p class="MsoNormal"> Rather than bind your control to the context, you could simply grab a reference to the node. This can be done because the context is a singleton. You can simply instantiate the context and grab data from the node. The context listener is valuable because it allows a control to react when the webdynpro UI changes values in the context.</p><p class="MsoNormal">For example:</p><p>ComponentController controller = ComponentController.getInstance();<br />ComponentControllerContext.TContextNode sharedContext = (ComponentControllerContext.TContextNode) controller.getContext().getRootNode(); <br />ComponentControllerContext.TUserElement userElement = (ComponentControllerContext.TUserElement) sharedContext.nodeUsers().getElementAt(0);</p><p> </p><p>Service Objects</p><p>Again, service objects are singletons. You can instantiate them directly, however, you need to make sure you add them as dependencies.</p><p> UserserviceModel model = (UserserviceModel)OcaRoot.getInstance().getModel(UserserviceModel.class.getName());<br />UserService service = model.getServiceUserService();<br />Collection areaCol = service.queryGetUsersByFirstName("Bob");</p><p> </p><p>Affecting the Webdynpro UI *

Webdynpro singletons can be used to alter your views from the custom control, or to invoke business methods contained in your view controller.

//Grab a reference to the view to change buttons or other UI
//elements
EditUserView view = (EditUserView) EditUserView.getInstance();
view.getBtnBackViewItem().setText("Back");

//Grab a reference to the view controller, perhaps to
//fire a plug or leverage business logic
EditUser editUserViewController = EditUser.getInstance();
editUserViewController.wdFirePlugToAlertView();

 


 

 

4 Comments