java- repaint()方法表现不正常 – 2?

huangapple go评论71阅读模式
英文:

java- repaint() method is misbehaving - 2?

问题

这个问题是 https://stackoverflow.com/questions/62618571/java-repaint-method-is-misbehaving 的延伸。

我正在开发一个"音乐播放器"。

我正在使用一个JSlider作为进度条,并使用一个JLabel来在屏幕上绘制文本,比如歌曲名。

我对Graphics2D还不熟悉。

以下是精简后的代码:

public class JSliderDemo extends JFrame {
    
    JLabel label;
    JSlider seek = new JSlider();
    int y = 10;
    
    public JSliderDemo() {
        // ... 窗口设置部分 ...
        createWindow();
        setVisible(true);
        startThread();
    }
    
    // ... 其他方法 ...
    
    protected void startThread() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        if (y == label.getHeight()) {
                            y = 1;
                        }
                        label.repaint();
                        y += 1;
                        
                        Thread.sleep(100);
                    }
                } catch (Exception ex) {}
            }
        });
        
        thread.start();
    }
    
    protected class Component extends JLabel {
        @Override
        public void paintComponent(Graphics g) {
            Graphics2D gr = (Graphics2D) g;
            gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            gr.setColor(Color.RED);
            gr.setFont(new Font("Calibri", Font.PLAIN, 16));
            gr.drawString("Song Name", 50, y);
            
            gr.dispose();
        }
    }
    
    public static void main(String[] args) {
        new JSliderDemo();
    }
}

问题是,当我为JLabel调用repaint()时,它会自动重绘JSlider,即使JSlider未包含在JLabel中。

输出:

Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted..........

现在,如果我从线程中移除label.repaint(),则JSlider不会重新绘制。

输出:

Slider re-painted
Slider re-painted

repaint()方法应该是这样工作的吗?

在我上一个问题中,有人告诉我使用布局管理器,当我确实使用GridLayout进行检查时,它确实起作用了!

只有JLabel被重新绘制。

但是我想要将JLabel叠加在JSlider上,所以我考虑使用JLayeredPane。现在,问题又回来了。

我该如何解决这个问题?

底线:我如何在不导致repaint()方法行为异常的情况下将JLabel叠加在JSlider上?

或者

repaint()方法是这样工作的吗?

英文:

This question is an extension of https://stackoverflow.com/questions/62618571/java-repaint-method-is-misbehaving
(Reading it, is optional)

I am working on a Music Player

I am using a JSlider as seek bar and using a JLabel to draw text on screen, such as song name.

I am new to Graphics2D

Here's the minimized code:

public class JSliderDemo extends JFrame
{
JLabel label;
JSlider seek = new JSlider();
int  y = 10;    
public JSliderDemo()
{
setSize(400, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
createWindow();
setVisible(true);
startThread();
}        
public void createWindow()
{
JPanel panel = new JPanel(new BorderLayout());
panel.setOpaque(true);
panel.setBackground(Color.BLUE);
panel.setBorder(new LineBorder(Color.YELLOW));
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(300, 310));
label = new Component();
label.setSize(300, 300);
createSlider();
layeredPane.add(seek, new Integer(50));
layeredPane.add(label, new Integer(100));
panel.add(layeredPane);
add(panel);
}        
protected void createSlider()
{
seek.setUI(new SeekBar(seek, 300, 10, new Dimension(20, 20), 5, 
Color.DARK_GRAY, Color.RED, Color.RED));
seek.setOrientation(JProgressBar.HORIZONTAL);
seek.setOpaque(false);
seek.setLocation(10, 50);
seek.setSize(300, 20);
seek.setMajorTickSpacing(0);
seek.setMinorTickSpacing(0);
seek.setMinimum(0);
seek.setMaximum(1000);    
seek.setBorder(new MatteBorder(5, 5, 5, 5, Color.CYAN));
}        
protected void startThread()
{
Thread thread = new Thread(new Runnable(){
@Override
public void run()
{
try
{
while(true)
{
if(y == label.getHeight()){y = 1;}   
label.repaint();
y += 1;
Thread.sleep(100);   
}    
}   
catch(Exception ex){}
}
});
thread.start();
}        
protected class Component extends JLabel
{
@Override
public void paintComponent(Graphics g)
{
Graphics2D gr = (Graphics2D) g;
gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gr.setColor(Color.RED);
gr.setFont(new Font("Calibri", Font.PLAIN, 16));
gr.drawString("Song Name", 50, y);
gr.dispose();
}       
}          
public static void main(String[] args) 
{
new JSliderDemo();
}
}

The problem is, when I call repaint() for JLabel it automatically repaints JSlider with it even though JSlider is not included in JLabel.

Output :

Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted
Slider re-painted.........

Now if I remove label.repaint() from the Thread, then the JSlider is not re-painted.

Output:

Slider re-painted
Slider re-painted

Is the repaint() method supposed to work like this?

In my last question, I was told to use Layout Manager and when I did use GridLayout just for checking if it's the solution, then it worked!

Only JLabel was repainted.

But I want to overlap JLabel on JSlider, so I thought of using JLayeredPane. And now, the problem is back.

How can I solve this?

Bottom Line : How can I overlap JLabel on JSlider without leading to repaint() method misbehave ?

OR

Does the repaint() method work like this?

答案1

得分: 0

