在Windows上SWT:在光标滚动控制(不集中)

我们正在将Swing应用程序转换为SWT,并且已经可用。 真正让我吃惊的是,在Windows上,SWT(与Swing相比),即使鼠标光标在另一个控件上,也只能滚动焦点控件(例如表格,列表,多行文本字段)。

是否有可能改变我们的应用程序中的这种行为(不必安装第三方实用程序),例如,通过安装一些控件无关的滚动事件的钩子/filter,将事件redirect到当前光标位置的控件或首先自动移动焦点。 提前致谢。

原来的解决方案有一些问题。

  • 它应该使用反射(它建议自己)。
  • 它应该走上小部件的层次结构来找到一个应该处理轮子事件的父部件,而不是鼠标下的实际部件。 这是必要的,因为如果鼠标下的小部件没有设置SWT.V_SCROLL或SWT.H_SCROLL样式位并且包括各自的本地ScrollBar小部件,则不会处理该事件。
  • 此外,鼠标下的小部件或其父小部件之一可能会附加SWT.MouseWheel的侦听器。 假设意图是在这些听众中处理SWT.MouseWheel事件可能是保存,所以尽管事实上平台不会在没有滚动条的情况下将轮子事件传递给这样的小部件,但是开发者可能希望这些小部件接收事件。

