Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Scout/HowTo/3.8/Add a custom GUI component"

< Scout‎ | HowTo‎ | 3.8
(Merged in SWT implementation from 3.7 howto)
(Added chapter on how to add persistence)
Line 1: Line 1:
{{ScoutPage|cat=HowTo 3.8}}
+
{{ScoutPage|cat=HowTo 3.8}}  
  
If the controls provided by Scout don’t fit your needs, you can easily create custom controls. This howto describes how this can be done.
+
If the controls provided by Scout don’t fit your needs, you can easily create custom controls. This howto describes how this can be done.  
  
To create new controls it is important to understand how scout displays a control. Remember we have the client plugin where you can define the form and the controls on it, let’s call them scout controls. Additionally we have the gui plugin which creates the effective SWT respectively Swing-Controls based on the code in the client plugin. But how does the gui plugin know which client controls belong to which gui controls? This mapping is done by an extension point.
+
To create new controls it is important to understand how scout displays a control. Remember we have the client plugin where you can define the form and the controls on it, let’s call them scout controls. Additionally we have the gui plugin which creates the effective SWT respectively Swing-Controls based on the code in the client plugin. But how does the gui plugin know which client controls belong to which gui controls? This mapping is done by an extension point.  
  
As an example we will create an AbstractDrawLineField. In this field a user can draw a line from a start point to an end point.
+
As an example we will create an AbstractDrawLineField. In this field a user can draw a line from a start point to an end point.  
  
[[Image:Scout custom draw line field.png]]
+
[[Image:Scout custom draw line field.png]]  
  
==Creating the Scout Control==
+
== Creating the Scout Control ==
Every scout control on a form extends from <code>AbstractFormField</code>. So what you have to do is to create a new class which extends from AbstractFormField. Instead of directly extend from AbstractFormField you could use AbstractCustomField as super class which is actually the recommended way to go. AbstractCustomField does not provide any additional logic but since it implements the Interface ICustomField it is easier to distinguish between controls provided by the framework and controls provided by you. Additionally to the main class it is recommended to create an interface for your control.
+
For our AbstractDrawLineField, we create a new package com.bsiag.minicrm.client.ui.form.fields.ext in the client plugin. In this package we create an interface IDrawLineField and IDrawLineUiFacade. IDrawLineField extends the interface IFormField. Then we create the class AbstractDrawLineField which extends AbstractCustomField and implements IDrawLineField.
+
  
===Properties===
+
Every scout control on a form extends from <code>AbstractFormField</code>. So what you have to do is to create a new class which extends from AbstractFormField. Instead of directly extend from AbstractFormField you could use AbstractCustomField as super class which is actually the recommended way to go. AbstractCustomField does not provide any additional logic but since it implements the Interface ICustomField it is easier to distinguish between controls provided by the framework and controls provided by you. Additionally to the main class it is recommended to create an interface for your control. For our AbstractDrawLineField, we create a new package com.bsiag.minicrm.client.ui.form.fields.ext in the client plugin. In this package we create an interface IDrawLineField and IDrawLineUiFacade. IDrawLineField extends the interface IFormField. Then we create the class AbstractDrawLineField which extends AbstractCustomField and implements IDrawLineField.
Now you have to think about what your field should provide. A common scout field normally consists of some properties and some execXY methods. Please notice that every form field is also an IPropertyObserver. So if you think that someone else could be interested in your property, then you should make use of the protected field m_propertySupport. This is especially necessary if the property defines behaviour of the gui control. If someone dynamically changes the property on your field the gui control has to be informed about it otherwise it would not be reflected on the gui.
+
 
The AbstractDrawLineField has two properties, the start and the end point. Thus we add now the following code to the IDrawLineField interface:
+
=== Properties ===
 +
 
 +
Now you have to think about what your field should provide. A common scout field normally consists of some properties and some execXY methods. Please notice that every form field is also an IPropertyObserver. So if you think that someone else could be interested in your property, then you should make use of the protected field m_propertySupport. This is especially necessary if the property defines behaviour of the gui control. If someone dynamically changes the property on your field the gui control has to be informed about it otherwise it would not be reflected on the gui. The AbstractDrawLineField has two properties, the start and the end point. Thus we add now the following code to the IDrawLineField interface:  
  
 
<source lang="java">
 
<source lang="java">
Line 28: Line 28:
  
 
IDrawLineUiFacade getUiFacade();
 
IDrawLineUiFacade getUiFacade();
</source>
+
</source> This interface is now already finished. The getUiFacade method will be needed to receive events from the gui control. In the AbstractDrawLineField, we implement now these methods with the usage of m_propertySupport: <source lang="java">
This interface is now already finished. The getUiFacade method will be needed to receive events from the gui control. In the AbstractDrawLineField, we implement now these methods with the usage of m_propertySupport:
+
<source lang="java">
+
 
   private IDrawLineUiFacade m_uiFacade;
 
   private IDrawLineUiFacade m_uiFacade;
  
Line 57: Line 55:
 
     return m_uiFacade;
 
     return m_uiFacade;
 
   }
 
   }
</source>
+
</source>  
  
===Default Values===
+
=== Default Values ===
Another important thing about properties is the way they are initialized. For each property there should be a getConfiguredPropertyName-function. With this method the user is able to set the initial value of the property. This initial value is read in the method initConfig(). This means for your custom field that it has to override the method initConfig() too and has to fill up the actual properties with the initial values of the getConfigured functions.
+
 
For the AbstractDrawLineField we need four getConfigured methods, two for each point. The annotations are needed for the Scout SDK only.
+
Another important thing about properties is the way they are initialized. For each property there should be a getConfiguredPropertyName-function. With this method the user is able to set the initial value of the property. This initial value is read in the method initConfig(). This means for your custom field that it has to override the method initConfig() too and has to fill up the actual properties with the initial values of the getConfigured functions. For the AbstractDrawLineField we need four getConfigured methods, two for each point. The annotations are needed for the Scout SDK only. <source lang="java">
<source lang="java">
+
 
@ConfigProperty(ConfigProperty.INTEGER)
 
@ConfigProperty(ConfigProperty.INTEGER)
 
@Order(500)
 
@Order(500)
Line 97: Line 94:
 
   setEnd(new Point(getConfiguredEndX(), getConfiguredEndY()));
 
   setEnd(new Point(getConfiguredEndX(), getConfiguredEndY()));
 
}
 
}
</source>
+
</source>  
===Receiving GUI Events===
+
 
Now we have to think about which gui events we want to propagate to the scout control. These events are handled by uiFacade.
+
=== Receiving GUI Events ===
In our example, we have two important events. Setting a new start and setting a new end point. Add to the façade interface these two methods:
+
 
void setStartFromUI(Point p);
+
Now we have to think about which gui events we want to propagate to the scout control. These events are handled by uiFacade. In our example, we have two important events. Setting a new start and setting a new end point. Add to the façade interface these two methods: void setStartFromUI(Point p); void setEndFromUI(Point p); Now add this inner Class to AbstractDrawLineField: <source lang="java">
void setEndFromUI(Point p);
+
Now add this inner Class to AbstractDrawLineField:
+
<source lang="java">
+
 
private class P_UiFacade implements IDrawLineUiFacade{
 
private class P_UiFacade implements IDrawLineUiFacade{
 
