多个 JSliders 激活和停用 - 共享值

Multiple JSliders activating and deactivating - Sharing values

上下文

我正在编写一段包含 1 个以上滑块的代码。这些滑块形成一个组。该组滑块值的总和必须始终为 100。但是,作为一项必需的功能,用户可以禁用和启用该组中的 (add/remove) 滑块。因此,这需要正确调整滑块值。

在尝试编写自己的组代码后,我决定寻找 better/tested 并实现代码。它比我的有所改进,但是,出现了一些问题。


主要问题

通过选择或取消选择复选框来添加或删除滑块会导致错误并且滑块停止工作。请注意,在这种情况下,添加仅意味着启用先前禁用的滑块(通过取消选中复选框)。


初步解决方案

以下代码是在 Whosebug 中找到的。我确实在我的代码中实现了,但由于我不能 post 它,我决定调整在 Whosebug 示例中找到的代码来代表我的场景。


怎么办?

非常感谢任何帮助。我不确定如何在不导致更多错误的情况下解决此问题。我确实尝试重新调整在更新方法上完成的微积分,但它只会引起更多的废话。我发现 post 我在这里的所有尝试都没有成效,因为 Whosebug 会说它的代码太多,而且我不确定它是否有助于找到答案。

参考文献


import java.awt.GridLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ConnectedSliders {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI() {
        JSlider s0 = new JSlider(0, 100, 30);
        JSlider s1 = new JSlider(0, 100, 40);
        JSlider s2 = new JSlider(0, 100, 30);

        SliderGroup sliderGroup = new SliderGroup();
        //sliderGroup.add(s0);
        //sliderGroup.add(s1);
        //sliderGroup.add(s2);

        JPanel panel = new JPanel(new GridLayout(0, 3));
        panel.add(s0);
        panel.add(createListeningLabel(s0));
        panel.add(createCheckBox(s0, sliderGroup));

        panel.add(s1);
        panel.add(createListeningLabel(s1));
        panel.add(createCheckBox(s1, sliderGroup));

        panel.add(s2);
        panel.add(createListeningLabel(s2));
        panel.add(createCheckBox(s2, sliderGroup));

        panel.add(createListeningLabel(s0, s1, s2));

        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(panel);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static JLabel createListeningLabel(final JSlider... sliders) {
        final JLabel label = new JLabel("");
        for (JSlider slider : sliders) {
            slider.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    int sum = 0;
                    for (JSlider slider : sliders) {
                        sum += slider.getValue();
                    }
                    label.setText("Sum: " + sum);
                }
            });
        }
        return label;
    }

    private static JLabel createListeningLabel(final JSlider slider) {
        final JLabel label = new JLabel("");
        slider.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                label.setText(String.valueOf(slider.getValue()));
            }
        });
        return label;
    }

    private static JCheckBox createCheckBox(final JSlider slider, SliderGroup group) {
        final JCheckBox checkBox = new JCheckBox();
        checkBox.setSelected(true);

        checkBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    group.add(slider);
                    slider.setEnabled(true);
                } else if(e.getStateChange() == ItemEvent.DESELECTED) {
                    group.remove(slider);
                    slider.setEnabled(false);
                }
            }
        });

        return checkBox;

    }

}

class SliderGroup {

    private final Map<JSlider, Integer> values;
    private final LinkedList<JSlider> candidates;

    private final ChangeListener changeListener;
    private boolean updating = false;

