Scala Swing 中的听众和反应
Posted
技术标签:
【中文标题】Scala Swing 中的听众和反应【英文标题】:Listeners and reactions in Scala Swing 【发布时间】:2012-10-23 08:39:07 【问题描述】:我在 Eclipse 中进行了大量搜索和反复试验,但在使用 Swing 在 Scala 中编写 GUI 时,我对听众和反应的理解似乎存在差距。
是否每个侦听器都获得一个反应块,或者我是否在所有可能生成事件的组件上注册侦听器并在带有案例语句的大型反应块中对每个组件做出反应?
监听器和反应块到底属于哪里。
这是我的 GUI 代码的缩写版本:
import scala.swing._
import scala.swing.event.ButtonClicked
import scala.swing.event.KeyTyped
import scala.swing.event.KeyPressed
object HumanGUI extends SimpleGUIApplication
val basicPane = new java.awt.Dimension(800, 200)
val botPane = new java.awt.Dimension(400, 200)
val felt = new java.awt.Color(35, 125, 35)
def top = new MainFrame
title = "Blackjack GUI"
val ConnectionPanel = new BoxPanel(Orientation.Vertical)
background = felt
preferredSize = new java.awt.Dimension(155, 90)
minimumSize = preferredSize
maximumSize = preferredSize
val ipAddressLabel = new Label("House IP:")
ipAddressLabel.foreground = java.awt.Color.WHITE
ipAddressLabel.horizontalTextPosition = scala.swing.Alignment.Left
val portLabel = new Label("House port:")
portLabel.foreground = java.awt.Color.WHITE
portLabel.horizontalTextPosition = scala.swing.Alignment.Left
val ipAddressTextField = new TextField
val portTextField = new TextField
contents += ipAddressLabel
contents += ipAddressTextField
contents += portLabel
contents += portTextField
val DetailPanel = new BoxPanel(Orientation.Vertical)
background = felt
preferredSize = new java.awt.Dimension(100, 160)
minimumSize = preferredSize
maximumSize = preferredSize
val nameLabel = new Label("Your name:")
nameLabel.foreground = java.awt.Color.WHITE
nameLabel.horizontalTextPosition = scala.swing.Alignment.Left
val bankrollLabel = new Label("Bankroll:")
bankrollLabel.foreground = java.awt.Color.WHITE
bankrollLabel.horizontalTextPosition = scala.swing.Alignment.Left
val betLabel = new Label("Bet:")
betLabel.foreground = java.awt.Color.WHITE
betLabel.horizontalTextPosition = scala.swing.Alignment.Left
val nameTextField = new TextField
val bankrollTextField = new TextField
val betTextField = new TextField
val goButton = new Button("Go!")
contents += nameLabel
contents += nameTextField
contents += bankrollLabel
contents += bankrollTextField
contents += betLabel
contents += betTextField
contents += goButton
val PlayPanel = new BoxPanel(Orientation.Vertical)
background = felt
val hitButton = new Button("Hit")
val stayButton = new Button("Stay")
val doubleButton = new Button("Double")
val quitButton = new Button("Quit")
contents += hitButton
contents += stayButton
contents += doubleButton
contents += quitButton
val playerPanel = new BoxPanel(Orientation.Horizontal)
background = felt
border = new javax.swing.border.LineBorder(java.awt.Color.WHITE)
preferredSize = basicPane
minimumSize = basicPane
maximumSize = basicPane
opaque = true
contents += ConnectionPanel
contents += DetailPanel
contents += PlayPanel
contents = new BoxPanel(Orientation.Vertical)
contents += playerPanel
所以问题是我应该把我的听众和反应块放在哪里? 我想对 PlayPanel 中的按钮以及 ConnectionPanel 和 DetailPanel 中的文本字段做出反应。 我是将听众和反应块放置在尽可能靠近我感兴趣的元素的位置,还是在 MainFrame 部分的末尾放置一大块听众和反应? 这还重要吗?
编辑 我已经取得了重大进展,并且完成了很多我需要做的工作,并且对我以前没有得到的概念有了更好的理解。
这段摘自 Odersky 的“Scala 编程”对我帮助最大。具体来说,此页面中的示例:
http://www.artima.com/pins1ed/gui-programming.html
代码来自文本的第一版,所以我质疑Scala 2.9中是否有更好的方法,但它简洁明了,总结了我的误解。
从这个简单的华氏到摄氏度转换器的例子中,我了解到监听器和反应块属于 MainFrame 的内容块之后。
所以我最终得到了:
object HumanGUI extends SimpleSwingGUIApplication
def top = new MainFrame
title = "My Blackjack GUI"
//The fields I want to work with are instantiated as object
object ipAddressTextField extends TextField columns = 15
object portNumberTextField extends TextField columns = 5
//other panels, objects, etc would go here
val OtherPanel = new BoxPanel(Orientation.Horizontal)
label = "Other Panel"
//and here we have the contents += block for the mainframe, other panels, etc from
//above would be added to the main frame here
contents = new BoxPanel(Orientation.Vertical)
contents += ipAddressTextField
contents += portNumberTextField
//here's the listen to, listening on the object created above, and it's enclosed in
//in backticks, a good explanation of that is found in the link below
listenTo(`ipAddressTextField`)
reactions +=
case EditDone('ipAddressTextField`) =>
//do something!
Need clarification on Scala literal identifiers (backticks)
所以看来我的问题的答案是 listenTo 和反应块属于 MainFrame 块,但应该出现在它的内容 += //contents 块之后。
Eclipse 中的额外试验和错误表明,虽然这个解决方案对我有用,但显然还有很多我不明白的地方。例如,虽然我无法让 KeyPress 事件的侦听器工作,但如果我尝试在 val OtherPanel = new BoxPanel(Orientation.Horizontal) 上面代码的一部分,我能够注册一个按钮并像这样工作:
val OtherPanel = new BoxPanel(Orientation.Horizontal)
val betLabel = new Label("Bet:")
val betTextField = new TextField
val goButton = new Button("Go!")
listenTo(goButton)
reactions +=
case ButtonClicked(b) =>
betTextField.text = "Go!"
contents += betLabel
contents += betTextField
contents += goButton
为什么这行得通,但我尝试按照
的方式做一些事情val OtherPanel = new BoxPanel(Orientation.Horizontal)
val betLabel = new Label("Bet:")
val betTextField = new TextField
val goButton = new Button("Go!")
listenTo(betTextField)
reactions +=
case KeyTyped(betTextField, Enter, _, _) =>
println("Caught enter")
contents += betLabel
contents += betTextField
contents += goButton
没有工作仍然让我感到困惑。我假设它应该可以工作,而我只是做错了。也许将这种方法与案例 EditDone 而不是案例 KeyTyped(,,,) 融合会奏效,但我现在有点筋疲力尽,无法跟进关于那个。
我还没有接受答案,因为我希望看到这个的人可以澄清我仍然不明白的观点。如果这种情况没有发生并且这个问题在几天内仍未得到解答,我可能会接受 @som-snytt 的回答,因为他的代码非常有帮助。
【问题讨论】:
我建议您在深入了解 Scala Swing 之前先查看 JavaFX。最新的 Swing 版本至少有十年的历史了,而 Scala Swing 的开发现在也已经过时了。 JavaFX 是在 JVM 上进行 GUI 开发的现代选择。 我真希望有人能告诉我的教授,在他给我们一个要求在 Scala 中构建 GUI 的任务之前... 不要误会我的意思,我绝不是建议将 Scala 放在一边——只是 Scala-Swing。 JavaFX 与 Scala 很好地集成。 @NickAbbey - 可惜。 Scala Swing 使 Swing 变得尽可能好,但 Swing 不是很好。那好吧。它仍然有很多,所以你可以稍微安慰一下自己,这可能对现实世界的编程很有价值。 (虽然这可能是在 Java 中,Swing 库更不友好。) javafx 需要自己的特殊运行时,因此它不仅仅是一个库。 ScalaFX 还没有任何预编译的 jars,几乎没有文档记录,并且目前缺少大部分 javafx 库,因此建议人们使用它还为时过早。 【参考方案1】:Swing 具有教育意义,而 Scala-Swing 具有教育意义。特别是如果课程是“摇摆的历史:兴衰”。
我的第一个 Scala 程序也使用了 Swing。我忘记了细节,但我会分享我在源代码中看到的内容。
显然,我有一个名为 LightBox 的主要 UI 组件来处理一些 UI 事件,还有一个中介组件 LightBoxMediator 进行协调。
有趣的部分是,使用蛋糕模式进行组合,并将业务逻辑(或游戏逻辑)交互移动到一个组件中,为 UI 适当“调解”。 LightBox 也发布事件。
因此,您的问题的答案是:利用发布者框架,但要区分 UI 事件和应用程序事件。 (这个小游戏也有基于actor的控制器。)
也许这足以说明关注点的分离:
/**
* Draws the House of Mirrors.
* The LightBox is just a list of rays (line segments) and gates (various objects).
* The UI emits requests to move and rotate gates.
*/
class LightBox extends Panel
this.peer.addComponentListener(
new ComponentAdapter
override def componentResized(e: ComponentEvent)
if (e.getID == ComponentEvent.COMPONENT_RESIZED && e.getComponent == LightBox.this.peer)
calculateScale()
)
listenTo(mouse.clicks, mouse.moves, mouse.wheel, keys)
reactions +=
case KeyPressed(_, Key.N, _, _) => highlightNextMoveableGate()
case KeyPressed(_, Key.P, _, _) => highlightPreviousMoveableGate()
case e: MousePressed => startDrag(e)
case e: MouseDragged => doDrag(e)
case e: MouseReleased => endDrag(e)
case e: MouseWheelMoved => wheeling(e)
case _ => null // println ("Unreacted event")
和调解员
trait ViewComponents
this: ControllerComponents with ModelComponents =>
val lightBoxMediator: LightBoxMediator
val statusBarMediator: StatusBarMediator
val statusIconMediator: StatusIconMediator
val applicationMediator: ApplicationMediator
/**
* Handles update notifications from the application
* and user input from the LightBox.
*/
class LightBoxMediator(val ui: LightBox) extends Reactor with Observing
/** Attempt to track our selection across updates: the point is where the gate should end up. */
private var selectionContinuity: (Option[Gate], Option[Point]) = (None, None)
listenTo(ui, ui.keys, ui.mouse.clicks)
reactions +=
case KeyPressed(_, Key.Q, _, _) => sys.exit()
case KeyPressed(_, Key.Space, _, _) => rotateSelectedGate()
case KeyPressed(_, Key.Enter, _, _) => rotateOtherwiseSelectedGate()
case KeyPressed(_, Key.Up, _, _) => moveUp()
case KeyPressed(_, Key.Down, _, _) => moveDown()
case KeyPressed(_, Key.Left, _, _) => moveLeft()
case KeyPressed(_, Key.Right, _, _) => moveRight()
case KeyPressed(_, Key.PageUp, _, _) => previousLevel()
case KeyPressed(_, Key.PageDown, _, _) => nextLevel()
case DragEvent(from, to) => handleDrag(from, to)
case ClickEvent(where, button) => handleClick(where, button)
//case x => println("Unreacted event " + x)
observe(controller.modelEvents) e => e match
case LevelLoaded(v) => onLevelLoaded(v)
case TraceResult(s) => onTrace(s)
case unknown => println("Lightbox mediator ignored: "+ unknown)
true
刚刚注意到其他问题。巧合的是,我正在清理旧代码,实际上是一个从 sfgate.com 抓取图像的小应用程序(当然,当他们更改站点时它停止工作;但通常你现在可以右键单击保存),我碰巧请注意以下关于重新订阅的评论。我隐约记得 UIElement 是一个 LazyPublisher 的那一点,因为我记得那一记耳光。但如果我没有写下那条微不足道的评论,那这些信息就会在古代历史中消失。
我认为有人想要支持 scala-swing 并且可能会处理头部拍打。
package com.maqicode.sfg.jfc
import java.awt.Color
import java.awt.Color.WHITE => White, RED => Red
import java.net.URI, URISyntaxException
import javax.swing._
import swing.TextField
import swing.event.EditDone, MouseEntered, ValueChanged
import com.maqicode.sfg.BadGateURLException
import com.maqicode.sfg.GateUrlTranslator.translate
abstract class URIField extends TextField
reactions +=
case e: EditDone => editDone(e)
case other: ValueChanged => editing(other)
case m: MouseEntered => onMouseEntered()
case _ => null
// necessary to resubscribe this so that onFirstSubscribe registers ActionListener
listenTo(this, mouse.moves)
def onMouseEntered()
val t: Option[String] = ClipboardInput.contents
if (t.isDefined && t.get != this.text)
this.text = t.get
submitURL(t.get)
def editing(e: ValueChanged)
clearError()
def editDone(e: EditDone)
submitURL(this.text)
def submitURL(s: String)
val u = s.trim
if (!u.isEmpty)
try
submitURI(translate(new URI(u)))
clearError()
catch
case t: BadGateURLException => flagError()
case t: URISyntaxException => flagError()
def flagError()
colorCode(Red)
def clearError()
colorCode(White)
private def colorCode(c: Color)
if (this.background != c) this.background = c
def submitURI(uri: URI): Unit
【讨论】:
以上是关于Scala Swing 中的听众和反应的主要内容,如果未能解决你的问题,请参考以下文章