   public void setEndFromUI(Point p){
 
   public void setEndFromUI(Point p){
Line 114: Line 108:
 
   }
 
   }
 
}
 
}
</source>
+
</source> And add this line of code as the first statement in the initConfig method: <source lang="java">
And add this line of code as the first statement in the initConfig method:
+
<source lang="java">
+
 
m_uiFacade = new P_UiFacade();
 
m_uiFacade = new P_UiFacade();
</source>
+
</source>  
  
==Creating the GUI Control==
+
== Creating the GUI Control ==
After creating the custom field, the corresponding gui control has to be created. Every gui control extends from SwtScoutFieldComposite (SWT) or from SwingScoutFieldComposite (Swing).
+
We will create both a swing and SWT implementation for our AbstractDrawLineField.
+
  
===Implementation===
+
After creating the custom field, the corresponding gui control has to be created. Every gui control extends from SwtScoutFieldComposite (SWT) or from SwingScoutFieldComposite (Swing). We will create both a swing and SWT implementation for our AbstractDrawLineField.  
Some important methods in a gui field are initializeSwt / initializeSwing, attachScout and handleScoutPropertyChange. The actual gui composite is created in the initializeSwt / initializeSwing method. Properties of the scout field are normally handled in the method attachScout respectively in the method handleScoutPropertyChange.
+
To fire events which should be handled in the scout control and which normally leads to an execution of an execXY method the UIFacade of a scout field should be used.
+
For the Swt/SwingScoutDrawLineField we have a mouse listener that propagates the point changes from the gui to the scout control. When then the scout control adjusts the point properties, these changes are handled in the handleScoutPropertyChange method and there the line in the gui will be updated.
+
  
====Swing Implementation====
+
=== Implementation ===
Create a new package com.bsiag.minicrm.ui.swing.form.fields.ext in the ui.swing plugin. Create in this package a new class SwingScoutDrawLineField that extends SwingScoutFieldComposite<IDrawLineField>.
+
  
Paste the following code in the SwingScoutDrawLineField class:
+
Some important methods in a gui field are initializeSwt / initializeSwing, attachScout and handleScoutPropertyChange. The actual gui composite is created in the initializeSwt / initializeSwing method. Properties of the scout field are normally handled in the method attachScout respectively in the method handleScoutPropertyChange. To fire events which should be handled in the scout control and which normally leads to an execution of an execXY method the UIFacade of a scout field should be used. For the Swt/SwingScoutDrawLineField we have a mouse listener that propagates the point changes from the gui to the scout control. When then the scout control adjusts the point properties, these changes are handled in the handleScoutPropertyChange method and there the line in the gui will be updated.
<source lang="java">
+
 
 +
==== Swing Implementation  ====
 +
 
 +
Create a new package com.bsiag.minicrm.ui.swing.form.fields.ext in the ui.swing plugin. Create in this package a new class SwingScoutDrawLineField that extends SwingScoutFieldComposite&lt;IDrawLineField&gt;.
 +
 
 +
Paste the following code in the SwingScoutDrawLineField class: <source lang="java">
 
public class SwingScoutDrawLineField extends SwingScoutFieldComposite<IDrawLineField> {
 
public class SwingScoutDrawLineField extends SwingScoutFieldComposite<IDrawLineField> {
 
private P_MouseListener m_mouseListener;
 
private P_MouseListener m_mouseListener;
Line 233: Line 224:
 
}
 
}
 
}
 
}
</source>
+
</source>  
  
====SWT Implementation====
+
==== SWT Implementation ====
Create a new package com.bsiag.minicrm.ui.swt.form.fields.ext in the ui.swt plugin. Create in this package a new class SwtScoutDrawLineField that extends SwtScoutFieldComposite<IDrawLineField>.
+
  
Paste the following code in the SwtScoutDrawLineField class:
+
Create a new package com.bsiag.minicrm.ui.swt.form.fields.ext in the ui.swt plugin. Create in this package a new class SwtScoutDrawLineField that extends SwtScoutFieldComposite&lt;IDrawLineField&gt;.
<source lang="java">
+
 
 +
Paste the following code in the SwtScoutDrawLineField class: <source lang="java">
 
public class SwtScoutDrawLineField extends SwtScoutFieldComposite<IDrawLineField> {
 
public class SwtScoutDrawLineField extends SwtScoutFieldComposite<IDrawLineField> {
 
   private P_MouseListener m_mouseListener;
 
   private P_MouseListener m_mouseListener;
Line 341: Line 332:
 
   }
 
   }
 
}
 
}
</source>
+
</source>  
 +
 
 +
== Map the Controls  ==
 +
 
 +
To tell the gui plugin which control belongs to which gui control an extension has to be add in the gui plugin. For a SWT gui field we need the extension point org.eclipse.scout.rt.ui.swt.formfields and for a Swing we use org.eclipse.scout.rt.ui.swing.formfields. In this extension point we have to specify which model class maps to which ui class. As model class we use usually the interface and not the concrete class.
 +
 
 +
=== Swing extension  ===
 +
 
 +
For our example, open the plugin.xml from the ui.swing plugin. Go to the Extensions tab on the bottom of the manifest editor and click Add…. Uncheck the checkbox at the bottom and select the org.eclipse.scout.rt.ui.swing.formfields extension point before you click Finish. Right-click the newly created extension in your manifest editor and select New &gt; formField. Now enter in the name text box “Draw Line Field” and select as modelClass “com.bsiag.minicrm.client.ui.form.fields.ext.IDrawLineField”. Now we have to specify the ui class. Right-click on the formField extension and select New &gt; uiClass. There we select the class “com.bsiag.minicrm.ui.swing.form.fields.ext.SwingScoutDrawLineField”.
 +
 
 +
=== SWT extenstion  ===
 +
 
 +
Open the plugin.xml from the ui.swt plugin. Go to the Extensions tab on the bottom of the manifest editor and click Add…. Uncheck the checkbox at the bottom and select the org.eclipse.scout.rt.ui.swt.formfields extension point before you click Finish. Right-click the newly created extension in your manifest editor and select New &gt; formField. Now enter in the name text box “Draw Line Field” and select as modelClass “com.bsiag.minicrm.client.ui.form.fields.ext.IDrawLineField”. Now we have to specify the ui class. Right-click on the formField extension and select New &gt; uiClass. There we select the class “com.bsiag.minicrm.ui.swt.form.fields.ext.SwingScoutDrawLineField”.
 +
 
 +
== Test the DrawLineField  ==
 +
 
 +
You can now create a new Form where you use the DrawLineField. If you set the grid height to about a value of eight and disable the label you get a big field.
 +
 
 +
<br>
 +
 
 +
== Making the DrawLineField persistent  ==
 +
 
 +
So far, the line in our custom control can be pre-configured in the SDK and manually re-drawn using the mouse.
 +
 
 +
However, it is not yet possible to persistently store and load the line points from/to the database. This chapter shows how the custom field needs to be modified to allow persistence.
 +
 
 +
=== Data structures for the FormData  ===
 +
 
 +
*Create a class '''org.eclipse.minicrm.shared.data.form.fields.ext.LinePoins''':
 +
<pre>package org.eclipse.minicrm.shared.data.form.fields.ext;
 +
 
 +
import java.io.Serializable;
 +
 
 +
public class LinePoints implements Serializable {
 +
  private static final long serialVersionUID = 1L;
 +
 
 +
  private LinePoint start;
 +
  private LinePoint end;
 +
 
