【问题标题】:How to keep SWT menu open after user clicks menu item?用户单击菜单项后如何保持 SWT 菜单打开?
【发布时间】:2012-08-30 13:29:11
【问题描述】:

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

【问题讨论】:

    标签: java swt eclipse-rcp rcp


    【解决方案1】:

    我在 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,请看下面的评论。

    在 32 位 Linux 上用 eclipse 3.6.2 试过这个,不幸的是 好像没用。

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

    【讨论】:

    • 在 Linux 32bit 和 eclipse 3.6.2 上试过这个,不幸的是它似乎不起作用。单击后菜单会隐藏,并且在下一次右键单击(在不同位置)时,它会出现在上一个位置。
    • @Baz - 看起来像操作系统相关的功能。更新我的答案。
    • @Baz - 你试过ulmangt提供的原始sn-p吗?它在 32Bit Linux 下工作吗?
    • 是的,确实有效。唯一可以改进的是,在重新打开菜单后,Action/MenuItem 没有突出显示。我必须移动鼠标才能实现这一点。但是,应该有一个更简单的方法来解决这个问题。自己尝试过,但没有成功。
    • @Favonius 我相信通过对您的解决方案进行一些调整,我有一些可以在 Linux 和 Windows 中运行的东西。不过,这确实是一个 hack,因为它需要反思。无论如何,我用我更新的代码编辑了你的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-17
    • 1970-01-01
    • 2014-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    相关资源
    最近更新 更多