2010-03-11 4 views
7

Мне нужен JButton с приложением выпадающего меню. Поэтому я взял JPopupMenu и привязал его к JButton так, как вы можете видеть в коде ниже. Что нужно сделать, это:Отображение/скрытие JPopupMenu из JButton; FocusListener не работает?

  • показать всплывающее окно при нажатии
  • скрыть его, если нажата второй раз
  • скрыть это, если элемент выбран во всплывающем окне
  • скрыть его, если пользователь клики в другом месте на экране

Эти 4 вещи работают, но из-за булевого флага, который я использую, если пользователь щелкает в другом месте или выбирает элемент, я должен дважды щелкнуть по кнопке, прежде чем она покажет снова. Вот почему я попытался добавить FocusListener (который абсолютно не отвечает), чтобы исправить это и установить флаг false в этих случаях.

EDIT: Последняя попытка в ответ сообщение ...

Вот слушатели: (. Это в классе, простирающейся JButton, поэтому второй слушатель находится на JButton)

// Show popup on left click. 
menu.addFocusListener(new FocusListener() { 
@Override 
public void focusLost(FocusEvent e) { 
    System.out.println("LOST FOCUS"); 
    isShowingPopup = false; 
} 

@Override 
public void focusGained(FocusEvent e) { 
    System.out.println("GAINED FOCUS"); 
} 
}); 

addActionListener(new ActionListener() { 
@Override 
public void actionPerformed(ActionEvent e) { 
    System.out.println("isShowingPopup: " + isShowingPopup); 
    if (isShowingPopup) { 
    isShowingPopup = false; 
    } else { 
    Component c = (Component) e.getSource(); 
    menu.show(c, -1, c.getHeight()); 
    isShowingPopup = true; 
    } 
} 
}); 

Я уже давно сражаюсь с этим. Если кто-то может дать мне понять, что с этим не так, было бы здорово!

Спасибо!

Код:

public class Button extends JButton { 

    // Icon. 
    private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png"); 

    // Unit popup menu. 
    private final JPopupMenu menu; 

    // Is the popup showing or not? 
    private boolean isShowingPopup = false; 

    public Button(int height) { 
     super(ARROW_SOUTH); 
     menu = new JPopupMenu(); // menu is populated somewhere else 

     // FocusListener on the JPopupMenu 
     menu.addFocusListener(new FocusListener() { 
      @Override 
      public void focusLost(FocusEvent e) { 
       System.out.println("LOST FOCUS"); 
       isShowingPopup = false; 
      } 

      @Override 
      public void focusGained(FocusEvent e) { 
       System.out.println("GAINED FOCUS"); 
      } 
     }); 

     // ComponentListener on the JPopupMenu 
     menu.addComponentListener(new ComponentListener() { 
      @Override 
      public void componentShown(ComponentEvent e) { 
       System.out.println("SHOWN"); 
      } 

      @Override 
      public void componentResized(ComponentEvent e) { 
       System.out.println("RESIZED"); 
      } 

      @Override 
      public void componentMoved(ComponentEvent e) { 
       System.out.println("MOVED"); 
      } 

      @Override 
      public void componentHidden(ComponentEvent e) { 
       System.out.println("HIDDEN"); 
      } 
     }); 

     // ActionListener on the JButton 
     addActionListener(new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       System.out.println("isShowingPopup: " + isShowingPopup); 
       if (isShowingPopup) { 
        menu.requestFocus(); 
        isShowingPopup = false; 
       } else { 
        Component c = (Component) e.getSource(); 
        menu.show(c, -1, c.getHeight()); 
        isShowingPopup = true; 
       } 
      } 
     }); 

     // Skip when navigating with TAB. 
     setFocusable(true); // Was false first and should be false in the end. 

     menu.setFocusable(true); 
    } 

} 
+0

Итак, главная проблема, с которой я сталкиваюсь, заключается в том, что focusGained() и focusLost() никогда не запускаются, хотя я продолжаю создавать всплывающее окно и исчезать. – Joanis

ответ

1

Вот еще один подход, который не слишком вреден для взлома, если не изящного, и который, насколько я мог судить, работает. Сначала, на самом верху, я добавил второе логическое имя: showPopup.

FocusListener должен быть следующим:

