JavaFX Stage.close() funktioniert nicht im jar-File

Raikbit

Mitglied
Hi,

wie der Titel schon sagt funktioniert die Stage.close() Methode in meiner runnable-Jar nicht.

Also kurz zum Scenario:

Ich habe ein Programm welches eine Stage öffnet. Wenn ich in dieser Stage eine bestimmte Aktion ausführe öffnet sich eine zweite Stage welche Buttons enthält ... auf ButtonAction soll eine Aktion ausgeführt werden und dann die Stage per Stagename.close() geschlossen werden.

Im Eclipse funktioniert das alles ohne Probleme. In meiner Jar führt er zwar die Aktion des Buttons aus, aber schließt die Stage nicht.

Hier ein auszug vom Code:

Code:
private void openColorRequest() throws MalformedURLException {
final Stage colorRequest = new Stage(StageStyle.UTILITY);
Button black = new Button("Schwarz");

//onAction
black.setOnAction(new EventHandler<ActionEvent>() {
			@Override
			public void handle(ActionEvent e) {
				ColorTmp = 1;
// hier kommen noch paar andere sachen die er ausführt
                                colorRequest.close();
                        }
colorRequest.initModality(Modality.APPLICATION_MODAL);
		colorRequest.setScene(new Scene(VBoxBuilder
				.create()
				.children(new Text("Wähle Farbe"), black, ...,.... usw.).alignment(Pos.CENTER).padding(new Insets(15)).build()));
		colorRequest.show();
	}

Hat irgendjemand ne Idee warums im Jar-File nicht geht?
 
Ich hab mir Dialoge mal mit einer Hilfsklasse erledigt (vielleicht hatte ich sogar dein Problem, aber das weiß ich nicht mehr), die von Stage erbt. In dem Fall hat mein #close() immer funktoniert!

Hier der Code - keine Hexerei... Aber für deine Sache vielleicht Overkill.

Java:
package de.dzim.jfx.ui.dialog;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import de.dzim.jfx.ui.resource.ImageResource;
import de.dzim.jfx.ui.resource.ImageResource.ImageResourceType;
import de.dzim.jfx.util.InternalAdapter;

public abstract class Dialog<T> extends Stage implements InternalAdapter {

	/**
	 * Default constructor.
	 * 
	 * @param owner
	 * @param modality
	 * @param style
	 * @param title
	 */
	public Dialog(Window owner, Modality modality, StageStyle style,
			String title) {

		super(style);

		initOwner(owner);
		initModality(modality);
		setOpacity(1);
		if (title != null && !title.isEmpty())
			setTitle(title);
	}

	/**
	 * Default constructor without a text for the dialogs title.
	 * 
	 * @param owner
	 * @param modality
	 * @param style
	 */
	public Dialog(Window owner, Modality modality, StageStyle style) {
		this(owner, modality, style, null);
	}

	/**
	 * Default constructor without a specific StageStyle.
	 * 
	 * @param owner
	 * @param modality
	 * @param title
	 */
	public Dialog(Window owner, Modality modality, String title) {
		this(owner, modality, null, title);
	}

	/**
	 * This constructor is for convenience, be sure to set the owner, the
	 * modality the title and so on, before you show it!
	 */
	public Dialog() {
	}

	private Double contentHeight;
	private Double contentWidth;

	/**
	 * set the min size of this dialog, must be set before the methods
	 * {@link #show()}, {@link #showAndWait()} or {@link #showDialog()} are
	 * used.
	 * 
	 * @param height
	 * @param width
	 */
	public void setMinSize(double height, double width) {
		contentHeight = height;
		contentWidth = width;
	}

	/**
	 * See the parent {@link #showAndWait()} method. Simply returns a custom
	 * value, if the implementation make use of it, otherwise the value might be
	 * <code>null</code>.
	 * 
	 * @return a result, might be <code>null</code>
	 */
	public T showDialog(boolean wait) {

		createDialogContent();

		if (wait)
			super.showAndWait();
		else
			super.show();

		return result;
	}

	public T showDialog() {
		return showDialog(true);
	}

	/**
	 * a might-be result
	 */
	protected T result;

	/**
	 * The result on "ok"/"cancel" (if not overrided).
	 */
	protected Boolean closeAs;

	/**
	 * The dialog content.
	 */
	protected BorderPane borderPane;

	/**
	 * The dialogs main content.
	 */
	private Node centerNode;

	/**
	 * A horizontal box of buttons.
	 */
	private HBox buttonHBox;

	private Map<ButtonID, Button> buttons = new HashMap<ButtonID, Button>();
	private Map<String, Button> customButtons = new HashMap<String, Button>();
	private List<Button> orderedButtonList = new ArrayList<Button>();

	/**
	 * A custom CSS stylesheet.
	 */
	private String stylesheetLocation = null;

	/**
	 * create the container and load the stuff for the center of the BorderPane
	 * by calling the abstract method {@link #createCenterContent()}
	 */
	private void createDialogContent() {

		borderPane = new BorderPane();
		borderPane.setUserData(this);
		borderPane.setId("dialog");

		borderPane.setPadding(new Insets(10, 10, 10, 10));

		buttonHBox = new HBox(5);
		buttonHBox.setUserData(this);
		buttonHBox.setAlignment(Pos.BOTTOM_RIGHT);
		buttonHBox.setPadding(new Insets(10, 5, 0, 5));

		for (Button b : orderedButtonList)
			buttonHBox.getChildren().add(b);

		centerNode = createCenterContent();
		centerNode.setUserData(this);
		if (centerNode != null)
			borderPane.setCenter(centerNode);

		borderPane.setBottom(buttonHBox);

		if (contentHeight != null)
			borderPane.setMinHeight(contentHeight);
		if (contentWidth != null)
			borderPane.setMinWidth(contentWidth);

		BorderPane.setAlignment(centerNode, Pos.CENTER);
		BorderPane.setAlignment(buttonHBox, Pos.BOTTOM_RIGHT);

		this.showingProperty().addListener(new ChangeListener<Boolean>() {

			@Override
			public void changed(ObservableValue<? extends Boolean> observable,
					Boolean oldValue, Boolean newValue) {
				if (newValue) {
					Dialog.this.layout();
				}
			}
		});
		Scene scene = new Scene(borderPane);
		if (stylesheetLocation != null)
			scene.getStylesheets().add(stylesheetLocation);
		this.setScene(scene);
	}

	void layout() {

		double maxWidth = 0;
		for (Button b : orderedButtonList) {
			maxWidth = Math.max(maxWidth, b.prefWidth(-1));
		}

		for (Button b : orderedButtonList) {
			b.setPrefWidth(maxWidth);
		}

		// Point2D size = getInitialSize();
		// stage.setWidth(size.getX());
		// stage.setHeight(size.getY());
		// stage.sizeToScene();
	}

	/**
	 * Set a custom stylsheet to be used by the dialog.
	 * 
	 * @param stylesheetLocation
	 *            Since a relative path would mean relative to the Dialog class,
	 *            you might need to specify a full path.
	 * @see {@link #stylesheetLocation}
	 */
	public void setStylesheetLocation(String stylesheetLocation) {
		this.stylesheetLocation = stylesheetLocation;
	}

	/**
	 * The parent for the content is a BorderPane and will per default be added
	 * to center. The bottom of the pane is used for the buttons - an HBox.
	 * </br>Keep that in mind when trying to add other elements to this
	 * BorderPane: You "only" have the top, left and right positions left for
	 * other content.
	 * 
	 * @return
	 */
	protected abstract Node createCenterContent();

	/**
	 * see {@link #showDialog()}
	 * 
	 * @return a result, might be <code>null</code>
	 */
	public T getResult() {
		return result;
	}

	/**
	 * for any unmodified OK/CANCEL result.
	 * 
	 * @return
	 */
	public Boolean getCloseAs() {
		return closeAs;
	}

	/**
	 * Add a default button (text &amp; icon).
	 * 
	 * @param buttonId
	 * @return
	 */
	public Button addButton(ButtonID buttonId) {
		final Button b = new Button();
		b.setText(buttonId.title);
		if (buttonId.icon != null)
			b.setGraphic(new ImageView(buttonId.icon));
		switch (buttonId) {
		case OK:
			b.setOnAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(final ActionEvent event) {
					handleOk(event);
				}
			});
			// TODO fix problematic NPE on VK_ENTER press
			// b.setDefaultButton(true);
			break;
		case CANCEL:
			b.setOnAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(final ActionEvent event) {
					handleCancel(event);
				}
			});
			break;
		case NEXT:
			b.setOnAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(final ActionEvent event) {
					handleNext(event);
				}
			});
			break;
		case BACK:
			b.setOnAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(final ActionEvent event) {
					handleBack(event);
				}
			});
			break;
		case HELP:
			b.setOnAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(final ActionEvent event) {
					handleHelp(event);
				}
			});
			break;
		}
		orderedButtonList.add(b);
		buttons.put(buttonId, b);
		return b;
	}

	/**
	 * add a custom button with a custom id for it.
	 * 
	 * @param id
	 * @param button
	 * @return
	 */
	public Button addCustomButton(String id, Button button) {
		orderedButtonList.add(button);
		customButtons.put(id, button);
		return button;
	}

	/**
	 * The default implementation for the "ok" button sets the {@link #closeAs}
	 * value to <code>true</code> and closes the window.
	 * 
	 * @param event
	 */
	protected void handleOk(ActionEvent event) {
		Dialog.this.closeAs = Boolean.TRUE;
		Dialog.this.close();
	}

	/**
	 * The default implementation for the "ok" button sets the {@link #closeAs}
	 * value to <code>false</code> and closes the window.
	 * 
	 * @param event
	 */
	protected void handleCancel(ActionEvent event) {
		Dialog.this.closeAs = Boolean.FALSE;
		Dialog.this.close();
	}

	/**
	 * The default implementation for the "next" button does nothing.
	 * 
	 * @param event
	 */
	protected void handleNext(ActionEvent event) {
	}

	/**
	 * The default implementation for the "back" button does nothing.
	 * 
	 * @param event
	 */
	protected void handleBack(ActionEvent event) {
	}

	/**
	 * The default implementation for the "help" button does nothing.
	 * 
	 * @param event
	 */
	protected void handleHelp(ActionEvent event) {
	}

	/**
	 * get a default button
	 * 
	 * @param buttonID
	 * @return
	 */
	public Button getButton(ButtonID buttonID) {
		return buttons.get(buttonID);
	}

	/**
	 * get a custom button via it's id
	 * 
	 * @param id
	 * @return
	 */
	public Button getCustomButton(String id) {
		return customButtons.get(id);
	}

	@Override
	public Object getInternalAdapter(Class<?> adapter) {
		if (List.class.isAssignableFrom(adapter))
			return orderedButtonList;
		return null;
	}

	/**
	 * Default IDs for the buttons at the bottom HBox of the parent BorderPane.
	 * 
	 * @author dzimmermann
	 * 
	 */
	public enum ButtonID {
		// ok
		OK("_OK", ImageResource.getImage(ImageResourceType.OK_16)),
		// cancel
		CANCEL("_Cancel", ImageResource.getImage(ImageResourceType.CANCEL_16)),
		// next
		NEXT("_Next", ImageResource.getImage(ImageResourceType.NEXT_16)),
		// back
		BACK("_Back", ImageResource.getImage(ImageResourceType.BACK_16)),
		// help
		HELP("_Help", ImageResource.getImage(ImageResourceType.QUESTION_16));

		private final String title;
		private final Image icon;

		private ButtonID(String title, Image icon) {
			this.title = title;
			this.icon = icon;
		}

		public String getTitle() {
			return title;
		}

		public Image getIcon() {
			return icon;
		}
	}
}

