How can I enable a WinForms or WPF project in F#?

前端 未结 4 1761
盖世英雄少女心
盖世英雄少女心 2021-02-05 05:15

I have the very latest version of Visual Studio 2017 installed. I selected F# language support and F# desktop support. After restarting and going to File -> New Project I was ex

4条回答
  •  醉话见心
    2021-02-05 06:10

    This isn't an easy answer. There is no built-in project template for those projects because neither the Windows Forms nor the WPF designer understand F#. We just got the ASP.NET Core templates for F# so we shouldn't complain... much.

    On the other hand, a Winforms or WPF application is just C# code. Anything you do in the designer ends up as code in the form file. A Winforms or WPF application are "just" Console applications that set up the proper environment and create the forms and controls.

    This is actually easier using XAML and WP, especially if you use MVVM or a similar architecture. That's because you can develop the XAML markup separately from the F# code and bind your data to XAML. You can even create a WPF Class library project, create Windows, pages and controls there without any code and then use them in your F# project.

    There are a few libraries that make this easier. One option is Elmish.WPF. You can create the XAML pages and windows in a separate project and then create models etc in F#, bind them together and start the main form. If you check the repo's example most of the code defines records and commands as F# functions, eg :

    type ClockMsg =
        | Tick of DateTime
    
    type ClockModel =
        { Time: DateTime }
    
    type Msg =
        | ClockMsg of ClockMsg
        | Increment
        | Decrement
        | SetStepSize of int
    
    type Model = 
        { Count: int
          StepSize: int
          Clock: ClockModel }
    

    and

    let init() = { Count = 0; StepSize = 1; Clock = { Time = DateTime.Now }}
    
    let clockUpdate (msg:ClockMsg) (model:ClockModel) =
        match msg with
        | Tick t -> { model with Time = t }
    
    let update (msg:Msg) (model:Model) =
        match msg with
        | Increment -> { model with Count = model.Count + model.StepSize }
        | Decrement -> { model with Count = model.Count - model.StepSize }
        | SetStepSize n -> { model with StepSize = n }
        | ClockMsg m -> { model with Clock = clockUpdate m model.Clock }
    

    Binding them together is relatively simple :

    let view _ _ = 
        let clockViewBinding : ViewBindings =
            [ "Time" |> Binding.oneWay (fun m -> m.Time) ]
    
        [ "Increment" |> Binding.cmd (fun m -> Increment)
          "Decrement" |> Binding.cmdIf (fun m -> Decrement) (fun m -> m.StepSize = 1)
          "Count" |> Binding.oneWay (fun m -> m.Count)
          "StepSize" |> Binding.twoWay (fun m -> (double m.StepSize)) (fun v m -> v |> int |> SetStepSize)
          "Clock" |> Binding.vm (fun m -> m.Clock) clockViewBinding ClockMsg ]
    

    And running the entire application

    []
     let main argv = 
         Program.mkSimple init update view
         |> Program.withSubscription subscribe
         |> Program.runWindow (Elmish.CounterViews.MainWindow())
    

    The code above comes from the Counter sample. The XAML views are defined in the CounterViews project. You can even delete the .cs files and the project will compile just fine.

提交回复
热议问题