menu.addFocusListener(new FocusListener() { 
     @Override 
     public void focusLost(FocusEvent e) { 
      System.out.println("LOST FOCUS"); 
      isShowingPopup = false; 
     } 

     @Override 
     public void focusGained(FocusEvent e) { 
      System.out.println("GAINED FOCUS"); 
      isShowingPopup = true; 
     } 
    }); 

isShowingPopup булево не переодеться где-нибудь еще - если он получает фокус, он предполагает, что это показано и, если он теряет фокус, то предполагается, что он ISN» т.

Далее, ActionListener на кнопку отличается:

addActionListener(new ActionListener() { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
      System.out.println("isShowingPopup: " + isShowingPopup); 
      if (showPopup) { 
       Component c = (Component) e.getSource(); 
       menu.show(c, -1, c.getHeight()); 
       menu.requestFocus(); 
      } else { 
       showPopup = true; 
      } 
     } 
    }); 

Сейчас идет действительно новый бит. Это MouseListener на кнопку:

addMouseListener(new MouseAdapter() { 
     @Override 
     public void mousePressed(MouseEvent e) { 
      System.out.println("ispopup?: " + isShowingPopup); 
      if (isShowingPopup) { 
       showPopup = false; 
      } 
     } 

     @Override 
     public void mouseReleased(MouseEvent e) { 
      showPopup = true; 
     } 
    }); 

В основном, mousePressed вызывается до меню теряет фокус, так isShowingPopup отражает было показано, является ли всплывающее окно до нажатия кнопки. Затем, если меню было там, мы просто установили showPopup в false, так что метод actionPerformed не отображает меню после его вызова (после того как мышь отпустится).

Это было как ожидалось в каждом случае, кроме одного: если меню показывалось, и пользователь нажал кнопку мыши на кнопке, но выпустил ее вне нее, actionPerformed никогда не вызывался. Это означало, что showPopup остался ложным, и меню не было показано при следующем нажатии кнопки. Чтобы исправить это, метод mouseReleased сбрасывает showPopup. Насколько мне известно, метод mouseReleased вызывается после actionPerformed.

Я немного поиграл с результирующей кнопкой, выполнив все действия, которые я мог придумать кнопке, и это сработало, как ожидалось. Тем не менее, я не уверен на 100%, что события всегда будут происходить в том же порядке.

В конечном счете, я думаю, что это, по крайней мере, стоит попробовать.

+0

Ничего себе, я не знаю, почему работает FocusListener (я тоже вызвал requestFocus()!) ... но я просто проверил все это и, похоже, отлично работает! Это именно то, чего не хватало! Хорошая работа! Большое спасибо! – Joanis

+1

Я думаю, что проблема заключалась в том, что вы вызвали requestFocus() - я вызвал его сразу после menu.show(), благодаря чему он сразу же стал фокусироваться, пока вы его вызывали в блоке, который был запущен, если меню уже было показывая «if (isShowingPopup) ...», что заставляет его попытаться сосредоточиться в неподходящее время. –

+0

Не проверите тест, вы тоже, вероятно, правы, так как он работает сейчас! Еще раз спасибо. – Joanis

1

Вы можете использовать JPopupMenu.isVisible() вместо вашей булевой переменной, чтобы проверить текущее состояние всплывающего меню.

+1

Сначала я попытался сделать это, но проблема в следующем: Как только вы нажимаете кнопку (или где-либо еще), когда всплывающее окно видимо, всплывающее окно автоматически закрывается немедленно. Таким образом isVisible() возвращает false независимо от того, что. Эта проблема (очевидно) также применима к isFocusOwner() и isShowing(). – Joanis

0

Ну, я не могу быть уверен, не увидев весь ваш код, но разве возможно, что всплывающее окно никогда не фокусируется? У меня были проблемы с тем, что вещи не были должным образом настроены в Swing раньше, так что это может быть преступником. Попробуйте позвонить setFocusable(true) в меню, а затем вызвать requestFocus() при появлении меню.

+0

Просто попробовал; не работает. Я отправлю код через секунду. – Joanis

1

Вы пробовали добавить ComponentListener к JPopupMenu, так что вы знаете, когда это было показано и скрытые (и обновить isShowingPopup флаг соответственно)? Я не уверен, что прослушивание изменений фокуса обязательно является правильным.

+0

