我通过编辑一个通过点击处理事件的模板应用程序来学习F#
。当按下按钮时,我正试图更新TextBox
。目前,点击只更新到控制台。
这个问题的主要目标是学习。我将尽我最大的努力解释我的思维过程,如果有什么错误,请随时纠正我。
主要:
open FSharpx.Control.Observable // Fsharpx
module Main =
type State = int list
module GuiInterface =
type UserAction =
| Zero
| One
| Two
| Three
let observables = List.reduce Observable.merge [
Observable.map (fun _-> Zero) Gui.btnAdd0.Click;
Observable.map (fun _-> One) Gui.btnAdd1.Click;
Observable.map (fun _-> Two) Gui.btnAdd2.Click;
Observable.map (fun _-> Three) Gui.btnAdd3.Click;
]
let rec loop observable (state : State) = async{
printfn "%A" state
let! somethingObservable = Async.AwaitObservable(observable)
match somethingObservable with
| GuiInterface.Zero -> return! loop observable (0::state)
| GuiInterface.One -> return! loop observable (1::state)
| GuiInterface.Two -> return! loop observable (2::state)
| GuiInterface.Three -> return! loop observable (3::state)
| _ -> failwith "Not implemented yet!"
}
Async.StartImmediate(loop GuiInterface.observables []); System.Windows.Forms.Application.Run(Gui.form)
Gui:
module Gui =
open System.Windows.Forms
let form = new Form(Text="FSharpx", TopMost=true)
let btnAdd0 = new Button(Text="Add 0")
let btnAdd1 = new Button(Text="Add 1", Top=20)
let btnAdd2 = new Button(Text="Add 2", Top=40)
let btnAdd3 = new Button(Text="Add 3", Top=60)
let display = new TextBox(Text="click the buttons", Width=200, Left=200)
form.Controls.AddRange [| btnAdd0 ; btnAdd1; btnAdd2; btnAdd3; display |]
在let observables
中,我认为它将点击转换为我们在用户操作类型中拥有的任何值。然后将它们合并在一起。
当用户单击时,observable
等待输入,然后从GuiInterface
类型转换为程序的任何逻辑。
如前所述,我不知道我将如何处理而不是打印列表到控制台做同样的事情,但TextBox
。我知道列表中包含int类型,需要将其转换为string类型。
要将列表转换为字符串,可以使用sprintf
。您可以将当前的printf
语句替换为以下赋值:
Gui.display.Text <- sprintf "%A" state
这是有效的,但是它可能有点过于命令式,并且它将GUI与逻辑紧密地耦合在一起-对于学习来说很好,但可能不是一个很好的实践。另一种方法是定义一个新的事件(可观察对象),并通过
传递文本更新:type State = int list
module GuiInterface =
type UserAction = Zero | One | Two | Three
let observables = List.reduce Observable.merge [
Observable.map (fun _-> Zero) Gui.btnAdd0.Click;
Observable.map (fun _-> One) Gui.btnAdd1.Click;
Observable.map (fun _-> Two) Gui.btnAdd2.Click;
Observable.map (fun _-> Three) Gui.btnAdd3.Click;
]
let stateChange = new Event<_>()
let rec loop observable (state : State) = async{
GuiInterface.stateChange.Trigger(sprintf "%A" state)
let! somethingObservable = Async.AwaitObservable(observable)
match somethingObservable with
| GuiInterface.Zero -> return! loop observable (0::state)
| GuiInterface.One -> return! loop observable (1::state)
| GuiInterface.Two -> return! loop observable (2::state)
| GuiInterface.Three -> return! loop observable (3::state)
| _ -> failwith "Not implemented yet!"
}
GuiInterface.stateChange.Publish.Add(fun s ->
Gui.display.Text <- s)
Async.StartImmediate(loop GuiInterface.observables [])
System.Windows.Forms.Application.Run(Gui.form)
然而,你也可以用Observable.scan
函数替换你的循环,让你维护状态,并根据其他事件更新它:
type State = int list
module GuiInterface =
type UserAction = Zero | One | Two | Three
let observables = List.reduce Observable.merge [
Observable.map (fun _-> Zero) Gui.btnAdd0.Click;
Observable.map (fun _-> One) Gui.btnAdd1.Click;
Observable.map (fun _-> Two) Gui.btnAdd2.Click;
Observable.map (fun _-> Three) Gui.btnAdd3.Click;
]
let stateChanged =
observables
|> Observable.scan (fun state update ->
match update with
| Zero -> (0::state)
| One -> (1::state)
| Two -> (2::state)
| Three -> (3::state)
| _ -> failwith "Not implemented yet!") []
|> Observable.map (sprintf "%A")
GuiInterface.stateChanged.Add(fun s ->
Gui.display.Text <- s)
System.Windows.Forms.Application.Run(Gui.form)
也就是说,如果你真的对f#中以函数方式开发UI感兴趣,那么最好看看Elmish,它是一组很好的函数抽象——比Windows Forms好用得多。