正如评论中已经提到的,你的 JSlider 被重新绘制的原因是它与 JLabel 有重叠的边界。即使你的标签没有覆盖滑块的区域,Swing 仍会将重叠的区域标记为脏区域(即需要重新绘制滑块的重叠部分),因为 Swing 不知道你只在组件的一部分进行绘制。

为了减少重新绘制的次数,你需要让你的 JLabel 大小变小,最好只有它需要的大小,通过调用它的 getPreferredSize() 方法来实现。然后,你可以通过移动标签的位置来移动文本。

此外,不应该在普通的 Thread 中更新 GUI,而应该使用 javax.swing.Timer。它确保所有对 GUI 的更新都在 Swing 事件线程上进行,而这正是应该进行这些更新的地方。

在对代码进行了这些调整之后,只有在标签实际上视觉上覆盖在滑块上时,滑块才会被重新绘制。

public class JSliderDemo extends JFrame {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(JSliderDemo::new);
    }

    private final JLabel label = new CustomLabel();

    public JSliderDemo() {
        setSize(400, 400);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);

        createWindow();
        setVisible(true);
        startTimer();
    }

    public void createWindow() {
        JPanel panel = new JPanel(new BorderLayout());

        JLayeredPane layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize(new Dimension(300, 310));

        label.setLocation(0, 0);
        label.setBorder(new LineBorder(Color.RED));
        label.setSize(label.getPreferredSize());

        layeredPane.add(createSlider(), Integer.valueOf(50));
        layeredPane.add(label, Integer.valueOf(100));

        panel.add(layeredPane);
        setContentPane(panel);
    }

    protected JSlider createSlider() {
        JSlider seek = new CustomSlider();
        seek.setOrientation(JProgressBar.HORIZONTAL);
        seek.setOpaque(false);
        seek.setLocation(10, 50);
        seek.setSize(300, 20);
        seek.setMajorTickSpacing(0);
        seek.setMinorTickSpacing(0);
        seek.setMinimum(0);
        seek.setMaximum(1000);
        seek.setBorder(new LineBorder(Color.BLUE));
        return seek;
    }

    private void startTimer() {
        new Timer(100, e -> {
            int y = label.getY();
            int maxY = label.getParent().getHeight();
            if (y == maxY) {
                y = -label.getHeight();
            }
            label.setLocation(label.getX(), y + 1);
            label.repaint();
        }).start();
    }

    private static class CustomLabel extends JLabel {

        protected CustomLabel() {
            setFont(new Font("Calibri", Font.PLAIN, 16));
            setText("歌曲名称");
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            System.out.println("绘制标签");
        }
    }

    protected static class CustomSlider extends JSlider {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            System.out.println("绘制滑块");
        }
    }
}
英文:

As was already mentioned in the comments, the reason for your JSlider being repainted is that it has overlapping bounds with the JLabel. Even though your label doesn't paint over the area of the slider swing will still mark the overlapping area as dirty (i.e. the overlapping part of the slider will need to be repainted) because swing doesn't know that you are only painting in one part of the component.

To reduce the amount of repaints you will need to make the size of your JLabel smaller. Preferably only as large as it needs to be by invoking its getPreferredSize() method. You'll then be able to move the text by moving the location of the label.

Also you shouldn't be doing updates to the gui in a plain Thread. Use javax.swing.Timer instead. It ensures that all updates to the gui happen on the swing event thread, which is where they should be made.

After making these adjustments to your code the slider is only repainted while the label is actually visually over the slider.

public class JSliderDemo extends JFrame {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(JSliderDemo::new);
    }

    private final JLabel label = new CustomLabel();

    public JSliderDemo() {
        setSize(400, 400);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);

        createWindow();
        setVisible(true);
        startTimer();
    }

    public void createWindow() {
        JPanel panel = new JPanel(new BorderLayout());

        JLayeredPane layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize(new Dimension(300, 310));

        label.setLocation(0, 0);
        label.setBorder(new LineBorder(Color.RED));
        label.setSize(label.getPreferredSize());

        layeredPane.add(createSlider(), Integer.valueOf(50));
        layeredPane.add(label, Integer.valueOf(100));

        panel.add(layeredPane);
        setContentPane(panel);
    }

    protected JSlider createSlider() {
        JSlider seek = new CustomSlider();
        seek.setOrientation(JProgressBar.HORIZONTAL);
        seek.setOpaque(false);
        seek.setLocation(10, 50);
        seek.setSize(300, 20);
        seek.setMajorTickSpacing(0);
        seek.setMinorTickSpacing(0);
        seek.setMinimum(0);
        seek.setMaximum(1000);
        seek.setBorder(new LineBorder(Color.BLUE));
        return seek;
    }

    private void startTimer() {
        new Timer(100, e -> {
            int y = label.getY();
            int maxY = label.getParent().getHeight();
            if (y == maxY) {
                y = -label.getHeight();
            }
            label.setLocation(label.getX(), y + 1);
            label.repaint();
        }).start();
    }

    private static class CustomLabel extends JLabel {

        protected CustomLabel() {
            setFont(new Font("Calibri", Font.PLAIN, 16));
            setText("Song Name");
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            System.out.println("Painting Label");
        }
    }

    protected static class CustomSlider extends JSlider {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            System.out.println("Painting Slider");
        }
    }
}

huangapple
  • 本文由 发表于 2020年6月29日 11:26:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/62630754.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定