Это кажется очень хорошей идеей, но как и FocusListener, ComponentListener не отвечает (ну, только один раз: когда всплывающее окно отображается в первый раз, я получаю звонок на «изменение размера»). Я поместил вызов println («») в каждый из его методов и ничего не печатался. Я выложу полный код. – Joanis

1

Что вам нужно, это PopupMenuListener:

 menu.addPopupMenuListener(new PopupMenuListener() { 

      @Override 
      public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) { 

      } 

      @Override 
      public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) { 
       System.out.println("MENU INVIS"); 
       isShowingPopup = false;  
      } 

      @Override 
      public void popupMenuCanceled(PopupMenuEvent arg0) { 
       System.out.println("MENU CANCELLED"); 
       isShowingPopup = false;      
      } 
     }); 

Я вставил это в свой код и проверить, что он работает.

+0

Благодарим вас за ввод. Я уже пробовал этот подход ... Это устраняет проблемы, описанные выше, но создает новый. При этом мы не можем закрыть всплывающее нажатие на кнопку, потому что оно откроется независимо от того, закрыто или открыто вначале. – Joanis

+0

Когда всплывающее меню становится видимым, вы можете изменить прослушиватель действий на кнопке, чтобы он не открывал всплывающее окно (т. Е. Просто удалял прослушиватель). Затем, когда меню становится невидимым, установите его обратно (добавьте прослушиватель назад). – Dave

+0

Вы правы. Я не уверен, есть ли способ сделать это «правильно», поскольку JPopupMenu объединяет все другие события мыши в PopupMenuEvent. Вот (БОЛЬШОЙ) взлом: сохраните System.currentTimeMillis() внутри каждого события popupMenuWillBecomeInvisible, а затем внутри actionPerformed выполните «if (isShowingPopup || (System.currentTimeMillis() - savedTime) <100), а затем покажите ... Хорошо, я не предлагаю, чтобы вы сохранили его таким образом, но если он должен так работать ... Единственное, что я могу придумать, это написать собственную реализацию JPopupMenu и обработать события мыши так, как вы хотите. –

3

Вот вариант предложения «большого взлома» янтарного шаха, которое я только что сделал. Без флага isShowingPopup ...

Это не пуленепробиваемый, но он работает достаточно хорошо, пока кто-то не придет с невероятно медленным щелчком, чтобы закрыть всплывающее окно (или очень быстрый второй щелчок, чтобы открыть его ...).

public class Button extends JButton { 

// Icon. 
private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png"); 

// Popup menu. 
private final JPopupMenu menu; 

// Last time the popup closed. 
private long timeLastShown = 0; 

public Button(int height) { 
    super(ARROW_SOUTH); 
    menu = new JPopupMenu(); // Populated somewhere else. 

    // Show and hide popup on left click. 
    menu.addPopupMenuListener(new PopupMenuListener() { 
    @Override 
    public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) { 
    timeLastShown = System.currentTimeMillis(); 
    } 
    @Override public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {} 
    @Override public void popupMenuCanceled(PopupMenuEvent arg0) {} 
    }); 
    addActionListener(new ActionListener() { 
    @Override 
    public void actionPerformed(ActionEvent e) { 
    if ((System.currentTimeMillis() - timeLastShown) > 300) { 
    Component c = (Component) e.getSource(); 
    menu.show(c, -1, c.getHeight()); 
    } 
    } 
    }); 

    // Skip when navigating with TAB. 
    setFocusable(false); 
} 

} 

Как я уже говорил в комментариях, что это не самое элегантное решение, но это ужасно просто, и это работает в 98% случаев.

Открыт для предложений!

0

Я попробовал ответ Тихона Джелвиса (введение умной комбинации focusListener и mouseListener). Это не работает для меня в Linux (Java7/gtk). :-(

Чтение http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 там написано «Обратите внимание, что использование этого метода не рекомендуется, поскольку его поведение зависит от платформы.»

Это может быть, что порядок слушателя вызовов изменился с Java7 или она изменяется с GTK vs Windows. Я бы не рекомендовал это решение, если вы хотите быть независимым от платформы.

BTW: Я создал новую учетную запись в stackoverflow, чтобы дать этот намек. Кажется, мне не разрешено комментировать его ответ (из-за репутация). Но, похоже, у меня есть кнопка для ее редактирования. Этот stackoverflow - очень забавная вещь.:-)