在MVC模式中处理Textfield SetOnAction的正确方法是什么?



我有一个问题,试图让我的TextField与ActionEvent处理我定义在我的控制器类连接。该错误将显示为java.lang.reflect.InvocationTargetException。我一直在尝试做的是在视图中创建我的控制器类的实例,然后使用lamba方法引用来调用控制器类中的句柄方法。

视图类

package converter;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
public class View extends BorderPane{

private Controller control = new Controller(new Model(), new View());

//TextField
private TextField input = new TextField();
private TextField input2 = new TextField();
//RadioButton
private RadioButton distence = new RadioButton();
private RadioButton tempeture = new RadioButton();
private RadioButton weight = new RadioButton();
//ToggleGroup
private ToggleGroup group = new ToggleGroup();

public String getConversion() 
{
return group.getSelectedToggle().getUserData().toString();
}
public double getInput() 
{
return Double.parseDouble(input.getText());
}
public double getInput2() 
{
return Double.parseDouble(input2.getText());
}
public void setInput(double value) 
{
input.setText(Double.toString(value));
}
public void setInput2(double value) 
{
input2.setText(Double.toString(value));
}


public View() 
{

System.out.println(control);
//SetID
//input.setPromptText("Input");
//output.setPromptText("Output");

input.setId("input");
input2.setId("input2");
//SetUserData
distence.setUserData("dist");
tempeture.setUserData("temp");
weight.setUserData("weight");

//Set Label
distence.setText("Mile and Kilometer");
tempeture.setText("Celsius and Fahrenheit");
weight.setText("Pounds and Kilograms");
//SetGroup
distence.setToggleGroup(group);
tempeture.setToggleGroup(group);
weight.setToggleGroup(group);

//Add TextField ActionEvent
input.setOnAction(control::handle);
input2.setOnAction(control::handle);

//Add Group Listener
group.selectedToggleProperty().addListener((ov, o , n) ->{
//          System.out.println(n.getUserData().toString());
String tog = n.getUserData().toString();
if (tog.equals("dist")) {

input.setPromptText("Mile");
input2.setPromptText("Kilometer");

}else if(tog.equals("temp")) {
input.setPromptText("Fahrenheit");
input2.setPromptText("Celsius");
}else if(tog.equals("weight")) {
input.setPromptText("Pound");
input2.setPromptText("Kilogram");
}
});

StackPane left = new StackPane();
StackPane right = new StackPane();
VBox leftbox = new VBox(3);
VBox rightbox = new VBox(2);
leftbox.setSpacing(5);
rightbox.setSpacing(10);
leftbox.getChildren().addAll(distence, tempeture, weight);
left.getChildren().add(leftbox);
rightbox.getChildren().addAll(input, input2);
right.getChildren().add(rightbox);
this.setLeft(left);
this.setRight(right);


}
}
控制器类

public class Controller implements EventHandler<ActionEvent>{
private Model model;
private View view;
public Controller(Model model, View view) 
{
this.model = model;
this.view = view;
}

public Controller() 
{
initalize();
}

public Controller initalize() 
{
this.model = new Model();
this.view = new View();

return this;
}

@Override
public void handle(ActionEvent event) 
{
String id = ((javafx.scene.Node)event.getSource()).getId();
String conversion = view.getConversion();
switch (id) {
case "input":
if(conversion.equals("dist")) {
view.setInput2(model.kilometer(view.getInput()));
break;
}else if(conversion.equals("temp")) {
view.setInput2(model.cToF(view.getInput()));
break;
}else if(conversion.equals("weight")) {
view.setInput2(model.kilogram(view.getInput()));
break;
}
case "input2":
if(conversion.equals("dist")) {
view.setInput(model.mile(view.getInput2()));
break;
}else if(conversion.equals("temp")) {
view.setInput(model.fToC(view.getInput2()));
break;
}else if(conversion.equals("weight")) {
view.setInput(model.pound(view.getInput2()));
break;
}
default:
break;
}
}
}

主类

package converter;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
private View view;

