События MouseDown не доставляются до тех пор, пока MouseUp не будет присутствовать при наличии источника перетаскивания
У меня есть слушатель мыши. Он имеет некоторый код для ответа на события mouseUp и mouseDown. Это работает правильно.
Однако, как только я добавлю DragSource, мое событие mouseDown больше не будет доставлено - пока я не отпущу кнопку мыши!
Это тривиально для воспроизведения. Ниже приведена простая программа, содержащая обычную оболочку с прослушивателем мыши и прослушивателем перетаскивания. Когда я запускаю это (на Mac), и я нажимаю и удерживаю кнопку мыши, ничего не происходит - но как только я отпускаю кнопку мыши, я сразу вижу, как мышь, так и мышь. Если я прокомментирую источник перетаскивания, события мыши будут переданы так, как они должны быть.
Я искал других с подобными проблемами, и ближайший я нашел объяснение:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=26605#c16
"Если вы перехватываете обнаружение перетаскивания, операционная система должна есть события мыши, пока не определит, что вы перетащили или нет".
Однако я не понимаю, почему это правда - зачем операционной системе есть мышиные события, чтобы определить, есть ли у меня перетаскивание или нет? Перетаскивание не начинается до тех пор, пока не будет нажата кнопка мыши с событием.
Что еще более важно: может ли кто-нибудь предложить обходное решение? (Я пытался динамически добавлять и удалять свой источник перетаскивания при нажатии мыши, но тогда я не мог получить перетаскивание, чтобы нормально функционировать, так как он никогда не видел, чтобы начальная клавиша нажата - и я не могу найти способ программно инициировать перетаскивание.)
Здесь пример программы:
package swttest;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class SwtTest {
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.addMouseListener(new MouseListener() {
public void mouseUp(MouseEvent e) {
System.out.println("mouseUp");
}
public void mouseDown(MouseEvent e) {
System.out.println("mouseDown");
}
public void mouseDoubleClick(MouseEvent e) {
System.out.println("mouseDoubleClick");
}
});
DragSourceListener dragListener = new DragSourceListener() {
public void dragFinished(DragSourceEvent event) {
System.out.println("dragFinished");
}
public void dragSetData(DragSourceEvent event) {
System.out.println("dragSetData");
}
public void dragStart(DragSourceEvent event) {
System.out.println("dragStart");
}
};
DragSource dragSource = new DragSource(shell, DND.DROP_COPY | DND.DROP_MOVE);
dragSource.addDragListener(dragListener);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
Ответы
Ответ 1
Чтобы ответить на ваш конкретный вопрос о том, почему это происходит - на Cocoa мы не рассматриваем перетаскивание, которое нужно было запустить, пока мышь не переместила несколько пикселей. Это позволяет избежать "случайных" перетаскиваний, если вы небрежны с кликами. В Linux и Win32 инструментарий окна может выполнять обнаружение перетаскивания. Если вы просто удерживаете нажатой кнопку, время обнаружения и выключение мыши будут доставлены. На Cocoa у нас нет времени, поэтому ничего не происходит до тех пор, пока не будет обнаружено перетаскивание или не появится мышь.
Это много деталей, но вывод заключается в том, что поведение непоследовательно, и мы всегда должны иметь возможность немедленно доставить мышь, не дожидаясь завершения обнаружения перетаскивания.
Я не вижу обходного пути, так как это происходит до того, как элемент управления видит событие.
Смотрите эта ошибка, в которой есть исправления для win32, gtk и Cocoa SWT.
Ответ 2
Я столкнулся с той же проблемой и нашел решение. Когда вы присоедините DragSource к своему пользовательскому виджету, цикл событий будет заблокирован в этом виджетах с помощью мыши и будет содержать события перемещения мыши для обнаружения перетаскивания. (Я только посмотрел в GTK-код SWT, чтобы найти это, поэтому на других платформах он может работать по-другому, но мое решение работает на GTK, Win32 и Cocoa.) В моей ситуации я не был так сильно заинтересовался обнаружением события мыши, когда это произошло, но мне было интересно значительно уменьшить задержку обнаружения перетаскивания, поскольку вся цель моей реализации Canvas заключалась в том, что пользователь перетаскивал вещи. Чтобы отключить блокировку цикла событий и встроенное обнаружение перетаскивания, все, что вам нужно сделать, это:
setDragDetect(false);
В моем коде я делаю это, прежде чем присоединять DragSource. Как вы уже указали, это оставит вас с проблемой, что вы больше не можете инициировать перетаскивание. Но я нашел решение для этого. К счастью, генерация событий перетаскивания является чистой Java, а не специфичной для платформы в SWT (только для обнаружения перетаскивания). Таким образом, вы можете просто создать собственное событие DragDetect в то время, когда вам это удобно. Я привязал MouseMoveListener к своему Canvas и сохранил последнюю позицию мыши, накопленное расстояние перетаскивания и независимо от того, создал ли он событие DragDetect (среди других полезных вещей). Это реализация mouseMove():
public void mouseMove(MouseEvent e) {
if (/* some condition that tell you are expecting a drag*/) {
int deltaX = fLastMouseX - e.x;
int deltaY = fLastMouseY - e.y;
fDragDistance += deltaX * deltaX + deltaY * deltaY;
if (!fDragEventGenerated && fDragDistance > 3) {
fDragEventGenerated = true;
// Create drag event and notify listeners.
Event event = new Event();
event.type = SWT.DragDetect;
event.display = getDisplay();
event.widget = /* your Canvas class */.this;
event.button = e.button;
event.stateMask = e.stateMask;
event.time = e.time;
event.x = e.x;
event.y = e.y;
if ((getStyle() & SWT.MIRRORED) != 0)
event.x = getBounds().width - event.x;
notifyListeners(SWT.DragDetect, event);
}
}
fLastMouseX = e.x;
fLastMouseY = e.y;
}
И это заменит встроенное, блокирующее обнаружение перетаскивания для вас.