JSliderとテキスト入力欄を重ねて配置する
Total: 55
, Today: 10
, Yesterday: 10
Posted by aterai at
Last-modified:
概要
JSlider
とJFormattedTextField
をOverlayLayout
で重ねて配置した数値入力コンポーネントを作成します。
Screenshot
Advertisement
サンプルコード
private static Component makeCompactSlider4() {
JSlider slider = new JSlider(0, 100, 50) {
@Override public void updateUI() {
super.updateUI();
setForeground(Color.LIGHT_GRAY);
setUI(new FlatSliderUI(this));
setFocusable(false);
setAlignmentX(RIGHT_ALIGNMENT);
}
};
JFormattedTextField field = new JFormattedTextField() {
@Override public void updateUI() {
super.updateUI();
setFormatterFactory(new NumberFormatterFactory());
setHorizontalAlignment(RIGHT);
setOpaque(false);
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
@Override public void commitEdit() throws ParseException {
super.commitEdit();
Optional.ofNullable(getValue())
.filter(Integer.class::isInstance)
.map(Integer.class::cast)
.ifPresent(slider::setValue);
}
@Override public Dimension getMaximumSize() {
return super.getPreferredSize();
}
};
field.setColumns(3);
field.setValue(slider.getValue());
field.setHorizontalAlignment(SwingConstants.RIGHT);
field.setAlignmentX(RIGHT_ALIGNMENT);
slider.addChangeListener(e -> {
JSlider source = (JSlider) e.getSource();
field.setValue(source.getValue());
source.repaint();
});
slider.addMouseWheelListener(e -> {
JSlider source = (JSlider) e.getComponent();
int oldValue = source.getValue();
int intValue = oldValue - e.getWheelRotation();
int max = source.getMaximum();
int min = source.getMinimum();
if (min <= intValue && intValue <= max) {
source.setValue(intValue);
field.setValue(intValue);
}
});
JPanel p = new JPanel() {
@Override public boolean isOptimizedDrawingEnabled() {
return false;
}
@Override public Dimension getPreferredSize() {
return slider.getPreferredSize();
}
};
p.setLayout(new OverlayLayout(p));
p.setOpaque(false);
p.setBorder(BorderFactory.createLineBorder(Color.GRAY));
p.add(field);
p.add(slider);
Box box = Box.createHorizontalBox();
box.add(p);
box.add(Box.createHorizontalStrut(2));
box.add(makeButton(-5, field, slider.getModel()));
box.add(makeButton(+5, field, slider.getModel()));
box.add(Box.createHorizontalGlue());
JPanel panel = new JPanel(new BorderLayout());
panel.add(p);
panel.add(box, BorderLayout.EAST);
return panel;
}
View in GitHub: Java, Kotlin解説
JSpinner#paintComponent(...)
をオーバーライドしてSwingUtilities.paintComponent(...)
でJSpinner
のエディタ上にJProgressBar
を描画JSpinner
のエディタなどの背景をsetOpaque(false)
で描画しないよう設定し、JProgressBar
を描画WindowsLookAndFeel
ではsetOpaque(false)
を設定しても常にJSpinner
の背景が描画されるバグ?が存在するため、Windows 11
環境ではJSpinner
のテキストがJProgressBar
の背後に隠れてしまう場合がある
private static JSpinner makeSpinner(JProgressBar progressBar) {
BoundedRangeModel m = progressBar.getModel();
int value = m.getValue();
int min = m.getMinimum();
int max = m.getMaximum();
return new JSpinner(new SpinnerNumberModel(value, min, max, 5)) {
private final JPanel renderer = new JPanel();
@Override public void updateUI() {
super.updateUI();
setOpaque(false);
JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) getEditor();
editor.setOpaque(false);
JTextField field = editor.getTextField();
field.setOpaque(false);
field.setBorder(BorderFactory.createEmptyBorder());
}
@Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
JComponent editor = getEditor();
Rectangle r = editor.getBounds();
SwingUtilities.paintComponent(g2, progressBar, renderer, r);
g2.dispose();
}
};
}
JLayer
をJSpinner
に設定し、LayerUI#paint(...)
をオーバーライドしてSwingUtilities.paintComponent(...)
でJSpinner
のエディタ上にJProgressBar
を描画
private static Component makeCompactSlider2() {
BoundedRangeModel m = new DefaultBoundedRangeModel(50, 0, 0, 100);
JProgressBar progressBar = makeProgressBar(m);
JSpinner spinner = makeSpinner2(m);
// JSpinner spinner = makeSpinner(progressBar);
initListener(spinner, progressBar);
LayerUI<JSpinner> layerUI = new LayerUI<JSpinner>() {
private final JPanel renderer = new JPanel();
@Override public void paint(Graphics g, JComponent c) {
// super.paint(g, c);
if (c instanceof JLayer) {
Component view = ((JLayer<?>) c).getView();
if (view instanceof JSpinner) {
JComponent editor = ((JSpinner) view).getEditor();
Rectangle r = editor.getBounds();
Graphics2D g2 = (Graphics2D) g.create();
SwingUtilities.paintComponent(g2, progressBar, renderer, r);
g2.dispose();
}
}
super.paint(g, c);
}
};
return new JLayer<>(spinner, layerUI);
}
JFormattedTextField#paintComponent(...)
をオーバーライドしてSwingUtilities.paintComponent(...)
でJFormattedTextField
上にJProgressBar
を描画- 増減ボタンはJButtonがマウスで押されている間、アクションを繰り返すTimerを設定すると同様の
JButton
で作成
- 増減ボタンはJButtonがマウスで押されている間、アクションを繰り返すTimerを設定すると同様の
private static JTextField makeSpinner3(JProgressBar progressBar) {
JFormattedTextField field = new JFormattedTextField() {
private final JPanel renderer = new JPanel();
@Override public void updateUI() {
super.updateUI();
setOpaque(false);
setFormatterFactory(new NumberFormatterFactory());
setHorizontalAlignment(RIGHT);
}
@Override protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
Rectangle r = SwingUtilities.calculateInnerArea(this, null);
SwingUtilities.paintComponent(g2, progressBar, renderer, r);
g2.dispose();
super.paintComponent(g);
}
@Override public void commitEdit() throws ParseException {
super.commitEdit();
Optional.ofNullable(getValue())
.filter(Integer.class::isInstance)
.map(Integer.class::cast)
.ifPresent(progressBar::setValue);
}
};
field.setHorizontalAlignment(SwingConstants.RIGHT);
field.setOpaque(false);
field.setColumns(16);
field.setValue(50);
field.addMouseWheelListener(e -> {
JFormattedTextField source = (JFormattedTextField) e.getComponent();
BoundedRangeModel model = progressBar.getModel();
Integer oldValue = (Integer) source.getValue();
int intValue = oldValue - e.getWheelRotation();
int max = model.getMaximum();
int min = model.getMinimum();
if (min <= intValue && intValue <= max) {
source.setValue(intValue);
progressBar.setValue(intValue);
}
});
return field;
}
OverlayLayout
を設定したJPanel
にJSlider
とJFormattedTextField
を重ねて配置- OverlayLayoutの使用
JSlider
はフォーカス不可とすることでマウスイベントでの増減は可能だが、フォーカスやキー入力イベントはJFormattedTextField
が優先して編集可能になるよう設定- 増減ボタンはJButtonがマウスで押されている間、アクションを繰り返すTimerを設定すると同様の
JButton
で作成
参考リンク
- Compact sliders - Development release GIMP 2.99.2 is out - GIMP
- このサンプルは
GIMP 3.0
のCompact slider
を参考に作成
- このサンプルは
- JButtonがマウスで押されている間、アクションを繰り返すTimerを設定する
- OverlayLayoutの使用