2012-10-10 15 views
6

Poddaję się. Trwające od kilku tygodni, aby dowiedzieć się, co jest blokowane otrzymane dane szeregowe od aktualizacji przez część graficzną mojego kodu. Pierwsze programowanie w Javie. Mają około 15 lat doświadczenia w programowaniu micros i jestem przyzwyczajony do rozwiązywania własnych problemów, ale to wykracza poza punkt, w którym ta taktyka jest produktywna. Moja aplikacja składa się z dwóch plików.Aktualizacja grafiki

Jeden plik pochodzi z projektu RXTX i przechwytuje dane szeregowe wysłane w kilku pakietach dwa razy na sekundę. To działa jak zaklęcie (zajęło trochę czasu) i widzę, że przechwycone dane są poprawne i stabilne.

Drugi plik jest graficzny i składa się z około 80 menu, w których użytkownik końcowy może odczytać i zapisać wartości. Nawigacja odbywa się za pomocą zdarzeń myszy na przyciskach i paska przewijania do tej pory. Ta część działa również tak, jak powinna. Wartości można odczytywać, zmieniać i zapisywać itd.

Częścią, w której utknąłem, jest to, że zaktualizowane wartości z pliku seryjnego nigdy nie aktualizują się na ekranie graficznym na . Próbowałem podążać za setkami przykładów i tutoriali (wielu z tej strony) bez powodzenia.

Pojęcie języków związanych z obiektami jest dla mnie nowością i nadal jest dość mylące. Jestem pewien, że mój problem dotyczy dziedziczenia i klas. Wątki to kolejny kandydat ... Zredukowaliśmy kod do najmniejszego rozmiaru, który nadal działałby pod numerem i przedstawiam mój problem i mam nadzieję, że ktoś zobaczy, co jest nie tak.

package components; 

import gnu.io.CommPort; 
import gnu.io.CommPortIdentifier; 
import gnu.io.SerialPort; 
import gnu.io.SerialPortEvent; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import javax.swing.SwingUtilities; 

public class SerialComm extends ScreenBuilder implements java.util.EventListener { 

InputStream in; 

public SerialComm() { 
    super(); 
} 

public interface SerialPortEventListener 
     extends java.util.EventListener { 
} 

void connect(String portName) throws Exception { 
    CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier("COM1"); 
    if (portIdentifier.isCurrentlyOwned()) { 
     System.out.println("Error: Port is currently in use"); 
    } else { 
     CommPortIdentifier.getPortIdentifier("COM1"); 
     System.out.println("" + portName); 
     CommPort commPort = portIdentifier.open("COM1", 2000); 
     if (commPort instanceof SerialPort) { 
      SerialPort serialPort = (SerialPort) commPort; 
      serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_2, SerialPort.PARITY_NONE); 
      InputStream in = serialPort.getInputStream(); 
      OutputStream out = serialPort.getOutputStream(); 
      serialPort.addEventListener(new SerialComm.SerialReader(in)); 
      serialPort.notifyOnDataAvailable(true); 

      (new Thread(new SerialComm.SerialReader(in))).start(); 
      // TX functionality commented for now 
      //    (new Thread(new SerialWriter(out))).start(); 

     } else { 
      System.out.println("Error: Only serial ports are handled by this  example."); 
     } 
    } 
} 

public class SerialReader extends SerialComm implements Runnable, 
     gnu.io.SerialPortEventListener { 

    public SerialReader(InputStream in) { 
     this.in = in; 
    } 

    @Override 
    public void run() { 
    count=11; // just for test. run is normally empty 
    count2=count; // and real code runs within serialEvent() 
    System.out.println("SerialReader " + count); 
    dspUpdate(); // do some desperate stuff in graphics file 
    System.out.println("Post Update " + count); 
    } 

    @Override 
    public void serialEvent(SerialPortEvent event) { 
    System.out.println("SerialEvent"); 
     switch (event.getEventType()) { 
      case SerialPortEvent.DATA_AVAILABLE: 
       try { 
        synchronized (in) { 
         while (in.available() < 0) { 
          in.wait(1, 800000); 
         } //in real code RX data is captured here twice a sec 
        } //and stored into buffers defined in ScreenBuilder 
    //dspUpdate() is called from here to make ScreenBuilder update its screen 
    //That never happens despite all my attempts    
       } catch (IOException e) { 
        System.out.println("IO Exception"); 
       } catch (InterruptedException e) { 
        System.out.println("InterruptedException caught"); 
       } 
     } 
    } 
} 

/* "main" connect PC serial port and start graphic part of application 
* To demonstrate problem with no serial data stream present 
* order of init between serial port and graphics are switched 
*/ 

