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.Horizo​​ntal) 上面代码的一部分,我能够注册一个按钮并像这样工作:

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 中的听众和反应的主要内容,如果未能解决你的问题,请参考以下文章

如何获得一系列反应 Discord JS

Swing下,鼠标点击组件不是自动获得焦点的么?

《即兴演讲》-如何创作即兴演讲脚本-了解听众

JAVA中设置鼠标点击事件怎么设置啊?为啥没反应?跪谢

ChatGPT给总统写演讲稿,骗过现场2万听众

Java swing 窗口最小化到任务栏 点击左键显示