Okno przestaje się aktualizować gdy kursor je opuści

0

Witam,
Mam aplikację napisaną w Swingu, która odbiera pakiety danych, a następnie w GUI sygnalizuje odbiór każdego pakietu (na przykład co sekundę) poprzez sygnalizator (lampkę) zmieniający swój kolor. Sygnalizator jest zaimplementowany w postaci JLabel, a zmiana jego koloru następuje przez podmianę ikony metodą setIcon(Icon icon).
Odbiór pakietów jest wykonywany z innego wątku (nie wiem, czy ma to znaczenie), a do aktualizacji GUI wykorzystuję standardowy mechanizm invokeLater.

W celu uszczegółowienia działania sygnalizatora, dodam, że ma on dwa stany ENABLE_LED i DISABLE_LED, a czas wyświetlania danego stanu określony jest poprzez DELAY timera, który po odebraniu pakietu jest resetowany.

Podczas odbioru pakietów zaobserwowałem problem z odświeżaniem sygnalizatora w GUI, jeśli kursor myszy przeciągnę poza obszar okna aplikacji. W takim przypadku zwykle (nie zawsze i nie na każdym komputerze z systemem Linux) nastąpi prawidłowa sygnalizacja przez kilka sekund, a po później sygnalizator nie odświeża się (inne komponenty GUI odświeżają się prawidłowo). Po najechaniu myszą ponownie na obszar okna aplikacji (bez klikania) sygnalizator zaczyna odświeżać się prawidłowo.

Próbowałem rożnych metod z wymuszaniem odświeżania obiektu JPanel, który zawiera w sobie modyfikowany JLabel dodany do niego za pomocą addComponent(), jednak żadna z poniższych metod nie naprawiła tego problemu.

private void initTimer() {
  receiveTimer = new Timer(DELAY, (ActionEvent ae) -> {
    EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {
        receiveLabel.setIcon(DISABLE_LED);
    
        // Testowe użycie metod do odświeżania (jednej lub kilku jednocześnie)
        receivePanel.updateUI();
        receivePanel.validate();
        receivePanel.revalidate();
        receivePanel.repaint();
      }
    });
  });

  receiveTimer.setRepeats(false);
}
public static void receivedPacket() {
  receiveTimer.restart();

  EventQueue.invokeLater(new Runnable() {
    @Override
    public void run() {
      receiveLabel.setIcon(ENABLE_LED);
  
      // Testowe użycie metod do odświeżania (jednej lub kilku jednocześnie)
      receivePanel.updateUI();
      receivePanel.validate();
      receivePanel.revalidate();
      receivePanel.repaint();
    }
  });
}

Proszę o podpowiedź, jak można rozwiązać ten problem.

0

A jesteś pewien, że to GUI, a nie buforowanie pakietów? np. logujesz gdzieś, że pakiet się pojawił i w tym czasie GUI nie zostało zaktualizowane?

0
yarel napisał(a):

A jesteś pewien, że to GUI, a nie buforowanie pakietów? np. logujesz gdzieś, że pakiet się pojawił i w tym czasie GUI nie zostało zaktualizowane?

Tak, pakiety są logowane oraz poprawnie wyświetlane w tabelce w tym samym oknie. W tabelce nie ma żadnych problemów z odświeżaniem, jedynie z tymi migającymi LED-ami.

0

UPDATE:

Kolejne testy pokazały, że brak odświeżania lampki LED w GUI następuje nie tylko wtedy, gdy kursor znajdzie się poza oknem aplikacji, ale także, gdy zostanie przesunięty w obszar aplikacji i np. 5 sekund jest nieruchomy. Wtedy np. poruszenie kursora, przykrycie okna aplikacji innym oknem lub przeciągnięcie okna aplikacji w inne miejsce powoduje wznowienie odświeżania.
W momencie zatrzymania odświeżania, lampka LED zawsze pozostaje w stanie wygaszenia. Problem występuje gdy aplikację uruchamiam na Linuxie (Mint 21), a na Windowsie nie ma tego problemu.

Wydaje mi się, że Linux może w inny sposób dzielić sobie obsługę wątków, a odświeżanie niektórych elementów w GUI staje się czasem jakby zamrożone do czasu np. poruszenia kursora w obszarze okna aplikacji. Pakiety odbieram w innym wątku co sekundę i w tym samym momencie powinna zapalić się lampka LED na 0,3 sekundy. Z kolei tabela, która wyświetla odebrane w tym samym wątku pakiety (przez aktualizacje jej modelu), zawsze poprawnie się aktualizuje.

UPDATE 2:

Napisałem prosty programik, który używa wyżej opisanego mechanizmu do zapalania i gaszenia LED przy użyciu timera. Program ten zmienia tylko cyklicznie kolor LED z czerwonego na zielony co sekundę, na okres 300ms (powstaje cykliczne miganie lampki). Poniżej zamieszczam kod tego programu.

Wniosek:
Okazuje się, że w przypadku umieszczenia kursora poza obszarem okna aplikacji jest ten sam problem. Zrobiłem też test, w którym nie używam timera ledDelayTimer do wyświetlania zielonej lampki przez 300ms, ale tylko cyklicznie za pomocą eventTimer co sekundę na przemian ustawiam LED czerwony i zielony, i wtedy problem z odświeżaniem nigdy nie występuje.

public class GuiTimerIcon {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        new MainWindow();
    }
}
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.Timer;

public class MainWindow extends javax.swing.JFrame {

    private static Timer eventTimer;
    private static Timer ledDelayTimer;
    private static ImageIcon ENABLED_LED;
    private static ImageIcon DISABLED_LED;
    
    /**
     * Creates new form MainWindow
     */
    public MainWindow() {
        initComponents();
        this.setVisible(true);
        
        try {
            BufferedImage buffEnabled = ImageIO.read(new File("/home/greenLed.png"));
            BufferedImage buffDisabled = ImageIO.read(new File("/home/redLed.png"));
            
            ENABLED_LED = new ImageIcon(buffEnabled);
            DISABLED_LED = new ImageIcon(buffDisabled);
        } catch (IOException ex) {
            Logger.getLogger(MainWindow.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        initLedDelayTimer();
        
        eventTimer = new Timer(1000, (ActionEvent ae) -> {
            jLabel1.setIcon(ENABLED_LED);
            ledDelayTimer.restart();
        });
        
        eventTimer.start();
    }
    
    private void initLedDelayTimer() {
        ledDelayTimer = new Timer(300, (ActionEvent ae) -> {
            jLabel1.setIcon(DISABLED_LED);
        });
        
        ledDelayTimer.setRepeats(false);
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jPanel1 = new javax.swing.JPanel();
        jLabel1 = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setMinimumSize(new java.awt.Dimension(400, 300));

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addGap(82, 82, 82)
                .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(98, Short.MAX_VALUE))
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addGap(28, 28, 28)
                .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(29, Short.MAX_VALUE))
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    // Variables declaration - do not modify                     
    private javax.swing.JLabel jLabel1;
    private javax.swing.JPanel jPanel1;
    // End of variables declaration                   
}

UPDATE 3:
Problem z odświeżaniem rozwiązuje użycie polecenia -Dsun.java2d.opengl=True, ale wtedy większość kontrolek w oknie ma kolor czarny (chwilowo pomaga resize okna).

Może ma ktoś inny sposób na implementację zapalania lampki LED na określony czas?

1 użytkowników online, w tym zalogowanych: 0, gości: 1