@Override
public void init() 
{
Model model = new Model();
view = new View();
new Controller(model, view);
}
@Override
public void start(Stage primaryStage) {
try {
primaryStage.setMinWidth(350);
primaryStage.setMinHeight(150);

primaryStage.setScene(new Scene(view));
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}

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

堆栈跟踪
Exception in Application init method
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:465)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:364)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1071)
Caused by: java.lang.RuntimeException: Exception in Application init method
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:896)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: java.lang.StackOverflowError
at javafx.graphics/javafx.scene.Node.getScene(Node.java:1148)
at javafx.graphics/javafx.scene.Node.updateCanReceiveFocus(Node.java:8502)
at javafx.graphics/javafx.scene.Node.setTreeVisible(Node.java:8420)
at javafx.graphics/javafx.scene.Node.updateTreeVisible(Node.java:8411)
at javafx.graphics/javafx.scene.Node.<init>(Node.java:2596)
at javafx.graphics/javafx.scene.Parent.<init>(Parent.java:1418)
at javafx.graphics/javafx.scene.layout.Region.<init>(Region.java:627)
at javafx.graphics/javafx.scene.layout.Pane.<init>(Pane.java:136)
at javafx.graphics/javafx.scene.layout.BorderPane.<init>(BorderPane.java:219)
at converter.View.<init>(View.java:52)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
at converter.View.<init>(View.java:12)
Exception running application converter.Main

您的异常发生是因为您在构建View实例时有无限递归:

public class View {
// ...
private Controller control = new Controller(new Model(), new View());
// ...
}

当你创建一个View实例,你尝试创建一个新的View实例(传递给Controller构造函数),这反过来将创建一个新的View实例传递给Controller构造函数,等等。即使你在这里消除了依赖关系,你也希望这些对象对同一个实例有引用;您不希望到处创建新实例。

MVC有几种不同的变体。看起来您正在尝试实现一个"传统的">

  • View观察模型,并在模型发生变化时更新
  • 视图将它封装的组件(例如文本字段)上的用户操作委托给控制器
  • 控制器更新模型

:

  • 视图应该同时引用控制器和模型
  • 控制器应该有对模型的引用
  • 模型不应该知道视图或控制器的任何信息

我还建议不要使控制器实现任何EventHandler接口;只需定义处理用户输入所需的方法。为每个用户动作定义单独的方法,而不是用一个单一的handle()方法和无数的switchif-else语句。

比如:

public class View { 
private final Controller controller ;
private final Model model ;
private final TextField input ;
private final TextField input2 ;
// ...
public View(Model model, Controller controller) {
this.model = model ;
this.controller = controller ;
input = new TextField();
input2 = new TextField();
model.someProperty().addListener((obs, oldValue, newValue) -> {
/* update controls */
});
model.someOtherProperty().addListener((obs, oldValue, newValue) -> {
/* update controls */
});
input.setOnAction(event -> controller.handleInput(input.getText()));
input2.setOnAction(event -> controller.handleInput2(input2.getText()));
// layout etc
}
}
public class Controller {
public final Model model ;
public Controller(Model model) {
this.model = model ;
}
public void handleInput(String input) {
model.setSomeValue(input); 
}
public void handleInput2(String input) {
model.setSomeOtherValue(input2);
}
// etc
}

然后将其与

这样的代码组合
Model model = new Model();
Controller controller = new Controller(model);
View view = new View(model, controller);

参见使用不带FXML的JavaFX控制器的完整示例。

您在这里创建了一个循环依赖。控制器依赖于视图依赖于控制器

这是个糟糕的设计。

您应该实现控制反转并依赖于抽象。抽象要么在视图的构造中给出,要么有一个工厂或一些依赖注入框架。另一个选项是让视图发出控制器订阅的特定于视图的事件。有很多方法可以正确地解决这个问题——循环依赖不是其中之一。

抽象应该是尽可能小的完整抽象(接口隔离原则),所以如果你需要一个处理程序,你可能有一个handle(event)函数的接口,而不是更多。

这并不禁止控制器实现handle函数,它可能违反单一职责原则,但这要在控制器的问题和实现中决定。

参见此处的SOLID原则:Wikipedia SOLID

更新:你几乎就在那里,只需添加setEventHandler作为视图属性。

public void setEventHandler(@NotNull EventHandler handler) {
this.handler = handler;
updateHandler();
}
private void updateHandler() {
//Add TextField ActionEvent
input.setOnAction(handler::handle);
input2.setOnAction(handler::handle);
}

并添加到你的Main::init:

Controller controller = new Controller(model , view);
view.setEventHandler(controller);

你还必须修复控制器的默认构造函数。

相关内容

  • 没有找到相关文章

最新更新