Skip to content
This repository has been archived by the owner on Jun 25, 2023. It is now read-only.

Latest commit

 

History

History
181 lines (153 loc) · 7.36 KB

Basic-Template.md

File metadata and controls

181 lines (153 loc) · 7.36 KB
layout title author list-order guide-category
guide
Basic Template
Avalonia Community
1
beginner

The basic template contains three files

  • {ProjectName}.fsproj
  • Counter.fs
  • Program.fs

this is the simplest way to get started with Avalonia.FuncUI it's a straight "counter" sample

Program.fs

Inside Program.fs two main classes allow you to start your Avalonia application

The first one you'll see it's

type MainWindow() as this =
    inherit HostWindow()
    do
        base.Title <- "Basic"
        base.Width <- 400.0
        base.Height <- 400.0

        //this.VisualRoot.VisualRoot.Renderer.DrawFps <- true
        //this.VisualRoot.VisualRoot.Renderer.DrawDirtyRects <- true


        Elmish.Program.mkSimple (fun () -> Counter.init) Counter.update Counter.view
        |> Program.withHost this
        |> Program.run

this is (as the name says) the main window of the basic template, it inherits from HostWindow which is a special class that adds some of the functionality needed to allow normal Avalonia Windows to work fine with the Elmish based approach.

Anything you would do with a window in Avalonia, you may do so with any class that inherits HostWindow, in this case, the most important part is the last three lines of code

Elmish.Program.mkSimple (fun () -> Counter.init) Counter.update Counter.view
|> Program.withHost this
|> Program.run

Here we're starting the Elmish program with our Counter module defined inside Counter.fs, then we set up the host Program.withHost and lastly run the program.

the second class is

type App() =
    inherit Application()

    override this.Initialize() =
        this.Styles.Load "avares://Avalonia.Themes.Default/DefaultTheme.xaml"
        this.Styles.Load "avares://Avalonia.Themes.Default/Accents/BaseDark.xaml"

    override this.OnFrameworkInitializationCompleted() =
        match this.ApplicationLifetime with
        | :? IClassicDesktopStyleApplicationLifetime as desktopLifetime ->
            desktopLifetime.MainWindow <- MainWindow()
        | _ -> ()

This class is the equivalent of App.xaml and App.xaml.cs in Avalonia here we override the the Initialize() method to load the default styles that come from Avalonia, try switching BaseDark.xaml to BaseLight.xaml to use the light theme on your application, here you may also load your custom styles. For more information on that please check the Full Template.

the OnFrameworkInitializationCompleted() method is overridden to set our MainWindow class as the application's MainWindow

Counter.fs

The counter is a plain Elmish module with the Avalonia.FuncUI DSL in action

namespace Basic

module Counter =
    open Avalonia.Controls
    open Avalonia.FuncUI.DSL
    open Avalonia.Layout

    type State = { count : int }
    type Msg = Increment | Decrement | Reset

    let init = { count = 0 }
    let update (msg: Msg) (state: State) : State =
        match msg with
        | Increment -> { state with count = state.count + 1 }
        | Decrement -> { state with count = state.count - 1 }
        | Reset -> init

    let view (state: State) (dispatch) =
        DockPanel.create [
            DockPanel.children [
                Button.create [
                    Button.dock Dock.Bottom
                    Button.onClick (fun _ -> dispatch Reset)
                    Button.content "reset"
                ]
                Button.create [
                    Button.dock Dock.Bottom
                    Button.onClick (fun _ -> dispatch Decrement)
                    Button.content "-"
                ]
                Button.create [
                    Button.dock Dock.Bottom
                    Button.onClick (fun _ -> dispatch Increment)
                    Button.content "+"
                ]
                TextBlock.create [
                    TextBlock.dock Dock.Top
                    TextBlock.fontSize 48.0
                    TextBlock.verticalAlignment VerticalAlignment.Center
                    TextBlock.horizontalAlignment HorizontalAlignment.Center
                    TextBlock.text (string state.count)
                ]
            ]
        ]

You can refer the patern represented here as MVU (Model, View, Update) and can learn more about it here in the meantime, we will give a brief explanation here

State

type State = { count : int }

Also known as Model in the MVU acronym. The State type represents the shape of your data for this module.

In this case, we defined the shape of our data as { counter: int } which means our model has a counter that contains an integer value. The init function gives us the initial value which we used inside Program.fs

Elmish.Program.mkSimple (fun _ -> Counter.init) Counter.update Counter.view

in this case, the counter starts at 0 { count = 0 }

Msg

The Msg type allows us to identify which "events" our application is responding to

type Msg = Increment | Decrement | Reset

in this case, we have three possible events

  • Increment
  • Decrement
  • Reset

these Msg types should be descriptive of what they are doing/reacting to in your module

Update

In F# the data is often immutable this means every time you need to do changes you have to return updated copies of your data. The update function is the central place where you make changes to your state (model) depending on the message that was dispatched

let update (msg: Msg) (state: State) : State =
    match msg with
    | Increment -> { state with count = state.count + 1 }
    | Decrement -> { state with count = state.count - 1 }
    | Reset -> init

In this case, the messages are simple as well as the state, so the changes are pretty self-descriptive Increment updates the count by adding 1 integer, Decrement substracts 1 integer, Reset takes our initial model back to 0

View

This will be a brief explanation of the parts that glue everything together, for other examples and a more concise explanation of the View Controls and their attributes, you can check Views and Attributes

let view (state: State) (dispatch: Msg -> unit) =
    DockPanel.create [
        DockPanel.children [
            Button.create [
                Button.onClick (fun _ -> dispatch Reset)
            ]
            Button.create [
                Button.onClick (fun _ -> dispatch Decrement)
            ]
            Button.create [
                Button.onClick (fun _ -> dispatch Increment)
            ]
            TextBlock.create [
                TextBlock.text (string state.count)
            ]
        ]
    ]

The view has two parameters: state which is the current state (model) of your module as well as a dispatch function, this dispatch function takes a Msg (it can be any of our Msg types: Increment, Decrement, Reset) and doesn't have a special return type (unit) Elmish and Avalonia.FuncUI glue the dispatch function. To use the information that is stored in the state, we simply use state.{property} In this case, we have a text block (label) that renders out the current count on the screen.