• category: swing folder: PopupVolumeSlider title: JPopupMenuに垂直JSliderを配置してJToggleButtonの上部に表示する tags: [JSlider, JPopupMenu, JToolTip, JToggleButton] author: aterai pubdate: 2021-10-04T11:17:36+09:00 description: JPopupMenuに垂直JSliderを配置し、JToggleButtonに設定したJToolTipを表示するときにその上部に重ねて表示します。 image: https://drive.google.com/uc?id=1JlCYec_huorcrZoTG5_-vs7oFTK0TRhF

概要

JPopupMenuに垂直JSliderを配置し、JToggleButtonに設定したJToolTipを表示するときにその上部に重ねて表示します。

サンプルコード

JPopupMenu popup = new JPopupMenu();
popup.setLayout(new BorderLayout());
popup.addMouseWheelListener(InputEvent::consume);

UIManager.put("Slider.paintValue", Boolean.TRUE);
UIManager.put("Slider.focus", UIManager.get("Slider.background"));
JSlider slider = new JSlider(SwingConstants.VERTICAL, 0, 100, 80);
slider.addMouseWheelListener(e -> {
  JSlider s = (JSlider) e.getComponent();
  if (s.isEnabled()) {
    BoundedRangeModel m = s.getModel();
    m.setValue(m.getValue() - e.getWheelRotation() * 2);
  }
  e.consume();
});
popup.add(slider);

JToggleButton button = new JToggleButton("🔊") {
  @Override public JToolTip createToolTip() {
    JToolTip tip = super.createToolTip();
    tip.addHierarchyListener(e -> {
      long flg = e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED;
      if (flg != 0 && e.getComponent().isShowing()) {
        Dimension d = popup.getPreferredSize();
        popup.show(this, (getWidth() - d.width) / 2, -d.height);
      }
    });
    return tip;
  }

  @Override public Point getToolTipLocation(MouseEvent e) {
    // Dimension d = popup.getPreferredSize();
    return new Point(getWidth() / 2, -getHeight());
  }

  @Override public void setEnabled(boolean b) {
    super.setEnabled(b);
    setText(b ? "🔊" : "🔇");
  }
};
button.setToolTipText("");
button.addMouseListener(new MouseAdapter() {
  @Override public void mousePressed(MouseEvent e) {
    if (!button.isEnabled()) {
      slider.setValue(80);
      button.setEnabled(true);
    }
    Component b = (Component) e.getSource();
    Dimension d = popup.getPreferredSize();
    popup.show(b, (b.getWidth() - d.width) / 2, -d.height);
  }

  @Override public void mouseExited(MouseEvent e) {
    ToolTipManager.sharedInstance().setEnabled(true);
  }
});
popup.addPopupMenuListener(new PopupMenuListener() {
  @Override public void popupMenuCanceled(PopupMenuEvent e) {
    /* not needed */
  }

  @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
    EventQueue.invokeLater(() -> ToolTipManager.sharedInstance().setEnabled(false));
  }

  @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
    button.setSelected(false);
  }
});
View in GitHub: Java, Kotlin

解説

  • JPopupMenu
    • レイアウトをBorderLayoutに変更して垂直JSliderをひとつだけ追加
    • JPopupMenu#addMouseWheelListener(InputEvent::consume)を追加して余白などでマウスホイールを回転しても閉じないよう設定
    • PopupMenuListenerを追加してJPopupMenuが開いたあとはJToolTipを無効化、閉じる前にJToggleButtonの選択状態を解除
  • JSlider
    • MouseWheelListenerを追加してマウスホイールの回転で値を変更可能に設定
    • ChangeListenerを追加して値が0(最小値)になったらJToggleButtonを無効化
    • UIManager.put("Slider.paintValue", Boolean.TRUE)で垂直JSlider上部に値を常時表示
  • JToggleButton
    • JToggleButton#setToolTipText("")で空文字(JPopupMenuの幅より短くなる文字)をToolTipTextとして設定
    • JToggleButton#createToolTip()をオーバーライドしJToolTipHierarchyListenerを追加
    • JToggleButton#getToolTipLocation()をオーバーライドし常にJPopupMenuの背面に表示されるよう位置を調整
    • JToggleButton#setEnabled(...)をオーバーライドし、有効なら🔊、無効なら🔇にテキストを変更
    • JSliderの値が0JToggleButtonが無効状態でもJToolTipは有効
    • 無効状態のJToggleButtonをクリックした場合もJPopupMenuを開くようにするためActionListenerではなくMouseListenerを追加
      • MouseListener#mousePressed(...)JPopupMenuを開く前にJToggleButtonが無効状態の場合は値をデフォルトの80まで戻す
      • MouseListener#mouseExited(...)JPopupMenuの表示後に無効化したJToolTipを有効に戻す
  • JToolTip
    • 追加したHierarchyListenerJToolTipの表示後、重ねてJPopupMenuを表示するよう設定
      • JPopupMenuの表示後にJPopupMenuに追加した上記のPopupMenuListener#popupMenuWillBecomeVisible(...)EventQueue.invokeLater*1を実行してJToolTipを無効化

参考リンク

コメント