我有一个带有复选框菜单项菜单的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 );
}
}
} );
}
}
}
答案 0 :(得分:4)
我在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(e.x, e.y);
}
});
shell.setMenu(menu);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
<小时/> 的 更新 强>
感谢@Baz,请参阅下面的评论。
使用eclipse 3.6.2在Linux 32bit上试过这个,不幸的是 似乎不起作用。
更新2(按ulmangt)
以下是对64位Windows 7和64位Ubuntu Linux上的解决方案的修改。
org.eclipse.swt.widgets.Menu
类具有包保护字段,用于确定是否已设置菜单位置。如果没有,至少在Linux上,鼠标单击下会出现菜单。
因此,获得正确的行为需要使用反射将此布尔字段重置为false。或者,菜单可能会被处理和重新创建。
最后,Linux似乎希望menu.setLocation( point )
阻止menu.setVisible( true )
和asyncExec
。
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( e.x, e.y );
}
} );
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( );
}
}