5

私はかなりの検索を行い、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の両方のテキストフィールドに反応したいと思います。
リスナーとリアクションブロックを関心のある要素のできるだけ近くに配置しますか、それともメインフレームセクションの最後にリスナーとリアクションの大きなブロックを配置しますか?
それも重要ですか?

編集
私は大きな進歩を遂げ、私が以前に得ていなかった概念のより良い理解とともに、私が働く必要があるものの多くを持っています。

Oderskyの「ProgramminginScala」からのこの抜粋は、私を最も助けてくれました。具体的には、このページの例:

http://www.artima.com/pins1ed/gui-programming.html

コードはテキストの初版のものなので、Scala 2.9にもっと良い方法があるかどうか疑問に思いますが、簡潔で、私が誤解していたことを要約しました。

単純な華氏から摂氏へのコンバーターである例から、リスナーとリアクションブロックがメインフレームのコンテンツブロックの後に属していることがわかりました。

だから私は結局:

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!
    }
  }

Scalaリテラル識別子(バックティック)を明確にする必要があります

したがって、私の質問に対する答えは、listenToブロックとreactionsブロックがMainFrameブロックに属しているようですが、そのcontents + ={//contents}ブロックの後に表示されるはずです。

Eclipseでの追加の試行錯誤は、このソリューションは私には有効ですが、私が理解していないことが明らかにはるかに多いことを示しています。たとえば 、上記のコードの
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
}

うまくいかなかったのはまだ私を困惑させています。私はそれがうまくいくはずだと思っています、そして私はただ何か間違ったことをしています。おそらく、そのアプローチをケースKeyTyped(、、、)の代わりにケースEditDoneと融合させることうまくいったでしょうが私は今少し燃え尽きすぎてそれをフォローアップできません。

私はまだ答えを受け入れていません。これを見た人が私がまだ理解していない点を明らかにしてくれることを望んでいるからです。それが起こらず、質問が数日間答えられないままである場合、彼のコードは非常に役に立ったので、私はおそらく@som-snyttの答えを受け入れるでしょう。

4

1 に答える 1

5

Swingは教育的であり、Scala-Swingは教育的です。特にコースが「スイングの歴史:上昇と下降」の場合。

私の最初のScalaプログラムもSwingを使用していました。詳細を忘れてしまいましたが、ソースで確認できることを共有します。

どうやら、私はいくつかのUIイベントを処理するLightBoxと呼ばれるメイン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が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
}
于 2012-11-02T17:46:40.373 に答える