public static void main(String[] args) { 

    SwingUtilities.invokeLater(new Runnable() { 

     @Override 
     public void run() { 
      ScreenBuilder screen = new ScreenBuilder(); 
      screen.createAndShowGUI(); 
      System.out.println("Created GUI"); 
     } 
    }); 
    try { 
     (new SerialComm()).connect("COM1"); 
    } catch (Exception e) { 
     System.out.println("Error"); 
     e.printStackTrace(); 
    } 
    } 
} 

a grafika złożyć

package components; 

import java.awt.*; 
import javax.swing.SwingUtilities; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.BorderFactory; 
import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.Font; 
import java.awt.Graphics; 
import java.awt.event.*; 

public class ScreenBuilder extends JPanel implements ActionListener { 

public Font smallFont = new Font("Dialog", Font.PLAIN, 12); 
Color screenColor; 
Color lineColor; 
short btn=0; 
short count; 
short count2; 
Button helpButton; 

public static void createAndShowGUI() { 
    System.out.println("Created GUI on EDT? " 
      + SwingUtilities.isEventDispatchThread()); 
    JFrame f = new JFrame("JUST A TEST"); 
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    f.add(new ScreenBuilder()); 
    f.pack(); 
    f.setVisible(true); 
} 

public void dspButton() { 
    setLayout(null);// 
    helpButton = new Button("?"); 
    helpButton.setLocation(217, 8); // set X, Y 
    helpButton.setSize(16, 14); //Set Size X, Y // 
    helpButton.addActionListener(this); 
    add(helpButton); 
    setBackground(Color.black); 
    helpButton.setBackground(Color.black); 
    screenColor = Color.black; 
    helpButton.setForeground(Color.white); 
    lineColor = Color.white; 
} 

@Override 
public void actionPerformed(ActionEvent e) { 
    if (e.getSource() == helpButton) { 
     count2++; 
     System.out.println("Pressed Button "); 
     repaint(); 
    } 
} 

public ScreenBuilder() { 
    setBorder(BorderFactory.createLineBorder(Color.black)); 
} 

@Override 
public Dimension getPreferredSize() { 
    return new Dimension(240, 180); 
} 

public void dspUpdate() { 
    /* 
    * This function is called from SerialComm 
    * Should be called when serial packets have arrived (twice a second) 
    * and update screen with values from serial stream 
    * For now just a test var to validate that values from SerialComm 
    * get to here (they do) 
    */ 
count++; 
System.out.println("Update Count " + count); 
System.out.println("Update Count2 " + count2); 
// revalidate(); // another futile attempt to update screen 
// repaint(); 
} 

@Override 
public void paintComponent(Graphics g) { 
    super.paintComponent(g); 
    g.setColor(lineColor); 
    g.setFont(smallFont); 
    count++; 
    g.drawString("" + count, 130, 20); 
    g.drawString("" + count2, 150, 20); 
    if (btn == 0) { 
     dspButton(); 
     btn = 1; 
    } 
    } 
} 
+4

Nigdy się nie podnosić, nigdy się nie poddawać – mKorbel

+0

Nie jestem zaznajomiony z huśtaniem, ale czy możesz wyjaśnić związek między wywołaniami metod? Wyjaśnienie tego, czego nie otrzymuję, jest nieco trudne: przede wszystkim 'SerialComm' wywołuje' dspUpdate() '. Ta metoda wywoła 'repaint" (dobrze myślę), odświeża wywołania 'paintComponent', który wywołuje' dspUpdate'? – phineas

+0

@phineas s/on ma problem ze Concurency w Swingu, tam wszystkie aktualizacje GUI muszą być wykonane na EDT, – mKorbel

Odpowiedz

2

Największym problemem używasz do wkłada wszystko do klas GUI. Postaraj się oddzielić swój model (elementy komunikacji szeregowej z backendem) od swojego interfejsu (ładne rzeczy w GUI), a zaoszczędzisz sobie dużo bólu głowy. W przykładzie, który próbowałem zrobić dla ciebie - jest w jednym pliku, ale powinieneś prawdopodobnie podzielić go na 3: Model, Widok i Kontrola (kontrola jest tym, co komunikuje się między modelem a widokiem).

Jeśli dodasz kod Serial Communication (który powiedziałeś, że pracował) do Modelu zamiast przykładowego wątku, powinieneś być w stanie komunikować się pomiędzy widokiem a modelem bez zbytniego wysiłku. Starałem się zachować jak najwięcej kodu, jak tylko mogłem.

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.*; 

public class TranslucentWindow { 

