2012-10-10 8 views
6

Mi arrendo. Cercando da alcune settimane per scoprire cosa è il blocco ricevuto dati seriali da aggiornare dalla parte grafica del mio codice. Prima programmazione in Java. Ho circa 15 anni di esperienza nella programmazione di micros e sono abituato a risolvere i miei problemi ma questo va oltre il punto in cui la tattica è produttiva. La mia applicazione consiste di due file.Aggiornamento grafica

Un file deriva dal progetto RXTX e cattura i dati seriali inviati in diversi pacchetti due volte al secondo. Funziona come un incantesimo (ha richiesto del tempo) e posso vedere che i dati acquisiti sono corretti e stabili.

L'altro file è grafico e consiste di circa 80 menu in cui l'utente finale può leggere e talvolta scrivere valori a . La navigazione viene eseguita con gli eventi del mouse sui pulsanti e la barra di scorrimento fino a quel momento. Anche questa parte funziona come dovrebbe. I valori possono essere letti, modificati e salvati, ecc.

La parte in cui sono bloccato è che i valori aggiornati dal file seriale non aggiornano mai lo schermo grafico . Ho provato a seguire centinaia di esempi e tutorial (molti da questo sito) senza fortuna.

Il concetto di lingue relative agli oggetti è nuovo per me e ancora piuttosto confuso. Abbastanza sicuro il mio problema riguarda l'ereditarietà e le classi. Threads è un altro candidato ... Avere tagliato il codice alla dimensione più piccola che sarebbe ancora in esecuzione e presentare il mio problema e spero che qualcuno possa vedere che cosa è sbagliato.

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(); 
    } 
    } 
} 

e la grafica il file

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

mai consegnare, mai arrendersi – mKorbel

+0

Non sono così familiare con lo swing, ma puoi spiegare la relazione tra le chiamate di metodo? È un po 'complicato spiegare cosa non ottengo: in primo luogo 'SerialComm' chiama' dspUpdate() '. Questo metodo chiamerà 'repaint' (bene, credo), ridipingere le chiamate' paintComponent' che chiama 'dspUpdate'? – phineas

+0

@phineas ha problemi con Concurency in Swing, tutti gli aggiornamenti alla GUI devono essere eseguiti su EDT, – mKorbel

risposta

2

Il problema più grande si sta eseguendo in sta mettendo tutto nelle classi GUI. Prova a separare il tuo modello (materiale di comunicazione seriale back-end) dal tuo front-end (cose piuttosto interessanti della GUI), e ti risparmierai un sacco di mal di testa. Nell'esempio che ho provato a farlo per te - è in un file, ma probabilmente dovresti separarlo in 3: Model, View e Control (il controllo è ciò che comunica tra il modello e la vista).

Se aggiungi il codice di comunicazione seriale (che hai detto funzionante) al modello anziché al thread di esempio, dovresti essere in grado di comunicare tra la vista e il modello senza troppi problemi. Ho cercato di conservare il più possibile il tuo codice.

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 Io userei 'SwingWorker', ma un thread separato per l'I/O seriale è essenziale; guarda anche questa [risposta] (http://stackoverflow.com/a/12731752/230513). – trashgod

+0

@Nick Rippe Grazie mille! Il codice seriale "reale" implica il salvataggio di ~ 200 byte in entrata in buffer condivisi dalla parte grafica. Questo succede ogni 500ms in modo che l'evento sia il mio "timer". È necessario aggiornare anche i valori "nascosti" nei buffer che potrebbero essere visualizzati successivamente durante lo scorrimento dei menu. Accadrà questo nel codice che hai fornito? Il mio codice originale era di ~ 9000 linee, quindi mi ci vorrà del tempo per rimettere le cose secondo i vostri suggerimenti. Probabilmente torneremo domani con alcune domande lungo la strada. Devi andare a letto ora ... /Richard – user1735586

+0

Dovresti essere in grado di racchiudere quegli altri valori nello stesso listener - basta aggiungere più righe come questa che passano le informazioni dal modello alla vista. 'Control.this.screen.modelRefreshInfo = Control.this.model.count;' –