Обнаружение, когда пользователь закончил изменять размер оболочки SWT

У меня есть оболочка SWT, размер которой можно изменить. Каждый раз, когда он изменяется, мне приходится делать что-то ресурсоемкое.

Я могу зарегистрировать ControlListener в своей оболочке, но это постоянно генерирует события на протяжении всей операции изменения размера, и я понятия не имею, когда заканчивается операция мыши типа перетаскивания изменения размера.

Я хотел бы иметь возможность определять, когда пользователь завершает изменение размера оболочки, а затем инициировать мою операцию с интенсивными вычислениями. Любые идеи, как это сделать?


person jsn    schedule 15.01.2010    source источник


Ответы (5)


Как насчет использования таймера и запуска операции после задержки, скажем, в одну секунду с момента последнего полученного события изменения размера? Черновой набросок:

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();
person stacker    schedule 15.01.2010
comment
Это или что-то подобное, вероятно, лучшее решение. Гарантирует, что никакое изменение размера не производилось в течение как минимум секунды. Добавляет задержку во время обработки, но с этим, наверное, ничего не поделаешь. В других ответах использовался AsyncExec, который по-прежнему запускает события изменения размера в середине непрерывного перетаскивания изменения размера, но не так часто. Может кому пригодится. Я должен был указать, что под интенсивными вычислениями я имел в виду, что это может занять более минуты и не любит, когда его прерывают, поэтому предпочтительнее запускать его как можно меньше раз. - person jsn; 18.01.2010
comment
решение укладчика выглядит правильно, за исключением того, что я считаю, что тест на то, когда выполнять hardcoreCalculationTask, является обратным. Должно быть: if ( (lastEvent + 1000) < System.currentTimeMillis() ), а не > - person PeterVermont; 09.01.2012

Приведенное ниже решение было вдохновлено стекером и почти такое же, за исключением того, что оно использует только SWT API, а также гарантирует, что кнопка мыши нажата перед запуском задачи с интенсивным использованием ЦП.

Сначала тип, который выполняет эту работу:

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);
person torkildr    schedule 20.02.2012

Вот альтернативное предложение для той же проблемы: [platform-swt- dev] Слушатель изменения размера мыши:

Вы можете попробовать установить флаг и отложить работу по изменению размера с помощью Display.asyncExec(). Когда вы получите изменение размера, если флаг установлен, просто вернитесь. Это должно привести к изменению размера только тогда, когда пользовательский интерфейс бездействует.

Моя мгновенная идея заключалась в том, чтобы слушать события мыши, но, очевидно (я только что попробовал), события мыши не запускаются для действий мыши на границе оболочки. Может быть так чертовски легко...

person Andreas Dolk    schedule 16.01.2010

Я решил эту проблему общим способом, создав Executor, который может «дросселировать» задачи.

Задачи (Runnables) помещаются в DelayQueue, откуда планировщик-поток берет и выполняет их. Последнее запланированное задание также запоминается в переменной, поэтому, если Планировщик извлекает из очереди новое задание, он проверяет, является ли это последним запланированным заданием. Если да, то он его выполняет, если нет - пропускает.

Я использую String-идентификатор, чтобы проверить, какие задачи считаются принадлежащими одному «дросселю».

Это код, он также включает в себя обычные возможности планирования, но вы можете проверить основные части там.

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;
        }
    }
}
person Fabian Zeindl    schedule 10.01.2012

Если проблема заключается в блокировке потока пользовательского интерфейса при изменении размера, следует рассмотреть метод asyncExec класса 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);
    }
}
person Arnaud    schedule 18.01.2010
comment
asyncExec также блокирует поток пользовательского интерфейса, но не блокирует вызывающий поток. - person Fabian Zeindl; 10.01.2012