    SliderGroup() {
        this.values = new HashMap<JSlider, Integer>();
        this.candidates = new LinkedList<JSlider>();

        changeListener = new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                JSlider source = (JSlider) e.getSource();
                update(source);
            }
        };
    }

    private void update(JSlider source) {
        if (updating) {
            return;
        }
        updating = true;

        int delta = source.getValue() - values.get(source);
        if (delta > 0) {
            distributeRemove(delta, source);
        } else {
            distributeAdd(delta, source);
        }

        for (JSlider slider : candidates) {
            values.put(slider, slider.getValue());
        }

        updating = false;
    }

    private void distributeRemove(int delta, JSlider source) {
        int counter = 0;
        int remaining = delta;
        while (remaining > 0) {
            JSlider slider = candidates.removeFirst();
            counter++;

            if (slider == source) {
                candidates.addLast(slider);
            } else {
                if (slider.getValue() > 0) {
                    slider.setValue(slider.getValue() - 1);
                    remaining--;
                    counter = 0;
                }
                candidates.addLast(slider);
                if (remaining == 0) {
                    break;
                }
            }
            if (counter > candidates.size()) {
                String message = "Can not distribute " + delta + " among " + candidates;
                //System.out.println(message);
                //return;
                throw new IllegalArgumentException(message);
            }
        }
    }

    private void distributeAdd(int delta, JSlider source) {
        int counter = 0;
        int remaining = -delta;
        while (remaining > 0) {
            JSlider slider = candidates.removeLast();
            counter++;

            if (slider == source) {
                candidates.addFirst(slider);
            } else {
                if (slider.getValue() < slider.getMaximum()) {
                    slider.setValue(slider.getValue() + 1);
                    remaining--;
                    counter = 0;
                }
                candidates.addFirst(slider);
                if (remaining == 0) {
                    break;
                }
            }
            if (counter > candidates.size()) {
                String message = "Can not distribute " + delta + " among " + candidates;
                //System.out.println(message);
                //return;
                throw new IllegalArgumentException(message);
            }
        }
    }

    void add(JSlider slider) {
        candidates.add(slider);
        values.put(slider, slider.getValue());
        slider.addChangeListener(changeListener);
    }

    void remove(JSlider slider) {
        candidates.remove(slider);
        values.remove(slider);
        slider.removeChangeListener(changeListener);
    }

}

对您链接到的示例稍作调整,这应该是可行的。

基本思路是不仅有初始构造组的SliderGroup#addremove方法,还有addAndAdjustremoveAndAdjust方法(除了 adding/removing 滑块)分配添加或删除的滑块的值,使用与最初仅在一个滑块的值发生变化时调整滑块相同的方法。

我还为复选框添加了一个 keepOneSelected 方法:如果可以禁用 所有 滑块,则 none 具有剩余值.因此该方法确保至少有一个复选框始终处于选中状态。

(根据评论中的讨论编辑:)

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;

import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ConnectedSlidersExt
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JSlider s0 = new JSlider(0, 100, 33);
        JSlider s1 = new JSlider(0, 100, 33);
        JSlider s2 = new JSlider(0, 100, 34);

        int expectedSum = 100;
        SliderGroup sliderGroup = new SliderGroup(expectedSum);
        sliderGroup.add(s0);
        sliderGroup.add(s1);
        sliderGroup.add(s2);

        JPanel panel =new JPanel(new GridLayout(0,3));
        panel.add(s0);
        panel.add(createListeningLabel(s0));
        JCheckBox checkBox0 = createCheckBox(s0, sliderGroup);
        panel.add(checkBox0);
        panel.add(s1);
        panel.add(createListeningLabel(s1));
        JCheckBox checkBox1 = createCheckBox(s1, sliderGroup);
        panel.add(checkBox1);
        panel.add(s2);
        panel.add(createListeningLabel(s2));
        JCheckBox checkBox2 = createCheckBox(s2, sliderGroup);
        panel.add(checkBox2);

        keepOneSelected(checkBox0, checkBox1, checkBox2);

        panel.add(createListeningLabel(s0, s1, s2));

        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(panel);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static void keepOneSelected(JCheckBox ...checkBoxes)
    {
        ActionListener actionListener = new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                int numSelected = 0;
                for (JCheckBox checkBox : checkBoxes)
                {
                    if (checkBox.isSelected())
                    {
                        numSelected++;
                    }
                }
                if (numSelected == 1)
                {
                    for (int i = 0; i < checkBoxes.length; i++)
                    {
                        JCheckBox checkBox = checkBoxes[i];
                        if (checkBox.isSelected())
                        {
                            checkBox.setEnabled(false);
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < checkBoxes.length; i++)
                    {
                        JCheckBox checkBox = checkBoxes[i];
                        checkBox.setEnabled(true);
                    }
                }
            }
        };
        for (JCheckBox checkBox : checkBoxes)
        {
            checkBox.addActionListener(actionListener);
        }
    }

    private static JCheckBox createCheckBox(
        JSlider slider, SliderGroup group)
    {
        JCheckBox checkBox = new JCheckBox();
        checkBox.setSelected(true);
        checkBox.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (checkBox.isSelected())
                {
                    slider.setEnabled(true);
                    group.addAndAdjust(slider);
                }
                else
                {
                    slider.setEnabled(false);
                    group.removeAndAdjust(slider);
                }
            }
        });

        return checkBox;

    }

    private static JLabel createListeningLabel(final JSlider ... sliders)
    {
        final JLabel label = new JLabel("");
        for (JSlider slider : sliders)
        {
            slider.addChangeListener(new ChangeListener()
            {
                @Override
                public void stateChanged(ChangeEvent e)
                {
                    int sum = 0;
                    for (JSlider slider : sliders)
                    {
                        if (slider.isEnabled())
                        {
                            sum += slider.getValue();
                        }
                    }
                    label.setText("Sum: "+sum);
                }
            });
        }
        return label;
    }

    private static JLabel createListeningLabel(final JSlider slider)
    {
        final JLabel label = new JLabel("");
        slider.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                label.setText(String.valueOf(slider.getValue()));
            }
        });
        return label;
    }


}


