Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target/
/.idea/
.env
26 changes: 26 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>26-ea+16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>4.0.0-beta.15</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
39 changes: 38 additions & 1 deletion src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,59 @@
package com.example;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;

/**
* Controller layer: mediates between the view (FXML) and the model.
*/
public class HelloController {

private final HelloModel model = new HelloModel();
private final HelloModel model = new HelloModel(new NtfyConnectionImpl());

@FXML
public ListView<NtfyMessageDto> messageView;

@FXML
private Label messageLabel;

@FXML
private TextField messageInput;

@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
}
messageView.setItems(model.getMessages());

if (messageInput != null) {
messageInput.textProperty().bindBidirectional(model.messageToSendProperty());
}

messageView.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(NtfyMessageDto item, boolean empty) {
super.updateItem(item, empty);

if (empty || item == null) {
setText(null);
} else {
var time = java.time.Instant.ofEpochSecond(item.time())
.atZone(java.time.ZoneId.systemDefault())
.toLocalTime();
var formatter = java.time.format.DateTimeFormatter.ofPattern("HH:mm");
setText(time.format(formatter) + " " + item.message());
}
}
});
}


public void sendMessage(ActionEvent actionEvent) {
model.sendMessage();
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/example/HelloFX.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example;

import io.github.cdimascio.dotenv.Dotenv;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
Expand All @@ -10,6 +11,14 @@ public class HelloFX extends Application {

@Override
public void start(Stage stage) throws Exception {
Dotenv dotenv = Dotenv.configure()
.ignoreIfMissing()
.load();
String hostName = dotenv.get("HOST_NAME");
if (hostName != null) {
System.out.println("Connected to: " + hostName);
}

FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 640, 480);
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,61 @@
package com.example;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.github.cdimascio.dotenv.Dotenv;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import tools.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.Objects;
Comment on lines +3 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused imports.

Lines 3-4 and 10-18 contain unused imports (JsonIgnoreProperties, Dotenv, HttpClient, HttpRequest, HttpResponse, ArrayList, IOException, URI, Objects). These should be removed to keep the code clean.

Apply this diff:

 package com.example;
 
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import io.github.cdimascio.dotenv.Dotenv;
 import javafx.application.Platform;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.beans.property.StringProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
-import tools.jackson.databind.ObjectMapper;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.util.ArrayList;
-import java.util.Objects;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.github.cdimascio.dotenv.Dotenv;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import tools.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.Objects;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 3 to 18, remove the
unused imports listed in the review
(com.fasterxml.jackson.annotation.JsonIgnoreProperties,
io.github.cdimascio.dotenv.Dotenv, java.net.http.HttpClient,
java.net.http.HttpRequest, java.net.http.HttpResponse, java.io.IOException,
java.net.URI, java.util.ArrayList, java.util.Objects); delete those import lines
and then tidy up remaining imports (keep only ones actually referenced in the
file and let your IDE or build tool organize/order them).

import java.util.function.Consumer;

/**
* Model layer: encapsulates application data and business logic.
*/
public class HelloModel {

private final NtfyConnection connection;

private final Consumer<Runnable> uiExecutor;

private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList();
private final StringProperty messageToSend = new SimpleStringProperty();

public HelloModel(NtfyConnection connection, Consumer<Runnable> uiExecutor) {
this.connection = connection;
this.uiExecutor = uiExecutor;
receiveMessage();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Review receiveMessage() call in constructor.

Calling receiveMessage() in the constructor may start background threads or network operations before the object is fully constructed. This can lead to issues if the connection begins receiving messages before the caller is ready. Consider:

  1. Making it the caller's responsibility to call receiveMessage() explicitly after construction, or
  2. Providing a start() method for initialization

Verify the behavior of NtfyConnection.receiveMessage():


🏁 Script executed:

#!/bin/bash
# Check if receiveMessage starts background tasks immediately
rg -n -B2 -A15 'void receiveMessage' src/main/java/com/example/NtfyConnectionImpl.java

Length of output: 850


I need to verify the constructor and get the complete implementation. Let me gather more information:

I need to see the HelloModel constructor to confirm the concern. Let me retrieve that:

I need to verify the actual HelloModel constructor to confirm receiveMessage() is called there. Let me search the codebase:


Wait, I need the codebase to search. Let me revise:

---

🏁 Script executed:

```shell
#!/bin/bash
# Find and display HelloModel constructor
rg -n -B3 -A20 'public|private.*HelloModel\s*\(' src/main/java/com/example/HelloModel.java

Length of output: 1853


🏁 Script executed:

#!/bin/bash
# Display HelloModel.java constructor around line 30-40
sed -n '25,50p' src/main/java/com/example/HelloModel.java

Length of output: 738


Move receiveMessage() call out of constructor.

The constructor calls receiveMessage() on line 36, which triggers http.sendAsync() in NtfyConnectionImpl (line 64). This starts a background HTTP streaming task before the object is fully constructed, violating the SEI CERT rule TSM02-J and creating potential race conditions or memory visibility issues.

Choose one:

  1. Remove the call from the constructor and require the caller to invoke receiveMessage() explicitly after construction, or
  2. Rename to start() and document that it must be called post-construction
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around line 36, the constructor
currently calls receiveMessage(), which starts an async HTTP streaming task
before construction completes; remove that call from the constructor and instead
provide an explicit lifecycle method: either (a) leave the method name
receiveMessage() but require callers to invoke it after construction, or (b)
rename it to start() and add Javadoc stating it must be invoked
post-construction; update callers/tests to call the new method immediately after
creating HelloModel; ensure no other construction-time side effects remain.

}

public HelloModel(NtfyConnection connection) {
this(connection, Platform::runLater);
}

public ObservableList<NtfyMessageDto> getMessages() {
return messages;
}

public String getMessageToSend() {
return messageToSend.get();
}

public StringProperty messageToSendProperty() {
return messageToSend;
}

public void setMessageToSend(String message) {
messageToSend.set(message);
}

/**
* Returns a greeting based on the current Java and JavaFX versions.
*/
Expand All @@ -12,4 +64,15 @@ public String getGreeting() {
String javafxVersion = System.getProperty("javafx.version");
return "Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".";
}
public void sendMessage() {
String msg = messageToSend.get();
if (msg != null && !msg.isBlank()) {
connection.sendMessage(msg);
}
}
Comment on lines +67 to +72
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle sendMessage failure for user feedback.

The connection.sendMessage() returns a boolean indicating success or failure, but this return value is ignored. Users will have no feedback if message sending fails. Consider logging failures or providing UI feedback.

Apply this diff to at minimum log failures:

 public void sendMessage() {
     String msg = messageToSend.get();
     if (msg != null && !msg.isBlank()) {
-        connection.sendMessage(msg);
+        boolean success = connection.sendMessage(msg);
+        if (!success) {
+            System.err.println("Failed to send message: " + msg);
+        }
     }
 }

Alternatively, consider returning the boolean or exposing an error property for UI binding.

🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 59 to 64, the boolean
return value from connection.sendMessage(...) is ignored so failures are silent;
update sendMessage() to capture the boolean result, and when it is false log an
error via the class logger (including the message text and any connection
status), and either (a) return the boolean from sendMessage() so callers/UI can
react or (b) set an observable/error property on HelloModel for UI binding; at
minimum add the logging path for failures and preserve existing behavior for
success.


public void receiveMessage() {
connection.receiveMessage(m -> uiExecutor.accept(() -> messages.add(m)));
}
Comment on lines +74 to +76
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Add error handling and lifecycle management.

The receiveMessage() method has several concerns:

  1. No error handling if the connection fails or throws an exception
  2. No way to stop receiving messages or clean up resources
  3. If called multiple times, could register multiple handlers

Consider:

  • Adding try-catch for connection errors
  • Providing a close() or stop() method to clean up
  • Making the class implement AutoCloseable for proper resource management

Verify if the connection API supports cleanup:


🏁 Script executed:

#!/bin/bash
# Check NtfyConnection interface and implementation for lifecycle methods
rg -n -A5 'interface NtfyConnection' --type=java
rg -n 'close|stop|shutdown|disconnect' src/main/java/com/example/NtfyConnection*.java

Length of output: 447


🏁 Script executed:

#!/bin/bash
# Check HelloModel class full implementation and interfaces
rg -n -B5 -A15 'public void receiveMessage\(\)' src/main/java/com/example/HelloModel.java

# Check if HelloModel implements AutoCloseable or similar
rg -n 'class HelloModel' src/main/java/com/example/HelloModel.java

# Check NtfyConnection implementations to understand resource management
fd -e java -x grep -l 'implements NtfyConnection' {} \;

# Check for any try-catch in HelloModel
rg -n 'try|catch|finally' src/main/java/com/example/HelloModel.java

Length of output: 390


🏁 Script executed:

#!/bin/bash
# Get full HelloModel class declaration to see interfaces
rg -n -A2 'public class HelloModel' src/main/java/com/example/HelloModel.java

# Check NtfyConnectionImpl for lifecycle management
rg -n -B2 -A20 'class NtfyConnectionImpl' src/main/java/com/example/NtfyConnectionImpl.java

# Check if connection field in HelloModel is closeable or has cleanup
rg -n 'connection\s*=' src/main/java/com/example/HelloModel.java

# Check for multiple receiveMessage calls or subscriptions
rg -n 'receiveMessage' src/main/java/com/example/*.java

Length of output: 1459


🏁 Script executed:

#!/bin/bash
# Get full NtfyConnectionImpl.receiveMessage implementation
rg -n -A30 'public void receiveMessage' src/main/java/com/example/NtfyConnectionImpl.java

# Check entire NtfyConnectionImpl for close/cleanup methods
rg -n 'close|shutdown|cleanup|dispose' src/main/java/com/example/NtfyConnectionImpl.java

# Get full HelloModel class to see lifecycle
cat src/main/java/com/example/HelloModel.java

# Check if HttpClient is ever closed
rg -n 'http\.' src/main/java/com/example/NtfyConnectionImpl.java

Length of output: 4332


Add error handling and prevent resource leaks in receiveMessage() method.

The receiveMessage() method at lines 74-76 has critical issues:

  1. Unhandled exceptions: If connection.receiveMessage() throws an exception, it propagates uncaught and crashes the application. The underlying NtfyConnectionImpl can throw from HTTP operations.
  2. Resource leak: The HttpClient in NtfyConnectionImpl and the HTTP response streams are never closed. Each call to receiveMessage() opens an async stream that persists indefinitely.
  3. Multiple subscriptions: Calling receiveMessage() multiple times registers multiple handlers without cleanup.

Fixes required:

  • Wrap connection.receiveMessage() in try-catch to handle exceptions gracefully
  • Implement AutoCloseable on HelloModel to properly close the connection and release resources
  • Provide a stop() or close() method to terminate the message stream
  • Store the subscription handle from receiveMessage() to allow cleanup

}

10 changes: 10 additions & 0 deletions src/main/java/com/example/NtfyConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example;

import java.util.function.Consumer;

public interface NtfyConnection {

public boolean sendMessage(String message);

public void receiveMessage(Consumer<NtfyMessageDto> messageHandler);
}
95 changes: 95 additions & 0 deletions src/main/java/com/example/NtfyConnectionImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.example;

import io.github.cdimascio.dotenv.Dotenv;
import javafx.application.Platform;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused Platform import.

javafx.application.Platform is imported but not used in this file. Remove it to keep imports clean.

Apply this diff:

 import io.github.cdimascio.dotenv.Dotenv;
-import javafx.application.Platform;
 import tools.jackson.databind.ObjectMapper;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import javafx.application.Platform;
import io.github.cdimascio.dotenv.Dotenv;
import tools.jackson.databind.ObjectMapper;
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around line 4, the import
statement for javafx.application.Platform is unused; remove that import line (or
run your IDE's Organize/Optimize Imports) so the file no longer imports
Platform, then recompile to ensure no unused-import warnings remain.

import tools.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Objects;
import java.util.function.Consumer;

public class NtfyConnectionImpl implements NtfyConnection {

private final HttpClient http = HttpClient.newHttpClient();
private final String hostName;
private final ObjectMapper mapper = new ObjectMapper();

public NtfyConnectionImpl() {
Dotenv dotenv = Dotenv.load();
String envHost = dotenv.get("HOST_NAME");
if (envHost == null || envHost.isBlank()) {
throw new IllegalStateException("Environment variable 'HOST_NAME' is required but was not found.");
}
hostName = envHost;
}

public NtfyConnectionImpl(String hostName) {
this.hostName = hostName;
}

@Override
public boolean sendMessage(String message) {
//Todo: Send message using HTTPClient
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.timeout(java.time.Duration.ofSeconds(5))
.build();
Comment on lines +36 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: sendMessage ignores the parameter and sends hardcoded text.

The method accepts a message parameter but ignores it, sending "Hello World" instead. This breaks the contract and will cause all messages to be identical.

Apply this diff to use the actual message parameter:

     @Override
     public boolean sendMessage(String message) {
-        //Todo: Send message using HTTPClient
-
         HttpRequest httpRequest = HttpRequest.newBuilder()
-                .POST(HttpRequest.BodyPublishers.ofString("Hello World"))
+                .POST(HttpRequest.BodyPublishers.ofString(message))
                 .uri(URI.create(hostName + "/mytopic"))
                 .build();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//Todo: Send message using HTTPClient
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString("Hello World"))
.uri(URI.create(hostName + "/mytopic"))
.build();
@Override
public boolean sendMessage(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.build();
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around lines 32 to 37, the
HttpRequest body is hardcoded to "Hello World" instead of using the method's
message parameter; replace the hardcoded string with the actual message (e.g.,
BodyPublishers.ofString(message)), guard against null by defaulting to an empty
string or throwing an IllegalArgumentException, and set an appropriate
Content-Type header (e.g., "text/plain; charset=UTF-8") so the sent payload
correctly reflects the caller's input.

//Todo: handle long blocking send requests to not freeze the JavaFX thread
//1. Use thread send message?
//2. Use async?
var response = http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.orTimeout(5, java.util.concurrent.TimeUnit.SECONDS)
.join();
if (response.statusCode() != 200) {
System.err.println("Failed to send message. HTTP Status: " + response.statusCode());
System.err.println("Response body: " + response.body());
return false;
}
return true;
}


@Override
public void receiveMessage(Consumer<NtfyMessageDto> messageHandler) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(hostName + "/mytopic/json"))
.build();

http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> {
if (response.statusCode() != 200) {
System.err.println("Error connecting to stream. HTTP Status: " + response.statusCode());
return;
}
response.body()
.map(s -> {
try {
return mapper.readValue(s, NtfyMessageDto.class);
} catch (Exception e) {
System.err.println("Failed to parse JSON: " + e.getMessage());
return null;
}
})
.filter(Objects::nonNull)
.filter(message -> "message".equals(message.event()))
.peek(m -> {
var time = java.time.Instant.ofEpochSecond(m.time())
.atZone(java.time.ZoneId.systemDefault())
.toLocalTime();
System.out.println(time + " " + m.message());
})
.forEach(messageHandler);
}).exceptionally(e -> {
System.err.println("Async connection error: " + e.getMessage());
e.printStackTrace();
return null;
});
}
}

6 changes: 6 additions & 0 deletions src/main/java/com/example/NtfyMessageDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record NtfyMessageDto(String id, long time, String event, String topic, String message){}
4 changes: 4 additions & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
module hellofx {
requires javafx.controls;
requires javafx.fxml;
requires io.github.cdimascio.dotenv.java;
requires java.net.http;
requires tools.jackson.databind;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Update module name after fixing Jackson dependency.

This module name corresponds to the incorrect Jackson group ID in pom.xml. After correcting the dependency to com.fasterxml.jackson.core:jackson-databind, update this to:

-    requires tools.jackson.databind;
+    requires com.fasterxml.jackson.databind;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
requires tools.jackson.databind;
requires com.fasterxml.jackson.databind;
🤖 Prompt for AI Agents
In src/main/java/module-info.java around line 6, the module requires declaration
uses the incorrect module name "tools.jackson.databind"; update it to the
correct Jackson module name "com.fasterxml.jackson.databind" to match the fixed
Maven dependency (com.fasterxml.jackson.core:jackson-databind). Replace the
existing requires entry with the correct module name and verify there are no
other references to the old module name in module-info.java.

requires javafx.graphics;

opens com.example to javafx.fxml;
exports com.example;
Expand Down
13 changes: 9 additions & 4 deletions src/main/resources/com/example/hello-view.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.control.Label?>

<StackPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.HelloController">
<children>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextField?>
<VBox alignment="CENTER" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.HelloController">
<Label fx:id="messageLabel" text="Hello, JavaFX!" />
</children>
</StackPane>
<Button text="Send message" onAction="#sendMessage"/>
<ListView fx:id="messageView"/>
<TextField fx:id="messageInput"/>
</VBox>
Loading
Loading