Java - динамично променящ се jLabel текст според данните, получени от сериен порт


работя върху софтуер за управление на склад (за съхранение на дрехи). Всички артикули в склада имат RFID вътре, с уникален идентификатор, съхранен като низ.
Опитвам се да направя модул, който ще позволи на потребителите да четат RFID от елемент по антена и автоматично да показват получения ID в един от jLabels на GUI. Антената е свързана с компютър от rs232.
Успях да направя клас да чете данни от антената и работи добре. Той отваря порт за комуникация, задава свойства и чете данни, когато са налични (чрез SerialPortEvent.DATA_AVAILABLE event).
Но тогава се сблъсках с проблем:
Искам екземпляр на този клас да работи в различна нишка, така че антената да чака сканиране и всеки път след сканиране jLabel ще се променя според идентификатора на елемента (по-късно ще бъда правя по-сложни операции с този идентификатор, свързан с моята база данни, но сега просто искам да бъде показан някъде).
Започвам своето Java приключение и не знам как да се справя с многонишковостта.
В този случай Бих искал да започна сканиране от моя Netbeans GUI jFrame и докато сканирането ще тече, jFrame трябва динамично да опреснява стойността на jLabel според последния сканиран елемент.
Сценарий:
Потребителят натиска бутона, за да започне сканирането, сканира известно количество елементи, ID на всеки сканиран елемент преминава към jLabel (jReadLabel) за времето между сканиранията и когато сканирането приключи, потребителят натиска бутона, за да спре, така че приложението да знае кога да спра нишката.
Направих getter метод в моя клас ReadCOM (getChipID()), но не знам как да предавам данни на jFrame всеки път, когато възникне събитие. Ето какво направих досега:

import java.io.*;
import java.util.*;
import javax.comm.*;

public class ReadCOM implements Runnable, SerialPortEventListener {

static CommPortIdentifier portId;
static CommPortIdentifier saveportId;
static Enumeration portList;
InputStream inputStream;
SerialPort serialPort;
public static Thread readThread;
static OutputStream outputStream;
static boolean outputBufferEmptyFlag = false;
public String defaultPort;
boolean isRunning = true;
private String chip_id="";

public CzytajCOM(CommPortIdentifier portId, String defaultPort) {

    this.defaultPort = defaultPort;
    try {
        serialPort = (SerialPort) portId.open("Magazyn", 2000);
    } catch (PortInUseException e) {
        System.out.println("Connection Error. Port in use.");
    }

    try {
        inputStream = serialPort.getInputStream();
    } catch (IOException e) {
    }

    try {
        serialPort.addEventListener(this);
    } catch (TooManyListenersException e) {
    }
    serialPort.notifyOnDataAvailable(true);
    try {
        serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8,
                SerialPort.STOPBITS_1,
                SerialPort.PARITY_NONE);
    } catch (UnsupportedCommOperationException e) {
    }
    readThread = new Thread(this);
    readThread.start();       
}

public void initwritetoport() {

    try {
        outputStream = serialPort.getOutputStream();
    } catch (IOException e) {
    }

    try {
        serialPort.notifyOnOutputEmpty(true);
    } catch (Exception e) {
        System.exit(-1);
    }

}

public void writetoport() {
}

public void run() {

    while (isRunning) {
        try {
            while (isRunning) {
                Thread.sleep(100);
            }
        } catch (Exception e) {
            isRunning = false;
        }
    }
}

public void serialEvent(SerialPortEvent event) {
    switch (event.getEventType()) {
        case SerialPortEvent.BI:
        case SerialPortEvent.OE:
        case SerialPortEvent.FE:
        case SerialPortEvent.CD:
        case SerialPortEvent.CTS:
        case SerialPortEvent.DSR:
        case SerialPortEvent.RI:
            break;

        case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
            break;

        case SerialPortEvent.DATA_AVAILABLE:

            Object obj = event.getSource();
            if (obj instanceof javax.comm.SerialPort) {
                serialPort = (SerialPort) obj;
                try {
                    BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(serialPort.getInputStream()));
                    chip_id = bufferedReader.readLine();
                    //System.out.println("Data: "+chip_id);
                    bufferedReader.close();
                } catch (Exception ex) {
                    System.out.println("Reading from device failed!");
                }
            }
            break;
    }
}

public boolean isRunning() {
    return isRunning;
}

public String getChipId() {
    return chip_id;
}

public void setIsRunning(boolean isRunning) {
    this.isRunning = isRunning;
}

} И в моя jFrame файл тук е кодът на ButtonActionPerformed (бутонът, който започва и спира да чете, частта, в която съм изгубен...):

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
     if(isRead==false) {
          jStatus.setText("Reading in progress...");
          jLabel1.setText("Press the button to stop reading.");
          isRead=true;
          try {
        portId1=CommPortIdentifier.getPortIdentifier("COM4");
        ReadCOM reader=new ReadCOM(portId1, portId1.getName());
        reader.setIsRunning(true); //
        jReadLabel.setText(reader.getChipId());
              }
    catch (Exception ex){
                    System.out.println("Error in button pressed method.");
                   }
     }
               else {

        jStatus.setText("Reading stopped.");
        jLabel1.setText("Press the button to start reading.");
        isRead=false;
    }
}//GEN-LAST:event_jButton1ActionPerformed    

person demo    schedule 12.04.2012    source източник
comment
Не блокирайте EDT (нишката за изпращане на събития) - GUI ще „замръзне“, когато това се случи. Вместо да извиквате Thread.sleep(n), приложете Swing Timer за повтарящи се задачи или SwingWorker за дълго изпълняващи се задачи. Вижте Паралелност в Swing за повече подробности. (малко по-късно, но с "връзката").   -  person Andrew Thompson    schedule 12.04.2012


Отговори (2)


1) метод setText() е деклариран като безопасен за нишка, но работи, докато текущата нишка не бъде замразена от Thread.sleep(int),

2) метод setText() работи обвит в invokeLater() от Runnable#run() или util.Timer#run(), но вашият Thread.sleep(int) е извън тези API, тогава потенциално може да заключва събития към EventDispatchThread

3) вие отваряте CommPort от ActionListener, след което Swing GUI е замразен или не е безотговорен, докато всички събития от ActionListener не приключат, нищо не може да бъде изведено в JLabel

4) трябва да преместите (четене на стойност от CommPort) към фоновата задача

  • извикване на CommPort от Runnable.Thread или от util.Timer (тогава не се изисква спиране на цикъл чрез използване на Thread.sleep())

но най-добре и бих предложил да се използва

  • извикване от SwingWorker, тогава можете да използвате Thread.sleep() или util.Timer в метод doInBackground() и изход от методи publish() или process() може да бъде извикване на събития на EDT
person mKorbel    schedule 12.04.2012
comment
Съжалявам за закъснението, бях ранен след автомобилна катастрофа от почти месец :/ Благодаря много за вашите съвети @mKorbel - точно от това имах нужда! Използвах подхода на SwingWorker с публикуване (chip_id) в манипулатора на събития и сега работи перфектно. наздраве - person demo; 15.05.2012

От вашата нишка за четене на сериен порт изпълнете това:

SwingUtilities.invokeLater(new Runnable() { public void run() {
  // code that updates the label's contents
}});

Така че това е подход на натискане: вие не изтегляте rfid, вие го натискате в GUI, като обръщате специално внимание този код да се изпълнява в нишката за изпращане на събития (EDT). За това е invokeLater.

person Marko Topolnik    schedule 12.04.2012