【问题标题】:Updating graphics更新图形
【发布时间】:2012-10-01 05:49:01
【问题描述】:

我投降了。现在尝试了几个星期以找出收到的阻止内容 我的代码的图形部分不会更新串行数据。 第一次用Java编程。拥有约 15 年的编程经验 micros,我习惯于解决自己的问题,但这超出了 这种策略是有成效的。 我的应用程序包含两个文件。

一个文件来自 RXTX 项目并捕获在多个数据包中发送的串行数据 每秒两次。 这就像一个魅力(花了一些时间),我可以看到捕获的数据是正确的 并且稳定。

另一个文件是图形文件,包含大约 80 个菜单,最终用户可以在其中 读取和有时写入值。导航是通过鼠标事件完成的 到目前为止的按钮和滚动条。 这部分也可以正常工作。可以读取、更改和保存值等。

我被卡住的部分是串行文件中的更新值永远不会更新 图形屏幕。已尝试遵循数百个示例和教程(许多 从这个网站)没有运气。

对象相关语言的概念对我来说是新的并且仍然很混乱。 很确定我的问题涉及继承和类。线程是另一个候选人...... 已将代码缩减到仍然可以运行的最小尺寸 并提出我的问题,希望有人能看到问题所在。

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

还有图形文件

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

【问题讨论】:

  • 永不举手,永不投降
  • 我对swing不是很熟悉,但是你能解释一下方法调用之间的关系吗?解释我没有得到的东西有点棘手:首先SerialComm 调用dspUpdate()。此方法将调用repaint(我认为很好),重绘调用paintComponent 调用dspUpdate
  • @phineas 对 Swing 中的 Concurency 有疑问,GUI 的所有更新都必须在 EDT 上完成,
  • 嗨,来自 PaintComponents() 内部的 dspUpdate() 调用是我绝望的另一个测试,应该删除它。对不起...
  • 您能否准确描述您的预期以及实际发生的情况?仅供参考:revalidate() 必须在向容器添加一些组件之后调用,并且如果您使用的是布局管理器。如果最终您希望调用paintComponent(repaint()==重绘此组件),则应调用repaint()。 EDT=事件调度线程。所有与 GUI 相关的事情都必须在该线程上完成,并且永远不应被阻塞,否则 GUI 看起来会冻结(因为事件(其中有一个重绘事件)不能再被调度)。

标签: java swing inheritance concurrency serial-port


【解决方案1】:

您遇到的最大问题是将所有内容都放入 GUI 类中。尝试将您的模型(后端串行通信的东西)与前端(漂亮的 GUI 东西)分开,这样您就可以省去很多麻烦。在示例中,我尝试为您执行此操作 - 它在一个文件中,但您可能应该将其分为 3 个:模型、视图和控制(控制是模型和视图之间的通信)。

如果您将串行通信代码(您所说的工作)添加到模型而不是示例线程,您应该能够在视图和模型之间进行通信,而不会太麻烦。我试图尽可能多地保留您的代码。

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

【讨论】:

  • +1 我会使用SwingWorker,但是串行 IO 的单独线程是必不可少的;另见answer
  • @Nick Rippe 谢谢一百万! “真正的”串行代码涉及将大约 200 个传入字节保存到图形部分共享的缓冲区中。这每 500 毫秒发生一次,因此该事件将成为我的“计时器”。还需要刷新缓冲区中可能稍后在滚动菜单时显示的“隐藏”值。这会发生在您提供的代码中吗?我的原始代码大约是 9000 行,所以我需要一些时间来根据您的建议把东西放回去。可能明天会带着一些问题回来。现在需要睡觉了……/理查德
  • 您应该能够将这些其他值填充到同一个侦听器中 - 只需添加更多这样的行,从 Model the View 传递信息。 Control.this.screen.modelRefreshInfo = Control.this.model.count;
  • @user1735586 还有一个 +1 = 感谢 StackOverflow ;) 祝你好运!
  • 由于你们中的一些人要求更详细的描述,这里是:我已经创建了一个加热控制系统。有一个带图形 LCD 的遥控器来设置参数。 LCD 还会在运行时提供有关温度的信息。我想复制您在 LCD 上看到的内容。最终,Java 代码将存在于一个非常小的网络服务器 中,应用程序将在网络浏览器中打开。通过这种方式,您可以使用通过网络服务器连接到浏览器的智能手机来控制和研究正在运行的供暖系统。 (更多内容)
猜你喜欢
  • 1970-01-01
  • 2012-01-07
  • 2013-09-14
  • 2012-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-16
相关资源
最近更新 更多