class SliderGroup
{
    private final int expectedSum;
    private final LinkedList<JSlider> candidates;

    private final ChangeListener changeListener;
    private boolean updating = false;

    SliderGroup(int expectedSum)
    {
        this.expectedSum = expectedSum;
        this.candidates = new LinkedList<JSlider>();

        changeListener = new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                JSlider source = (JSlider)e.getSource();
                update(source);
            }
        };
    }

    private void update(JSlider source)
    {
        if (updating)
        {
            return;
        }
        updating = true;

        for (JSlider slider : candidates)
        {
            slider.setValueIsAdjusting(true);
        }

        if (candidates.size() > 1)
        {
            int delta = computeSum() - expectedSum;
            if (delta > 0)
            {
                distributeRemove(delta, source);
            }
            else
            {
                distributeAdd(delta, source);
            }
        }

        for (JSlider slider : candidates)
        {
            slider.setValueIsAdjusting(false);
        }

        updating = false;
    }


    private void distributeRemove(int delta, JSlider source)
    {
        int counter = 0;
        int remaining = delta;
        while (remaining > 0)
        {
            //System.out.println("remove "+remaining);

            JSlider slider = candidates.removeFirst();
            counter++;

            if (slider == source)
            {
                candidates.addLast(slider);
            }
            else
            {
                if (slider.getValue() > 0)
                {
                    slider.setValue(slider.getValue()-1);
                    remaining--;
                    counter = 0;
                }
                candidates.addLast(slider);
                if (remaining == 0)
                {
                    break;
                }
            }
            if (counter > candidates.size())
            {
                String message =
                    "Can not distribute " + delta + " among " + candidates;
                // System.out.println(message);
                // return;
                throw new IllegalArgumentException(message);
            }
        }
    }

    private void distributeAdd(int delta, JSlider source)
    {
        int counter = 0;
        int remaining = -delta;
        while (remaining > 0)
        {
            //System.out.println("add "+remaining);

            JSlider slider = candidates.removeLast();
            counter++;

            if (slider == source)
            {
                candidates.addFirst(slider);
            }
            else
            {
                if (slider.getValue() < slider.getMaximum())
                {
                    slider.setValue(slider.getValue()+1);
                    remaining--;
                    counter = 0;
                }
                candidates.addFirst(slider);
                if (remaining == 0)
                {
                    break;
                }
            }
            if (counter > candidates.size())
            {
                String message =
                    "Can not distribute " + delta + " among " + candidates;
                // System.out.println(message);
                // return;
                throw new IllegalArgumentException(message);
            }
        }
    }

    private int computeSum()
    {
        int sum = 0;
        for (JSlider slider : candidates)
        {
            sum += slider.getValue();
        }
        return sum;
    }

    void add(JSlider slider)
    {
        candidates.add(slider);
        slider.addChangeListener(changeListener);
    }

    void remove(JSlider slider)
    {
        candidates.remove(slider);
        slider.removeChangeListener(changeListener);
    }

    void addAndAdjust(JSlider slider)
    {
        add(slider);
        if (candidates.size() == 2)
        {
            update(candidates.get(0));
        }
        else
        {
            update(slider);
        }
    }

    void removeAndAdjust(JSlider slider)
    {
        remove(slider);
        update(slider);
        if (candidates.size() == 1)
        {
            JSlider candidate = candidates.get(0);
            int max = candidate.getMaximum();
            candidate.setValue(Math.min(max, expectedSum));
        }
    }


}