 +
  public LinePoints() {
 +
    start = null;
 +
    end = null;
 +
  }
 +
 
 +
  public LinePoints(LinePoint start, LinePoint end) {
 +
    this.start = start;
 +
    this.end = end;
 +
  }
 +
 
 +
  public LinePoint getStart() {
 +
    return start;
 +
  }
 +
 
 +
  public void setStart(LinePoint start) {
 +
    this.start = start;
 +
  }
 +
 
 +
  public LinePoint getEnd() {
 +
    return end;
 +
  }
 +
 
 +
  public void setEnd(LinePoint end) {
 +
    this.end = end;
 +
  }
 +
}
 +
</pre>
 +
*Create a class '''org.eclipse.minicrm.shared.data.form.fields.ext.LinePoint''':
 +
<pre>package org.eclipse.minicrm.shared.data.form.fields.ext;
 +
 
 +
import java.awt.Point;
 +
import java.io.Serializable;
 +
 
 +
public class LinePoint implements Serializable {
 +
  private static final long serialVersionUID = 1L;
 +
  private int x;
 +
  private int y;
 +
 
 +
  public LinePoint(int x, int y) {
 +
    this.x = x;
 +
    this.y = y;
 +
  }
 +
 
 +
  public LinePoint(Point p) {
 +
    this.x = p.x;
 +
    this.y = p.y;
 +
  }
 +
 
 +
  public void setX(int x) {
 +
    this.x = x;
 +
  }
 +
 
 +
  public void setY(int y) {
 +
    this.y = y;
 +
  }
 +
 
 +
  public int getX() {
 +
    return x;
 +
  }
 +
 
 +
  public int getY() {
 +
    return y;
 +
  }
 +
 
 +
  public Point getPoint() {
 +
    return new Point(x, y);
 +
  }
 +
}
 +
</pre>
 +
*Create a class '''org.eclipse.minicrm.shared.data.form.fields.ext.AbstractDrawLineFieldData''' (this is the formField class):
 +
<pre>package org.eclipse.minicrm.shared.data.form.fields.ext;
 +
 
 +
import java.io.Serializable;
 +
 
 +
import org.eclipse.scout.commons.TypeCastUtility;
 +
import org.eclipse.scout.commons.holders.IHolder;
 +
import org.eclipse.scout.rt.shared.data.form.fields.AbstractFormFieldData;
 +
 
 +
public class AbstractDrawLineFieldData extends AbstractFormFieldData implements IHolder&lt;LinePoints&gt;, Serializable {
 +
  private static final long serialVersionUID = 1L;
 +
  private LinePoints m_value;
 +
 
 +
  public AbstractDrawLineFieldData() {
 +
    super();
 +
  }
 +
 
 +
  @Override
 +
  public LinePoints getValue() {
 +
    return m_value;
 +
  }
 +
 
 +
  @Override
 +
  public void setValue(LinePoints o) {
 +
    m_value = o;
 +
    setValueSet(true);
 +
  }
 +
 
 +
  @SuppressWarnings("unchecked")
 +
  @Override
 +
  public Class&lt;LinePoints&gt; getHolderType() {
 +
    return TypeCastUtility.getGenericsParameterClass(getClass(), IHolder.class);
 +
  }
 +
}
 +
</pre>
 +
*Add the following annotation before the class definition of '''com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField''':
 +
<pre>@FormData(value = AbstractDrawLineFieldData.class, defaultSubtypeSdkCommand = DefaultSubtypeSdkCommand.CREATE, sdkCommand = SdkCommand.USE, genericOrdinal = 0)
 +
public abstract class AbstractDrawLineField extends AbstractCustomField implements IDrawLineField {
 +
</pre>
 +
=== Adjusting the interface and renderers  ===
 +
 
 +
*Remove the following two lines from '''com.bsiag.minicrm.client.ui.form.fields.ext.IDrawLineField''':
 +
<pre>public static final String PROP_START = "startPoint";
 +
public static final String PROP_END = "endPoint";
 +
</pre>
 +
*Change the <code>handleScoutPropertyChange()</code> method in both '''com.bsiag.minicrm.ui.swing.form.fields.ext.SwingScoutDrawLineField''' and '''com.bsiag.minicrm.ui.swt.form.fields.ext.SwtScoutDrawLineFieldfrom''' from:
 +
<pre>@Override
 +
protected void handleScoutPropertyChange(String name, Object newValue) {
 +
  if (name.equals(IDrawLineField.PROP_START) || name.equals(IDrawLineField.PROP_END)) {
 +
    updateLineFromScout();
 +
  }
 +
  super.handleScoutPropertyChange(name, newValue);
 +
}   
 +
</pre>
 +
to
 +
<pre>@Override
 +
protected void handleScoutPropertyChange(String name, Object newValue) {
 +
  if (name.equals(IValueField.PROP_VALUE)) {
 +
    updateLineFromScout();
 +
  }
 +
  super.handleScoutPropertyChange(name, newValue);
 +
}
 +
</pre>
 +
=== Accessing start and end points from PROP_VALUE  ===
 +
 
 +
*Replace the <code>getStart()</code>, <code>getEnd()</code>, <code>setStart()</code> and <code>setEnd()</code> methods in '''com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineFieldwith''' the following code:
 +
<pre>  @Override
 +
  public Point getEnd() {
 +
    LinePoints value = (LinePoints) propertySupport.getProperty(IValueField.PROP_VALUE);
 +
    return (value == null&nbsp;? null&nbsp;: value.getEnd() == null&nbsp;? null&nbsp;: value.getEnd().getPoint());
 +
  }
 +
 
 +
  @Override
 +
  public void setEnd(Point p) {
 +
    LinePoints oldValue = getValue();
 +
    LinePoints value;
 +
    if (oldValue&nbsp;!= null) {
 +
      value = new LinePoints(oldValue.getStart(), oldValue.getEnd());
 +
    }
 +
    else {
 +
      value = new LinePoints();
 +
    }
 +
    value.setEnd(new LinePoint(p));
 +
    boolean changed = propertySupport.setPropertyNoFire(IValueField.PROP_VALUE, value);
 +
    if (changed) {
 +
      propertySupport.firePropertyChange(IValueField.PROP_VALUE, oldValue, value);
 +
    }
 +
  }
 +
 
 +
  @Override
 +
  public Point getStart() {
 +
    LinePoints value = (LinePoints) propertySupport.getProperty(IValueField.PROP_VALUE);
 +
    return (value == null&nbsp;? null&nbsp;: value.getStart() == null&nbsp;? null&nbsp;: value.getStart().getPoint());
 +
  }
 +
 
 +
  @Override
 +
  public void setStart(Point p) {
 +
    LinePoints oldValue = getValue();
 +
    LinePoints value;
 +
    if (oldValue&nbsp;!= null) {
 +
      value = new LinePoints(oldValue.getStart(), oldValue.getEnd());
 +
    }
 +
    else {
 +
      value = new LinePoints();
 +
    }
 +
    value.setStart(new LinePoint(p));
 +
    boolean changed = propertySupport.setPropertyNoFire(IValueField.PROP_VALUE, value);
 +
    if (changed) {
 +
      propertySupport.firePropertyChange(IValueField.PROP_VALUE, oldValue, value);
 +
    }
 +
  } 
 +
</pre>
 +
=== AbstractDrawLineField  ===
 +
 