Wenn du das nutzen willst, kannst du ja den InternalAdapter kicken, ansonsten ist er das hier:

Java:
package de.dzim.jfx.util;

public interface InternalAdapter {

	/**
	 * Returns an object which is an instance of the given class associated with
	 * this object. Returns null if no such object can be found.
	 * 
	 * @param adapter
	 *            the adapter class to look up
	 * @return a object castable to the given class, or null if this object does
	 *         not have an adapter for the given class
	 */
	public Object getInternalAdapter(Class<?> adapter);
}

ImageResource wäre so etwas hier:

Java:
package de.dzim.jfx.ui.resource;

import java.util.HashMap;
import java.util.Map;

import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

public class ImageResource {

	public enum ImageResourceType {

		// exit icon
		EXIT_16("fugue_door-open-in.png"),
		// new icon
		NEW_16("new.gif"),
		// open icon
		OPEN_16("fldr_obj.gif"),
		// save icon
		SAVE_16("save_edit.gif"),
		// save as icon
		SAVE_AS_16("saveas_edit.gif"),
		// error icon (with circle around it)
		ERROR_16("fugue_cross-circle.png"),
		// error icon (simple cross)
		ERROR_SIMPLE_16("fugue_cross.png"),
		// info icon
		INFORMATION_16("fugue_information.png"),
		// warning icon
		WARNING_16("fugue_exclamation.png"),
		// question icon
		QUESTION_16("fugue_question.png"),
		// ok icon
		OK_16("fugue_tick.png"),
		// cancel icon
		CANCEL_16("fugue_minus-circle.png"),
		// add icon (simple plus)
		ADD_16("fugue_plus.png"),
		// edit icon (a pencil)
		EDIT_16("fugue_pencil.png"),
		// remove icon (simple minus)
		REMOVE_16("fugue_minus.png"),
		// error icon (with circle around it) - small
		ERROR_SMALL_16("fugue_cross-small-circle.png"),
		// add icon (simple plus) - small
		ADD_SMALL_16("fugue_plus-small.png"),
		// edit icon (a pencil) - small
		EDIT_SMALL_16("fugue_pencil-small.png"),
		// remove icon (simple minus) - small
		REMOVE_SMALL_16("fugue_minus-small.png"),
		// error icon (with circle around it)
		ERROR_32("fugue_32_cross-circle.png"),
		// error icon (simple cross)
		ERROR_SIMPLE_32("fugue_32_cross.png"),
		// info icon
		INFORMATION_32("fugue_32_information.png"),
		// warning icon
		WARNING_32("fugue_32_exclamation.png"),
		// question icon
		QUESTION_32("fugue_32_question.png"),
		// ok icon
		OK_32("fugue_32_tick.png"),
		// cancel icon
		CANCEL_32("fugue_32_minus-circle.png"),
		// add icon (simple plus)
		ADD_32("fugue_32_plus.png"),
		// remove icon (simple minus)
		REMOVE_32("fugue_32_minus.png"),
		// next / forward
		NEXT_16("fugue_arrow.png"),
		// back
		BACK_16("fugue_arrow-180.png"),
		// invoice
		CLIPBOARD_INVOICE_16("fugue_clipboard-invoice.png"),
		// lock
		LOCK_16("fugue_lock.png"),
		// lock 32x32
		LOCK_32("fugue_32_lock.png"),
		// calendar
		CALENDAR_16("fugue_calendar.png"),
		// calendar 32x32
		CALENDAR_32("fugue_32_calendar.png"),
		// calendar
		CALENDAR_BLUE_16("fugue_calendar-blue.png"),
		// calendar 32x32
		CALENDAR_BLUE_32("fugue_32_calendar-blue.png"),
		// color base
		COLOR_16("fugue_color.png"),
		// color base 32x32
		COLOR_32("fugue_32_color.png"),
		// counter
		COUNTER_16("fugue_counter.png"),
		// counter 32x32
		COUNTER_32("fugue_32_counter.png"),
		// external browser
		EXTERNAL_BROWSER_16("external_browser.gif"),
		// new db icon
		NEW_DATABASE_16("fugue_database--plus.png"),
		// open db icon
		OPEN_DATABASE_16("fugue_database.png"),
		// add group icon
		ADD_FOLDER_16("fugue_folder--plus.png"),
		// add sub group icon
		ADD_FOLDER_BOOKMARK_16("fugue_folder-bookmark.png"),
		// edit group icon
		EDIT_FOLDER_16("fugue_folder--pencil.png"),
		// remove group icon
		REMOVE_FOLDER_16("fugue_folder--minus.png"),
		// add entry icon
		ADD_KEY_16("fugue_key--plus.png"),
		// edit entry icon
		EDIT_KEY_16("fugue_key--pencil.png"),
		// remove entry icon
		REMOVE_KEY_16("fugue_key--minus.png");

