2012-10-10 54 views
6

Me entrego. Intentando desde hace algunas semanas averiguar qué está bloqueando los datos seriales que se actualizaron por la parte gráfica de mi código. Programación por primera vez en Java. Tengo unos 15 años de experiencia programando micros y estoy acostumbrado a resolver mis propios problemas, pero esto va más allá del punto donde esa táctica es productiva. Mi aplicación consta de dos archivos.Actualización de gráficos

Un archivo proviene del proyecto RXTX y captura los datos en serie enviados en varios paquetes dos veces por segundo. Esto funciona como un amuleto (llevó algo de tiempo) y puedo ver que los datos capturados son correctos y estables.

El otro archivo es gráfico y consta de alrededor de 80 menús donde el usuario final puede leer y, a veces escribir valores. La navegación se realiza con los eventos del mouse en los botones y la barra de desplazamiento hasta el momento. Esta parte también funciona como debería. Los valores se pueden leer, cambiar y guardar, etc.

La parte en la que estoy atascado es que los valores actualizados del archivo serie nunca actualizan la pantalla gráfica. He intentado seguir cientos de ejemplos y tutoriales (muchos desde este sitio) sin suerte.

El concepto de lenguajes relacionados con objetos es nuevo para mí y sigue siendo bastante confuso. Bastante seguro de que mi problema involucra herencia y clases. Threads es otro candidato ... Ha reducido el código al tamaño más pequeño que todavía se ejecutará y presenta mi problema y espero que alguien pueda ver cuál es incorrecto.

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

Y los archivo de gráficos

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

Nunca te entreguen, nunca te rindas – mKorbel

+0

No estoy tan familiarizado con el swing, pero ¿puedes explicar la relación entre las llamadas a métodos? Es un poco complicado explicar lo que no entiendo: en primer lugar 'SerialComm' llama a' dspUpdate() '. Este método llamará a 'repintado' (bueno, creo), vuelve a pintar llamadas 'paintComponent' que llama' dspUpdate'? – phineas

+0

@phineas s/he tiene problemas con Concurency en Swing, allí todas las actualizaciones de la GUI deben hacerse en EDT, – mKorbel

Respuesta

2

El mayor problema que se está ejecutando en se poner todo en las clases de GUI. Intenta separar tu modelo (cosas de comunicación serial de fondo) de tu interfaz (cosas bonitas de GUI), y te ahorrarás muchos dolores de cabeza. En el ejemplo que he intentado hacer por usted, está en un archivo, pero probablemente debería separarlo en 3: Modelo, Vista y Control (el control es lo que se comunica entre el modelo y la vista).

Si agrega su código de comunicación serie (que dijo que estaba trabajando) al modelo en lugar del hilo de muestra, debería poder comunicarse entre la vista y el modelo sin mucha molestia. Traté de preservar la mayor cantidad de código que pude.

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 Usaría 'SwingWorker', pero un hilo separado para el IO serial es esencial; ver también esto [respuesta] (http://stackoverflow.com/a/12731752/230513). – trashgod

+0

@Nick Rippe ¡Gracias a un millón! El código de serie "real" implica guardar ~ 200 bytes entrantes en búferes compartidos por la parte gráfica. Esto sucede cada 500 ms, por lo que ese evento será mi "temporizador". También es necesario actualizar los valores "ocultos" en los almacenamientos intermedios que pueden visualizarse más adelante al desplazarse por los menús. ¿Esto sucederá en el código que proporcionó? Mi código original era ~ 9000 líneas, así que me tomará un tiempo devolver las cosas de acuerdo con sus sugerencias. Probablemente vuelva mañana con algunas preguntas en el camino. Necesito irme a la cama ahora ... /Richard – user1735586

+0

Debería poder meter esos otros valores en el mismo oyente, solo agregue más líneas como esta que pasan la información del Modelo de la Vista. 'Control.this.screen.modelRefreshInfo = Control.this.model.count;' –