 +
*Add the following new member variables to '''com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField''':
 +
<pre>private static final IScoutLogger LOG = ScoutLogManager.getLogger(AbstractDrawLineField.class);
 +
 
 +
private int m_valueChanging;
 +
private LinePoints m_initValue;
 +
</pre>
 +
*Add the following constructors to '''com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField''':
 +
<pre>public AbstractDrawLineField() {
 +
  this(true);
 +
}
 +
 
 +
public AbstractDrawLineField(boolean callInitializer) {
 +
  super(callInitializer);
 +
}
 +
</pre>
 +
=== Importing from and exporting to FormData  ===
 +
 
 +
*Add the following two methods to '''com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField''':
 +
<pre>  @Override
 +
  public void exportFormFieldData(AbstractFormFieldData target) throws ProcessingException {
 +
    AbstractDrawLineFieldData v = (AbstractDrawLineFieldData) target;
 +
    v.setValue(this.getValue());
 +
  }
 +
 
 +
  @SuppressWarnings("unchecked")
 +
  @Override
 +
  public void importFormFieldData(AbstractFormFieldData source, boolean valueChangeTriggersEnabled) {
 +
    AbstractDrawLineFieldData v = (AbstractDrawLineFieldData) source;
 +
    if (v.isValueSet()) {
 +
      try {
 +
        if (!valueChangeTriggersEnabled) {
 +
          setValueChangeTriggerEnabled(false);
 +
        }
 +
        //
 +
        LinePoints newValue;
 +
        Object o = v.getValue();
 +
        if (o&nbsp;!= null) {
 +
          Class castType = getHolderType();
 +
          if (castType.isAssignableFrom(o.getClass())) {
 +
            newValue = (LinePoints) o;
 +
          }
 +
          else {
 +
            newValue = (LinePoints) TypeCastUtility.castValue(o, castType);
 +
          }
 +
        }
 +
        else {
 +
          newValue = null;
 +
        }
 +
        this.setValue(newValue);
 +
      }
 +
      finally {
 +
        if (!valueChangeTriggersEnabled) {
 +
          setValueChangeTriggerEnabled(true);
 +
        }
 +
      }
 +
    }
 +
  }
 +
</pre>
 +
=== Storing to and loading from XML files  ===
 +
 
 +
*Add the following two methods to '''com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField''':
 +
<pre>      @Override
 +
      public void storeXML(SimpleXmlElement x) throws ProcessingException {
 +
        super.storeXML(x);
 +
        LinePoints value = getValue();
 +
        try {
 +
          x.setObjectAttribute("value", value);
 +
        }
 +
        catch (IOException e) {
 +
          if (LOG.isInfoEnabled()) {
 +
            LOG.info("not serializable value in field " + getClass().getName() + "/" + getLabel() + ": " + e);
 +
          }
 +
        }
 +
      }
 +
 
 +
      @Override
 +
      public void loadXML(SimpleXmlElement x) throws ProcessingException {
 +
        super.loadXML(x);
 +
        try {
 +
          LinePoints value = TypeCastUtility.castValue(x.getObjectAttribute("value", null), getHolderType());
 +
          setValue(value);
 +
        }
 +
        catch (Exception e) {
 +
          // be lenient, maybe the field was changed
 +
          LOG.warn(null, e);
 +
        }
 +
      } 
 +
</pre>
 +
=== Keeping track of the changed status  ===
 +
 
 +
This is needed so the form can detect whether any changes were made to the field in case the Cancel button on a form is pressed.
 +
 
 +
*Add the following methods to '''com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField''':
 +
<pre>      public void setInitValue(LinePoints initValue) {
 +
        m_initValue = initValue;
 +
      }
 +
 
 +
      public LinePoints getInitValue() {
 +
        return m_initValue;
 +
      }
 +
 
 +
      @Override
 +
      protected boolean execIsSaveNeeded() throws ProcessingException {
 +
        LinePoints value = getValue();
 +
        LinePoints initValue = getInitValue();
 +
        if (value == null &amp;&amp; initValue == null) {
 +
          return false;
 +
        }
 +
        else if (value == null || initValue == null) {
 +
          return true;
 +
        }
 +
        else {
 +
          if (value.getStart().equals(initValue.getStart()) &amp;&amp;
 +
              value.getEnd().equals(initValue.getEnd())) {
 +
            return false;
 +
          }
 +
          else {
 +
            return true;
 +
          }
 +
        }
 +
      }
 +
 
 +
      @Override
 +
      protected void execMarkSaved() throws ProcessingException {
 +
        super.execMarkSaved();
 +
        LinePoints value = getValue();
 +
        setInitValue(value);
 +
      }
 +
 
 +
      @Override
 +
      protected boolean execIsEmpty() throws ProcessingException {
 +
        return getValue() == null;
 +
      } 
 +
</pre>
 +
=== Setting and getting the value (property access)  ===
 +
 
 +
*Add the following methods to '''com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField''':
 +
<pre>      public LinePoints getValue() {
 +
        return (LinePoints) propertySupport.getProperty(IValueField.PROP_VALUE);
 +
      }
 +
 
 +
      public final void setValue(LinePoints newValue) {
 +
        /**
 +
        * @rn imo, 22.02.2006, set verifyInput flag while firing triggers when a
 +
        *    message box is shown, the doOK of the form might overrun this
 +
        *    command. setting isVerifyInput() cancels the ok task
 +
        */
 +
        if (isValueChanging()) {
 +
          Exception caller1 = new Exception();
 +
          LOG.warn("Loop detection in " + getClass().getName() + " with value " + newValue, caller1);
 +
          return;
 +
        }
 +
        try {
 +
          setFieldChanging(true);
 +
          setValueChanging(true);
 +
          //
 +
          LinePoints oldValue = getValue();
 +
          boolean changed = propertySupport.setPropertyNoFire(IValueField.PROP_VALUE, newValue);
 +
          if (changed) {
 +
            propertySupport.firePropertyChange(IValueField.PROP_VALUE, oldValue, newValue);
 +
            checkSaveNeeded();
 +
          }
 +
        }
 +
        finally {
 +
          setValueChanging(false);
 +
          setFieldChanging(false);
 +
        }
 +
      }
 +
 
 +
      public boolean isValueChanging() {
 +
        return m_valueChanging &gt; 0;
 +
      }
 +
 
 +
      private void setValueChanging(boolean b) {
 +
        if (b) {
 +
          m_valueChanging++;
 +
        }
 +
        else {
 +
          m_valueChanging--;
 +
        }
 +
      }
 +
 
