4

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

1 回答 1

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( );
    }
}
于 2012-09-06T04:02:00.747 回答