How to keep a SWT menu open after a user clicks a menu item?

I have an Eclipse RCP / SWT application with a Check Box menu item menu.

I would like to be able to check / uncheck a few items before clicking elsewhere to close the menu. However, the default SWT behavior is to close the menu after a single click.

I implemented the following very hacky solution, which works, but, of course, is not elegant and probably will not work properly on all platforms or under any circumstances. Therefore, I am very interested in a simpler technique, if one exists.

The following code should compile and run inside eclipse right out of the box (apologies for the length, its shortest self-contained example that I could create):

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 ); } } } ); } } } 
+4
source share
1 answer

I tried my code on Win7 32bit and with eclipse 4.2. Unfortunately, this gave problems and flickered. Anyway, here is another variation. In my opinion, you should use at least two listeners, one for your menu item, which is necessary in any case, and the other to get the menu coordinates:

 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(); } } 


Update

Thanks @Baz, see the comment below.

Tried this on Linux 32bit with eclipse 3.6.2 and unfortunately this doesn't seem to work.

Update 2 (by ulmangt)

The following is a modification of your solution, which works for me on both 64-bit Windows 7 and 64-bit Ubuntu Linux.

The org.eclipse.swt.widgets.Menu class has a package protection field that determines whether a menu location has been set. If not, at least on Linux, a menu appears under the click of a mouse.

Therefore, to obtain the correct behavior, you need to use reflection to reset this logical field to false. As an alternative, the menus could probably be positioned and recreated.

Finally, Linux seems that menu.setLocation( point ) and menu.setVisible( true ) should be made from an asyncExec block.

 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( ); } } 
+4
source

Source: https://habr.com/ru/post/1432559/


All Articles