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/4.0/Uncached images and icons"

< Scout‎ | HowTo‎ | 4.0
m
(Replaced content with "The Scout documentation has been moved to https://eclipsescout.github.io/.")
 
Line 1: Line 1:
{{ScoutPage|cat=HowTo 4.0}}
+
The Scout documentation has been moved to https://eclipsescout.github.io/.
 
+
This how-to describes how to work with uncached images and icons.
+
 
+
= Background  =
+
 
+
When using icons/images in a table or a form an <code>IconLocator</code> (which in turn uses a [[http://wiki.eclipse.org/Scout/HowTo/3.8/Add_an_icon#Providing_an_IconProviderService <code>IconProviderService</code>]]) is used. The default <code>IconLocator</code> used for the Swing environment is <code>org.eclipse.scout.rt.ui.swing.SwingIconLocator</code>, the one for the SWT environment is <code>SWTIconLocator</code>. The SwingIconLocator caches the images when they are first requested. This means that whenever a <code>setIconId()</code> method on a form field or table column is called with an <code>iconId</code> that has previously been used, the first image retrieved under that name will be returned, even if the image has changed since then (because it has changed on disk, for example).
+
 
+
There are various options to work around this but while all of them will work for form fields (<code>AbstractImageField</code>), not all of them work for table columns. This page tries to list the various methods that can be used together with their advantages and disadvantages.
+
 
+
= Solutions  =
+
 
+
== Custom field: UncachedImageField  ==
+
 
+
{| width="100%" cellspacing="1" cellpadding="1" border="1" align="left"
+
|-
+
| valign="top" | '''Synopsis:'''
+
| Create a "deep" copy of the ImageField model and renderer and modify it to use an uncached IconLocator
+
|-
+
| valign="top" | '''Advantages:'''
+
|
+
*Each image field on a form can either be of type AbstractImageField (cached) or AbstractUncachedImageField, allowing for fine granularity which images are cached or not
+
 
+
|-
+
| valign="top" | '''Disadvantages:'''
+
|
+
*The ImageField classes must be copied fully. Apart from code bloat, this also introduces redundancy and the need to mirror any updates to the classes that were copied to the copies
+
*Works for forms but not tables; theoretically, the same method could be applied to the TableField classes, but there the code bloat would be even greater
+
 
+
|-
+
| valign="top" | '''Status''':
+
| Not recommended<br>
+
|-
+
| valign="top" | '''Detailed steps needed:'''
+
|
+
*Create the class org.eclipsescout.demoscout.demo.minicrm.ui.swing.'''SwingUncachedIconLocator''':
+
<pre>public class SwingUncachedIconLocator extends SwingIconLocator {
+
  private static final IScoutLogger LOG = ScoutLogManager.getLogger(SwingUncachedIconLocator.class);
+
 
+
  public static final Pattern IMAGE_WITH_STATE_PATTERN = Pattern.compile("(.*)(_active&#124;_disabled&#124;_mouse&#124;_mouse_over&#124;_open&#124;_over&#124;_pressed&#124;_rollover&#124;_selected)", Pattern.CASE_INSENSITIVE);
+
 
+
  private final Object m_cacheLock = new Object();
+
 
+
  private IIconLocator m_iconLocator;
+
 
+
  public SwingUncachedIconLocator(IIconLocator iconLocator) {
+
    super(iconLocator);
+
    m_iconLocator = iconLocator;
+
  }
+
 
+
  @Override
+
  public Image getImage(String name) {
+
    if (name == null &#124;&#124; AbstractIcons.Null.equals(name) &#124;&#124; name.length() == 0) {
+
      return null;
+
    }
+
    Image img;
+
    synchronized (m_cacheLock) {
+
      img = createImageImpl(name);
+
      if (img == null) {
+
        img = autoCreateMissingImage(name);
+
      }
+
      if (LOG.isDebugEnabled()) {
+
        LOG.debug("load image '" + name + "' as " + img);
+
      }
+
      if (img == null) {
+
        warnImageNotFound(name);
+
      }
+
    }
+
    return img;
+
  }
+
 
+
  private Image createImageImpl(String name) {
+
    IconSpec iconSpec = m_iconLocator.getIconSpec(name);
+
    if (iconSpec&nbsp;!= null) {
+
      Image img = Toolkit.getDefaultToolkit().createImage(iconSpec.getContent());
+
      if (img&nbsp;!= null) {
+
        //decorate window icon in development mode
+
        if (Platform.inDevelopmentMode() &amp;&amp; name&nbsp;!= null &amp;&amp; name.matches("^(window\\d+&#124;tray)$")) {
+
          img = decorateForDevelopment(img);
+
        }
+
      }
+
      return img;
+
    }
+
    return null;
+
  }
+
}
+
</pre>
+
*Open '''org.eclipse.scout.rt.client.ui.form.fields.imagebox.IImageField''' and save it as '''org.eclipsescout.demo.minicrm.client.form.fields.ext.IUncachedImageField''' and replace all occurences of "ImageField" with "UncachedImageField"
+
*Open '''org.eclipse.scout.rt.client.ui.form.fields.imagebox.IImageFieldUIFacade''' and save it as '''org.eclipsescout.demo.minicrm.client.form.fields.ext.IUncachedImageFieldUIFacade''' and replace all occurences of "ImageField" with "UncachedImageField"
+
*Open '''org.eclipse.scout.rt.client.ui.form.fields.imagebox.ImageFieldEvent''' and save it as '''org.eclipsescout.demo.minicrm.client.form.fields.ext.UncachedImageFieldEvent''' and replace all occurences of "ImageField" with "UncachedImageField"
+
*Open '''org.eclipse.scout.rt.client.ui.form.fields.imagebox.ImageFieldListener''' and save it as '''org.eclipsescout.demo.minicrm.client.form.fields.ext.UncachedImageFieldListener''' and replace all occurences of "ImageField" with "UncachedImageField"
+
*Open '''org.eclipse.scout.rt.client.ui.form.fields.imagebox.AbstractImageField''' and save it as '''org.eclipsescout.demo.minicrm.client.form.fields.ext.AbstractUncachedImageField''' and replace all occurences of "ImageField" with "UncachedImageField"
+
*Open '''org.eclipse.scout.rt.ui.swing.form.fields.imagebox.ISwingScoutImageField''' and save it as '''org.eclipsescout.demo.minicrm.ui.swing.form.fields.ext.ISwingScoutUncachedImageField''' and replace all occurences of "ImageField" with "UncachedImageField"
+
*Open '''org.eclipse.scout.rt.ui.swing.form.fields.imagebox.SwingScoutImageField''' and save it as '''org.eclipsescout.demo.minicrm.ui.swing.form.fields.ext.SwingScoutUncachedImageField''' and replace all occurences of "ImageField" with "UncachedImageField",<br>then add the following code:
+
<pre>  private SwingUncachedIconLocator m_uncachedIconLocator;
+
 
+
  protected SwingUncachedIconLocator getUncachedIconLocator() {
+
    if (m_uncachedIconLocator == null) {
+
      m_uncachedIconLocator = new SwingUncachedIconLocator(getSwingEnvironment().getScoutSession().getIconLocator());
+
    }
+
    return m_uncachedIconLocator;
+
  }
+
</pre>
+
and modify the '''setImageFromScout()''' method as follows:
+
<pre>  protected void setImageFromScout(String imageId, Object image) {
+
    if (image == null) {
+
      if (imageId&nbsp;!= null) {
+
        // try to use uncached image
+
        image = getUncachedIconLocator().getImage(imageId);
+
        if (image == null) {
+
          // as fallback, use cached image
+
          image = getSwingEnvironment().getImage(imageId);
+
        }
+
      }
+
    }
+
    getSwingImageViewer().setImage(image);
+
  }
+
</pre>
+
|}
+
 
+
<br> <br>
+
 
+
.
+
 
+
== Extending SwingScoutImageField  ==
+
 
+
{| width="100%" cellspacing="1" cellpadding="1" border="1" align="left"
+
|-
+
| valign="top" | '''Synopsis:'''
+
| Provide a new plugin in which UncachedSwingScoutImageField extends SwingScoutImageField and uses the UncachedIconLocator.
+
|-
+
| valign="top" | '''Advantages:'''
+
|
+
*No code redundancies
+
 
+
|-
+
| valign="top" | '''Disadvantages:'''
+
|
+
*All AbstractImageFields become uncached
+
*Only works for forms, not tables
+
*Requries an extension plugin
+
 
+
|-
+
| valign="top" | '''Status''':
+
| Not recommended
+
|-
+
| valign="top" |
+
'''Detailed steps needed:'''
+
 
+
'''Note: '''This solution has not been tried (though it should work)<br>
+
 
+
|
+
*Create the class org.eclipsescout.demo.minicrm.ui.swing.'''SwingUncachedIconLocator '''as described above.
+
*Create a plugin project org.eclipsescout.demo.minicrm.scout.rt.ui.swing
+
*Make sure the package '''org.eclipsescout.demo.minicrm.scout.rt.ui.swing.form.fields''' is exported in MANIFEST.MF
+
*Create a class '''org.eclipsescout.demo.minicrm.scout.rt.ui.swing.form.fields.SwingScoutUncachedImageField''' with the following content:
+
<pre>public class SwingScoutUncachedImageField extends SwingScoutImageField {
+
  private SwingUncachedIconLocator m_uncachedIconLocator;
+
 
+
  protected SwingUncachedIconLocator getUncachedIconLocator() {
+
    if (m_uncachedIconLocator == null) {
+
      m_uncachedIconLocator = new SwingUncachedIconLocator(getSwingEnvironment().getScoutSession().getIconLocator());
+
    }
+
    return m_uncachedIconLocator;
+
  }
+
 
+
  @Override
+
  protected void setImageFromScout(String imageId, Object image) {
+
    if (image == null) {
+
      if (imageId&nbsp;!= null) {
+
        // try to use uncached image
+
        image = getUncachedIconLocator().getImage(imageId);
+
        if (image == null) {
+
          // as fallback, use cached image
+
          image = getSwingEnvironment().getImage(imageId);
+
        }
+
      }
+
    }
+
    getSwingImageViewer().setImage(image);
+
  }
+
}
+
</pre>
+
*In the plugin's '''plugin.xml''' file add the following extension point:
+
<pre>&lt;plugin&gt;
+
  &lt;extension point="org.eclipse.scout.rt.ui.swing.formfields"&gt;
+
      &lt;formField
+
            scope="global"
+
            active="true"
+
            modelClass="org.eclipse.scout.rt.client.ui.form.fields.imagebox.IImageField"
+
            name="imagefield"&gt;
+
        &lt;uiClass
+
              class="org.eclipsescout.demo.minicrm.scout.rt.ui.swing.form.fields.SwingScoutUncachedImageField"&gt;
+
        &lt;/uiClass&gt;
+
      &lt;/formField&gt;
+
  &lt;/extension&gt;
+
&lt;/plugin&gt;
+
</pre>
+
|}
+
 
+
<br> <br>
+
 
+
.
+
 
+
== Overwrite SwingEnvironment.createIconLocator  ==
+
 
+
{| width="100%" cellspacing="1" cellpadding="1" border="1" align="left"
+
|-
+
| valign="top" | '''Synopsis:'''
+
| Overwriting SwingEnvironemtn.createIconLocator to always return a non-caching IconLocator
+
|-
+
| valign="top" | '''Advantages:'''
+
|
+
*No code redundancies
+
 
+
|-
+
| valign="top" | '''Disadvantages:'''
+
|
+
*All AbstractImageFields become uncached
+
*Only works for forms, not tables
+
 
+
|-
+
| valign="top" | '''Status:'''
+
| Not recommended
+
|-
+
| valign="top" | '''Detailed steps needed:'''
+
|
+
*Create the class org.eclipsescout.demo.minicrm.ui.swing.'''SwingUncachedIconLocator''' as described above.
+
*Add the following method to org.eclipsescout.demo.minicrm.ui.swing.'''SwingEnvironment''':
+
<pre>  protected SwingIconLocator createIconLocator() {
+
    return new SwingUncachedIconLocator(getScoutSession().getIconLocator());
+
  }
+
</pre>
+
|}
+
 
+
<br> <br>
+
 
+
.
+
 
+
== Extending SwingScoutImageField and overwriting SwingEnvironment.createFormField  ==
+
 
+
{| width="100%" cellspacing="1" cellpadding="1" border="1" align="left"
+
|-
+
| valign="top" | '''Synopsis:'''
+
| Extend
+
|-
+
| valign="top" | '''Advantages:'''
+
|
+
*No code redundancies
+
*No plugin needed
+
*Possibility to decide which ImageFields are cached and which aren't
+
 
+
|-
+
| valign="top" | '''Disadvantages:'''
+
|
+
*Additional criteria/rules to decide which fields need to be cached/uncached need to be hard coded into SwingEnvironment
+
*Only works for forms, not tables
+
 
+
|-
+
| valign="top" | '''Status:'''
+
| Not recommended
+
|-
+
| valign="top" | '''Detailed steps needed:'''
+
|
+
*Create the class org.eclipsescout.demo.minicrm.ui.swing.'''SwingUncachedIconLocator''' as described above.
+
*Create a class org.eclipsescout.demo.minicrm.ui.swing.'''SwingScoutUncachedImageField''' with the following content:
+
<pre>public class SwingScoutUncachedImageField extends SwingScoutImageField {
+
  private SwingUncachedIconLocator m_uncachedIconLocator;
+
 
+
  protected SwingUncachedIconLocator getUncachedIconLocator() {
+
    if (m_uncachedIconLocator == null) {
+
      m_uncachedIconLocator = new SwingUncachedIconLocator(getSwingEnvironment().getScoutSession().getIconLocator());
+
    }
+
    return m_uncachedIconLocator;
+
  }
+
 
+
  @Override
+
  protected void setImageFromScout(String imageId, Object image) {
+
    if (image == null) {
+
      if (imageId&nbsp;!= null) {
+
        // try to use uncached image
+
        image = getUncachedIconLocator().getImage(imageId);
+
        if (image == null) {
+
          // as fallback, use cached image
+
          image = getSwingEnvironment().getImage(imageId);
+
        }
+
      }
+
    }
+
    getSwingImageViewer().setImage(image);
+
  }
+
}
+
</pre>
+
*Add the following method to org.eclipsescout.demo.minicrm.ui.swing.'''SwingEnvironment''':
+
<pre>@Override
+
  public ISwingScoutFormField&lt;?&gt; createFormField(JComponent parent, IFormField field) {
+
    if (field instanceof IImageField /* &amp;&amp; add further conditions here */) {
+
      SwingScoutUncachedImageField ui = new SwingScoutUncachedImageField();
+
      ui.createField((IImageField) field, this);
+
      decorate(field, ui);
+
      return ui;
+
    }
+
    return super.createFormField(parent, field);
+
  }
+
</pre>
+
|}
+
 
+
<br> <br>
+
 
+
.
+
 
+
== Use ImageField.setImage() instead of ImageField.setImageId()  ==
+
 
+
{| width="100%" cellspacing="1" cellpadding="1" border="1" align="left"
+
|-
+
| valign="top" | '''Synopsis:'''
+
| Instead of passing the loaded image to an IconProviderService and then calling setImageId(imageId) on the ImageField, the content is directly passed to the ImageField using setImage(content)
+
|-
+
| valign="top" | '''Advantages:'''
+
|
+
*No code redundancies
+
*renderer independent (SWT/Swing)
+
 
+
|-
+
| valign="top" | '''Disadvantages:'''
+
|
+
*Only works for forms, not tables
+
 
+
|-
+
| valign="top" | '''Status:'''
+
| Recommended
+
|-
+
| valign="top" | '''Detailed steps needed:'''
+
|
+
*Add byte[] member to form class:
+
<pre>public class MyForm extends AbstractForm {
+
  private byte[] content;
+
</pre>
+
*Setting image from database:
+
<pre>      content = SERVICES.getService(IMyProcessService.class).loadImage(keyValue);
+
      getImageField().setImage(content);
+
</pre>
+
*Setting image from file:
+
<pre>  public void updateImageFile(File file) throws ProcessingException {
+
    if (file&nbsp;!= null) {
+
      content = new byte[(int) file.length()];
+
      DataInputStream dis;
+
      try {
+
        dis = new DataInputStream(new FileInputStream(file));
+
        dis.readFully(content);
+
        dis.close();
+
 
+
        getImageField().setImage(content);
+
      }
+
      catch (FileNotFoundException e) {
+
        throw new ProcessingException(e.getMessage());
+
      }
+
      catch (IOException e) {
+
        throw new ProcessingException(e.getMessage());
+
      }
+
    }
+
  }
+
</pre>
+
*Clearing image:
+
<pre>      content = null;
+
      getImageField().setImage(content);
+
</pre>
+
|}
+
 
+
<br> <br>
+
 
+
.
+
 
+
== Not re-using iconId  ==
+
 
+
{| width="100%" cellspacing="1" cellpadding="1" border="1" align="left"
+
|-
+
| valign="top" | '''Synopsis:'''
+
| Instead of using the key of the record showing on the form as imageId, the imageId is increased every time the image content changes (or even whenever the image is set).
+
|-
+
| valign="top" | '''Advantages:'''
+
|
+
*No code modification needed
+
*Works for forms and tables
+
 
+
|-
+
| valign="top" | '''Disadvantages:'''
+
|
+
*Cache fills up quickly (and in many cases unnecessarily); no "garbage collection" process
+
 
+
|-
+
| valign="top" | '''Status:'''
+
| Not recommended
+
|-
+
| valign="top" | '''Detailed steps needed:'''
+
|
+
*A static Long member would need to be added to the form class. This member would need to be increased and appended to the imageId whenever the image is updated, the remaining code would stay unchanged compared to the solutions using an UncachedImageField.
+
<pre>      String imageId = "Image-" + keyValue + staticCounter++;
+
      content = SERVICES.getService(IMyProcessService.class).loadImage(imageId);
+
      org.eclipsescout.demo.minicrm.client.Activator.getDefault().cacheImage(imageId, content);
+
      getUncachedImageField().setImageId(imageId);
+
</pre>
+
|}
+
 
+
<br> <br>
+
 
+
.
+
 
+
== Use MD5 checksum as iconId  ==
+
 
+
{| width="100%" cellspacing="1" cellpadding="1" border="1" align="left"
+
|-
+
| valign="top" | '''Synopsis:'''
+
| Instead of using a constantly increasing imageId, independently of whether the image changed or not the MD5 checksum over the image data is used. This checksum only changes if the image content changed as well. The probability for identical MD5 checksums for different images is negligable.
+
|-
+
| valign="top" | '''Advantages:'''
+
|
+
*No code modifications needed
+
*Works for forms and tables
+
*New cache entries only created when needed
+
 
+
|-
+
| valign="top" | '''Disadvantages:'''
+
|
+
*Cache still fills up (though only when necessary)
+
 
+
|-
+
| valign="top" | '''Status:'''
+
| Recommended
+
|-
+
| valign="top" | '''Detailed steps needed:'''
+
|
+
*Setting an image on a form:
+
<pre>      if (content&nbsp;!= null) {
+
        String md5;
+
        try {
+
          md5 = Base64Utility.encode(EncryptionUtility.signMD5(content));
+
        }
+
        catch (NoSuchAlgorithmException e) {
+
          throw new ProcessingException("Could not create MD5 hash for person portrait: " + e.getMessage());
+
        }
+
        String imageId = md5;
+
        content = SERVICES.getService(IMyProcessService.class).loadImage(imageId);
+
        org.eclipsescout.demo.minicrm.client.Activator.getDefault().cacheImage(imageId, content);
+
        getUncachedImageField().setImageId(imageId);
+
      } else {
+
        getUncachedImageField().setImageId(null);
+
      }
+
</pre>
+
*Setting an image in a table column: Instead of the imageId, the MD5 checksum is returned from the OutlineService:
+
<pre>      @Override
+
      protected void execDecorateCell(Cell cell, ITableRow row) throws ProcessingException {
+
        String keyValue = getKeyValueColumn().getValue(row);
+
        String imageId = getValue(row); // this is the MD5 checksum
+
        if (!org.eclipsescout.demo.minicrm.client.Activator.getDefault().isImageCached(imageId)) {
+
          byte[] content = SERVICES.getService(IMyProcessService.class).loadImage(keyValue);
+
          org.eclipsescout.demo.minicrm.client.Activator.getDefault().cacheImage(imageId, content);
+
        }
+
        cell.setIconId(imageId);
+
        cell.setText(null);
+
      }
+
</pre>
+
|}
+
 
+
<br> <br>
+
 
+
.
+
 
+
= Recommended solution  =
+
 
+
{| cellspacing="1" cellpadding="1" border="1" align="left"
+
|-
+
| valign="top" | '''Synopsis:'''
+
| The recommended solution is to combine two of the methods above:
+
*[http://wiki.eclipse.org/Scout/HowTo/4.0/Uncached_images_and_icons#Use_ImageField.setImage.28.29_instead_of_ImageField.setImageId.28.29 use setImage() for forms]
+
*[http://wiki.eclipse.org/Scout/HowTo/4.0/Uncached_images_and_icons#Use_MD5_checksum_as_iconId use MD5 checksums as imageId for table columns]
+
 
+
|-
+
| valign="top" | '''Advantages:'''
+
|
+
*No code modifications needed
+
*Form images are not cached at all
+
*Table images are only cached when they change
+
*Works for forms and tables
+
 
+
|-
+
| valign="top" | '''Disadvantages:'''
+
|
+
*None
+
 
+
|}
+
<br> <br>
+
 
+
.
+
 
+
.
+
 
+
.
+
 
+
.
+
 
+
.
+
 
+
.
+

Latest revision as of 07:37, 18 March 2024

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

Back to the top