		private final String name;

		private ImageResourceType(String name) {
			this.name = name;
		}
	}

	private static Map<ImageResourceType, Image> images = new HashMap<ImageResourceType, Image>();

	public static Image getImage(ImageResourceType type) {
		if (images.get(type) == null)
			images.put(
					type,
					new Image(ImageResource.class
							.getResourceAsStream(type.name)));
		return images.get(type);
	}

	public static ImageView getImageView(ImageResourceType type) {
		Image img = getImage(type);
		if (img != null)
			return new ImageView(img);
		return null;
	}
}

Ich hab es meist mit ShowAndWait aufgerufen, dann hatte ich ein "Ergebnis" des Dialogs (wenn er geschlossen wurde).
Du musst nur in einer Implementierung die [c]#createCenterContent()[/c]-Methode überschreiben (ich z.B. habe hier manchmal ein FXML-File geladen und zurück gegeben - als Controller kannst du ja dann die Dialog-Klasse nutzen, und sie dem FXMLLoader geben).

Vielleicht helfen dir die Ansätze ja...

Grüsse

PS: Ein Message-Dialog kann damit etwa wie folgt erstellt werden:
Java:
package de.dzim.jfx.ui.dialog;

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.HBoxBuilder;
import javafx.stage.Modality;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import de.dzim.jfx.ui.dialog.Dialog.ButtonID;
import de.dzim.jfx.ui.resource.ImageResource;
import de.dzim.jfx.ui.resource.ImageResource.ImageResourceType;

