EMF.Edit Performance

Whenever you are using EMF generated models in rich client applications don’t forget to generate appropriate EMF.Edit item providers for all your domain objects. Even if you are using other UI frameworks than SWT, this will definitely save a lot of time. Especially if your user interface has to react on complex model changes. Complex changes are bulked collection modifications that might trigger refresh operations in table or tree based viewers.

If you are not familiar with the basic concepts of EMF and EMF.Edit the What every Eclipse developer should know about EMF blog post is a good starting point.

As you can see in the illustration below, the AdapterFactoryContentProvider reacts on model changes (actually notified by the wrapped AdapterFactory and a specific ModelItemProvider) and refreshes its associated viewer instance.

 

This works pretty fine in most cases. However if your underlying domain model is very ‘nervous’ with dozens of model updates per second (e.g. stock prices, simulation models, sensor data) frequent refresh operations slow down the user interface. In this case you should try to use a delayed content provider illustrated here:

 

 

Instead of performing dozens of smaller viewer updates it could be better to await a couple of model notifications and perform a full refresh operations afterwards.

The snippet shows an exemplary implementation that I currently use in a customers monitoring application for high frequent simulation models. The provider implementation itself is time based. If the first notification arrives, a full viewer refresh is performed after a short delay (Display.timerExec(..)). Notifications arriving in the meantime can be ignored due to the subsequent full viewer refresh. Delays around 500ms feel a little lazy but the UI reacts still as smooth as expected.

 


import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Display;

/**
 * * Use this {@link AdapterFactoryContentProvider} if the viewer's underlying
 * domain model frequently changes and would produce many notification events.
 * In this case it's much more efficient to trigger a full refresh after a short
 * delay.
 */
public class DelayedAdapterFactoryContentProvider extends
		AdapterFactoryContentProvider {

	private static final int DELAY_IN_MS = 500;
	private final AtomicBoolean refresh = new AtomicBoolean(false);
	private Viewer viewer;

	public DelayedAdapterFactoryContentProvider(AdapterFactory adapterFactory) {
		super(adapterFactory);
	}

	@Override
	public void notifyChanged(Notification notification) {

		if (!notification.isTouch() && !refresh.getAndSet(true)) {

			Display.getDefault().asyncExec(new Runnable() {
				@Override
				public void run() {

					Display.getDefault().timerExec(DELAY_IN_MS, new Runnable() {
						@Override
						public void run() {
							refresh.set(false);

							if (viewer.getControl() != null
									&& !viewer.getControl().isDisposed()) {
								viewer.refresh();
							}
						}
					});
				}
			});

		}
	}

	@Override
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		this.viewer = viewer;
		super.inputChanged(viewer, oldInput, newInput);
	}
}

Screen capture with Java and SWT

Creating screenshots with Java is really straightforward. The more complicated part is to implement a convenient way to select a rectangular area on the screen. The snippet below shows how to implement a rectangular clipping selector using SWT and the Tracker widget.

 


import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;

public class ClippingSelector {

    public Rectangle select() {

        final Display display = Display.getDefault();

        //convert desktop to image
        final Image backgroundImage = new Image(display, display.getBounds().width, display.getBounds().height);
        GC gc = new GC(display);
        gc.copyArea(backgroundImage, display.getBounds().x, display.getBounds().y);
        gc.dispose();

        //invisible shell and parent for tracker
        final Shell shell = new Shell(display.getActiveShell(), SWT.NO_BACKGROUND | SWT.ON_TOP);
        shell.setCursor(new Cursor(Display.getCurrent(), SWT.CURSOR_CROSS));
        shell.setBounds(display.getBounds());

        final Rectangle result = new Rectangle(0, 0, 0, 0);

        shell.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {

                Tracker tracker = new Tracker(shell, SWT.RESIZE);
                tracker.setStippled(true);
                tracker.setRectangles(new Rectangle[] { new Rectangle(e.x, e.y, 0, 0) });
                tracker.open();

                Rectangle selection = tracker.getRectangles()[0];

                result.width = selection.width;
                result.height = selection.height;

                result.x = shell.toDisplay(selection.x, selection.y).x;
                result.y = shell.toDisplay(selection.x, selection.y).y;

                shell.dispose();

            }
        });

        shell.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                shell.dispose(); //any key pressed close shell
            }
        });

        shell.addShellListener(new ShellAdapter() {
            @Override
            public void shellDeactivated(ShellEvent e) {
                shell.dispose(); //close shell if another shell is activated
            }

        });

        shell.addPaintListener(new PaintListener() {
            @Override
            public void paintControl(PaintEvent e) {
                e.gc.drawImage(backgroundImage, -1, -1); //paint background image on invisible shell
            }
        });

        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }

        backgroundImage.dispose();
        shell.dispose();

        return result;

    }

    public static void main(String[] args) {

        final Display display = new Display();
        final Shell shell = new Shell(display);

        final Rectangle rect = new ClippingSelector().select();

        if (rect.height == 0 || rect.width == 0) return;

        //we show the selected area in a new shell. Just for demonstration!
        final Image image = new Image(display, rect);
        final GC gc = new GC(display);
        gc.copyArea(image, rect.x, rect.y);
        gc.dispose();

        shell.setBounds(rect);
        shell.addPaintListener(new PaintListener() {

            @Override
            public void paintControl(PaintEvent e) {
                e.gc.drawImage(image, 0, 0);
            }
        });

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep();
        }
        display.dispose();

    }
}