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