下面是一个可以复制的代码,这个代码是基于原始答案的,但是可以处理所有这些问题。

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.listner; import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.swt.widgets.Scrollable; /** * The standard platform behavior on Windows is to scroll the widget with * keyboard focus when the user turns the mouse wheel, instead of the widget * currently under the mouse pointer. Many consider this annoying and Windows * itself, as well as many popular Windows software, breaks this rule and * implements the behavior seen on other platforms, which is to scroll the * widget under the mouse. * * Win32MouseWheelFilter is a listner implementation which will filter for * SWT.MouseWheel events delivered to any Widget and try to redirect the event * to the widget under the mouse or one of it's parents. The widget, or one of * it's parents is considered a suitable target, if it either has listners for * SWT.MouseWheel attached (assuming that those listeners would do something * sensible with the event), or if its style bits contain SWT.H_SCROLL and/or * SWT.V_SCROLL. In the later case a low level system event is generated, which * is necessary to get the event handled by the native ScrollBar widgets. A * vertical ScrollBar is preferred as the target, unless it is for some reason * unsuitable for scrolling. In that case, horizontal scrolling would take * place, if there is a suitable horizontal ScrollBar. * * Simply creating a new Win32MouseWheelFilter instance will install it as an * event filter in the Display passed to the constructor. At an appropriate * time, you may call dispose() to remove the filter again. On SWT platforms * other than "win32", constructing an Win32MouseWheelFilter will have no effect. */ public class Win32MouseWheelFilter implements listner { private final Display fDisplay; private int WM_VSCROLL; private int WM_HSCROLL; private int SB_LINEUP; private int SB_LINEDOWN; private Method fSendEventMethod32; private Method fSendEventMethod64; /** * Creates a new Win32MouseWheelFilter instance and registers it as global * event filter in the provided Display. Nothing will happen if the SWT * platform is not "win32". If for some reason some SWT internals have * changed since the writing of this class, and the Reflection-based * extraction of some win32 specific fields of the SWT OS class fails, * no filtering of wheel events will take place either. * * @param display * The Display instance that the Win32MouseWheelFilter should install * itself into as global event filter. */ public Win32MouseWheelFilter(Display display) { fDisplay = display; if (!SWT.getPlatform().equals("win32")) return; try { Class<?> os = Class.forName("org.eclipse.swt.internal.win32.OS"); WM_VSCROLL = os.getDeclaredField("WM_VSCROLL").getInt(null); WM_HSCROLL = os.getDeclaredField("WM_HSCROLL").getInt(null); SB_LINEUP = os.getDeclaredField("SB_LINEUP").getInt(null); SB_LINEDOWN = os.getDeclaredField("SB_LINEDOWN").getInt(null); try { // Try the 32-bit version first fSendEventMethod32 = os.getDeclaredMethod("SendMessage", int.class, int.class, int.class, int.class); } catch (NoSuchMethodException e) { // Fall back to the 64-bit version fSendEventMethod64 = os.getDeclaredMethod("SendMessage", long.class, int.class, long.class, long.class); } display.addFilter(SWT.MouseWheel, this); return; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } System.out.println("Warning: Running on win32 SWT platform, " + "but unable to install Win32MouseWheelFilter filter."); } /** * If the receiver had previously installed itself as global event filter, * this method will remove it again from the display's filters. */ public final void dispose() { fDisplay.removeFilter(SWT.MouseWheel, this); } public final void handleEvent(Event event) { Control cursorControl = event.display.getCursorControl(); if (event.widget == cursorControl || cursorControl == null) return; if (event.widget instanceof Control) { // If the original target control's bounds contain the mouse // location, do not re-target the event, since it may indeed be the // Control that needs to handle scrolling for an embedded Control // that has focus. Control control = (Control) event.widget; Rectangle bounds = control.getBounds(); bounds.x = 0; bounds.y = 0; Point cursorPos = control.toControl(display.getCursorLocation()); if (bounds.contains(cursorPos)) return; } // Try to find the best target widget for the event, based on the // cursorControl. A suitable target control is either one that has // a listener for SWT.MouseWheel attached, or one that has either // SWT.H_SCROLL or SWT.V_SCROLL in its style bits. Control wheelControl = cursorControl; int scrollStyle = SWT.H_SCROLL | SWT.V_SCROLL; while (wheelControl != null && (wheelControl.getStyle() & scrollStyle) == 0 && wheelControl.getlistners(SWT.MouseWheel).length == 0) { wheelControl = wheelControl.getParent(); } if (wheelControl == null) { // The event would not be handled by anyone, bail out. return; } int style = wheelControl.getStyle(); if ((style & scrollStyle) != 0 && wheelControl instanceof Scrollable) { // Construct the data for the low level event based on which // direction the target can scroll in. We need to use a low-level // event since otherwise it won't be handled by the native // ScrollBar widgets. int msg; // Prefer vertical scrolling. However, if the // there is no vertical ScrollBar, or if it's somehow disabled, // then switch to horizontal scrolling instead. if ((style & SWT.V_SCROLL) != 0 ) { ScrollBar vBar = ((Scrollable) wheelControl).getVerticalBar(); if (vBar == null || ((vBar.getMinimum() == 0 && vBar.getMaximum() == 0 && vBar.getSelection() == 0) || !vBar.isEnabled() || !vBar.isVisible())) { // There is no vertical ScrollBar, or it can't be used. msg = WM_HSCROLL; } else msg = WM_VSCROLL; } else { msg = WM_HSCROLL; } int count = event.count; int wParam = SB_LINEUP; if (event.count < 0) { count = -count; wParam = SB_LINEDOWN; } try { // Obtain the control's handle via Reflection and // deliver the event using the low level platform method. // (64 and 32 bit versions) if (fSendEventMethod32 != null) { int handle = org.eclipse.swt.widgets.Control.class .getDeclaredField("handle").getInt(wheelControl); for (int i = 0; i < count; i++) fSendEventMethod32.invoke(null, handle, msg, wParam, 0); } else { long handle = org.eclipse.swt.widgets.Control.class .getDeclaredField("handle").getLong(wheelControl); for (int i = 0; i < count; i++) fSendEventMethod64.invoke(null, handle, msg, wParam, 0); } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } } else { // It makes no sense using the low-level OS event delivery, since // Widgets without the scrolling style bits won't receive this // event. Since we selected this widget based on the fact that it // has SWT.MouseWheel listeners attached, use the regular SWT event // notification system. // Convert mouse location, since the event contains it in the wrong // coordinate space (the one of the original event target). Point cursorPos = wheelControl.toControl( event.display.getCursorLocation()); event.x = cursorPos.x; event.y = cursorPos.y; event.widget = wheelControl; wheelControl.notifylistners(event.type, event); } // We re-targeted the event, or re-posted a new event to another widget, // so prevent this event from being processed any further. event.type = SWT.None; event.doit = false; } } 

在Windows上,我使用下面的类来解决这个问题。 我几年前在某个地方发现它,不记得了(我可能已经反编译了ru.nlmk.utilplugins ):

 public class AutoMouseWheelAdapter implements listner { int WM_VSCROLL = OS.WM_VSCROLL; int WM_HSCROLL = OS.WM_HSCROLL; int SB_LINEUP = OS.SB_LINEUP; int SB_LINEDOWN = OS.SB_LINEDOWN; public AutoMouseWheelAdapter() { if ( SWT.getPlatform().equals("win32") ) { Display.getCurrent().addFilter(SWT.MouseWheel, this); } } public void handleEvent( Event event ) { Control cursorControl = Display.getCurrent().getCursorControl(); if ( event.widget == cursorControl || cursorControl == null ) { return; } event.doit = false; int msg = WM_VSCROLL; int style = cursorControl.getStyle(); if ( (style & SWT.V_SCROLL) != 0 && cursorControl instanceof Scrollable ) { ScrollBar verticalBar = ((Scrollable)cursorControl).getVerticalBar(); if ( verticalBar != null && ((verticalBar.getMinimum() == 0 && verticalBar.getMaximum() == 0 && verticalBar.getSelection() == 0) || !verticalBar.isEnabled() || !verticalBar .isVisible()) ) { msg = WM_HSCROLL; } } else if ( (style & SWT.H_SCROLL) == 0 ) { return; } else { msg = WM_HSCROLL; } int count = event.count; int wParam = SB_LINEUP; if ( event.count < 0 ) { count = -count; wParam = SB_LINEDOWN; } for ( int i = 0; i < count; i++ ) { OS.SendMessage(cursorControl.handle, msg, wParam, 0); } } } 

在创建显示线程之后,只需在代码中添加new AutoMouseWheelAdapter() ,或者删除构造函数并将其自己注册为Filter。

这个实现很明显依赖于Win32 SWT。 如果不需要编译时间依赖性,请将方法调用和字段包装在反射调用中。

我们正在使用stippi的解决方案,上面(谢谢!)。 有一个例外 – 我们发现,如果光标完全离开了应用程序窗口(至少在Windows 7上),鼠标滚轮事件仍然传递给控件。 这显然是不可取的,所以我们对上面的解决方案做了小小的改进。

替换此代码:

  if ( event.widget == cursorControl || cursorControl == null ) { return; } 

有了这个:

  if (cursorControl == null) { // The cursor is not in our display window, so prevent this event from being processed any further. event.type = SWT.None; event.doit = false; return; } if (event.widget == cursorControl) return;