Имитация панели масштабирования из Office 2007 на Java (модифицированный JSlider)
-
05-07-2019 - |
Вопрос
Я пытаюсь имитировать (или найти ранее существующий компонент), который имитирует ползунок масштабирования из Word 2007:
Есть два основных отличия между этим компонентом и стандартным Java JSlider:
- Не привязывается к отметкам, за исключением 100%, и привязывается, когда вы перемещаете панель, а не когда отпускаете мышь
- Слайдер не является линейным по всей длине:Левая половина ползунка увеличивается с 10% до 100%;правая сторона увеличивается со 100% до 500%.
Вот что у меня есть на данный момент:
Источник:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
*
* @author NDUNN
* @date Nov 25, 2009
*/
public class ZoomBar extends JPanel implements ActionListener, ChangeListener {
private JLabel zoomAmount;
private JButton minus;
private JButton plus;
private JSlider slider;
private static final int MIN_ZOOM = 10;
private static final int MAX_ZOOM = 200;
private static final int DEFAULT_ZOOM = 100;
private static final int MAJOR_ZOOM_SPACING = 50;
private static final int MINOR_ZOOM_SPACING = 10;
public ZoomBar() {
super();
minus = new JButton("-");
plus = new JButton("+");
slider = new JSlider(MIN_ZOOM, MAX_ZOOM, DEFAULT_ZOOM);
slider.setMinorTickSpacing(MINOR_ZOOM_SPACING);
slider.setMajorTickSpacing(MAJOR_ZOOM_SPACING);
slider.setPaintTicks(true);
slider.setSnapToTicks(true);
zoomAmount = new JLabel(slider.getValue() + "%");
add(zoomAmount);
add(minus);
add(slider);
add(plus);
plus.addActionListener(this);
minus.addActionListener(this);
slider.addChangeListener(this);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Zoom bar clone");
frame.setContentPane(new ZoomBar());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == plus) {
slider.setValue(slider.getValue() + MINOR_ZOOM_SPACING);
}
else if (e.getSource() == minus) {
slider.setValue(slider.getValue() - MINOR_ZOOM_SPACING);
}
}
public void stateChanged(ChangeEvent e) {
if (slider.getValueIsAdjusting()) {
return;
}
zoomAmount.setText(slider.getValue() + "%");
}
}
По сути, просто имитирует внешний вид, но без этих двух функций, упомянутых выше.Я ничего не вижу в API JSlider это позволяет мне добиться такого поведения.Должен ли я начинать с нуля, чтобы добиться такого поведения?Если это так, то это не стоит моего времени, но если кто-нибудь знает хороший способ добиться такого поведения, я думаю, это было бы неплохо использовать в проекте, над которым я работаю.
Спасибо,
Ник
Редактировать:
Спасибо PSpeed за идею о том, чтобы просто сопоставить 0 .. 50 и 50 .. 100 с разными значениями.Код для этого приведен ниже.
К сожалению, идея об изменении setValue на snap не сработала.
public void stateChanged(ChangeEvent e) {
// While slider is moving, snap it to midpoint
int value = slider.getValue();
if (slider.getValueIsAdjusting()) {
return;
}
zoomValue = fromSlider(value);
zoomLabel.setText(zoomValue + "%");
}
public int fromSlider(int sliderValue) {
int mappedValue = 0;
if (sliderValue <= 50) {
// Map from [0, 50] to [MIN ... DEFAULT]
mappedValue = (int) map(sliderValue, 0, 50, MIN_ZOOM, DEFAULT_ZOOM);
}
else {
// Convert from (50, 100] to (DEFAULT ... MAX]
mappedValue = (int) map(sliderValue, 50, 100, DEFAULT_ZOOM, MAX_ZOOM);
}
return mappedValue;
}
public int toSlider(int modelValue) {
int mappedValue = 0;
if (modelValue <= DEFAULT_ZOOM) {
// Map from [MIN_ZOOM, DEFAULT_ZOOM] to [0 ... 50]
mappedValue = (int) map(modelValue, MIN_ZOOM, DEFAULT_ZOOM, 0, 50);
}
else {
// Convert from (DEFAULT ... MAX] to (50, 100]
mappedValue = (int) map(modelValue, DEFAULT_ZOOM, MAX_ZOOM, 50, 100);
}
return mappedValue;
}
/**
* @param value The incoming value to be converted
* @param low1 Lower bound of the value's current range
* @param high1 Upper bound of the value's current range
* @param low2 Lower bound of the value's target range
* @param high2 Upper bound of the value's target range
* @return
*/
public static final double map(double value, double low1, double high1, double low2, double high2) {
double diff = value - low1;
double proportion = diff / (high1 - low1);
return lerp(low2, high2, proportion);
}
public static final double lerp(double value1, double value2, double amt) {
return ((value2 - value1) * amt) + value1;
}
Правка 2:Еще одно отличие, которое я замечаю, заключается в том, что JButton не позволяет вам удерживать нажатой кнопку запуска несколько раз, в то время как кнопки + / - в office это делают.Есть идеи, как это имитировать?
Решение
Я полагаю, что вы можете получить все это поведение с помощью пользовательской BoundedRangeModel.Ключ в том, чтобы сделать отчет модели обычным типом диапазона значений, но относиться к нему по-другому, когда вам нужен свой коэффициент масштабирования.
Так, например, если вы разрешите своему диапазону работать от 0 до 100, вы будете обрабатывать 0-50 одним способом, а 50-100 - другим (10-100% и 100-500% соответственно).
Чтобы получить поведение привязки, я почти уверен, что вы можете просто переопределить setValue(), чтобы привязать нужный диапазон.Таким образом, используя 0-100 в качестве диапазона значений, если setValue() вызывается с 47-53, то просто привязывайте значение к 50.