新手上路,各种记录: Scala Swing

一直都想做一下 C/S,但是没什么机会。这几天趁着有空玩了一下唯一接触过一点的 Swing,顺便总结总结。话说,绝大部分时间花在了代码着色上了,结果无功而返哈,笑:)

话说,这篇文章就在这连半成品都不算的小应用上写的呢。中间一直想换成 docat webapp 来写,担心出了神马毛病整篇文章就都完了。:P

首先给个应用截图:

在此输入图片描述

这是一个即时预览的 Markdown 编辑器,功能非常的少,开发的时间也没两天,基于 Scala Swing。

#####下面是代码结构:

在此输入图片描述

你会发现我几乎把所有的组件都拆开了,这是因为因为个人比较喜欢把组件描述放在父容器中,而组件的逻辑都独立出来。但是这样导致了一个问题,我稍后再讲。


###Swing 多线程

首先要说的是一个很不起眼的地方,那就是 Swing 多线程。

基于 Scala 如果不用多线程就没啥意思了,但很不凑巧的是 Swing 本身是单线程的。有关UI、事件派发的操作都需要由 Swing 分发。Java 的 invokeLater 在 Scala 的 Actor 上是行不通。

但是,我们可以设置 Actor 调度器来做到这点:继承 Actor 的 class 或 object 需要手动覆盖调度器。

<!-- lang: scala -->
object EventMonitorAct extends Actor {

    override val scheduler = new SchedulerAdapter {
        def execute(fun: => Unit) { Swing.onEDT(fun) }
     }
 }

另外acotr {} 方法是默认使用父调度器的,而我们在入口处就已经将初始化方法交给 Swing调度器来处理了:

<!-- lang: scala -->
object Marky {
    def main(args: Array[String]) = Swing.onEDT { startUp(args) }

    def startUp(args: Array[String]) { //... }
 }

其实我是在看到 invokeLater 才知道 Swing 单线程这回事的(典型零基础),至于 Scala 调度器?那是翻 Scala Swing 源码翻出来的,所以或许大概极有可能不是这样 = =

###Reactor/Publisher

Reactor/Publisher 是 Scala Swing 的亮点,非常简便的使用方式和扩展能力。只要在任何对象 混入(mixin) Reactor,你就可以监听组件的事件了。

但是,有一点令我很不爽,这种模型会侵入到各个组件,我需要把 Publisher 传到 Reactor 中。比如:在菜单里点击打开文件,然后将内容插入到编辑区中并改变标题。那么我需要在 Frame 和 EditorPane 中监听 MenuBar 的事件,或者将它们都写在一起。(如果你有其他方法还请告知..)

然后,就有下一个题目了。

###事件订阅/广播

在此输入图片描述

这是一个通用模块,可以用在任何对象。当发布者向事件中心发送一个消息,所有的订阅这都会得到这个消息并选择处理或丢弃。

拿上面那个例子来说:当在 MenuBar 中点击打开文件,该组件就只负责打开一个文件选择窗口并得到选中文件对象,MenuBar 只需做到这里就完成了它的职责,然后向事件中心发送一个名为 openfile 的消息并附带一个 File 对象的数组。只要任何一个订阅者响应这个 openfile 消息就能获得 File对象并且自动调用相应代码。

####菜单 MenuDef

<!-- lang: scala -->
    // 混入 Publishable 
trait MenuDef extends I18N with Publishable { this: MenuBar =>
   /*
    * File menu
    */
  val _new = new MenuItem(t("new"))
  val _open = new MenuItem(t("open"))
  val _save = new MenuItem(t("save"))
  val _quit = new MenuItem(t("quit"))

  contents += new Menu(t("file")) {
      contents += _new
      contents += _open
      contents += _save
      contents += new Separator
      contents += _quit
    }

    listenTo(_new, _open, _save, _quit)
    reactions += {
       case ButtonClicked(`_open`) =>
           val chooser = new FileChooser
           chooser.showOpenDialog(this)
           val file = chooser.selectedFile
           if (file != null)
              fire('openfile, Array(MdFile(file)))    //发布 openfile消息,附带参数数组
    }
}

####编辑器 Editor.scala:

<!-- lang: scala -->
class Editor extends EditorPane with I18N with Publishable with Listenable { self =>
    // 注册该监听器
    register()
    //订阅 openfile 消息
    on('openfile) {
        (event) =>
            {
                // 获取并读入文本
                val file = event.args.first.asInstanceOf[MdFile]
                file >> (this.text = _)
            }
     }
}

####主窗口 Frame:

<!-- lang: scala -->
val frame = new Frame with Listenable {
    title = "Marky"
    
    menuBar = new MenuBar with MenuDef {
        border = Swing.EmptyBorder(2, 3, 0, 3)
    }
    visible = true

    register()
    on('openfile, 'savefile) {
         (event) =>
              val file = event.args.first.asInstanceOf[MdFile]
              title = file.name + " - Marky"
      }
  }

最后是 MarkyEvent.scala ,大概有70行左右。 不过这 2B 编辑器要自己一行行缩进,我就不再这贴了=。 =

###Markdown2HTML

没啥好说的,一个多线程的 Actor(即非Swing调度器)做转换,一个模块负责订阅编辑区内容,发送并接受字符串,然后修改显示区内容。

###I18N 更没啥好说的了,只是为了不在代码中写中文而已。在这里个人也建议尽量不要在代码中写中文的好。


没了。不玩了。回去继续捣鼓 WEB。

转载于:https://my.oschina.net/aiasfina/blog/98773

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值