Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[platform-swt-dev] org.eclipse.swt.widgets.Menu.setLocation complications

Hello,

I need to open a popup menu above a button. The button is at the bottom of the shell. Pushing the button triggers the popup menu to be shown.

I have had many complications to achieve this :

1) Menu.getBounds is package visible so I was obliged to use java refection to call it. There is no computeSize method since Menu is not a Control be only a Widget.

2) Menu.getBounds only returns a result when the menu is visible.

3) Menu.getBounds only returns a result when the menu is visible but not immediately when 
 menu.setVisible(true) is done. Therefore a Display.asyncExec was necessary to do the menu.getBounds a bit later than the menu.setVisible(true).

4) menu.setLocation has not effect on a menu that is already visible. Therefore I had to do menu.setVisible(false) before menu.setLocation and then menu.setVisible(true) again.

Here is the code :

  public static void main(String[] args) {
    final Display display = Display.getDefault();
    Shell shell = new Shell(display);

    shell.setLayout(new GridLayout());

    final Button button = new Button(shell, SWT.PUSH);
    button.setText("Button");
    GridData buttonGridData = new GridData();
    buttonGridData.verticalAlignment = SWT.BOTTOM;
    buttonGridData.grabExcessVerticalSpace = true;
    button.setLayoutData(buttonGridData);

    final Menu menu = new Menu(shell, SWT.POP_UP);
    final MenuItem item = new MenuItem(menu, SWT.NONE);
    item.setText("Item");

    button.addListener(SWT.MouseDown, new Listener() {
      public void handleEvent(Event event) {
        menu.setVisible(true);
        display.asyncExec(new Runnable() {
          public void run() {
            Rectangle bounds = getMenuBoundsReflect(menu);
            int verticalSpacing = 3;
            Point location = display.map(button, null, 0, -verticalSpacing);
            menu.setVisible(false);
            menu.setLocation(location.x, location.y - bounds.height);
            menu.setVisible(true);
          }
        });
      }
    });

    shell.setSize(300, 200);
    shell.open();
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch()) {
        display.sleep();
      }
    }
  }

  public static Rectangle getMenuBoundsReflect(Menu menu) {
    try {
      Method m = Menu.class.getDeclaredMethod("getBounds", (Class[]) null);
      m.setAccessible(true);
      return (Rectangle) m.invoke(menu, (Object[]) null);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

I have tested this code only on Windows (7).

Is there a better / simpler way to achieve this ?

Thanks a lot in advance,

Damien

Back to the top