是否可以使用gtk2hs清理一些代码?

时间:2014-05-18 15:46:27

标签: haskell boilerplate gtk2hs

我正在使用haskell和gtk2hs启动GUI。 我有一个笔记本小部件,我想用“F1,F2 ... F11”键切换页面。

我的工作代码是:

import Control.Monad.Trans (liftIO)
import Graphics.UI.Gtk

main = do
  initGUI

  builder <- builderNew
  builderAddFromFile builder "M62.glade"

  window <- builderGetObject builder castToWindow "window1"
  notebook <- builderGetObject builder castToNotebook "notebook1"

  window `on` keyPressEvent $ tryEvent $ do "F1" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 0
  window `on` keyPressEvent $ tryEvent $ do "F2" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 1
  window `on` keyPressEvent $ tryEvent $ do "F3" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 2
  window `on` keyPressEvent $ tryEvent $ do "F4" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 3
  window `on` keyPressEvent $ tryEvent $ do "F5" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 4
  window `on` keyPressEvent $ tryEvent $ do "F6" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 5
  window `on` keyPressEvent $ tryEvent $ do "F7" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 6
  window `on` keyPressEvent $ tryEvent $ do "F8" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 7
  window `on` keyPressEvent $ tryEvent $ do "F9" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 8
  window `on` keyPressEvent $ tryEvent $ do "F10" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 9
  window `on` keyPressEvent $ tryEvent $ do "F11" <- eventKeyName
                                            liftIO $ notebookSetCurrentPage notebook 10

  onDestroy window mainQuit
  widgetShowAll window
  mainGUI

是否有更好的方法和/或简明的方法来做到这一点? 我试图用'main'来处理它,但只有“F1”才有效。 我不知道如何管理这个样板。

3 个答案:

答案 0 :(得分:7)

我不太了解gtk2hs,但使用forM_循环关键索引应该会有很长的路要走。此外,似乎事件是MonadPlus,因此模式匹配失败可以有利地替换为guard

forM_ [0..10] $ \i -> do
    let key = "F" ++ show (i + 1)
    window `on` keyPressEvent $ tryEvent $ do
        pressed <- eventKeyName
        guard (pressed == key)
        liftIO $ notebookSetCurrentPage notebook i

答案 1 :(得分:3)

这个怎么样:

window `on` keyPressEvent $ tryEvent $ do
    'F':n_ <- eventKeyName
    let (n, ""):_ = reads n_
    liftIO . notebookSetCurrentPage notebook $ n - 1

这是无可救药的部分:有两个部分模式匹配可以抛出异常。但那没关系,因为这就是tryEvent的用途。在撰写本文时,所有其他答案都涉及注册许多事件处理程序,而这一个只注册一个。这应该具有(轻微)性能优势。

答案 2 :(得分:2)

尝试将重复的部分拆分成一个函数,如下所示:

import Control.Monad
import Graphics.UI.Gtk


main = do
  initGUI

  builder <- builderNew
  builderAddFromFile builder "M62.glade"

  window <- builderGetObject builder castToWindow "window1"
  notebook <- builderGetObject builder castToNotebook "notebook1"

  -- Split the repeated code into a reusable function, like this
  let registerKeyPressEvent n =
    window `on` keyPressEvent $ tryEvent $ do
      pressed <- eventKeyName
      guard (pressed == ("F" ++ show (n + 1)))
      liftIO $ notebookSetCurrentPage notebook n
  -- Thanks to Tarmil for catching a bug in the code that used to be above.
  -- Tarmil got it right, so I'm borrowing his/her version.

  -- Then you can call it more than once
  registerKeyPressEvent  0
  registerKeyPressEvent  1
  registerKeyPressEvent  2
  registerKeyPressEvent  3
  registerKeyPressEvent  4
  registerKeyPressEvent  5
  registerKeyPressEvent  6
  registerKeyPressEvent  7
  registerKeyPressEvent  8
  registerKeyPressEvent  9
  registerKeyPressEvent 10

  -- But even that is too verbose.
  -- You can shorten it even further like this:
  mapM_ registerKeyPressEvent [0..10]

mapMmap类似,但monad除外。 map的类型为:

map :: (a -> b) -> [a] -> [b]

意味着它接受一个函数并将其应用于列表的每个元素,并返回结果。 mapM的类型为:

mapM :: Monad m => (a -> m b) -> [a] -> m [b]

意味着它需要一个monadic函数(例如registerKeyPressEventIO monad中的函数会产生注册按键事件的副作用)。 mapM然后对列表中的每个元素执行一次此函数,不仅将结果收集到列表中,还将monadic动作收集到生成的monad中,这意味着运行registerKeyPressEvent 11的副作用时间按顺序进行。

最后一个难题是,如果使用mapM,可能会出现类型错误,因为它假设您关心结果列表,因此返回m [b]。但是,在这种情况下,主要类型为IO (),而()不会与[b]匹配。因此,您希望mapM稍微改变一下,抛弃结果列表,只收集monadic动作:

mapM_ :: Monad m => (a -> m b) -> [a] -> m ()

这具有您正在寻找的返回类型。