Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » scout » Update the value of a field during its own execChangedValue() event.(org.eclipse.scout.rt.client.ui.form.fields.AbstractValueField.setValue(AbstractValueField.java:258) Loop detection in ***Field with value ***)
Update the value of a field during its own execChangedValue() event. [message #1362156] Sat, 17 May 2014 14:11 Go to next message
Jeremie Bresson is currently offline Jeremie BressonFriend
Messages: 950
Registered: October 2011
Senior Member
In our current project, we have started to use exeChangedValue() to recalculate the state of the whole form.

In the field:
@Override
protected void execChangedValue() throws ProcessingException {
  doCalculateForm();
}


And in the form:
private void doCalculateForm() throws ProcessingException {
  IMyService service = SERVICES.getService(IMyService.class);
  MyFormData formData = new MyFormData();
  exportFormData(formData);
  formData = service.calculate(formData);
  importFormData(formData);
}


This works well (considering that importFormData() does not trigger an execChangedValue() event), but we now have a problem when our calculation logic consider that the value of the field triggering the calculation should be changed:

!ENTRY org.eclipse.scout.rt.client 2 0 2014-05-16 19:42:57.745
!MESSAGE org.eclipse.scout.rt.client.ui.form.fields.AbstractValueField.setValue(AbstractValueField.java:258) Loop detection in simple.myapp.client.MyForm$MainBox$LoremField with value hello
!STACK 0
java.lang.Exception
    at org.eclipse.scout.rt.client.ui.form.fields.AbstractValueField.setValue(AbstractValueField.java:257)
    at simple.myapp.client.MyForm.doCalculateForm(MyForm.java:139)
    at simple.myapp.client.MyForm.access$0(MyForm.java:138)
    at simple.myapp.client.MyForm$MainBox$LoremField.execChangedValue(MyForm.java:120)
    at org.eclipse.scout.rt.client.ui.form.fields.AbstractValueField.setValue(AbstractValueField.java:311)
    at org.eclipse.scout.rt.client.ui.form.fields.AbstractValueField.parseValue(AbstractValueField.java:478)
    at org.eclipse.scout.rt.client.ui.form.fields.stringfield.AbstractStringField$P_UIFacade.setTextFromUI(AbstractStringField.java:405)
    at ...


What is the correct approach to update a value of a field in its own execChangedValue() event?

Re: Update the value of a field during its own execChangedValue() event. [message #1403062 is a reply to message #1362156] Mon, 21 July 2014 07:09 Go to previous messageGo to next message
Jeremie Bresson is currently offline Jeremie BressonFriend
Messages: 950
Registered: October 2011
Senior Member
I forgot to write something about the answer I got. Here is how we decided to do it. Maybe this will help someone else.

We solve this by scheduling a ClientSyncJob:

@Override
protected void execChangedValue() throws ProcessingException {
  ClientSyncJob job = new ClientSyncJob("doCalculateForm", ClientSession.get()) {

    @Override
    protected void runVoid(IProgressMonitor monitor) throws ProcessingException {
      doCalculateForm();
    }

  };
  job.setSystem(true);
  job.schedule();
}

Re: Update the value of a field during its own execChangedValue() event. [message #1439631 is a reply to message #1403062] Tue, 07 October 2014 10:48 Go to previous messageGo to next message
Jeremie Bresson is currently offline Jeremie BressonFriend
Messages: 950
Registered: October 2011
Senior Member
This pattern works well, but if the calculation job takes too long, this is not optimal for the user: the UI is not frozen and he can continue to work (and to enter some values).

What do I need to do to prevent UI Interactions?

(Eclipse Scout 4.0.0, SWT-UI, Eclipse runtime 3.8)
Re: Update the value of a field during its own execChangedValue() event. [message #1439947 is a reply to message #1439631] Tue, 07 October 2014 18:27 Go to previous messageGo to next message
Jeremie Bresson is currently offline Jeremie BressonFriend
Messages: 950
Registered: October 2011
Senior Member
Here some input I got:

System-Jobs are Jobs that are not visible to the user (They are not displayed in the Job View).
(In our project, we have removed the "Job View" completely).

A typical Client-Sync-Job freezes the user interface, but after a delay.
With the SwtBusyHandler, it is possible to influence this behavior.
For example, it is possible to freeze the whole workbench or just a part of it.

If the whole Workbench is blocked, Eclipse adds a red stop button in the status bar. (See the screenshot below and/or this discussion: Stop Button / "Cancel Current Operation")

Here is an example how you can freeze the whole workbench:

Add your own SwtBusyHandler - create a class:
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.scout.rt.client.IClientSession;
import org.eclipse.scout.rt.shared.TEXTS;
import org.eclipse.scout.rt.ui.swt.ISwtEnvironment;
import org.eclipse.scout.rt.ui.swt.busy.SwtBusyHandler;
import org.eclipse.scout.rt.ui.swt.busy.strategy.workbench.BlockWorkbenchJob;

public class MySwtBusyHandler extends SwtBusyHandler {

  public MySwtBusyHandler(IClientSession session, ISwtEnvironment env) {
    super(session, env);
  }

  @Override
  protected void runBusy(Job job) {
    BlockWorkbenchJob busyWaitJob = new BlockWorkbenchJob(TEXTS.get("BusyJob"), this);
    busyWaitJob.setSystem(true);
    busyWaitJob.setUser(false);
    busyWaitJob.schedule();
  }

}


In the SwtEnvironment of your application, override the createBusyHandler() method. Instead of using the default busy handler, you now instantiate your specific BusyHandler created previously:
@Override
protected SwtBusyHandler createBusyHandler() {
  return new MySwtBusyHandler(getClientSession(), this);
}


My first tests (on a small PoC application) look promising.

index.php/fa/19410/0/

One issue still need to be fixed:
When the form is opened (as view in my case), if the load operation (in ModifyHandler.execLoad()) takes a long time, the form do not have the focus when it appears.

This is a problem for our user.
- A form without focus cannot be closed with the "escape" KeyStroke we have in our forms
- Our users want to start working with the form, without having to TAB until the first field is reached.

I hope we will manage to fix this last issue.
  • Attachment: Workbench.png
    (Size: 39.24KB, Downloaded 154 times)
Re: Update the value of a field during its own execChangedValue() event. [message #1451065 is a reply to message #1362156] Thu, 23 October 2014 09:21 Go to previous messageGo to next message
Daniel Wiehl is currently offline Daniel WiehlFriend
Messages: 26
Registered: April 2010
Junior Member
Hi Jeremie

The time the workbench is blocked, the focus request is ignored because the workbench filters the resulting OS-event from the SWT event queue. Actually, this is exactly the behaviour wanted to prevent the user from interacting with your application.

To solve your problem, you have to delay requesting the focus until the workbench is not blocked anymore. The easiest way to do that is by contributing your own implementation of SwtScoutForm#handleRequestFocusFromScout. There, one possible way could be to try to set the focus repeatedly as long as not being accepted by the UI. Obviously, this is not good practice and it would be way better to only set the focus once the workbench is not blocked anymore. If having a look at IBusyHandler, you may notice that there is a state-lock you can wait for to get awaken once the long-running operation completes. Unfortunately, this will not work very reliable because being the same trigger for also exiting the workbench blocking mode as well.

As a consequence, you have to introduce your own BusyJob and BusyHandler with your BusyHandler firing a notification once the blocking mode is exited which is shortly after the long-running operation completed. With that mechanism installed, you finally can wait for that event in
SwtScoutForm#handleRequestFocusFromScout and only set the focus if being in non-blocked state.

In your previous post you presented a solution of how to block the workbench immediately when running a ClientSyncJob. Actually, this might lead to unexpected side effects. Imagine, you have a FormField that validates on every key. Every time you type in some characters, the workbench is blocked and the focus restored afterwards. However, the field is focused anew and you lose the current caret position. To face that problem, BusyJob is divided into 2 temporal timed phases: In the first phase, typically a busy cursor is present but the workbench not blocked yet. Only if the operation takes some more time, the workbench gets blocked. That is why I suggest you not to bypass the first phase but to reduce the duration to detect long-running operations instead.

Another point to consider is that when requesting the initial focus in AbstractFormHandler #execLoad, the Scout model is not attached to the UI yet, meaning that the focus cannot be applied yet. Once the model is attached to the UI, the event-history is looked for such a focus request which is applied if present. The point is that this history only keeps events for some maximum period of time (by default this is 5 seconds). To preserve events longer in history, you have to set a bigger expiration duration in AbstractForm#createEventHistory or more easily, simply request the focus at the very end of your execLoad method instead.

In the following, please find the changes required:

If you like to have that behavior in Scout, please file a bug on Bugzilla.

Use your own SwtBusyHandler:
/**
 * Handler to block the workbench for long-running operations and to provide feedback if the workbench is
 * accessible again.
 *
 * @see WorkbenchBlockingJobEx
 * @see #waitForWorkbenchBlockingToComplete()
 */
public class SwtBusyHandlerEx extends SwtBusyHandler {

  private static final IScoutLogger LOG = ScoutLogManager.getLogger(SwtBusyHandlerEx.class);

  private final Object m_blockingLock = new Object();
  private int m_blockingCount;

  public SwtBusyHandlerEx(IClientSession session, ISwtEnvironment env) {
    super(session, env);

    // configure the timeout after which the workbench should block to a reasonable value.
    setLongOperationMillis(300);
  }

  @Override
  protected void runBusy(Job job) {
    new WorkbenchBlockingJobEx(this).schedule();
  }

  /**
   * Blocks the caller as long as the workbench is blocked by a long-runnning operation; has no effect if there is no
   * long-running operation in progress.
   */
  public void waitForWorkbenchBlockingToComplete() {
    synchronized (m_blockingLock) {
      while (m_blockingCount > 0) {
        try {
          m_blockingLock.wait();
        }
        catch (InterruptedException e) {
          LOG.warn("Interrupted while waiting for the workbench not to be blocked anymore.");
        }
      }
    }
  }

  /**
   * Notify the handler about start blocking the workbench.
   */
  void notifyWorkbenchBlockingStarted() {
    synchronized (m_blockingLock) {
      m_blockingCount++;
    }
  }

  /**
   * Notify the handler about not blocking the workbench anymore.
   */
  void notifyWorkbenchBlockingStopped() {
    synchronized (m_blockingLock) {
      if (m_blockingCount == 0) {
        return;
      }

      m_blockingCount--;

      // notify
      if (m_blockingCount == 0) {
        m_blockingLock.notifyAll();
      }
    }
  }
}


Use your own BusyJob:
/**
 * Blocks the Workbench during the execution of long-running operations to prevent the user from interacting
 * with the application. As long as the <code>shortTimeMillis</code> timeout is not reached, a busy cursor is
 * shown and the workbench not blocked yet.
 *
 * @see {@link IBusyHandler#getShortOperationMillis()}
 * @see {@link IBusyHandler#getLongOperationMillis()}
 */
public class WorkbenchBlockingJobEx extends BusyJob {

  private static final IScoutLogger LOG = ScoutLogManager.getLogger(WorkbenchBlockingJobEx.class);

  public WorkbenchBlockingJobEx(SwtBusyHandler handler) {
    super(TEXTS.get("BusyJob"), handler);
    setSystem(false);
    setUser(false);
  }

  @Override
  protected SwtBusyHandlerEx getBusyHandler() {
    return (SwtBusyHandlerEx) super.getBusyHandler();
  }

  @Override
  protected void runBusy(IProgressMonitor monitor) {
    // Show a busy cursor during execution of short-running operations.
    IRunnableWithProgress busyRunnable = new IRunnableWithProgress() {
      @Override
      public void run(IProgressMonitor monitor2) throws InvocationTargetException, InterruptedException {
        WorkbenchBlockingJobEx.super.runBusy(monitor2);
      }
    };
    SwtBusyUtility.showBusyIndicator(getBusyHandler(), busyRunnable, monitor);
  }

  @Override
  protected void runBlocking(IProgressMonitor monitor) {
    SwtBusyHandlerEx handler = getBusyHandler();

    // Block the workbench during execution of long-running operations.
    IRunnableWithProgress blockingRunnable = new IRunnableWithProgress() {
      @Override
      public void run(IProgressMonitor monitor2) throws InvocationTargetException, InterruptedException {
        WorkbenchBlockingJobEx.super.runBlocking(monitor2);
      }
    };

    handler.notifyWorkbenchBlockingStarted(); // notify the handler about start blocking the workbench.
    try {
      SwtBusyUtility.showWorkbenchIndicator(handler, blockingRunnable);
    }
    finally {
      handler.notifyWorkbenchBlockingStopped(); // notify the handler about not blocking the workbench anymore.
    }
  }
}


Change the behaviour of 'Focus-Request' in SwtScoutForm:
/**
 * SwtScoutForm with support for setting the focus control even if the workbench is blocked and therefore would
 * not accept focus requests.
 */
public class SwtScoutFormEx extends SwtScoutForm {

  private static IScoutLogger LOG = ScoutLogManager.getLogger(SwtScoutFormEx.class);

  @Override
  protected void handleRequestFocusFromScout(final IFormField modelField, final boolean force) {
    // This request is run on behalf of the SWT-Thread.

    // Get the installed busy handler to request the focus only if the workbench is not blocked.
    // Otherwise, the focus request would not be accepted as filtered in blocking state by the workbench.
    final IClientSession clientSession = getEnvironment().getClientSession();
    final IBusyHandler busyHandler = SERVICES.getService(IBusyManagerService.class).getHandler(clientSession);

    if (busyHandler instanceof SwtBusyHandlerEx) {
      // Asynchronously wait for the workbench to be accessible without blocking the SWT-Thread.
      Job job = new Job("Wait for blocked workbench to be complete") {

        @Override
        protected IStatus run(IProgressMonitor monitor) {
          // Wait for the workbench to be accessible to accept focus requests.
          ((SwtBusyHandlerEx) busyHandler).waitForWorkbenchBlockingToComplete();

          // Synchronize with SWT-Thread to request the focus.
          getEnvironment().getDisplay().asyncExec(new Runnable() {

            @Override
            public void run() {
              // Request the focus.
              SwtScoutFormEx.super.handleRequestFocusFromScout(modelField, force);
            }
          });
          return Status.OK_STATUS;
        }
      };
      job.setSystem(true);
      job.setUser(false);
      job.schedule();

    }
    else {
      SwtScoutFormEx.super.handleRequestFocusFromScout(modelField, force);
    }
  }
}


Register your BusyHandler und SwtScoutForm in SwtEnvironment:
  @Override
  public ISwtScoutForm createForm(Composite parent, IForm scoutForm) {
    // Create SwtScoutForm with support for setting focus in blocking workbench state.
    SwtScoutForm uiForm = new SwtScoutFormEx();
    uiForm.createField(parent, scoutForm, this);
    assignWidgetId(scoutForm, uiForm.getSwtField(), uiForm.getSwtContainer());
    return uiForm;
  }

  @Override
  protected SwtBusyHandler createBusyHandler() {
    // Create BusyHandler with functionality to wait for long-running operations to complete.
    return new SwtBusyHandlerEx(ClientSession.get(), this);
  }
Re: Update the value of a field during its own execChangedValue() event. [message #1573296 is a reply to message #1451065] Mon, 19 January 2015 17:10 Go to previous message
Jeremie Bresson is currently offline Jeremie BressonFriend
Messages: 950
Registered: October 2011
Senior Member
The described workaround is working well with Luna.

Daniel Wiehl wrote on Thu, 23 October 2014 11:21
If you like to have that behavior in Scout, please file a bug on Bugzilla.


See Bug 457859.

The company having this problem (and using the workaround in its scout application) has decided to sponsor the effort to bring this in Scout.
Previous Topic:Menu bar missing on OS X build
Next Topic:Error: TypeError: Cannot read property 'getFocusRoot' of null
Goto Forum:
  


Current Time: Mon May 04 17:15:59 GMT 2015

Powered by FUDForum. Page generated in 0.03121 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software