Scala Swing中的听众和反应

时间:2012-11-02 16:35:36

标签: swing scala button listener textfield

我在eclipse中做了一些搜索和一些试验和错误,但是当我使用Swing在Scala中编写GUI时,我对听众和反应的理解似乎存在差距。

每个侦听器是否都获得了一个反应块,或者我是否在所有可能生成事件的组件上注册了侦听器,并对带有case语句的大型反应块中的每个组件做出反应?

听众和反应块究竟属于哪里。

这是我的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和react块属于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的回答,因为他的代码非常有用。

1 个答案:

答案 0 :(得分:4)

Swing很有教育意义,Scala-Swing很有教育意义。特别是如果课程是“摇摆历史:兴衰”。

我的第一个Scala程序也使用了Swing。我已经忘记了细节,但我会分享我在源头上看到的内容。

显然,我有一个名为LightBox的主UI组件处理一些UI事件,还有一个协调的中介组件LightBoxMediator。

有趣的部分是,使用蛋糕模式进行合成,并将业务逻辑(或游戏逻辑)交互转移到一个“适当”调整UI的组件中。 LightBox也发布了事件。

因此,您的问题的答案将是:利用发布者框架,但将UI事件与应用程序事件区分开来。 (这个小游戏也有基于演员的控制器。)

也许这足以说明关注点的分离:

/**
 * 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是一个懒惰的发布者的一点,因为我记得头脑一巴掌。但如果我没有写出微薄的评论,那么这些信息就会在古代历史中丢失。

我认为有人想要支持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
}