如何处理 Haskell+GTK 中的大量小部件?

How to deal with lots of widgets in Haskell+GTK?

我有一个描述我的界面的 Glade 文件。它的目标是通过本地网络控制 Nec 显示器。因此有很多小部件控制监视器的工作方式:

我需要在 get/set from/to 监视器和小部件之间创建 link。这意味着 getting/setting 值并将函数附加到小部件。

GTK 库中的 Builder 需要使用 IO () 并强制转换每个小部件,以便调用它们的特定函数。这有点无聊并添加(不必要的)代码行。

module GUI where

import Graphics.UI.Gtk

data NecControlGUI = NecControlGUI
    { winNecControl :: Window
    , entIPAddress :: Entry
    , btnConnection :: Button
    , cbtVideoInput :: ComboBox
    , cbtPIPInput :: ComboBox
    , sclSharpness :: Scale
    , sclContrast :: Scale
    , sclBrightness :: Scale
    , sclBlackLevel :: Scale
    , sclColorTemperature :: Scale
    , cbtGamma :: ComboBox
    , sclBalance :: Scale
    , sclTreble :: Scale
    , sclBass :: Scale
    , sclVolume :: Scale
    , cbtLanguage :: ComboBox
    , sclMenuDisplayTime :: Scale
    }

loadGUI :: String -> IO NecControlGUI
loadGUI guiPath = do
    bdr <- builderNew

    builderAddFromFile bdr guiPath

    window <- builderGetObject bdr castToWindow "winNecControl"
    ipaddress <- builderGetObject bdr castToEntry "entIPAddress"
    connection <- builderGetObject bdr castToButton "btnConnection"
    videoinput <- builderGetObject bdr castToComboBox "cbtVideoInput"
    pipinput <- builderGetObject bdr castToComboBox "cbtPIPInput"
    sharpness <- builderGetObject bdr castToScale "sclSharpness"
    contrast <- builderGetObject bdr castToScale "sclContrast"
    brightness <- builderGetObject bdr castToScale "sclBrightness"
    blacklevel <- builderGetObject bdr castToScale "sclBlackLevel"
    colortemperature <- builderGetObject bdr castToScale "sclColorTemparature"
    gamma <- builderGetObject bdr castToComboBox "cbtGamma"
    balance <- builderGetObject bdr castToScale "sclBalance"
    treble <- builderGetObject bdr castToScale "sclTreble"
    bass <- builderGetObject bdr castToScale "sclBass"
    volume <- builderGetObject bdr castToScale "sclVolume"
    language <- builderGetObject bdr castToComboBox "cbtLanguage"
    menudisplaytime <- builderGetObject bdr castToScale "sclMenuDisplayTime"

    return NecControlGUI
        { winNecControl = window
        , entIPAddress = ipaddress
        , btnConnection = connection
        , cbtVideoInput = videoinput
        , cbtPIPInput = pipinput
        , sclSharpness = sharpness
        , sclContrast = contrast
        , sclBrightness = brightness
        , sclBlackLevel = blacklevel
        , sclColorTemperature = colortemperature
        , cbtGamma = gamma
        , sclBalance = balance
        , sclTreble = treble
        , sclBass = bass
        , sclVolume = volume
        , cbtLanguage = language
        , sclMenuDisplayTime = menudisplaytime
        }

有没有办法自动化这部分?

我听说过 LGtk,但这是唯一的方法吗?

编辑 2016-09-20 1

我尝试采用应用方式,但 Haskell 似乎不喜欢组合应用 IO? (但可能是我)

module NecControlGUI where

import Graphics.UI.Gtk

data NecControlGUI = NecControlGUI
    { adjBalance :: Adjustment
    , winNecControl :: Window
    }

loadNecControlGUI :: String -> IO NecControlGUI
loadNecControlGUI guiPath = do
    bdr <- builderNew
    builderAddFromFile bdr guiPath
    let bget = builderGetObject bdr
    NecControlGUI <$> bget castToAdjustment "adjBalance"
                  <*> bget castToWindow "winNecControl"

编译输出如下错误:

.../src/NecControlGUI.hs:16:23: error:
    • Couldn't match type ‘Adjustment’ with ‘Window’
      Expected type: IO Window
        Actual type: IO Adjustment
    • In the second argument of ‘(<*>)’, namely
        ‘bget castToWindow "winNecControl"’
      In a stmt of a 'do' block:
        NecControlGUI <$> bget castToAdjustment "adjBalance"
        <*> bget castToWindow "winNecControl"
      In the expression:
        do { bdr <- builderNew;
             builderAddFromFile bdr guiPath;
             let bget = builderGetObject bdr;
             NecControlGUI <$> bget castToAdjustment "adjBalance"
             <*> bget castToWindow "winNecControl" }

.../src/NecControlGUI.hs:16:28: error:
    • Couldn't match type ‘Window’ with ‘Adjustment’
      Expected type: GObject -> Adjustment
        Actual type: GObject -> Window
    • In the first argument of ‘bget’, namely ‘castToWindow’
      In the second argument of ‘(<*>)’, namely
        ‘bget castToWindow "winNecControl"’
      In a stmt of a 'do' block:
        NecControlGUI <$> bget castToAdjustment "adjBalance"
        <*> bget castToWindow "winNecControl"

编辑 2016-09-20 2

如果我去掉 let bdr = ... 语句,一切都会按预期进行。

这不是您要查找的内容,但它更短。使用 Applicative 实例将对 builderGetObject 的调用与数据构造函数结合起来。

module GUI where

import Graphics.UI.Gtk

data NecControlGUI = NecControlGUI
    { ...
    }

loadGUI :: String -> IO NecControlGUI
loadGUI guiPath = do
    bdr <- builderNew

    builderAddFromFile bdr guiPath

    let bget = builderGetObject bdr

    NecControlGUI <$> bget castToWindow "winNecControl"
                  <*> bget castToEntry "entIPAddress"
                  <*> bget castToButton "btnConnection"
                  <*> bget castToComboBox "cbtVideoInput"
                  <*> bget castToComboBox "cbtPIPInput"
                  <*> bget castToScale "sclSharpness"
                  <*> bget castToScale "sclContrast"
                  <*> bget castToScale "sclBrightness"
                  <*> bget castToScale "sclBlackLevel"
                  <*> bget castToScale "sclColorTemparature"
                  <*> bget castToComboBox "cbtGamma"
                  <*> bget castToScale "sclBalance"
                  <*> bget castToScale "sclTreble"
                  <*> bget castToScale "sclBass"
                  <*> bget castToScale "sclVolume"
                  <*> bget castToComboBox "cbtLanguage"
                  <*> bget castToScale "sclMenuDisplayTime"