Introduction
Perhaps you already know what ChatGPT is but I’m going to let it introduce itself by submitting the prompt: “Describe ChatGPT”. The answer it gave me is this:
“I am ChatGPT, a large language model developed by OpenAI. I am based on the GPT-3.5 architecture, which is a powerful neural network that has been trained on an enormous amount of text data. This enables me to understand natural language input and generate human-like responses.
I have access to a vast amount of information and knowledge from various sources, including books, articles, websites, and other online resources. I can understand and process information in multiple languages, and I can answer a wide range of questions, from factual queries to more complex ones that require reasoning and interpretation.
As a language model, I am designed to communicate with humans in natural language, using both text-based and voice-based interfaces. I can be used for a variety of applications, including chatbots, virtual assistants, customer service, and more.”
As you can tell, ChatGPT has a pretty lofty opinion about itself and feels it can converse with humans in natural language. I decided to test its abilities by seeking help on a coding problem for which I did not know the answer but could definitely check if I got a reasonable response. Below is my dialogue with ChatGPT.
Conversation with ChatGPT
Here is my question: “I have a user interface written in TornadoFX. I would like to add a button that will bring up a modal window where I can change the values of some parameters in the model. How do I do this?”
This was the response:
To create a button that brings up a modal window in TornadoFX, you can use the button
function to create the button and then use the action
function to define what happens when the button is clicked. You can create a custom modal window by extending the Fragment
class and then use the openModal
function to open it.
Here’s a simple example that demonstrates how to create a button that opens a custom modal window with some input fields for changing parameter values:
- First, create a class for the custom modal window that extends
Fragment
. In this example, we’ll call itParameterEditor
.
import tornadofx.*
class ParameterEditor : Fragment("Edit Parameters") {
val parameter1 = SimpleStringProperty()
val parameter2 = SimpleIntegerProperty()
override val root = form {
fieldset("Parameters") {
field("Parameter 1") {
textfield(parameter1)
}
field("Parameter 2") {
textfield(parameter2) {
filterInput { it.controlNewText.isInt() }
}
}
}
buttonbar {
button("Save") {
isDefaultButton = true
action {
// Implement logic to save the updated parameters
// Close the window when done
close()
}
}
button("Cancel") {
isCancelButton = true
action {
// Close the window without saving changes
close()
}
}
}
}
}
- Next, add a button to your main UI, and use the
action
function to open the custom modal window when the button is clicked.
import tornadofx.*
class MainView : View("TornadoFX Example") {
override val root = vbox {
button("Edit Parameters") {
action {
find<ParameterEditor>().openModal()
}
}
}
}
Now, when you run the application and click the “Edit Parameters” button, it will open the ParameterEditor
modal window where you can edit the values of the parameters. After making changes, you can click “Save” to save the updated values, or “Cancel” to close the window without saving any changes.
This was certainly a good start, I thought, but left me wondering how I would get the parameter values into the modal window and how to write out changes to the model? I looked at the documentation for TornadoFX and saw a suggestion to inject a ViewController into the window in order to get values in and out. But, there was no specific description in the documentation of how to do that so I decided to ask ChatGPT:
“How do I inject a ViewController into the ParameterEditor?”
To inject a ViewController into the ParameterEditor, you’ll first need to create a class for the ViewController. In this example, we’ll call it ParameterViewController
. You can use TornadoFX’s dependency injection mechanism to inject the controller into the ParameterEditor
fragment.
- Create a class for the ViewController:
import tornadofx.*
class ParameterViewController : Controller() {
fun updateParameters(parameter1: String, parameter2: Int) {
// Implement logic to update the model with new parameter values
println("Parameter 1: $parameter1, Parameter 2: $parameter2")
}
}
- Use the
inject
function in theParameterEditor
fragment to inject an instance ofParameterViewController
:
import tornadofx.*
class ParameterEditor : Fragment("Edit Parameters") {
val parameter1 = SimpleStringProperty()
val parameter2 = SimpleIntegerProperty()
// Inject the ParameterViewController
private val viewController: ParameterViewController by inject()
override val root = form {
fieldset("Parameters") {
field("Parameter 1") {
textfield(parameter1)
}
field("Parameter 2") {
textfield(parameter2) {
filterInput { it.controlNewText.isInt() }
}
}
}
buttonbar {
button("Save") {
isDefaultButton = true
action {
// Call the updateParameters function in the viewController
viewController.updateParameters(parameter1.value, parameter2.value.toInt())
// Close the window when done
close()
}
}
button("Cancel") {
isCancelButton = true
action {
// Close the window without saving changes
close()
}
}
}
}
}
Now, when you click “Save” in the ParameterEditor
window, it will call the updateParameters
function in the injected ParameterViewController
instance. This function can be used to update your model with the new parameter values.
Nice, now I could envision how to get ahold of the parameters in my model and change them as well. Only one problem remained namely how to identify the source of the the object requesting the modal window? I had seen in the documentation that you could transfer parameters along with opening modal windows. I thought perhaps I could capture the name of the sending object? I asked ChatGPT how to do that with the following question:
“How do I transfer parameters by params?”
To transfer parameters between views or fragments using params
in TornadoFX, you can pass a map of key-value pairs when you open a new window. The receiving fragment or view can then access these parameters using the params
property.
Here’s how you can pass parameters using params
:
- In your main view or fragment, create a button that opens the
ParameterEditor
fragment with the parameters you want to pass.
import tornadofx.*
class MainView : View("TornadoFX Example") {
private val currentParameter1 = "Initial value"
private val currentParameter2 = 42
override val root = vbox {
button("Edit Parameters") {
action {
find<ParameterEditor>(
params = mapOf(
"parameter1" to currentParameter1,
"parameter2" to currentParameter2
)
).openModal()
}
}
}
}
- In the
ParameterEditor
fragment, use theparams
property to access the passed parameters and set the initial values for theSimpleStringProperty
andSimpleIntegerProperty
.
import tornadofx.*
class ParameterEditor : Fragment("Edit Parameters") {
val initialParameter1: String by param()
val initialParameter2: Int by param()
val parameter1 = SimpleStringProperty(initialParameter1)
val parameter2 = SimpleIntegerProperty(initialParameter2)
// Inject the ParameterViewController
private val viewController: ParameterViewController by inject()
override val root = form {
fieldset("Parameters") {
field("Parameter 1") {
textfield(parameter1)
}
field("Parameter 2") {
textfield(parameter2) {
filterInput { it.controlNewText.isInt() }
}
}
}
buttonbar {
button("Save") {
isDefaultButton = true
action {
// Call the updateParameters function in the viewController
viewController.updateParameters(parameter1.value, parameter2.value.toInt())
// Close the window when done
close()
}
}
button("Cancel") {
isCancelButton = true
action {
// Close the window without saving changes
close()
}
}
}
}
}
Now, when you open the ParameterEditor
fragment, it will display the initial values passed through the params
property. You can update these values and click “Save” to save the changes using the ParameterViewController
.
Results
The kind of responses I got from ChatGPT were good enough to implement my own parameter changing window. My application was for tuning PID controllers by giving them different gains and reset times. This is how my code turned out.
package controller
import javafx.beans.property.SimpleDoubleProperty
import tornadofx.*
class ParameterEditor : Fragment("Edit Parameters") {
val controllerName: String by param()
// Inject the ViewController
private val viewController: ViewController by inject()
val pidView = viewController.findPIDViewControllerByName(controllerName)
val gain = SimpleDoubleProperty(pidView.pid.gain)
val resetTime = SimpleDoubleProperty(pidView.pid.resetTime)
override val root = form {
fieldset("Parameters") {
field("Gain") {
textfield(gain)
}
field("Reset Time") {
textfield(resetTime) {
filterInput { it.controlNewText.isDouble() }
}
}
}
buttonbar {
button("Save") {
isDefaultButton = true
action {
viewController.setGainForController(controllerName, gain.value)
viewController.setResetForController(controllerName, resetTime.value)
// Close the window when done
close()
}
}
button("Cancel") {
isCancelButton = true
action {
// Close the window without saving changes
close()
}
}
}
}
}
I already had a viewController where all interface- and model logic are coordinated. I’m showing the few lines I had to add to this viewController to make the scheme work.
fun findPIDViewControllerByName(name: String): PIDViewController {
val controller = controllerMap[name] ?: throw IllegalArgumentException("PIDViewController not found")
return controller
}
fun setGainForController(name:String, gain: Double) {
val controller = controllerMap[name] ?: throw IllegalArgumentException("PIDViewController not found")
controller.pid.gain = gain
}
fun setResetForController(name:String, reset: Double) {
val controller = controllerMap[name] ?: throw IllegalArgumentException("PIDViewController not found")
controller.pid.resetTime = reset
}
The actual invocation of the modal window happens in the following function when the “Tune” button is pressed on a controller face plate in the GUI.
fun tuneController() {
find<ParameterEditor>(
params = mapOf(
"controllerName" to name,
)
).openModal()
}
We can summarize what happens as a few steps:
- When the Tune button on a controller face plate is pressed, the tuneController function is executed. It opens the ParameterEditor window and sends its name along under the key “controllerName”
- The ParameterEditor decodes the string associated with the parameter controllerName and now knows the name of the controller initiating the call.
- Now the main ViewController is found and the ParameterEditor uses the controller name to find the actual PID controller in the ViewController.
- With the PID controller identified we can retrieve and display the current parameter values. When these are changed we can also save the new values and close the modal window.
Conclusions
This non-trivial example demonstrates the power of ChatGPT, at least as a code writing assistant. I’m fairly certain that I could have come up with a solution without ChatGPT but it would have taken me a longer. What is particularly impressive is that you can refine the answer by asking more questions. ChatGPT remembers the previous context, and answers, so there is no need to reformulate the original question, just add to it and ask for more details.