Swing/CompactSlider のバックアップ(No.1)
- バックアップ一覧
- 差分 を表示
- 現在との差分 を表示
- 現在との差分 - Visual を表示
- ソース を表示
- Swing/CompactSlider へ行く。
- 1 (2024-11-11 (月) 00:49:33)
- category: swing folder: CompactSlider title: JSliderとテキスト入力欄を重ねて配置する tags: [JSlider, JSpinner, JProgressBar, JButton, OverlayLayout] author: aterai pubdate: 2024-11-11T00:36:23+09:00 description: JSliderとJFormattedTextFieldをOverlayLayoutで重ねて配置した数値入力コンポーネントを作成します。 image: https://drive.google.com/uc?id=167ykESFUCmExYLbelyFvvxEAk7O6ghkT
概要
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の使用