    public static void main(String[] args) { 

     SwingUtilities.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       try { 
        View screen = new View(); 
        System.out.println("Created GUI"); 
        Model model = new Model(); 

        Control c = new Control(screen, model); 
       } catch (Exception e) { 
        System.out.println("Error"); 
        e.printStackTrace(); 
       } 
      } 
     }); 
    } 

    //Only cares about the backend. Simplified because you said all the backend code was working right. 
    public static class Model{ 

     //Data that was updated - you can change this to whatever you want. 
     public String count; 
     //Listener that notifies anyone interested that data changed 
     public ActionListener refreshListener; 

     public void run() { 
      //As a sample, we're updating info every 1/2 sec. But you'd have your Serial Listener stuff here 
      Thread t = new Thread(new Runnable(){ 
       @Override 
       public void run() { 
        int i = 0; 
        while(true){ 
         dspUpdate(i++); 
         try { 
          Thread.sleep(500); 
         } catch (InterruptedException e) { 
          e.printStackTrace(); 
         } 
        } 
       }}); 
      t.start(); 
     } 

     //Update data and notify your listeners 
     public void dspUpdate(int input) { 
      count = String.valueOf(input); 
      System.out.println("Update Count " + count); 
      refreshListener.actionPerformed(new ActionEvent(this, input, "Update")); 
     } 

    } 


    //Only cares about the display of the screen 
    public static class View extends JPanel { 

     public Font smallFont = new Font("Dialog", Font.PLAIN, 12); 
     Color screenColor; 
     Color lineColor; 
     short btn=0; 
     String modelRefreshInfo; 
     int buttonPressCount; 
     Button helpButton; 

     public View(){ 
      //Build Panel 
      dspButton(); 

      //Create and show window 
      System.out.println("Created GUI on EDT? "+ SwingUtilities.isEventDispatchThread()); 
      JFrame f = new JFrame("JUST A TEST"); 
      f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
      f.add(this); 
      f.pack(); 
      f.setVisible(true); 
     } 

     public void dspButton() { 
      setLayout(null);// 
      helpButton = new Button("?"); 
      helpButton.setLocation(217, 8); // set X, Y 
      helpButton.setSize(16, 14); //Set Size X, Y // 
      add(helpButton); 
      setBackground(Color.black); 
      helpButton.setBackground(Color.black); 
      screenColor = Color.black; 
      helpButton.setForeground(Color.white); 
      lineColor = Color.white; 
     } 

     @Override 
     public Dimension getPreferredSize() { 
      return new Dimension(240, 180); 
     } 

     @Override 
     public void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      g.setColor(lineColor); 
      g.setFont(smallFont); 
      g.drawString("ModelUpdates: " + modelRefreshInfo, 10, 20); 
      g.drawString("RefreshCount: " + buttonPressCount, 10, 40); 
      if (btn == 0) { 
       dspButton(); 
       btn = 1; 
      } 
     } 
    } 

    //Links up the view and the model 
    public static class Control{ 
     View screen; 
     Model model; 

     public Control(View screen, Model model){ 
      this.screen = screen; 
      //Tells the screen what to do when the button is pressed 
      this.screen.helpButton.addActionListener(new ActionListener(){ 
       @Override 
       public void actionPerformed(ActionEvent e) { 
        //Update the screen with the model's info 
        Control.this.screen.buttonPressCount++; 
        System.out.println("Pressed Button "); 
        Control.this.screen.repaint(); 
       } 
      }); 

      this.model = model; 
      //Hands new data in the model to the screen 
      this.model.refreshListener = new ActionListener(){ 
       @Override 
       public void actionPerformed(ActionEvent e) { 
        //Update the screen with the model's info 
        Control.this.screen.modelRefreshInfo = Control.this.model.count; 
        System.out.println("Model Refreshed"); 
        Control.this.screen.repaint(); 
       } 
      }; 

      //Starts up the model 
      this.model.run(); 
     }  
    } 
} 
+0

+1 Użyłbym 'SwingWorker', ale osobny wątek dla seryjnego IO jest niezbędny; zobacz także [odpowiedź] (http://stackoverflow.com/a/12731752/230513). – trashgod

+0

@Nick Rippe Dzięki za milion! "Rzeczywisty" kod seryjny polega na zapisaniu ~ 200 przychodzących bajtów w buforach udostępnianych przez część graficzną. Zdarza się to co 500ms, więc wydarzenie będzie moim "stoperem". Trzeba odświeżyć również "ukryte" wartości w buforach, które mogą być wyświetlane później podczas przewijania menu. Czy zdarzy się to w podanym kodzie? Mój oryginalny kod był ~ 9000 linii, więc zajmie mi to trochę czasu, aby odłożyć rzeczy zgodnie z twoimi sugestiami. Prawdopodobnie wrócę jutro z kilkoma pytaniami po drodze. Muszę iść spać teraz ... /Richard – user1735586

+0

Powinieneś być w stanie zapchać te inne wartości w tym samym słuchaczu - po prostu dodaj więcej linii, takich jak te, które przekazują informacje z Modelu Widok. 'Control.this.screen.modelRefreshInfo = Control.this.model.count;' –