英文:
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");
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论