4

我创建了一个简单的 Java 应用程序,该应用程序每秒连续 10 秒添加一个新行到JTable. 它由三个类组成。

程序启动后调用的主类

public class JarBundlerProblem {
    public static void main(String[] args)
    {
        System.err.println("Initializing controller");
        new Controller();
    }
}

一个创建 GUI 并通过它改变它的控制器doWork()

public class Controller {
    public Controller()
    {
        doWork(null);
    }
    public static void doWork(String s)
    {
        GUI gui = new GUI();
        
        for (int i=0; i<10; i++)
        {
            gui.addRow("Line "+(i+1));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

最后,GUI

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class GUI {
    private JFrame frame = new JFrame();
    private DefaultTableModel model = new DefaultTableModel();
    private JTable table = new JTable(model);
    private JScrollPane pane = new JScrollPane(table);
    
    public GUI()
    {
        model.addColumn("Name");
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(pane);
        frame.pack();
        frame.setVisible(true);
    }
    public void addRow(String name)
    {
        model.addRow(new Object[]{name});
    }
}

由于我正在为 OS X 开发,并且我需要能够将我的应用程序与某种文件类型(比如说)相关联,我必须将我的文件.jarbundlerproblem捆绑到使用Apple Jar Bundler中。我已经成功地做到了,我的应用程序打开,数到十,每秒写出。JARAPP

现在,对于问题

默认情况下,双击 a.jarbundlerproblem并将文件与我的应用程序相关联,不会将我双击的文件作为参数传递给应用程序。显然,这只是 OS X 上的 Java 作品。

由于我需要能够查看双击了哪个文件,因此我使用的是OSXAdapter,它是 Apple 为此目的制作的 Java 库。这一点,我通过改变我的Controller类的构造函数并添加了另一个方法来实现registerForMacOSXEvents()

public Controller()
{
    registerForMacOSXEvents();
    //doWork(null);
}
public void registerForMacOSXEvents() {
    try {
        OSXAdapter.setFileHandler(this, getClass().getDeclaredMethod("doWork", new Class[] { String.class }));
    } catch (Exception e) {
        System.err.println("Error while loading the OSXAdapter:");
        e.printStackTrace();
    }
}

但是在这个(微小的)修改之后,我的应用程序开始起作用。有时,它不会打开,即使我可以在控制台中看到它刚刚启动(Initializing controller已写入),但经过几次尝试后,它最终会启动,但前 10 秒窗口将完全空白,并且之后,将添加 10 行。

帮助

现在,我一直在为此苦苦挣扎,而且似乎没有很多关于 OSXAdapter 和 Jar Bundler 的文档。我究竟做错了什么?还是我不应该首先使用 OSXAdapter 或 Jar Bundler?

4

3 回答 3

6

看起来您正在阻止事件调度线程(EDT)。SwingWorker将是一个更好的选择,但这个例子实现了Runnable.

附录:您可能会在这个项目中查看MVC架构的示例。它还展示了如何在不使用 JAR Bundler 的情况下构建 Mac OS 应用程序包。更多关于 MVC 的信息可以在这里找到。

顺便说一句,这个例子展示了一种自动滚动JTable. 点击拇指暂停滚动;释放以恢复。

附录:您的应用程序在启动时滞后 10 秒。由于这是Controller睡眠的确切时间,它肯定是在 EDT 上睡觉。sscce将是决定性的。相反,在另一个线程上进行工作并在 EDT 上更新模型。SwingWorker有一个process()自动执行此操作的方法,或者您可以使用invokeLater()如下所示的方法。在您的应用程序正确同步之前,让 Apple 事件正常工作的希望很小。

附录:您可以isDispatchThread()Controller检查中调用。引用的项目包括一个.dmg带有 Mac 应用程序和一个通过 target就地ant构建包的文件。dist2

附录:另请参阅此处显示的替代方法。

在此处输入图像描述

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

/** @seehttps://stackoverflow.com/questions/7519244 */
public class TableAddTest extends JPanel implements Runnable {

    private static final int N_ROWS = 8;
    private static String[] header = {"ID", "String", "Number", "Boolean"};
    private DefaultTableModel dtm = new DefaultTableModel(null, header) {

        @Override
        public Class<?> getColumnClass(int col) {
            return getValueAt(0, col).getClass();
        }
    };
    private JTable table = new JTable(dtm);
    private JScrollPane scrollPane = new JScrollPane(table);
    private JScrollBar vScroll = scrollPane.getVerticalScrollBar();
    private JProgressBar jpb = new JProgressBar();
    private int row;
    private boolean isAutoScroll;

    public TableAddTest() {
        this.setLayout(new BorderLayout());
        jpb.setIndeterminate(true);
        this.add(jpb, BorderLayout.NORTH);
        Dimension d = new Dimension(320, N_ROWS * table.getRowHeight());
        table.setPreferredScrollableViewportSize(d);
        for (int i = 0; i < N_ROWS; i++) {
            addRow();
        }
        scrollPane.setVerticalScrollBarPolicy(
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        vScroll.addAdjustmentListener(new AdjustmentListener() {

            @Override
            public void adjustmentValueChanged(AdjustmentEvent e) {
                isAutoScroll = !e.getValueIsAdjusting();
            }
        });
        this.add(scrollPane, BorderLayout.CENTER);
        JPanel panel = new JPanel();
        panel.add(new JButton(new AbstractAction("Add Row") {

            @Override
            public void actionPerformed(ActionEvent e) {
                addRow();
            }
        }));
        this.add(panel, BorderLayout.SOUTH);
    }

    private void addRow() {
        char c = (char) ('A' + row++ % 26);
        dtm.addRow(new Object[]{
                Character.valueOf(c),
                String.valueOf(c) + String.valueOf(row),
                Integer.valueOf(row),
                Boolean.valueOf(row % 2 == 0)
            });
    }

    private void scrollToLast() {
        if (isAutoScroll) {
            int last = table.getModel().getRowCount() - 1;
            Rectangle r = table.getCellRect(last, 0, true);
            table.scrollRectToVisible(r);
        }
    }

    @Override
    public void run() {
        while (true) {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    addRow();
                }
            });
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    scrollToLast();
                }
            });
            try {
                Thread.sleep(1000); // simulate latency
            } catch (InterruptedException ex) {
                System.err.println(ex);
            }
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                TableAddTest nlt = new TableAddTest();
                f.add(nlt);
                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);
                new Thread(nlt).start();
            }
        });
    }
}
于 2011-09-22T18:08:11.730 回答
4