 +
      public Class&lt;LinePoints&gt; getHolderType() {
 +
        return LinePoints.class;
 +
      } 
 +
</pre>
 +
=== Accessing the field in the ProcessService  ===
  
==Map the Controls==
+
There are several possibilties to access these fields in the ProcessService:
To tell the gui plugin which control belongs to which gui control an extension has to be add in the gui plugin. For a SWT gui field we need the extension point org.eclipse.scout.rt.ui.swt.formfields and for a Swing we use org.eclipse.scout.rt.ui.swing.formfields. In this extension point we have to specify which model class maps to which ui class. As model class we use usually the interface and not the concrete class.
+
===Swing extension===
+
For our example, open the plugin.xml from the ui.swing plugin. Go to the Extensions tab on the bottom of the manifest editor and click Add…. Uncheck the checkbox at the bottom and select the org.eclipse.scout.rt.ui.swing.formfields extension point before you click Finish. Right-click the newly created extension in your manifest editor and select New > formField.
+
Now enter in the name text box “Draw Line Field” and select as modelClass “com.bsiag.minicrm.client.ui.form.fields.ext.IDrawLineField”. Now we have to specify the ui class. Right-click on the formField extension and select New > uiClass. There we select the class “com.bsiag.minicrm.ui.swing.form.fields.ext.SwingScoutDrawLineField”.
+
===SWT extenstion===
+
Open the plugin.xml from the ui.swt plugin. Go to the Extensions tab on the bottom of the manifest editor and click Add…. Uncheck the checkbox at the bottom and select the org.eclipse.scout.rt.ui.swt.formfields extension point before you click Finish. Right-click the newly created extension in your manifest editor and select New > formField.
+
Now enter in the name text box “Draw Line Field” and select as modelClass “com.bsiag.minicrm.client.ui.form.fields.ext.IDrawLineField”. Now we have to specify the ui class. Right-click on the formField extension and select New > uiClass. There we select the class “com.bsiag.minicrm.ui.swt.form.fields.ext.SwingScoutDrawLineField”.
+
  
==Test the Drawlinefield==
+
==== Using IntegerHolders  ====
You can now create a new Form where you use the DrawLineField. If you set the grid height to about a value of eight and disable the label you get a big field.
+
<pre>      IntegerHolder startX = new IntegerHolder();
 +
      IntegerHolder startY = new IntegerHolder();
 +
      IntegerHolder endX = new IntegerHolder();
 +
      IntegerHolder endY = new IntegerHolder();
 +
      SQL.selectInto("SELECT 100,100,200,200 FROM DUAL INTO&nbsp;:startX,&nbsp;:startY,&nbsp;:endX,&nbsp;:endY",
 +
          new NVPair("startX", startX),
 +
          new NVPair("startY", startY),
 +
          new NVPair("endX", endX),
 +
          new NVPair("endY", endY));
 +
      formData.getDrawLine().getValue().getStart().setX(startX.getValue());
 +
      formData.getDrawLine().getValue().getStart().setY(startY.getValue());
 +
      formData.getDrawLine().getValue().getEnd().setX(endX.getValue());
 +
      formData.getDrawLine().getValue().getEnd().setY(endY.getValue());
 +
</pre>
 +
==== Using SQL.selectInto  ====
 +
<pre>      SQL.selectInto("SELECT 100, 100,200, 200 FROM DUAL INTO&nbsp;:drawLine2.value.start.x,&nbsp;:drawLine2.value.start.y,&nbsp;:drawLine2.value.end.x,&nbsp;:drawLine2.value.end.y", formData);
 +
</pre>
 +
{{Note|TODO|It is not yet quite clear why the additional <code>value</code> term is needed.}}

Revision as of 06:02, 7 March 2013

The Scout documentation has been moved to https://eclipsescout.github.io/.

If the controls provided by Scout don’t fit your needs, you can easily create custom controls. This howto describes how this can be done.

To create new controls it is important to understand how scout displays a control. Remember we have the client plugin where you can define the form and the controls on it, let’s call them scout controls. Additionally we have the gui plugin which creates the effective SWT respectively Swing-Controls based on the code in the client plugin. But how does the gui plugin know which client controls belong to which gui controls? This mapping is done by an extension point.

As an example we will create an AbstractDrawLineField. In this field a user can draw a line from a start point to an end point.

Scout custom draw line field.png

Creating the Scout Control

Every scout control on a form extends from AbstractFormField. So what you have to do is to create a new class which extends from AbstractFormField. Instead of directly extend from AbstractFormField you could use AbstractCustomField as super class which is actually the recommended way to go. AbstractCustomField does not provide any additional logic but since it implements the Interface ICustomField it is easier to distinguish between controls provided by the framework and controls provided by you. Additionally to the main class it is recommended to create an interface for your control. For our AbstractDrawLineField, we create a new package com.bsiag.minicrm.client.ui.form.fields.ext in the client plugin. In this package we create an interface IDrawLineField and IDrawLineUiFacade. IDrawLineField extends the interface IFormField. Then we create the class AbstractDrawLineField which extends AbstractCustomField and implements IDrawLineField.

Properties

Now you have to think about what your field should provide. A common scout field normally consists of some properties and some execXY methods. Please notice that every form field is also an IPropertyObserver. So if you think that someone else could be interested in your property, then you should make use of the protected field m_propertySupport. This is especially necessary if the property defines behaviour of the gui control. If someone dynamically changes the property on your field the gui control has to be informed about it otherwise it would not be reflected on the gui. The AbstractDrawLineField has two properties, the start and the end point. Thus we add now the following code to the IDrawLineField interface:

public static final String PROP_START = "startPoint";
public static final String PROP_END = "endPoint";
 
Point getStart();
void setStart(Point p);
 
Point getEnd();
void setEnd(Point p);
 
IDrawLineUiFacade getUiFacade();
This interface is now already finished. The getUiFacade method will be needed to receive events from the gui control. In the AbstractDrawLineField, we implement now these methods with the usage of m_propertySupport:
  private IDrawLineUiFacade m_uiFacade;

 

 @Override
 public Point getEnd() {
   return (Point) propertySupport.getProperty(PROP_END);
 }

 

 @Override
 public void setEnd(Point p) {
   propertySupport.setProperty(PROP_END, p);
 }

 

 @Override
 public Point getStart() {
   return (Point) propertySupport.getProperty(PROP_START);
 }

 

 @Override
 public void setStart(Point p) {
   propertySupport.setProperty(PROP_START, p);
 }

 

 @Override
 public IDrawLineUiFacade getUiFacade() {
   return m_uiFacade;
}

Default Values

Another important thing about properties is the way they are initialized. For each property there should be a getConfiguredPropertyName-function. With this method the user is able to set the initial value of the property. This initial value is read in the method initConfig(). This means for your custom field that it has to override the method initConfig() too and has to fill up the actual properties with the initial values of the getConfigured functions. For the AbstractDrawLineField we need four getConfigured methods, two for each point. The annotations are needed for the Scout SDK only.
@ConfigProperty(ConfigProperty.INTEGER)
@Order(500)
@ConfigPropertyValue("0")
protected int getConfiguredStartX(){
  return 0;
}
 
@ConfigProperty(ConfigProperty.INTEGER)
@Order(510)
@ConfigPropertyValue("0")
protected int getConfiguredStartY(){
  return 0;
}
 
@ConfigProperty(ConfigProperty.INTEGER)
@Order(520)
@ConfigPropertyValue("0")
protected int getConfiguredEndX(){
  return 0;
}
 
@ConfigProperty(ConfigProperty.INTEGER)
@Order(530)
@ConfigPropertyValue("0")
protected int getConfiguredEndY(){
  return 0;
}
 
@Override
protected void initConfig(){
  super.initConfig();
  setStart(new Point(getConfiguredStartX(), getConfiguredStartY()));
  setEnd(new Point(getConfiguredEndX(), getConfiguredEndY()));
}

Receiving GUI Events

Now we have to think about which gui events we want to propagate to the scout control. These events are handled by uiFacade. In our example, we have two important events. Setting a new start and setting a new end point. Add to the façade interface these two methods: void setStartFromUI(Point p); void setEndFromUI(Point p); Now add this inner Class to AbstractDrawLineField:
private class P_UiFacade implements IDrawLineUiFacade{
  public void setEndFromUI(Point p){
    setEnd(p);
  }
 
