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

时间:2012-09-05 19:00:03

标签: java swt eclipse-rcp rcp

我有一个带有复选框菜单项菜单的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 );
                    }
                }
            } );
        }
    }
}

1 个答案:

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