在这样做之后,我并不完全相信 SwingWorker 是一个更简单(又名:更好)的解决方案 - 仍然需要额外的线程同步(在工作线程和传递文件/名称的“外部”线程之间)。无论如何(借此机会学习,并且可能会出错:),下面是基本概念的粗略概念证明示例:

  • 将 Controller 实现为 SwingWorker,它将来自外部线程的输入汇集到 EDT
  • 使其通过方法 doWork(..) 接受输入(来自适配器,fi),该方法将输入排队以进行发布
  • 实现 doInBackground 以连续发布输入

开放式问题

  • 同步对本地列表的访问(不是并发专家,但很确定需要这样做)
  • 可靠地检测到外部线程的结束(这里只是在输入队列为空时停止)

欢迎反馈:-)

public class GUI {
    private JFrame frame = new JFrame();
    private DefaultTableModel model = new DefaultTableModel();
    private JTable table = new JTable(model);
    private JScrollPane pane = new JScrollPane(table);

    public GUI() {
        model.addColumn("Name");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(pane);
        frame.pack();
        frame.setVisible(true);
    }

    public void addRow(String name) {
        model.addRow(new Object[] { name });
    }

    /**
     * Controller is a SwingWorker.
     */
    public static class Controller extends SwingWorker<Void, String> {
        private GUI gui;

        private List<String> pending;

        public Controller() {
            gui = new GUI();
        }

        public void doWork(String newLine) {
            if (pending == null) {
                pending = new ArrayList<String>();
                pending.add(newLine);
                execute();
            } else {
                pending.add(newLine);
            }
        }

        @Override
        protected Void doInBackground() throws Exception {
            while (pending.size() > 0) {
                publish(pending.remove(0));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }

        /**
         * @inherited <p>
         */
        @Override
        protected void process(List<String> chunks) {
            for (String object : chunks) {
                gui.addRow(object);
            }
        }

    }

    /** 
     * Simulating the adapter.
     * 
     *  Obviously, the real-thingy wouldn't have a reference 
     *  to the controller, but message the doWork refectively 
     */
    public static class Adapter implements Runnable {

        Controller controller;

        public Adapter(Controller controller) {
            this.controller = controller;
        }

        @Override
        public void run() {
            for (int i=0; i<10; i++)
            {
                controller.doWork("Line "+(i+1));
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    public static void main(String[] args)
    {
        System.err.println("Initializing controller");
        new Adapter(new Controller()).run();
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(GUI.class.getName());
}
于 2011-09-27T10:10:33.323 回答
2

这是@kleopatra示例的变体,其中持续运行的Controller接受新条目doWork(),而 a在其后台线程中异步SwingWorker处理条目。处理同步。pendingArrayBlockingQueue

import java.awt.EventQueue;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.table.DefaultTableModel;

public class GUI {

    private static final Random rnd = new Random();
    private JFrame frame = new JFrame();
    private DefaultTableModel model = new DefaultTableModel();
    private JTable table = new JTable(model);
    private JScrollPane pane = new JScrollPane(table);

    public GUI() {
        model.addColumn("Name");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(pane);
        frame.pack();
        frame.setVisible(true);
    }

    public void addRow(String name) {
        model.addRow(new Object[]{name});
    }

    /**
     * Controller is a SwingWorker.
     */
    private static class Controller extends SwingWorker<Void, String> {

        private static final int MAX = 5;
        private GUI gui;
        private BlockingQueue<String> pending =
            new ArrayBlockingQueue<String>(MAX);

        public Controller() {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    gui = new GUI();
                }
            });
        }

        private void doWork(String newLine) {
            try {
                pending.put(newLine);
            } catch (InterruptedException e) {
                e.printStackTrace(System.err);
            }
        }

        @Override
        protected Void doInBackground() throws Exception {
            while (true) {
                // may block if nothing pending
                publish(pending.take());
                try {
                    Thread.sleep(rnd.nextInt(500)); // simulate latency
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
            }
        }

        @Override
        protected void process(List<String> chunks) {
            for (String object : chunks) {
                gui.addRow(object);
            }
        }
    }

    /** 
     * Exercise the Controller.
     */
    private static class Adapter implements Runnable {

        private Controller controller;

        private Adapter(Controller controller) {
            this.controller = controller;
        }

        @Override
        public void run() {
            controller.execute();
            int i = 0;
            while (true) {
                // may block if Controller busy
                controller.doWork("Line " + (++i));
                try {
                    Thread.sleep(rnd.nextInt(500)); // simulate latency
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("Initializing controller");
        // Could run on inital thread via
        // new Adapter(new Controller()).run();
        // but we'll start a new one
        new Thread(new Adapter(new Controller())).start();
    }
}
于 2011-09-27T12:15:36.770 回答