public class MessageDialog {

	/**
	 * if set, the generic {@link Dialog}s
	 * {@link Dialog#setStylesheetLocation(String)} is used to trigger a look
	 * and feel you desire.
	 */
	public static String CSS_PATH = null;

	public static void showError(final Window owner, final String title,
			final String message) {
		final Dialog<Boolean> dialog = createDialog(ImageResourceType.ERROR_32,
				owner, title, message);
		dialog.showDialog();
	}

	public static void showWarning(final Window owner, final String title,
			final String message) {
		final Dialog<Boolean> dialog = createDialog(
				ImageResourceType.WARNING_32, owner, title, message);
		dialog.showDialog();
	}

	public static void showInformation(final Window owner, final String title,
			final String message) {
		final Dialog<Boolean> dialog = createDialog(
				ImageResourceType.INFORMATION_32, owner, title, message);
		dialog.showDialog();
	}

	public static boolean showQuestion(final Window owner, final String title,
			final String message) {
		final Dialog<Boolean> dialog = createDialog(
				ImageResourceType.QUESTION_32, owner, title, message);
		dialog.addButton(ButtonID.CANCEL);
		dialog.showDialog();
		return Boolean.TRUE == dialog.closeAs;
	}

	private static Dialog<Boolean> createDialog(final ImageResourceType type,
			final Window owner, final String title, final String message) {
		final Dialog<Boolean> dialog = new Dialog<Boolean>(owner,
				Modality.APPLICATION_MODAL, StageStyle.DECORATED, title) {
			@Override
			protected Node createCenterContent() {
				ImageView icon = ImageResource.getImageView(type);
				HBox hbox = HBoxBuilder.create()
						.padding(new Insets(0, 5, 5, 5))
						.alignment(Pos.TOP_LEFT)
						.children(icon, new Label(message)).build();
				return hbox;
			}
		};
		if (CSS_PATH != null)
			dialog.setStylesheetLocation(CSS_PATH);
		dialog.setMinSize(100, 400);
		dialog.addButton(ButtonID.OK);
		return dialog;
	}
}
 
Das Problem lag woanders ... hatte die jar dann über die Console gestartet und siehe da ihm fehlte eine Datei ... was die mit meiner Stage zu tun hat kann ich nicht sagen, da die Stage nur eine Farbauswahl geöffnet hat, während in der Datei Strings für meinen Reader waren, aber das Problem war dann behoben.

Trotzdem danke
 

Zurück
Oben