I have the following Code:
textField(id: 'programfilter', actionPerformed: { println("execute some action") })
However, the actionPerformed-closure is only called when the textField has the focus and enter is pressed. What do I have to do so that the closure is called on different events e.g. clicking into the textField, selecting text in it or simply on every update of its text?
You can do that leveraging closure coercion. Just a quick example to demonstrate:
import groovy.swing.SwingBuilder
import java.awt.event.*
import javax.swing.event.*
import javax.swing.WindowConstants as WC
SwingBuilder.build() {
frame(title:'Swing Listener example', size:[300,100],
visible:true, defaultCloseOperation:WC.EXIT_ON_CLOSE) {
gridLayout(cols: 2, rows: 0)
label 'Input text: '
input = textField(columns:10, actionPerformed: { echo.text = input.text.toUpperCase() })
label 'Echo: '
echo = label()
input.document.addDocumentListener(
[insertUpdate: { echo.text = input.text },
removeUpdate: { echo.text = input.text },
changedUpdate: { e -> println e }] as DocumentListener)
input.addFocusListener(
[focusGained: { e -> println "Focus gained: $e.cause"},
focusLost: {e -> println "Focus lost: $e.cause"}] as FocusListener)
input.addCaretListener({ e -> println "Caret event: $e"})
}
}
Related
I am trying to write in Groovy application, using "Calculator like" keys functionality. So I would like swing action to be invoked, when I:
press JButton
press corresponding keyboard key, no matther whether there is focus on JButton or not.
Probably the best solution is to use Java Key Bindings, as described here:
https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
Following combination of Groovy and Java seems to work well:
package packageSwingTest4
import groovy.swing.SwingBuilder
import javax.swing.*
import java.awt.*
import javax.swing.WindowConstants as WC
class SwingTest4 {
def static b
static void main(args) {
def swing = new SwingBuilder()
def f2Action = swing.action(name:"F2", shortDescription:"F2 button", accelerator:"F2") {
println "F2 pressed"
}
swing.edt {
def f=frame(title: 'Keborad Binding test', size:[400,300],defaultCloseOperation: JFrame.EXIT_ON_CLOSE, show: true) {
panel {
b = button(text: 'F2', actionPerformed:{println "FFFF2222 pressed"})
b.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F2"),"F2 pressed")
b.getActionMap().put("F2 pressed",f2Action)
}
}
}
}
}
But I do not like to combine Groovy/Java in this way, namely as I will:
- loose nice structure of Groovy code related to structure of GUI
- I have to define Actions separately and not "nicely" as in case of Groovy swing builder
Do you know about "cleaner" solution of my problem using pure Groovy/ SwingBuilder? I would prefer to use Key Bindings, not Key Listener..
Thank you for help and recommendations
Not 100% sure, but do you mean like this:
import groovy.swing.*
import javax.swing.*
def swing = new SwingBuilder()
def f2Action = swing.action(name:"F2 text", shortDescription:"F2 button", keyStroke:"F2") {
println "F2 pressed"
}
swing.edt {
frame(title: 'Keborad Binding test', size:[400,300],defaultCloseOperation: JFrame.HIDE_ON_CLOSE, show: true) {
panel {
button(text: 'F2',
actionPerformed:{ println "FFFF2222 pressed" }) {
action(f2Action)
}
}
}
}
def swing = new SwingBuilder()
def f2Action = swing.action(name:"F2 text", shortDescription:"F2 button", focus: JComponent.WHEN_IN_FOCUSED_WINDOW, keyStroke:"F2") {
println "F2 pressed"
}
def f3Action = swing.action(name:"F3 text", shortDescription:"F2 button", keyStroke:"F3") {
println "F3 pressed"
}
swing.edt {
frame(title: 'Keborad Binding test', size:[400,300],defaultCloseOperation: JFrame.HIDE_ON_CLOSE, show: true) {
panel {
button('F2') {action(f2Action)}
button('F3') {action(f3Action)}
}
}
}
I'm trying to make a really simple text input field (to replicate later for a more complex purpose). Using IDEA 14 CE, not sure it matters. I wrote this code:
import groovy.swing.SwingBuilder
import groovy.beans.Bindable
import static javax.swing.JFrame.EXIT_ON_CLOSE
import java.awt.*
String word
#Bindable
class UserInput {
String word
//String toString() { "$word" }
}
def userInput = new UserInput(word: null)
def swingBuilder = new SwingBuilder()
swingBuilder.edt {
lookAndFeel 'nimbus'
// frame size
def width = 350
def height = 230
frame (
title: 'Input',
size: [width, height],
show: true,
locationRelativeTo: null,
defaultCloseOperation: EXIT_ON_CLOSE ) {
borderLayout(vgap: 5)
panel(constraints:
BorderLayout.CENTER,
border: compoundBorder([emptyBorder(10), titledBorder('Input:')]))
{
tableLayout {
tr {
td { label 'Input: ' }
td { textField userInput.word, id: userInput.word, columns: 20 }
}
}
}
panel(constraints: BorderLayout.SOUTH) {
button text: 'Print word', actionPerformed: {
println """Word: ${userInput.word}"""
}
}
}
}
When I run it, I get this Swing box:
No matter what I input, when I click Print Word it always prints:
Word: null
What am I doing wrong? Seems like I am failing to assign user input to a parameter or something like that, but I cannot figure it out.
Right, you need to use bean binding to get the text property of the textField bound to your model. This works:
import groovy.swing.SwingBuilder
import groovy.beans.Bindable
import static javax.swing.JFrame.EXIT_ON_CLOSE
import java.awt.*
String word
#Bindable
class UserInput {
String word
}
def userInput = new UserInput(word: null)
def swingBuilder = new SwingBuilder().edt {
lookAndFeel 'nimbus'
// frame size
def width = 350
def height = 230
frame (title: 'Input',
size: [width, height],
show: true,
locationRelativeTo: null ) {
borderLayout(vgap: 5)
panel(constraints: BorderLayout.CENTER,
border: compoundBorder([emptyBorder(10), titledBorder('Input:')])) {
tableLayout {
tr {
td { label 'Input: ' }
td { textField id:'input', columns: 20 }
}
}
}
panel(constraints: BorderLayout.SOUTH) {
button text: 'Print word', actionPerformed: {
println """Word: ${userInput.word}"""
}
}
// Bind the text field to the bean
bean userInput, word: bind { input.text }
}
}
I'm currently trying to code a minesweeper using scala, but I can't find the way to listen to a right click on a button.
I've already searched on the Internet the way to do it, but I definitely was not able to find it.
If anyone could help me out, I would be really grateful :)
Thanks,
Schnipp
(Note: Scala is a new language to me and I am not a Java user, so I am sorry if my questions sound dumb)
EDIT:
I am trying to find (or implement) a function 'ButtonClickedRight' that could listen to a right-click on a button.
like this
import scala.swing._
import scala._
import scala.swing.event._
object Right extends MainFrame with App {
title = ""
visible = true
val b = new button("")
listenTo(b)
reactions += {
case ButtonClicked(`b`) => *code*
case ButtonClickedRight(`b`) => *code*
}
}
EDIT 2 --
I would like to know if the user has clicked on the Button "1" or not. The problem I have is that this code prints "Mouse clicked at " + e.point+" type "+e.modifiers when I click on the label but not on the button.
object App extends SimpleSwingApplication {
lazy val ui = new GridPanel(2,1) {
contents += new Button("1")
contents += new Label("2")
listenTo(mouse.clicks)
reactions += {
case e: MouseClicked =>
println("Mouse clicked at " + e.point+" type "+e.modifiers)
}
}
def top = new MainFrame {
contents = ui
visible = true
preferredSize = new Dimension(500,500)
}
}
Button events are fired through a specific publisher .mouse.clicks.
import scala.swing._
import scala.swing.event._
object App extends SimpleSwingApplication {
lazy val ui = new GridPanel(2,1) {
val button = new Button("1")
contents += button
contents += new Label("2")
listenTo(button.mouse.clicks) // !
reactions += {
case evt # MouseClicked(`button`, pt, _, _, _) =>
val which = evt.peer.getButton
if (which > 1) {
println(s"Mouse clicked at (${pt.x}; ${pt.y}) - button: $which")
}
}
}
lazy val top = new MainFrame {
contents = ui
size = new Dimension(500,500)
}
}
Note that at least on Linux my right button has number 3 not 2. You could also use the triggersPopup flag, but then you must ensure to monitor both MousePressed and MouseReleased, as this flag is platform-dependent.
I think that you are on the right path, for my understanding of scala swings I think that the problem is that you are not attaching the listener correctly. For one I would assign the button to a value and call listenTo only on it:
val button = new Button("1")
listenTo(button)
Then, in the reactions, I would write the pattern checking in the event that it comes from the button (probably redundant if you only call listenTo passing the button) and that it has the correct button:
case ButtonClicked(b) if b == button && b.peer.getButton == MouseEvent.BUTTON_2 => ...
So the code you provided in your edit would become:
object App extends SimpleSwingApplication {
lazy val ui = new GridPanel(2,1) {
val button = new Button("1")
contents += button
contents += new Label("2")
listenTo(button)
reactions += {
case evt # MouseClicked(b, pt, _, _, _) if b == button && evt.peer.getButton == java.awt.event.MouseEvent.BUTTON2 =>
println(s"Mouse clicked at (${pt.x}; ${pt.y}) - button: ${evt.peer.getButton}")
}
}
def top = new MainFrame {
contents = ui
visible = true
preferredSize = new Dimension(500,500)
}
}
The following works for me:
new Button {
listenTo(mouse.clicks)
reactions += {
case MouseClicked(_, _, c, _, _) => handleClick(c == 0)
}
}
def handleClick(isLeftClick: Boolean): Unit = {
//
}
how do i listen on events on a radiobutton in scala? i have the following code, but for some reason the reaction is not executed. this is a Dialog, and i am looking to listen on radiobutton selection, and to change the Dialog window's title accordingly.
val dirFileSelector = {
List(
new RadioButton("Directory"){
name = "dir"
},
new RadioButton("File"){
name = "file"
}
)
}
val buttonGroup = new ButtonGroup
dirFileSelector map { button=>
listenTo(button)
buttonGroup.buttons.add(button)
}
contents = new BorderPanel{
add(new BoxPanel(Orientation.Horizontal) {contents ++= dirFileSelector}, BorderPanel.Position.North)
}
reactions += {
case SelectionChanged(buttonSelect) => {
println("buttonSelect selection changed")
buttonSelect.name match {
case "dir" => title = "Add Directory"
case "file" => title = "Add File"
}
}
}
As far as I can tell, RadioButtons don't emit the SelectionChanged event. They do however emit ButtonClicked.
This is a simple working example to get the effect you want:
import swing._
import swing.event._
object app extends SimpleSwingApplication {
val dirFileSelector = List(
new RadioButton() {
name = "dir"
text = "Directory"
},
new RadioButton() {
name = "file"
text = "File"
}
)
new ButtonGroup(dirFileSelector: _*)
def top = new MainFrame {
title = "Test"
contents = new BoxPanel(Orientation.Horizontal) {
contents ++= dirFileSelector
}
dirFileSelector.foreach(listenTo(_))
reactions += {
case ButtonClicked(button) => {
button.name match {
case "dir" => title = "Add Directory"
case "file" => title = "Add File"
}
}
}
}
}
I'm writing a GUI in Scala, and I've run across a strange problem while trying to register Button Events in a foreach statement: It should be that for every element objecti in a list of objects (object0 ... objectn), a corresponding Button x = buttoni is retrieved and a given Box is subscribed to it with box.listenTo(x). When the Button is pushed, some action related to the objecti should be performed (in this case, println("Event triggered: " + event)):
import scala.swing.ComboBox
import scala.collection.mutable.Buffer
import scala.swing.Button
import scala.swing.event.ButtonClicked
import scala.swing.Action
import scala.swing.SimpleSwingApplication
import scala.swing.MainFrame
import scala.swing.GridPanel
import scala.swing.BorderPanel
object EventSet extends SimpleSwingApplication {
object PhoneKeyEvent extends Enumeration {
val Key1 = Value("1")
val Key2 = Value("2")
}
/* Constants */
private val DisplayHistory = Buffer[String]()
private val KeypadKeyEvents = List(
PhoneKeyEvent.Key1, PhoneKeyEvent.Key2)
private val PhoneKeyEventButtonNames = Map(
PhoneKeyEvent.Key1 -> "1",
PhoneKeyEvent.Key2 -> "2"
)
/* End constants */
private var PhoneKeyEventButtons = Map[PhoneKeyEvent.Value, Button]()
private def createDisplay() : ComboBox[String] = {
new ComboBox(DisplayHistory) {
// Listen to keypad keys
// Get the set of all keypad key events
val keypadEvents = List(PhoneKeyEvent.Key1, PhoneKeyEvent.Key2)
println("keypadEvents: " + keypadEvents)
keypadEvents.foreach({ event =>
println("event: " + event)
// Listen to each button representing a keypad key event
var keypadEventButton = PhoneKeyEventButtons(event)
println("keypadEventButton: " + keypadEventButton)
listenTo(keypadEventButton)
reactions += {
case ButtonClicked(keypadEventButton) => {
// TODO: fix strange bug here: adds all possible inputs
println("Event triggered: " + event)
// selection.item = selection.item + event
}
}
})
}
}
private def createPhoneControllerPanel() : BorderPanel = {
new BorderPanel() {
val keypadControlPanel = createPhoneKeyEventTypeControlPanel(KeypadKeyEvents)
add(keypadControlPanel, BorderPanel.Position.Center)
add(createDisplay(), BorderPanel.Position.North)
focusable = true
requestFocus
}
}
/**
* Creates a new {#link Button} for a given {#link PhoneKeyEvent} and adds
* the button to the global map of such buttons to their respective events;
* that means multiple buttons cannot be created for the same key event.
*/
private def createPhoneKeyEventButton(phoneKeyEvent: PhoneKeyEvent.Value) : Button = {
// Only one button can be created per key event
require(!PhoneKeyEventButtons.contains(phoneKeyEvent),
{System.err.println("A Button for the PhoneKeyEvent " + phoneKeyEvent + "has already been created.")})
val keyEventButtonName = PhoneKeyEventButtonNames(phoneKeyEvent)
val result = new Button(Action(keyEventButtonName) {
println("Key event button pressed: " + phoneKeyEvent)
})
// Add the button to the map of all created key event buttons
PhoneKeyEventButtons += phoneKeyEvent -> result
return result
}
private def createPhoneKeyEventTypeControlPanel(keyEvents : Iterable[PhoneKeyEvent.Value]) : GridPanel = {
new GridPanel(4, 3) {
// Get the intersection of all key events of the given type and the events with button names
keyEvents.foreach(phoneKeyEvent => contents += createPhoneKeyEventButton(phoneKeyEvent))
}
}
override def top = new MainFrame {
contents = createPhoneControllerPanel()
}
}
However, I get some very strange behaviour, where clicking any Button results in all such object actions are triggered -- See the program output:
keypadEvents: List(1, 2)
event: 1
keypadEventButton: scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=
javax.swing.plaf.BorderUIResource$CompoundBorderUIResource#7633f09,flags=296,maximumSize=,minimumSize=,preferredSize=,de
faultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=2,left=14,bottom=2,right=14]
,paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,te
xt=1,defaultCapable=true]
event: 2
keypadEventButton: scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=
javax.swing.plaf.BorderUIResource$CompoundBorderUIResource#7633f09,flags=296,maximumSize=,minimumSize=,preferredSize=,de
faultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=2,left=14,bottom=2,right=14]
,paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,te
xt=2,defaultCapable=true]
Key event button pressed: 1
Event triggered: 1
Event triggered: 2
Key event button pressed: 2
Event triggered: 1
Event triggered: 2
I'm completely at a loss as to why this is happening; I'm quite new at Scala anyway, so it's quite unfamiliar territory, but I've tried fiddling with a lot of stuff and snooped around in the Swing source code, and still clueless... how can every value of a reference inside of a loop be used in every iteration? or how can every event be triggered by Swing at once? or...?
Edit: Here are two minimised versions, both of which behave differently:
import scala.swing.SimpleSwingApplication
object ButtonEvents extends SimpleSwingApplication {
import scala.swing.Button
import scala.swing.event.ButtonClicked
import scala.swing.Action
import scala.swing.MainFrame
import scala.swing.FlowPanel
override def top = new MainFrame {
contents = new FlowPanel {
val button1 = new Button(Action("1") {
println("Button 1 pressed")
})
contents += button1
val button2 = new Button(Action("2") {
println("Button 2 pressed")
})
contents += button2
val buttons = List(button1, button2)
buttons.foreach({ button =>
listenTo(button)
reactions += {
case ButtonClicked(button) => {
println("Event triggered: " + button.text)
}
}
})
}
}
}
Prints:
Button 1 pressed
Event triggered: 1
Event triggered: 1
Button 2 pressed
Event triggered: 2
Event triggered: 2
And a version which seems to behave correctly (but I'm not sure why):
import scala.swing.SimpleSwingApplication
object ButtonEvents extends SimpleSwingApplication {
import scala.swing.Button
import scala.swing.event.ButtonClicked
import scala.swing.Action
import scala.swing.MainFrame
import scala.swing.FlowPanel
override def top = new MainFrame {
contents = new FlowPanel {
val button1 = new Button(Action("1") {
println("Button 1 pressed")
})
contents += button1
val button2 = new Button(Action("2") {
println("Button 2 pressed")
})
contents += button2
val buttons = Map("1" -> button1, "2" -> button2)
buttons.foreach({ eventButton =>
listenTo(eventButton._2)
reactions += {
case ButtonClicked(eventButton._2) => {
println("Event triggered: " + eventButton._1)
}
}
})
}
}
}
Prints (correct):
Button 1 pressed
Event triggered: 1
Button 2 pressed
Event triggered: 2
In the line
reactions += {
case ButtonClicked(keypadEventButton) => {
you are creating a new val keypadEventButtonand assigning it to whatever is inside ButtonClicked(). Changing the line to case ButtonClicked(abstractButton) will still work and display same problem.
I'm guessing you are expecting this to match the use of keypadEventButton on the preceding lines. You probably want to create one reaction and then use the abstractButton to tell what button has been pressed.
#andy is correct. A good IDE like IDEA will highlight "suspicious shadowing by a variable pattern", since you're binding a new variable in the pattern match. Scala allows you to shadow variables as much as you want, within nested code blocks, for instance:
scala> val a = 1; {val a = 2; println(a)}; println(a)
2
1
a: Int = 1
So what does the following return?
val a = 1
2 match {
case a => "it was 1"
case _ => "something else"
}
It returns "it was 1" because a is shadowed. Now try:
2 match {
case `a` => "it was 1"
case _ => "something else"
}
This returns "something else" because we used backticks to refer to the value of he previously defined variable. (Also try this where the variable begins with a capital letter...)
So you just need to add backticks, i.e.
case ButtonClicked(`button`) => {