java osx swing jarbundler

java - JAR Bundler utilizando OSXAdapter causando la demora o la finalización de la aplicación



swing jarbundler (3)

Creé una aplicación Java simple que cada segundo durante 10 segundos consecutivos agrega una nueva fila a JTable . Consiste en tres clases.

La clase principal a la que se llama una vez que se inicia el programa

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

Un controlador que crea la GUI y la altera a través de 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(); } } } }

Y finalmente, la 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}); } }

Como estoy desarrollando para OS X, y necesito poder asociar mi aplicación a cierto tipo de archivo (digamos .jarbundlerproblem ), tengo que agrupar mi archivo JAR en una APP utilizando Apple Jar Bundler . Lo he hecho con éxito, mi aplicación se abre, cuenta hasta diez y escribe cada segundo.

Ahora, por el problema

De forma predeterminada, al hacer doble clic en un problema de .jarbundlerproblem y al asociar el archivo con mi aplicación, no .jarbundlerproblem el archivo en el que hice doble clic como argumento para la aplicación. Aparentemente, esto es solo que Java en OS X funciona.

Como necesito poder ver qué archivo se hizo doble clic, estoy usando OSXAdapter, que es una biblioteca Java creada por Apple para este propósito. Esto, lo he implementado alterando el constructor de mi clase Controller y agregué otro método 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(); } }

Pero después de esta modificación (menor), mi aplicación comienza a actuar mal. A veces, no se abre, aunque puedo ver en la consola que acaba de comenzar (se escribe el Initializing controller ), pero después de algunos intentos, finalmente se iniciará, pero las ventanas estarán completamente en blanco durante los primeros 10 segundos , y después de eso, se agregarán las 10 filas.

Ayuda

Ahora, he luchado bastante con esto, y parece que no hay mucha documentación sobre OSXAdapter ni Jar Bundler. ¿Qué estoy haciendo mal? ¿O no debería estar usando OSXAdapter o Jar Bundler en primer lugar?


Después de hacerlo, no estoy del todo convencido de que SwingWorker sea una solución más simple (mejor aún), aún requiere sincronización de subprocesos adicionales (entre el subproceso de trabajo y el subproceso "externo" que pasa en los archivos / nombres). De todos modos (aprovechando la oportunidad de aprender, y ya sea por errores :), a continuación se muestra un crudo ejemplo de prueba de concepto para la idea básica:

  • implementar el controlador como SwingWorker, que canaliza la entrada desde el hilo externo al EDT
  • hacer que acepte entrada (desde el adaptador, fi) a través de un método doWork (..) que pone en cola la entrada para publicación
  • implementar doInBackground para publicar de manera exitosa la entrada

problemas abiertos

  • sincronizar el acceso a la lista local (no un experto en concurrencia, pero bastante seguro de que debe hacerse)
  • detección confiable del final de la cadena externa (aquí simplemente se detiene cuando la cola de entrada está vacía)

Comentarios bienvenidos :-)

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


Aquí hay una variación del ejemplo de @ kleopatra en el que un Controller ejecuta continuamente acepta nuevas entradas en doWork() , mientras que un SwingWorker procesa las entradas pending forma asincrónica en su SwingWorker fondo. ArrayBlockingQueue maneja la sincronización.

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


Parece que estás bloqueando el hilo de envío del evento (EDT). SwingWorker sería una mejor opción, pero este ejemplo implementa Runnable .

Adición: Puede ver este proyecto para ver un ejemplo de la arquitectura MVC . También muestra cómo construir un paquete de aplicación Mac OS sin utilizar JAR Bundler. Puede encontrar más sobre MVC aquí .

Como un lado, este ejemplo muestra un enfoque para el desplazamiento automático de una JTable . Haga clic en el pulgar para suspender el desplazamiento; liberar para reanudar.

Adición: su aplicación se retrasa durante 10 segundos al inicio. Como este es el momento exacto en que el Controller duerme, seguramente está durmiendo en el EDT. Una sscce sería dispositiva. En su lugar, haga el trabajo en otro hilo y actualice el modelo en el EDT. SwingWorker tiene un método de process() que lo hace automáticamente, o puede usar invokeLater() como se muestra a continuación. Hasta que su aplicación esté sincronizada correctamente, hay pocas esperanzas de que los eventos de Apple funcionen.

Adición: puede invocar isDispatchThread() en el Controller para verificar. El proyecto citado incluye una .dmg con una aplicación Mac y un archivo ant que construye el paquete in situ a través del destino dist2 .

Adición: Vea también los enfoques alternativos que se muestran aquí .

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