问题
I've got an SWT shell that's resizable. Every time it is resized, I have to do something computationally intensive.
I can register a ControlListener
on my shell, but this generates events continuously throughout the resize operation, and I have no idea when a resize drag type mouse operation ends.
I'd like to be able to detect when the user is finished resizing the shell and then initiate my computationally intensive operation. Any ideas how to go about that?
回答1:
How about using a timer and start your operation after a delay of say one sec since last received resize event? A rough draft:
long lastEvent;
ActionListener taskPerformer = new ActionListener() {
public void doCalc(ActionEvent evt) {
if ( (lastEvent + 1000) < System.currentTimeMillis() ) {
hardcoreCalculationTask();
} else {
// this can be timed better
new Timer(1000, taskPerformer).start();
}
}
};
}
In your resize event:
lastEvent = System.currentTimeMillis();
new Timer(1000, taskPerformer).start();
回答2:
Here's an alternative suggestion for the same problem: [platform-swt-dev] Mouse resize listener:
You could try setting a flag and defering the resize work using Display.asyncExec(). When you get a resize, if the flag is set, just return. This should cause the resize work only when the UI is idle.
My instant idea was to listen to mouse up events but obviously (I just tried it), mouse events are not fired for mouse actions on the shell's border. Could be so damn easy...
回答3:
The solution below was inspired by stacker's and is pretty much the same except that it uses only SWT API and also makes sure the mouse button is up before starting the CPU intensive task.
First the type that does the job:
private class ResizeListener implements ControlListener, Runnable, Listener {
private long lastEvent = 0;
private boolean mouse = true;
public void controlMoved(ControlEvent e) {
}
public void controlResized(ControlEvent e) {
lastEvent = System.currentTimeMillis();
Display.getDefault().timerExec(500, this);
}
public void run() {
if ((lastEvent + 500) < System.currentTimeMillis() && mouse) {
...work
} else {
Display.getDefault().timerExec(500, this);
}
}
public void handleEvent(Event event) {
mouse = event.type == SWT.MouseUp;
}
}
Then we need to register it. Also make sure to unregister when done. One may also want to change the component used for mouse listening in order to be little bit more specific.
ResizeListener listener = new ResizeListener();
widget.addControlListener(listener);
widget.getDisplay().addFilter(SWT.MouseDown, listener);
widget.getDisplay().addFilter(SWT.MouseUp, listener);
回答4:
I solved this problem in a generic way by creating an Executor that can "throttle" tasks.
Tasks (Runnables) are put into a DelayQueue, from where a Scheduler-Thread takes and executes them. The latest scheduled task is also remembered in a variable, so if the Scheduler retrieves a new task from the queue, he checks if this is the latest task that was scheduled. If so, he executes it, if not it's skipped.
I use a String-identifier to check which tasks are considered to belong to one "throttle".
This is the code, it also includes normal scheduling-abilities, but you can check out the essential bits in there.
package org.uilib.util;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class SmartExecutor implements Throttle, Executor {
//~ Static fields/initializers -------------------------------------------------------------------------------------
private static final Logger L = LoggerFactory.getLogger(SmartExecutor.class);
//~ Instance fields ------------------------------------------------------------------------------------------------
private final ExecutorService executor = Executors.newCachedThreadPool();
private final DelayQueue<DelayedRunnable> taskQueue = new DelayQueue<DelayedRunnable>();
private final Map<String, ThrottledRunnable> throttledTasks = Maps.newHashMap();
//~ Constructors ---------------------------------------------------------------------------------------------------
/* schedule a Runnable to be executed a fixed period of time after it was scheduled
* if a new Runnable with the same throttleName is scheduled before this one was called, it will overwrite this */
public SmartExecutor() {
this.executor.execute(new Scheduler());
}
//~ Methods --------------------------------------------------------------------------------------------------------
/* execute a Runnable once */
@Override
public void execute(final Runnable runnable) {
this.executor.execute(runnable);
}
/* schedule a Runnable to be executed after a fixed period of time */
public void schedule(final long delay, final TimeUnit timeUnit, final Runnable runnable) {
this.taskQueue.put(new DelayedRunnable(runnable, delay, timeUnit));
}
/* schedule a Runnable to be executed using a fixed delay between the end of a run and the start of the next one */
public void scheduleAtFixedRate(final long period, final TimeUnit timeUnit, final Runnable runnable) {
this.taskQueue.put(new RepeatingRunnable(runnable, period, timeUnit));
}
/* shut the the executor down */
public void shutdown() {
this.executor.shutdownNow();
}
@Override
public void throttle(final String throttleName, final long delay, final TimeUnit timeUnit, final Runnable runnable) {
final ThrottledRunnable thrRunnable = new ThrottledRunnable(runnable, throttleName, delay, timeUnit);
this.throttledTasks.put(throttleName, thrRunnable);
this.taskQueue.put(thrRunnable);
}
//~ Inner Classes --------------------------------------------------------------------------------------------------
private static class DelayedRunnable implements Delayed, Runnable {
protected final Runnable runnable;
private final long endOfDelay;
public DelayedRunnable(final Runnable runnable, final long delay, final TimeUnit delayUnit) {
this.runnable = runnable;
this.endOfDelay = delayUnit.toMillis(delay) + System.currentTimeMillis();
}
@Override
public int compareTo(final Delayed other) {
final Long delay1 = this.getDelay(TimeUnit.MILLISECONDS);
final Long delay2 = other.getDelay(TimeUnit.MILLISECONDS);
return delay1.compareTo(delay2);
}
@Override
public long getDelay(final TimeUnit unit) {
return unit.convert(this.endOfDelay - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public void run() {
this.runnable.run();
}
}
private static final class RepeatingRunnable extends DelayedRunnable {
private final long periodInMillis;
public RepeatingRunnable(final Runnable runnable, final long period, final TimeUnit delayUnit) {
super(runnable, period, delayUnit);
this.periodInMillis = delayUnit.convert(period, TimeUnit.MILLISECONDS);
}
public RepeatingRunnable reschedule() {
return new RepeatingRunnable(this.runnable, this.periodInMillis, TimeUnit.MILLISECONDS);
}
}
private final class Scheduler implements Runnable {
@Override
public void run() {
while (true) {
try {
/* wait for the next runnable to become available */
final DelayedRunnable task = SmartExecutor.this.taskQueue.take();
if (task instanceof RepeatingRunnable) {
/* tell executor to run the action and reschedule it afterwards */
SmartExecutor.this.executor.execute(
new Runnable() {
@Override
public void run() {
task.run();
SmartExecutor.this.taskQueue.put(((RepeatingRunnable) task).reschedule());
}
});
} else if (task instanceof ThrottledRunnable) {
final ThrottledRunnable thrTask = (ThrottledRunnable) task;
/* run only if this is the latest task in given throttle, otherwise skip execution */
if (SmartExecutor.this.throttledTasks.get(thrTask.getThrottleName()) == thrTask) {
SmartExecutor.this.executor.execute(task);
}
} else {
/* tell the executor to just run the action */
SmartExecutor.this.executor.execute(task);
}
} catch (final InterruptedException e) {
SmartExecutor.L.debug("scheduler interrupted (shutting down)");
return;
}
}
}
}
private static final class ThrottledRunnable extends DelayedRunnable {
private final String throttleName;
public ThrottledRunnable(final Runnable runnable, final String throttleName, final long period,
final TimeUnit delayUnit) {
super(runnable, period, delayUnit);
this.throttleName = throttleName;
}
public String getThrottleName() {
return this.throttleName;
}
}
}
回答5:
If the problem is blocking the UI Thread during resizing, you should consider the method asyncExec
of the class Display
/**
* Causes the <code>run()</code> method of the runnable to
* be invoked by the user-interface thread at the next
* reasonable opportunity. The caller of this method continues
* to run in parallel, and is not notified when the
* runnable has completed. Specifying <code>null</code> as the
* runnable simply wakes the user-interface thread when run.
* <p>
* Note that at the time the runnable is invoked, widgets
* that have the receiver as their display may have been
* disposed. Therefore, it is necessary to check for this
* case inside the runnable before accessing the widget.
* </p>
*
* @param runnable code to run on the user-interface thread or <code>null</code>
*
* @exception SWTException <ul>
* <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li>
* </ul>
*
* @see #syncExec
*/
public void asyncExec (Runnable runnable) {
synchronized (Device.class) {
if (isDisposed ()) error (SWT.ERROR_DEVICE_DISPOSED);
synchronizer.asyncExec (runnable);
}
}
来源:https://stackoverflow.com/questions/2074966/detecting-when-a-user-is-finished-resizing-swt-shell