---
category: swing
folder: ToggleSwitch
title: JSliderでオン・オフ切り替え可能なスイッチボタンを作成する
tags: [JSlider, JLayer, NimbusLookAndFeel]
author: aterai
pubdate: 2020-11-23T14:41:16+09:00
description: JSliderを使用してマウスクリックやつまみのドラッグでオン・オフの切り替え可能なスイッチボタンを作成します。
image: https://drive.google.com/uc?id=1eHKdswGa47y1nDw6S6vz_3ogSY7RxHFJ
hreflang:
    href: https://java-swing-tips.blogspot.com/2020/11/create-switchable-buttons-in-jslider.html
    lang: en
---
* 概要 [#summary]
`JSlider`を使用してマウスクリックやつまみのドラッグでオン・オフの切り替え可能なスイッチボタンを作成します。

#download(https://drive.google.com/uc?id=1eHKdswGa47y1nDw6S6vz_3ogSY7RxHFJ)

* サンプルコード [#sourcecode]
#code(link){{
UIDefaults d = new UIDefaults();
d.put("Slider.thumbHeight", 40);
d.put("Slider.thumbWidth", 40);
d.put("Slider:SliderTrack[Enabled].backgroundPainter", (Painter<JSlider>) (g, c, w, h) -> {
  int arc = 40;
  int fillLeft = 2;
  int fillTop = 2;
  int trackWidth = w - fillLeft - fillLeft;
  int trackHeight = h - fillTop - fillTop;
  int baseline = trackHeight - fillTop - fillTop; // c.getBaseline(w, h);
  String off = "Off";

  g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  g.setColor(Color.GRAY);
  g.fillRoundRect(fillLeft, fillTop, trackWidth, trackHeight, arc, arc);
  g.setPaint(Color.WHITE);
  g.drawString(off, w - g.getFontMetrics().stringWidth(off) - fillLeft * 5, baseline);

  int fillRight = getXPositionForValue(c, new Rectangle(fillLeft, fillTop, trackWidth, trackHeight));
  int fillRight = getXPositionForValue(
      c, new Rectangle(fillLeft, fillTop, trackWidth, trackHeight));
  g.setColor(Color.ORANGE);
  g.fillRoundRect(fillLeft + 1, fillTop, fillRight - fillLeft, trackHeight, arc, arc);
  g.fillRoundRect(
      fillLeft + 1, fillTop, fillRight - fillLeft, trackHeight, arc, arc);

  g.setPaint(Color.WHITE);
  if (fillRight - fillLeft > 0) {
    g.drawString("On", fillLeft * 5, baseline);
  }
  g.setStroke(new BasicStroke(2.5f));
  g.drawRoundRect(fillLeft, fillTop, trackWidth, trackHeight, arc, arc);
});

Painter<JSlider> thumbPainter = (g, c, w, h) -> {
  int fillLeft = 8;
  int fillTop = 8;
  int trackWidth = w - fillLeft - fillLeft;
  int trackHeight = h - fillTop - fillTop;
  g.setPaint(Color.WHITE);
  g.fillOval(fillLeft, fillTop, trackWidth, trackHeight);
};
d.put("Slider:SliderThumb[Disabled].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Enabled].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused+MouseOver].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused+Pressed].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[MouseOver].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Pressed].backgroundPainter", thumbPainter);

JSlider slider = new JSlider(0, 1, 0) {
  @Override public Dimension getPreferredSize() {
    return new Dimension(100, 40);
  }
};
slider.setFont(slider.getFont().deriveFont(Font.BOLD, 32f));
slider.putClientProperty("Nimbus.Overrides", d);
}}


* 解説 [#explanation]
- `Default`
-- `JSlider`を最小値`0`、最大値`1`に設定
-- コンポーネントのサイズを`getPreferredSize()`をオーバーライドして変更
- `Thumb size`
-- つまみのサイズを`Slider.thumbWidth`、`Slider.thumbHeight`を設定して変更
- `SliderTrack`
-- トラックの背景を描画する`Painter`を設定して背景色やフチ、`On/Off`の文字列の描画を変更
-- トラックをプレスすると`On/Off`が切り替わる
-- つまみを描画する`Painter`を設定してその描画を変更
-- つまみをドラッグすると残像が表示される場合があるので`MouseMotionListener`を追加してドラッグ中に全体を再描画
-- つまみをプレス、またはクリックしても`On/Off`の切り替えは発生しない
- `JSlider + JLayer`
-- トラックとつまみの描画は`SliderTrack`と同様
-- `JLayer`を使用してトラックやつまみに関係なく`JSlider`をクリックすると`On/Off`が切り替わるよう設定
-- つまみのドラッグ中の再描画も以下のように`LayerUI#processMouseMotionEvent(...)`中で実行

#code{{
class ToggleSwitchLayerUI extends LayerUI<JSlider> {
  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JSlider> l) {
    if (e.getID() == MouseEvent.MOUSE_PRESSED && SwingUtilities.isLeftMouseButton(e)) {
      e.getComponent().dispatchEvent(new MouseEvent(
          e.getComponent(),
          e.getID(), e.getWhen(),
          InputEvent.BUTTON3_DOWN_MASK, // e.getModifiers(),
          e.getX(), e.getY(),
          e.getXOnScreen(), e.getYOnScreen(),
          e.getClickCount(),
          e.isPopupTrigger(),
          MouseEvent.BUTTON3)); // e.getButton());
      e.consume();
    } else if (e.getID() == MouseEvent.MOUSE_CLICKED && SwingUtilities.isLeftMouseButton(e)) {
      JSlider slider = l.getView();
      int v = slider.getValue();
      if (slider.getMinimum() == v) {
        slider.setValue(slider.getMaximum());
      } else if (slider.getMaximum() == v) {
        slider.setValue(slider.getMinimum());
      }
    }
  }

  @Override protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JSlider> l) {
    l.getView().repaint();
  }
}
}}

* 参考リンク [#reference]
- [[JSliderのトラックをクリックしても値の変更が発生しないように設定する>Swing/DisableScrollDueToClickInTrack]]
- [https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/_nimbusDefaults.html Nimbus Defaults (The Java™ Tutorials > Creating a GUI With JFC/Swing > Modifying the Look and Feel)]

* コメント [#comment]
#comment
#comment