Mostrando / escondendo uma JPopupMenu de um Jbutton; FocusListener não está funcionando?

StackOverflow https://stackoverflow.com/questions/2421914

Pergunta

Eu precisava de um JButton com um menu anexado estilo suspensa. Então eu peguei uma JPopupMenu e anexado-lo para o JButton na forma como você pode ver no código abaixo. O que ele precisa fazer é esta:

  • mostrar o pop-up quando clicado
  • escondê-lo se clicado uma segunda vez
  • escondê-lo, se um item é selecionado no pop-up
  • escondê-lo se o usuário clicar em algum outro lugar da tela

Estes 4 coisas trabalho, mas por causa do sinalizador booleano que estou usando, se o usuário clicar em algum outro lugar ou seleciona um item, eu tenho que clicar duas vezes no botão antes de ele mostra-se novamente. É por isso que eu tentei adicionar um focusListener (que absolutamente não está respondendo) a correção e definir a bandeira falsa nestes casos.

EDIT: Última tentativa em um post resposta ...

Aqui estão os ouvintes: (é em uma classe estendendo JButton, então o segundo ouvinte está no 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;
  }
 }
});

Eu estive lutando com isso por muito tempo agora. Se alguém pode me dar uma pista sobre o que há de errado com isso, seria ótimo!

Obrigado!

Código:

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);
    }

}
Foi útil?

Solução

Aqui é uma outra abordagem que não é tão ruim de um truque, se não elegante, e que, tanto quanto eu poderia dizer, obras. Em primeiro lugar, no topo, eu adicionei um segundo boolean chamado showPopup.

O FocusListener tem de ser a seguinte:

    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;
        }
    });

O boolean isShowingPopup não ter mudado em qualquer outro lugar - se ele ganha o foco, ele assume que é mostrado e se perde o foco, ele assume que não é

.

Em seguida, o ActionListener no botão é diferente:

   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;
            }
        }
    });

Agora vem o realmente novo bit. É um MouseListener no botão:

    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;
        }
    });

Basicamente, mousePressed é chamado antes que o menu perde o foco, assim isShowingPopup reflete se o popup foi mostrado antes que o botão é pressionado. Então, se o menu foi lá, nós apenas definir showPopup para false, para que o método actionPerformed não mostra o menu, uma vez que é chamado (após o mouse é deixar de ir).

Esta comportou como esperado em cada caso, mas um: se o menu foi mostrando e o usuário pressionou o mouse sobre o botão, mas lançou fora dele, actionPerformed nunca foi chamado. Isto significava que showPopup permaneceu falso e o menu não foi mostrado na próxima vez que o botão foi pressionado. Para corrigir isso, as redefinições de métodos mouseReleased showPopup. O método mouseReleased é chamado após actionPerformed, tanto quanto eu posso dizer.

Eu brinquei com o botão resultando um pouco, fazendo todas as coisas que eu poderia pensar para o botão, e funcionou como esperado. No entanto, eu não estou 100% de certeza que os eventos irão acontecer sempre na mesma ordem.

Em última análise, acho que esta é, pelo menos, vale a pena tentar.

Outras dicas

Aqui está uma variante da sugestão "grande corte" de Amber Shah Eu só fiz. Sem o sinalizador isShowingPopup ...

Não é à prova de balas, mas funciona muito bem até que alguém chega com um clique incrivelmente lento para fechar o pop-up (ou um muito rápido segunda clique para reabri-lo ...).

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);
 }

}

Como eu disse nos comentários, isso não é a solução mais elegante, mas é terrivelmente simples e funciona em 98% dos casos.

Abrir para sugestões!

Você pode usar o JPopupMenu.isVisible () em vez de seu variável booleana para verificar o estado actual do menu pop-up.

Você tentou adicionar um ComponentListener ao JPopupMenu, para que você saiba quando ele foi mostrado e escondido (e atualizar sua bandeira isShowingPopup conformidade)? Eu não tenho certeza de escuta para alterações de foco é necessariamente a abordagem correta.

O que você precisa é um 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;                     
            }
        });

I inserido isso em seu código e verificou-se que ele funciona.

Bem, eu não posso ter certeza sem ver todo o seu código, mas é possível que o pop-up nunca realmente recebe foco em tudo? Eu tive problemas com as coisas não recebendo foco corretamente no balanço antes, assim que poderia ser o culpado. Tente chamar setFocusable(true) no menu e, em seguida, chamando requestFocus() quando você fazer o menu aparecer.

Eu tentei a resposta de Tíkhon Jelvis (introdução de uma combinação inteligente de focusListener e MouseListener). Ele não funciona para mim no Linux (Java7 / GTK). : - (

http: / /docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 lá está escrito "Note-se que o uso deste método não é recomendado porque o seu comportamento é plataforma dependente. "

Pode ser que a ordem das chamadas ouvinte mudou com Java7 ou ele muda com o GTK vs do Windows. Eu não recomendaria essa solução se você quer ser independente de plataforma.

BTW: Eu criei uma nova conta no stackoverflow para dar essa dica. Parece que eu não estou autorizado a comentar a sua resposta (por causa da reputação). Mas parece que eu tenho um botão para editá-lo. Este stackoverflow é uma coisa muito engraçada. : -)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top