【问题标题】:Is it possible to clean some code using gtk2hs?是否可以使用 gtk2hs 清理一些代码?
【发布时间】:2014-05-18 15:46:27
【问题描述】:

我正在使用 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

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

【问题讨论】:

    标签: haskell boilerplate gtk2hs


    【解决方案1】:

    我对 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
    

    【讨论】:

    • 非常感谢,这是我试图做的解决方案!这非常有效!
    【解决方案2】:

    这个怎么样:

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

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

    【讨论】:

    • 非常感谢!我选择将此解决方案用于注册事实,但是您能否提供一些有关部分模式匹配的文档?我还不明白它们...... :)
    • @grwp 当然。你想知道什么?也许你想知道他们会匹配什么东西,或者......?
    【解决方案3】:

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

    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]
    

    mapM 类似于 map,除了 monad。 map 的类型为:

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

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

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

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

    最后一个难题是,如果您使用mapM,您可能会收到类型错误,因为它假定您关心结果列表,因此返回m [b]。但是,在这种情况下,main 的类型是IO (),而() 不会匹配到[b]。因此,您希望对 mapM 稍作改动,从而丢弃结果列表,只收集单子动作:

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

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

    【讨论】:

    • 很好的解释,但有一个错误:("F" ++ show (n + 1)) 不是模式,你不能把它放在&lt;- 的左侧。
    • 糟糕!给我一分钟。
    • 已修复,谢谢。我冒昧地借用了你的版本。希望你不要介意。
    • 如果我介意我的代码被借用,我不会把它放在 StackOverflow 上 :)
    • 非常感谢!解释很清楚,我忘记了 mapM_ 魔法,使用过一次,有时...... :) 但是“窗口”和“笔记本”需要成为 registerKeyPressEvent 函数的参数,因为它们只是主要的。
    猜你喜欢
    • 2011-04-04
    • 1970-01-01
    • 1970-01-01
    • 2012-08-21
    • 2011-10-10
    • 1970-01-01
    • 1970-01-01
    • 2021-05-17
    • 1970-01-01
    相关资源
    最近更新 更多