  public void setStartFromUI(Point p){
    setStart(p);
  }
}
And add this line of code as the first statement in the initConfig method:
m_uiFacade = new P_UiFacade();

Creating the GUI Control

After creating the custom field, the corresponding gui control has to be created. Every gui control extends from SwtScoutFieldComposite (SWT) or from SwingScoutFieldComposite (Swing). We will create both a swing and SWT implementation for our AbstractDrawLineField.

Implementation

Some important methods in a gui field are initializeSwt / initializeSwing, attachScout and handleScoutPropertyChange. The actual gui composite is created in the initializeSwt / initializeSwing method. Properties of the scout field are normally handled in the method attachScout respectively in the method handleScoutPropertyChange. To fire events which should be handled in the scout control and which normally leads to an execution of an execXY method the UIFacade of a scout field should be used. For the Swt/SwingScoutDrawLineField we have a mouse listener that propagates the point changes from the gui to the scout control. When then the scout control adjusts the point properties, these changes are handled in the handleScoutPropertyChange method and there the line in the gui will be updated.

Swing Implementation

Create a new package com.bsiag.minicrm.ui.swing.form.fields.ext in the ui.swing plugin. Create in this package a new class SwingScoutDrawLineField that extends SwingScoutFieldComposite<IDrawLineField>.

Paste the following code in the SwingScoutDrawLineField class:
public class SwingScoutDrawLineField extends SwingScoutFieldComposite<IDrawLineField> {
private P_MouseListener m_mouseListener;
 
@Override
protected void initializeSwing(){
  m_mouseListener = new P_MouseListener();
  JPanelEx container=new JPanelEx(new BorderLayoutEx());
  container.setOpaque(false);
  JStatusLabelEx label=new JStatusLabelEx();
  container.add(label,BorderLayoutEx.CENTER);
 
  MyComponent field = new MyComponent();
  SwingUtility.installDefaultFocusHandling(field);
  container.add(field);
 
  field.addMouseMotionListener(m_mouseListener);
  field.addMouseListener(m_mouseListener);
 
  setSwingContainer(container);
  setSwingLabel(label);
  setSwingField(field);
  //layout
  getSwingContainer().setLayout(new LogicalGridLayout(getSwingEnvironment(),1, 0));
}
 
@Override
protected void attachScout(){
super.attachScout();
  updateLineFromScout();
}
 
protected void updateLineFromScout(){
  getSwingField().repaint();
}
 
protected void handleSwingStartPointChanged(final Point location){
  RunnableWithData t=new RunnableWithData(){
    @Override
    public void run(){
      getScoutObject().getUiFacade().setEndFromUI(location);
      getScoutObject().getUiFacade().setStartFromUI(location);
    }
  };
  getSwingEnvironment().invokeScoutLater(t, 2345);
}
 
protected void handleSwingEndPointChanged(final Point location){
  RunnableWithData t=new RunnableWithData(){
    @Override
    public void run(){
      getScoutObject().getUiFacade().setEndFromUI(location);
    }
  };
  getSwingEnvironment().invokeScoutLater(t, 2345);
}
 
@Override
protected void handleScoutPropertyChange(String name, Object newValue){
  if(name.equals(IDrawLineField.PROP_START) || name.equals(IDrawLineField.PROP_END)){
    updateLineFromScout();
  }
  super.handleScoutPropertyChange(name, newValue);
}
 
private class MyComponent extends JComponent{
  private static final long serialVersionUID=1L;
 
  @Override
  public void paint(Graphics g){
    super.paint(g);
    g.setColor(getBackground());
    g.fillRect(0, 0, getWidth(), getHeight());
    g.setColor(getForeground());
    if(getScoutObject().getStart() != null && getScoutObject().getEnd() != null){
      g.drawLine(getScoutObject().getStart().x, getScoutObject().getStart().y, getScoutObject().getEnd().x, getScoutObject().getEnd().y);
    }
  }
}
 
private class P_MouseListener extends MouseAdapter{
  private boolean m_mouseDown = false;
  @Override
  public void mousePressed(MouseEvent e){
    m_mouseDown = true;
    handleSwingStartPointChanged(e.getPoint());
  }
  @Override
  public void mouseReleased(MouseEvent e){
    m_mouseDown = false;
    handleSwingEndPointChanged(e.getPoint());
  }
  @Override
  public void mouseDragged(MouseEvent e){
    if(m_mouseDown){
      handleSwingEndPointChanged(e.getPoint());
    }
  }
}
}

SWT Implementation

Create a new package com.bsiag.minicrm.ui.swt.form.fields.ext in the ui.swt plugin. Create in this package a new class SwtScoutDrawLineField that extends SwtScoutFieldComposite<IDrawLineField>.

Paste the following code in the SwtScoutDrawLineField class:
public class SwtScoutDrawLineField extends SwtScoutFieldComposite<IDrawLineField> {
  private P_MouseListener m_mouseListener;
 
  @Override
  protected void initializeSwt(Composite parent) {
    m_mouseListener = new P_MouseListener();
 
    Composite container = getEnvironment().getFormToolkit().createComposite(parent);
    StatusLabelEx label = getEnvironment().getFormToolkit().createStatusLabel(container, getEnvironment(), getScoutObject());
    Canvas canvas = getEnvironment().getFormToolkit().createCanvas(container);
 
    canvas.addPaintListener(new P_PaintListener());
    canvas.addMouseMoveListener(m_mouseListener);
    canvas.addMouseListener(m_mouseListener);
 
    setSwtContainer(container);
    setSwtLabel(label);
    setSwtField(canvas);
 
    // layout
    getSwtContainer().setLayout(new LogicalGridLayout(1, 0));
  }
 
  @Override
  protected void attachScout() {
    super.attachScout();
    updateLineFromScout();
  }
 
  protected void updateLineFromScout() {
    getSwtField().redraw();
  }
 
  protected void handleSwtStartPointChanged(final Point location) {
    RunnableWithData t = new RunnableWithData() {
      @Override
      public void run() {
        getScoutObject().getUiFacade().setEndFromUI(location);
        getScoutObject().getUiFacade().setStartFromUI(location);
      }
    };
    getEnvironment().invokeScoutLater(t, 2345);
  }
 
  protected void handleSwtEndPointChanged(final Point location) {
    RunnableWithData t = new RunnableWithData() {
      @Override
      public void run() {
        getScoutObject().getUiFacade().setEndFromUI(location);
      }
    };
    getEnvironment().invokeScoutLater(t, 2345);
  }
 
  @Override
  protected void handleScoutPropertyChange(String name, Object newValue) {
    if (name.equals(IDrawLineField.PROP_START) || name.equals(IDrawLineField.PROP_END)) {
      updateLineFromScout();
    }
    super.handleScoutPropertyChange(name, newValue);
  }
 
  private class P_PaintListener implements PaintListener {
    private static final long serialVersionUID = 1L;
 
    @Override
    public void paintControl(PaintEvent e) {
      if (getScoutObject().getStart() != null && getScoutObject().getEnd() != null) {
        e.gc.drawLine(getScoutObject().getStart().x, getScoutObject().getStart().y, getScoutObject().getEnd().x, getScoutObject().getEnd().y);
      }
    }
  }
 
