检测用户何时完成调整SWT shell的大小

4
我有一个可调整大小的 SWT shell。每次调整大小时,我都需要进行一些计算密集型操作。
我可以在我的 shell 上注册 ControlListener,但这会在整个调整大小操作期间不断生成事件,并且我不知道何时结束调整大小拖动类型的鼠标操作。
我希望能够检测到用户何时完成调整大小的操作,然后启动我的计算密集型操作。有什么好的想法吗?
5个回答

6
使用定时器,在上次接收到调整大小事件后延迟一秒钟开始操作,如何呢? 一个初步的草稿:
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();
                }
            }
        };
}

在你的调整大小事件中:
 lastEvent = System.currentTimeMillis();
 new Timer(1000, taskPerformer).start();

这个或类似的方法可能是最好的解决方案。保证至少有一秒钟没有进行调整大小。虽然会增加处理时间,但可能无法避免。其他回答使用了AsyncExec,它仍会在连续调整大小拖动中触发调整大小事件,但不会那么频繁。对某些人可能有用。我应该指定“计算密集型”是指可能需要一分钟以上,并且不喜欢被中断,因此尽可能少地重新启动它是更可取的。 - jsn
1
Stacker的解决方案看起来是正确的,但我认为判断何时执行hardcoreCalculationTask的测试是错误的。应该是:if ((lastEvent + 1000) < System.currentTimeMillis())而不是> - PeterVermont

4
下面的解决方案受到了 stacker 的启发,与其基本相同,只是它仅使用 SWT API,并确保在开始 CPU 密集任务之前鼠标按钮处于松开状态。
首先是执行任务的类型:
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;
    }

}

然后我们需要注册它。确保在完成后取消注册。有些人可能还想更改用于鼠标监听的组件,以使其更加具体。

    ResizeListener listener = new ResizeListener();
    widget.addControlListener(listener);
    widget.getDisplay().addFilter(SWT.MouseDown, listener);
    widget.getDisplay().addFilter(SWT.MouseUp, listener);

3
这里有一个相同问题的替代建议:[platform-swt-dev] 鼠标调整大小监听器
您可以尝试设置一个标志,并使用Display.asyncExec()延迟调整大小的工作。当您获得调整大小时,如果设置了标志,则只需返回即可。这应该只在UI处于空闲状态时才会引起调整大小的工作。
我的即时想法是监听鼠标抬起事件,但显然(我刚刚尝试过),鼠标事件不会对shell边框上的鼠标操作触发。可能如此简单...

2
我通过创建一个可以“限制”任务的执行器,以通用的方式解决了这个问题。
任务(Runnables)被放置在 DelayQueue 中,从那里调度线程取出并执行它们。最新调度的任务也被记在一个变量中,因此如果调度程序从队列中检索到一个新任务,他会检查是否这是最新计划的任务。如果是,则执行它;否则跳过它。
我使用一个字符串标识符来检查哪些任务被认为属于一个“限制”。
以下是代码,其中还包括普通调度功能,但您可以在其中查看关键部分。
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;
        }
    }
}

-2
如果问题在调整大小期间阻塞了UI线程,您应该考虑使用Display类的asyncExec方法。
/**
 * 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);
    }
}

asyncExec 也会阻塞 UI 线程,只是它不会阻塞调用线程。 - Fabian Zeindl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接