用户点击菜单项后如何保持SWT菜单打开?

我有一个带有Check Box菜单项菜单的Eclipse RCP / SWT应用程序。

我希望能够在点击其他地方关闭菜单之前检查/取消选中多个项目。 但是,默认的SWT行为是单击后关闭菜单。

我已经实现了以下非常黑客的解决方案,但它肯定不优雅,并且可能无法在所有平台上或在所有情况下正常工作。 所以我对一种更简单的技术非常感兴趣。

下面的代码应该在eclipse中编译和运行开箱即用(对于长度道歉,它是我可以创建的最短的自包含示例):

import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuListener2; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; public class MenuTest { public static void main( String[] args ) { // create a SWT Display and Shell final Display display = new Display( ); Shell shell = new Shell( display ); shell.setText( "Menu Example" ); // create a jface MenuManager and Menu MenuManager popupMenu = new MenuManager( ); Menu menu = popupMenu.createContextMenu( shell ); shell.setMenu( menu ); // create a custom listener class final PopupListener listener = new PopupListener( shell, menu ); // attach the listener to the Manager, Menu, and Shell (yuck!) popupMenu.addMenuListener( listener ); menu.addListener( SWT.Show, listener ); shell.addListener( SWT.MouseDown, listener ); // add an item to the menu popupMenu.add( new Action( "Test", Action.AS_CHECK_BOX ) { @Override public void run( ) { System.out.println( "Test checked: " + isChecked( ) ); listener.keepMenuVisible( ); } } ); // show the SWT shell shell.setSize( 800, 800 ); shell.setLocation( 0, 0 ); shell.open( ); shell.moveAbove( null ); while ( !shell.isDisposed( ) ) if ( !display.readAndDispatch( ) ) display.sleep( ); return; } public static class PopupListener implements Listener, IMenuListener2 { Menu menu; Control control; Point point; public PopupListener( Control control, Menu menu ) { this.control = control; this.menu = menu; } @Override public void handleEvent( Event event ) { // when SWT.Show events are received, make the Menu visible // (we'll programmatically create such events) if ( event.type == SWT.Show ) { menu.setVisible( true ); } // when the mouse is clicked, map the position from Shell // coordinates to Display coordinates and save the result // this is necessary because there appears to be no way // to ask the Menu what its current position is else if ( event.type == SWT.MouseDown ) { point = Display.getDefault( ).map( control, null, event.x, event.y ); } } @Override public void menuAboutToShow( IMenuManager manager ) { // if we have a saved point, use it to set the menu location if ( point != null ) { menu.setLocation( point.x, point.y ); } } @Override public void menuAboutToHide( IMenuManager manager ) { // do nothing } // whenever the checkbox action is pressed, the menu closes // we run this to reopen the menu public void keepMenuVisible( ) { Display.getDefault( ).asyncExec( new Runnable( ) { @Override public void run( ) { Event event = new Event( ); event.type = SWT.Show; event.button = 3; menu.notifyListeners( SWT.Show, event ); if ( point != null ) { menu.setLocation( point.x, point.y ); } } } ); } } } 

我在Win7 32bit和eclipse 4.2上尝试了你的代码。 不幸的是它给出了问题而且正在闪烁。 无论如何,这是另一种变化。 在我看来,你必须使用至少两个听众,一个用于你的菜单项,在任何情况下都需要,另一个用于获得菜单的坐标:

 import org.eclipse.swt.SWT; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MenuDetectListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; public class TestMenu { private static Point point; public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display); shell.setLayout(new GridLayout(1, false)); final Menu menu = new Menu(shell); MenuItem item = new MenuItem(menu, SWT.CHECK); item.setText("Check 1"); item.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { if(point == null) return; menu.setLocation(point); menu.setVisible(true); } }); shell.addMenuDetectListener(new MenuDetectListener() { public void menuDetected(MenuDetectEvent e) { point = new Point(ex, ey); } }); shell.setMenu(menu); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } } 

更新

感谢@Baz,请参阅下面的评论。

在Linux 32bit上使用eclipse 3.6.2尝试了这一点,不幸的是它似乎没有用。

更新2(通过ulmangt)

下面是对您的解决方案的修改,它适用于64位Windows 7和64位Ubuntu Linux。

org.eclipse.swt.widgets.Menu类有一个包受保护的字段,用于确定是否已设置菜单位置。 如果没有,至少在Linux上,鼠标单击下会出现菜单。

因此,获得正确的行为需要使用reflection将此布尔字段重置为false。 或者,可以处理和重新创建菜单。

最后,Linux似乎喜欢从asyncExecmenu.setLocation( point )menu.setVisible( true )

 import java.lang.reflect.Field; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MenuDetectListener; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.MenuListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; public class MenuTest { private static Point point; public static void main( String[] args ) { Display display = new Display( ); final Shell shell = new Shell( display ); shell.setLayout( new GridLayout( 1, false ) ); final Menu menu = new Menu( shell ); MenuItem item = new MenuItem( menu, SWT.CHECK ); item.setText( "Check 1" ); item.addSelectionListener( new SelectionAdapter( ) { public void widgetSelected( final SelectionEvent e ) { if ( point == null ) return; Display.getDefault( ).asyncExec( new Runnable( ) { @Override public void run( ) { menu.setLocation( point ); menu.setVisible( true ); } } ); } } ); shell.addMenuDetectListener( new MenuDetectListener( ) { public void menuDetected( MenuDetectEvent e ) { point = new Point( ex, ey ); } } ); menu.addMenuListener( new MenuListener( ) { @Override public void menuHidden( MenuEvent event ) { try { Field field = Menu.class.getDeclaredField( "hasLocation" ); field.setAccessible( true ); field.set( menu, false ); } catch ( Exception e ) { e.printStackTrace(); } } @Override public void menuShown( MenuEvent event ) { } }); shell.setMenu( menu ); shell.open( ); while ( !shell.isDisposed( ) ) { if ( !display.readAndDispatch( ) ) display.sleep( ); } display.dispose( ); } }