  private class P_MouseListener implements MouseListener, MouseMoveListener {
    private boolean m_mouseDown = false;
 
    @Override
    public void mouseDoubleClick(org.eclipse.swt.events.MouseEvent e) {
    }
 
    @Override
    public void mouseDown(org.eclipse.swt.events.MouseEvent e) {
      m_mouseDown = true;
      handleSwtStartPointChanged(new Point(e.x, e.y));
    }
 
    @Override
    public void mouseUp(org.eclipse.swt.events.MouseEvent e) {
      m_mouseDown = false;
      handleSwtEndPointChanged(new Point(e.x, e.y));
    }
 
    @Override
    public void mouseMove(org.eclipse.swt.events.MouseEvent e) {
      if (m_mouseDown) {
        handleSwtEndPointChanged(new Point(e.x, e.y));
      }
    }
 
  }
}

Map the Controls

To tell the gui plugin which control belongs to which gui control an extension has to be add in the gui plugin. For a SWT gui field we need the extension point org.eclipse.scout.rt.ui.swt.formfields and for a Swing we use org.eclipse.scout.rt.ui.swing.formfields. In this extension point we have to specify which model class maps to which ui class. As model class we use usually the interface and not the concrete class.

Swing extension

For our example, open the plugin.xml from the ui.swing plugin. Go to the Extensions tab on the bottom of the manifest editor and click Add…. Uncheck the checkbox at the bottom and select the org.eclipse.scout.rt.ui.swing.formfields extension point before you click Finish. Right-click the newly created extension in your manifest editor and select New > formField. Now enter in the name text box “Draw Line Field” and select as modelClass “com.bsiag.minicrm.client.ui.form.fields.ext.IDrawLineField”. Now we have to specify the ui class. Right-click on the formField extension and select New > uiClass. There we select the class “com.bsiag.minicrm.ui.swing.form.fields.ext.SwingScoutDrawLineField”.

SWT extenstion

Open the plugin.xml from the ui.swt plugin. Go to the Extensions tab on the bottom of the manifest editor and click Add…. Uncheck the checkbox at the bottom and select the org.eclipse.scout.rt.ui.swt.formfields extension point before you click Finish. Right-click the newly created extension in your manifest editor and select New > formField. Now enter in the name text box “Draw Line Field” and select as modelClass “com.bsiag.minicrm.client.ui.form.fields.ext.IDrawLineField”. Now we have to specify the ui class. Right-click on the formField extension and select New > uiClass. There we select the class “com.bsiag.minicrm.ui.swt.form.fields.ext.SwingScoutDrawLineField”.

Test the DrawLineField

You can now create a new Form where you use the DrawLineField. If you set the grid height to about a value of eight and disable the label you get a big field.


Making the DrawLineField persistent

So far, the line in our custom control can be pre-configured in the SDK and manually re-drawn using the mouse.

However, it is not yet possible to persistently store and load the line points from/to the database. This chapter shows how the custom field needs to be modified to allow persistence.

Data structures for the FormData

  • Create a class org.eclipse.minicrm.shared.data.form.fields.ext.LinePoins:
package org.eclipse.minicrm.shared.data.form.fields.ext;

import java.io.Serializable;

public class LinePoints implements Serializable {
  private static final long serialVersionUID = 1L;

  private LinePoint start;
  private LinePoint end;

  public LinePoints() {
    start = null;
    end = null;
  }

  public LinePoints(LinePoint start, LinePoint end) {
    this.start = start;
    this.end = end;
  }

  public LinePoint getStart() {
    return start;
  }

  public void setStart(LinePoint start) {
    this.start = start;
  }

  public LinePoint getEnd() {
    return end;
  }

  public void setEnd(LinePoint end) {
    this.end = end;
  }
}
  • Create a class org.eclipse.minicrm.shared.data.form.fields.ext.LinePoint:
package org.eclipse.minicrm.shared.data.form.fields.ext;

import java.awt.Point;
import java.io.Serializable;

public class LinePoint implements Serializable {
  private static final long serialVersionUID = 1L;
  private int x;
  private int y;

  public LinePoint(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public LinePoint(Point p) {
    this.x = p.x;
    this.y = p.y;
  }

  public void setX(int x) {
    this.x = x;
  }

  public void setY(int y) {
    this.y = y;
  }

  public int getX() {
    return x;
  }

  public int getY() {
    return y;
  }

  public Point getPoint() {
    return new Point(x, y);
  }
}
  • Create a class org.eclipse.minicrm.shared.data.form.fields.ext.AbstractDrawLineFieldData (this is the formField class):
package org.eclipse.minicrm.shared.data.form.fields.ext;

import java.io.Serializable;

import org.eclipse.scout.commons.TypeCastUtility;
import org.eclipse.scout.commons.holders.IHolder;
import org.eclipse.scout.rt.shared.data.form.fields.AbstractFormFieldData;

public class AbstractDrawLineFieldData extends AbstractFormFieldData implements IHolder<LinePoints>, Serializable {
  private static final long serialVersionUID = 1L;
  private LinePoints m_value;

  public AbstractDrawLineFieldData() {
    super();
  }

  @Override
  public LinePoints getValue() {
    return m_value;
  }

  @Override
  public void setValue(LinePoints o) {
    m_value = o;
    setValueSet(true);
  }

  @SuppressWarnings("unchecked")
  @Override
  public Class<LinePoints> getHolderType() {
    return TypeCastUtility.getGenericsParameterClass(getClass(), IHolder.class);
  }
}
  • Add the following annotation before the class definition of com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField:
@FormData(value = AbstractDrawLineFieldData.class, defaultSubtypeSdkCommand = DefaultSubtypeSdkCommand.CREATE, sdkCommand = SdkCommand.USE, genericOrdinal = 0)
public abstract class AbstractDrawLineField extends AbstractCustomField implements IDrawLineField {

Adjusting the interface and renderers

  • Remove the following two lines from com.bsiag.minicrm.client.ui.form.fields.ext.IDrawLineField:
public static final String PROP_START = "startPoint";
public static final String PROP_END = "endPoint";
  • Change the handleScoutPropertyChange() method in both com.bsiag.minicrm.ui.swing.form.fields.ext.SwingScoutDrawLineField and com.bsiag.minicrm.ui.swt.form.fields.ext.SwtScoutDrawLineFieldfrom from:
@Override
protected void handleScoutPropertyChange(String name, Object newValue) {
  if (name.equals(IDrawLineField.PROP_START) || name.equals(IDrawLineField.PROP_END)) {
    updateLineFromScout();
  }
  super.handleScoutPropertyChange(name, newValue);
}    

to

@Override
protected void handleScoutPropertyChange(String name, Object newValue) {
  if (name.equals(IValueField.PROP_VALUE)) {
    updateLineFromScout();
  }
  super.handleScoutPropertyChange(name, newValue);
}

Accessing start and end points from PROP_VALUE

  • Replace the getStart(), getEnd(), setStart() and setEnd() methods in com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineFieldwith the following code:
  @Override
  public Point getEnd() {
    LinePoints value = (LinePoints) propertySupport.getProperty(IValueField.PROP_VALUE);
    return (value == null ? null : value.getEnd() == null ? null : value.getEnd().getPoint());
  }

