Javafx program which can be controlled both by GUI and Command Line?

两盒软妹~` 提交于 2019-12-22 00:25:26

问题


I am working with the Javafx GUI but i also require the same level of functionality from the command line. I am wondering what the best way to make a main class which has functionality for both command line and Javafx at the same time so you can do one thing on the GUI and then do the next thing on command line. Command line would also update the GUI display.


回答1:


(Really, this question is off-topic, as it is too broad. It was interesting enough, though, for me to try a proof of concept of the approach that seemed natural to me, so I answered it anyway.)

You essentially need two things here:

  1. Use an MVC approach, with the model containing the data. You can share the same model instance with both the command line interface and the UI, so both update the same data. The UI, as usual, will observe the model and update if the data change.
  2. Launch the CLI from the JavaFX application's start() method, running it in a background thread so that it doesn't block the UI. You just need to make sure there that the model makes updates on the correct (i.e. the FX Application) thread.

Here's a simple example, which just computes the total of a list of integers. Here's the model, which stores the list and the total. It has methods to add a new value, or clear the list. Note how those methods execute their changes on the UI thread:

import java.util.stream.Collectors;

import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class AddingModel {

    private final ObservableList<Integer> values = FXCollections.observableArrayList();

    private final ReadOnlyIntegerWrapper total = new ReadOnlyIntegerWrapper();

    public AddingModel() {
        total.bind(Bindings.createIntegerBinding(() -> 
            values.stream().collect(Collectors.summingInt(Integer::intValue)), 
            values));
    }

    private void ensureFXThread(Runnable action) {
        if (Platform.isFxApplicationThread()) {
            action.run();
        } else {
            Platform.runLater(action);
        }
    }

    public void clear() {
        ensureFXThread(values::clear);
    }

    public void addValue(int value) {
        ensureFXThread(() -> values.add(value));
    }

    public final ReadOnlyIntegerProperty totalProperty() {
        return this.total.getReadOnlyProperty();
    }


    public final int getTotal() {
        return this.totalProperty().get();
    }


    public ObservableList<Integer> getValues() {
        return values ;
    }
}

Here's the UI code. First a view, in FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>

<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="AddingController">
    <top>
       <HBox spacing="5">
           <TextField fx:id="valueField" onAction="#addValue"/>
           <Button text="Clear" onAction="#clearValues"/>
       </HBox>
    </top>
    <center>
       <ListView fx:id="values"/>
    </center>
    <bottom>
       <Label fx:id="sum"/>
    </bottom>
</BorderPane>

and a controller, which observes and updates the model:

import java.util.function.UnaryOperator;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;

public class AddingController {

    private final AddingModel model ;

    @FXML
    private TextField valueField ;

    @FXML
    private ListView<Integer> values ;

    @FXML
    private Label sum ;

    public AddingController(AddingModel model) {
        this.model = model ;
    }

    @FXML
    private void initialize() {
        values.setItems(model.getValues());
        sum.textProperty().bind(model.totalProperty().asString("Total = %,d"));

        // Allow only integer values in the text field:
        UnaryOperator<TextFormatter.Change> filter = c -> {
            if (c.getControlNewText().matches("-?[0-9]*")) {
                return c;
            } else {
                return null ;
            }
        };
        valueField.setTextFormatter(new TextFormatter<>(filter));
    }

    @FXML
    private void addValue() {
        String text = valueField.getText();
        if (! text.isEmpty()) {
            int value = Integer.parseInt(text);
            model.addValue(value);
            valueField.clear();
        }
    }

    @FXML
    private void clearValues() {
        model.clear();
    }
}

Now a simple command line interpreter, which reads from the command line and references the model. It supports either integer entry (add value to the model), or the commands total, show, or clear:

import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;

public class AddingCLI {

    private final AddingModel model ;

    private final Pattern intPattern = Pattern.compile("-?[0-9]+");

    public AddingCLI(AddingModel model) {
        this.model = model ;
    }

    public void processCommandLine() {
        try (Scanner in = new Scanner(System.in)) {
            while (true) {
                String input = in.next().trim().toLowerCase();
                if (intPattern.matcher(input).matches()) {
                    int value = Integer.parseInt(input);
                    model.addValue(value);
                } else if ("show".equals(input)) {
                    outputValues();
                } else if ("clear".equals(input)) {
                    model.clear();
                    System.out.println("Values cleared");
                } else if ("total".equals(input)) {
                    System.out.println("Total = "+model.getTotal());
                }
            }
        }
    }

    private void outputValues() {
        List<Integer> values = model.getValues();
        if (values.isEmpty()) {
            System.out.println("No values");
        } else {
            values.forEach(System.out::println);
        }
    }
}

Finally, the JavaFX application which assembles all these. Note that the same model instance is passed to both the CLI and the UI controller, so both are updating the same data. You can enter some values in the text field, then type "show" in the command line, and you'll see the values. Type "clear" in the command line, and the values will be removed from the UI, etc.

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class AddingApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        AddingModel model = new AddingModel();
        AddingController controller = new AddingController(model);

        FXMLLoader loader = new FXMLLoader(AddingController.class.getResource("ValueTotaler.fxml"));
        loader.setControllerFactory(type -> {
            if (type == AddingController.class) {
                return controller ;
            } else {
                throw new IllegalArgumentException("Unexpected controller type: "+type);
            }
        });
        Parent root = loader.load();
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();

        AddingCLI cli = new AddingCLI(model);
        Thread cliThread = new Thread(cli::processCommandLine);
        cliThread.setDaemon(true);
        cliThread.start();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

You can, of course, just create the UI without the CLI, or create the CLI without the UI; both are independent of each other (they just both depend on the model).




回答2:


I think this is borderline to be too broad. One part of that is: your requirements are unclear. Do you intend to you use the command line like:

java -jar whatever.jar -command A
java -jar whatever.jar -command B
java -jar whatever.jar -command C

So - you invoke java repeatedly, and whatever.jar basically is a client that takes to some "server" that does the real work, or do you envision

java -jar whatever.jar
> Type your command:
> A
... ran command A
> Type your command:

Obviously, that makes a huge difference here.

But in the end, it also tells us where a solution is: by de-coupling these clients from the actual execution.

Meaning: you should do two things

  • define the functionality aka services that some server has to provide
  • then you can look into ways of creating different clients that make use of these services

Avoid baking all of these different aspects into one single main() method!




回答3:


Everything on the GUI is Event based. This means that methods get called when you press a button or interact with a JavaFX Window in another way like selecting an item on a list.

I suggest keeping your internal logic and GUI logic seperated. When clicking on a button you call a handleButton(ActionEvent actionEvent) method that is linked to the button. This method should call a method in one of your other classes that actually contains the logic.

You can get user input through the command line with a scanner:

public String getUserInput() {
    Scanner scan = new Scanner(System.in);
    String s = scan.next();
    return s
}

You can now check this user input string and connect the corresponding method with a switch(s) statement.

I'm not sure WHEN you want to get this input through the command line, but I suggest adding a developer button in your Stage that calls getUserInput() when pressed.



来源:https://stackoverflow.com/questions/49675539/javafx-program-which-can-be-controlled-both-by-gui-and-command-line

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!