ошибочное поведение repaint() в java (JPanel, JFrame)

package BlackjackPanels;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;

public class MainPanel extends JFrame implements ActionListener {

    private JPanel background;

    public MainPanel() {    
        super("Alan's Blackjack");
        setDefaultCloseOperation(EXIT_ON_CLOSE);        
        background = new JPanel() 
        {   
            @Override
            protected void paintComponent(Graphics g)
            {       
                try 
                {
                    BufferedImage image = ImageIO.read(new File("C:/Users/Ssangwook/Desktop/programming/javafiles/Blackjack/src/BlackjackImages/blackjackTableResized.jpg"));
                    super.paintComponent(g);
                    g.drawImage(image, 0, 0, this);
                } 
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        };

        background.setPreferredSize(new Dimension(1000,500));
        add(background);
        pack();
        setLocationRelativeTo(null);
        setResizable(false);
        //isRunning();
        setVisible(true);
    }

    public void isRunning() {
        background.setLayout(new BorderLayout(10, 10));
        DealerPanel dealer=new DealerPanel();
        background.add(dealer, BorderLayout.LINE_START);
        repaint();
    }

    @Override
    public void actionPerformed(ActionEvent a) {
    }

    public static void main(String [] args) {
        MainPanel game=new MainPanel(); 
        game.isRunning();
    }
}

Панель дилера

package BlackjackPanels;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JPanel;

public class DealerPanel extends JPanel {

    private JButton hit = new JButton("Hit");
    public DealerPanel() {
        super();
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        setPreferredSize(new Dimension(100,100));
        setOpaque(false);
        setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.DARK_GRAY), "DEALER"));

    }

    protected void askBets() {

    }

    public void addListener(ActionListener a) {
        hit.addActionListener(a);
    }
}

Привет, я только начинаю делать блэкджек с графическим интерфейсом. Однако у меня возникла проблема, и я действительно надеялся, что кто-нибудь сможет мне ее объяснить.

Проблема, похоже, связана с repaint() внутри метода isRunning(). Всякий раз, когда я запускаю программу в Eclipse, я получаю панель DealerPanel на своем фоне только в половине случаев. В другой половине случаев, когда панель не отображается, мне приходится сворачивать окно JFrame и снова открывать его, а панель для DealerPanel каким-то образом снова отображается.

Я сделал небольшой поиск самостоятельно и обнаружил, что repaint() просто запрашивает поток AWT для вызова update(), который затем вызывает paint() и, следовательно, не является прямым вызовом для рисования. Кроме того, repaint() может быть проблематичным, так как repaint() только планирует для paint() и немедленно возвращается. Может ли это иметь отношение к моей проблеме?

Подводя итог, моя панель для фона всегда отображается. Однако моя панель для панели дилера не всегда отображается при первом запуске программы. Когда панель дилера не отображается, я прячу и открываю окно, и панель отображается. Кто-нибудь знает причину такого странного поведения?


person Alan H    schedule 21.03.2014    source источник
comment
Не пытайтесь загрузить изображение в методах paint..! Загрузите его один раз и сохраните ссылку на него.   -  person Andrew Thompson    schedule 21.03.2014
comment
public void isRunning() { Метод, начинающийся с is, логически должен возвращать boolean, а не void..   -  person Andrew Thompson    schedule 21.03.2014
comment
Вы пробовали dealer.setFocusable(true);?   -  person Braj    schedule 21.03.2014
comment
@ Эндрю Томпсон Вы абсолютно правы! Спасибо за исправления.   -  person Alan H    schedule 21.03.2014
comment
@Braj Я думал, что все панели JPanel по умолчанию настроены на фокус, не так ли?   -  person Alan H    schedule 21.03.2014
comment
Взгляните на этот вопрос JPanel не отвечает на keylistener   -  person Braj    schedule 21.03.2014


Ответы (1)


  1. super.paintComponent(g); всегда должен вызываться, обычно первым, независимо от того, что еще вы можете делать в методе paintComponent
  2. Не выполняйте операции в методе рисования, которые могут занять много времени, например загрузка изображений. Загрузите эти изображения заранее. Методы рисования могут вызываться много раз, иногда в быстрой последовательности.
  3. Избегайте вызова setPreferred/Minimum/MaximumSize
  4. Возможно, вам придется вызвать revalidate (или invalidate, validate на JFrame) в контейнере верхнего уровня, чтобы иерархия контейнеров была помечена как требующая перераспределения, repaint этого делать не будет.
  5. Вы должны запускать свой пользовательский интерфейс в контексте EDT, см. Начальные потоки для более подробной информации
person MadProgrammer    schedule 21.03.2014
comment
Большое спасибо! Я последовал вашему совету и внес все изменения в свой код. После написания revalidate() в моем коде проблема, о которой я упоминал, исчезла. Если позволите, у меня есть еще один вопрос. Теперь я понимаю, что Java Swing по большей части не является потокобезопасным, и поэтому я всегда должен запускать его в EDT. Я провел некоторый поиск, и оказалось, что многие люди используют invokeLater() или invokeAndWait() для постановки события в очередь в EDT. Это то, что вы предлагаете мне сделать? Есть ли лучший способ запустить его в EDT? - person Alan H; 21.03.2014
comment
Да, но в вашем случае только в основном методе. Когда программа запускается, она запускается в так называемом основном потоке, вам нужно запустить свой код в EDT, используя invokeLater. Не думайте ничего о том, в каком потоке вы находитесь на данном этапе, invokeLater безопаснее - person MadProgrammer; 22.03.2014