  @Override
  public void setEnd(Point p) {
    LinePoints oldValue = getValue();
    LinePoints value;
    if (oldValue != null) {
      value = new LinePoints(oldValue.getStart(), oldValue.getEnd());
    }
    else {
      value = new LinePoints();
    }
    value.setEnd(new LinePoint(p));
    boolean changed = propertySupport.setPropertyNoFire(IValueField.PROP_VALUE, value);
    if (changed) {
      propertySupport.firePropertyChange(IValueField.PROP_VALUE, oldValue, value);
    }
  }

  @Override
  public Point getStart() {
    LinePoints value = (LinePoints) propertySupport.getProperty(IValueField.PROP_VALUE);
    return (value == null ? null : value.getStart() == null ? null : value.getStart().getPoint());
  }

  @Override
  public void setStart(Point p) {
    LinePoints oldValue = getValue();
    LinePoints value;
    if (oldValue != null) {
      value = new LinePoints(oldValue.getStart(), oldValue.getEnd());
    }
    else {
      value = new LinePoints();
    }
    value.setStart(new LinePoint(p));
    boolean changed = propertySupport.setPropertyNoFire(IValueField.PROP_VALUE, value);
    if (changed) {
      propertySupport.firePropertyChange(IValueField.PROP_VALUE, oldValue, value);
    }
  }  

AbstractDrawLineField

  • Add the following new member variables to com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField:
private static final IScoutLogger LOG = ScoutLogManager.getLogger(AbstractDrawLineField.class);

private int m_valueChanging;
private LinePoints m_initValue;
  • Add the following constructors to com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField:
public AbstractDrawLineField() {
  this(true);
}

public AbstractDrawLineField(boolean callInitializer) {
  super(callInitializer);
}

Importing from and exporting to FormData

  • Add the following two methods to com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField:
  @Override
  public void exportFormFieldData(AbstractFormFieldData target) throws ProcessingException {
    AbstractDrawLineFieldData v = (AbstractDrawLineFieldData) target;
    v.setValue(this.getValue());
  }

  @SuppressWarnings("unchecked")
  @Override
  public void importFormFieldData(AbstractFormFieldData source, boolean valueChangeTriggersEnabled) {
    AbstractDrawLineFieldData v = (AbstractDrawLineFieldData) source;
    if (v.isValueSet()) {
      try {
        if (!valueChangeTriggersEnabled) {
          setValueChangeTriggerEnabled(false);
        }
        //
        LinePoints newValue;
        Object o = v.getValue();
        if (o != null) {
          Class castType = getHolderType();
          if (castType.isAssignableFrom(o.getClass())) {
            newValue = (LinePoints) o;
          }
          else {
            newValue = (LinePoints) TypeCastUtility.castValue(o, castType);
          }
        }
        else {
          newValue = null;
        }
        this.setValue(newValue);
      }
      finally {
        if (!valueChangeTriggersEnabled) {
          setValueChangeTriggerEnabled(true);
        }
      }
    }
  }

Storing to and loading from XML files

  • Add the following two methods to com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField:
      @Override
      public void storeXML(SimpleXmlElement x) throws ProcessingException {
        super.storeXML(x);
        LinePoints value = getValue();
        try {
          x.setObjectAttribute("value", value);
        }
        catch (IOException e) {
          if (LOG.isInfoEnabled()) {
            LOG.info("not serializable value in field " + getClass().getName() + "/" + getLabel() + ": " + e);
          }
        }
      }

      @Override
      public void loadXML(SimpleXmlElement x) throws ProcessingException {
        super.loadXML(x);
        try {
          LinePoints value = TypeCastUtility.castValue(x.getObjectAttribute("value", null), getHolderType());
          setValue(value);
        }
        catch (Exception e) {
          // be lenient, maybe the field was changed
          LOG.warn(null, e);
        }
      }  

Keeping track of the changed status

This is needed so the form can detect whether any changes were made to the field in case the Cancel button on a form is pressed.

  • Add the following methods to com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField:
      public void setInitValue(LinePoints initValue) {
        m_initValue = initValue;
      }

      public LinePoints getInitValue() {
        return m_initValue;
      }

      @Override
      protected boolean execIsSaveNeeded() throws ProcessingException {
        LinePoints value = getValue();
        LinePoints initValue = getInitValue();
        if (value == null && initValue == null) {
          return false;
        }
        else if (value == null || initValue == null) {
          return true;
        }
        else {
          if (value.getStart().equals(initValue.getStart()) &&
              value.getEnd().equals(initValue.getEnd())) {
            return false;
          }
          else {
            return true;
          }
        }
      }

      @Override
      protected void execMarkSaved() throws ProcessingException {
        super.execMarkSaved();
        LinePoints value = getValue();
        setInitValue(value);
      }

      @Override
      protected boolean execIsEmpty() throws ProcessingException {
        return getValue() == null;
      }  

Setting and getting the value (property access)

  • Add the following methods to com.bsiag.minicrm.client.ui.form.fields.ext.AbstractDrawLineField:
      public LinePoints getValue() {
        return (LinePoints) propertySupport.getProperty(IValueField.PROP_VALUE);
      }

      public final void setValue(LinePoints newValue) {
        /**
         * @rn imo, 22.02.2006, set verifyInput flag while firing triggers when a
         *     message box is shown, the doOK of the form might overrun this
         *     command. setting isVerifyInput() cancels the ok task
         */
        if (isValueChanging()) {
          Exception caller1 = new Exception();
          LOG.warn("Loop detection in " + getClass().getName() + " with value " + newValue, caller1);
          return;
        }
        try {
          setFieldChanging(true);
          setValueChanging(true);
          //
          LinePoints oldValue = getValue();
          boolean changed = propertySupport.setPropertyNoFire(IValueField.PROP_VALUE, newValue);
          if (changed) {
            propertySupport.firePropertyChange(IValueField.PROP_VALUE, oldValue, newValue);
            checkSaveNeeded();
          }
        }
        finally {
          setValueChanging(false);
          setFieldChanging(false);
        }
      }

      public boolean isValueChanging() {
        return m_valueChanging > 0;
      }

      private void setValueChanging(boolean b) {
        if (b) {
          m_valueChanging++;
        }
        else {
          m_valueChanging--;
        }
      }

      public Class<LinePoints> getHolderType() {
        return LinePoints.class;
      }  

Accessing the field in the ProcessService

There are several possibilties to access these fields in the ProcessService:

Using IntegerHolders

      IntegerHolder startX = new IntegerHolder();
      IntegerHolder startY = new IntegerHolder();
      IntegerHolder endX = new IntegerHolder();
      IntegerHolder endY = new IntegerHolder();
      SQL.selectInto("SELECT 100,100,200,200 FROM DUAL INTO :startX, :startY, :endX, :endY",
          new NVPair("startX", startX),
          new NVPair("startY", startY),
          new NVPair("endX", endX),
          new NVPair("endY", endY));
      formData.getDrawLine().getValue().getStart().setX(startX.getValue());
      formData.getDrawLine().getValue().getStart().setY(startY.getValue());
      formData.getDrawLine().getValue().getEnd().setX(endX.getValue());
      formData.getDrawLine().getValue().getEnd().setY(endY.getValue());

Using SQL.selectInto

      SQL.selectInto("SELECT 100, 100,200, 200 FROM DUAL INTO :drawLine2.value.start.x, :drawLine2.value.start.y, :drawLine2.value.end.x, :drawLine2.value.end.y", formData);
Note.png
TODO
It is not yet quite clear why the additional value term is needed.

Back to the top