fsprojects / Avalonia.FuncUI

Develop cross-plattform GUI Applications using F# and Avalonia!
https://funcui.avaloniaui.net/
MIT License
955 stars 74 forks source link

Trouble with Grid row/columns when switching views #309

Closed b0wter closed 1 year ago

b0wter commented 1 year ago

I am only getting started with Avalonia und FuncUI so please excuse me if I made a basic error. My problem is a Grid. Or at least I am pretty sure that it is. My app is like a wizard. The user can switch between multiple pages (that each occupy the complete window). My issue is that whenever I switch pages I get an exception like this:

Unhandled exception. System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
   at System.SZArrayHelper.get_Item[T](Int32 index)
   at Avalonia.Controls.Grid.MeasureCellsGroup(Int32 cellsHead, Size referenceSize, Boolean ignoreDesiredSizeU, Boolean forceInfinityV, Boolean& hasDesiredSizeUChanged) in /_/src/Avalonia.Controls/Grid.cs:line 1001
   at Avalonia.Controls.Grid.MeasureOverride(Size constraint) in /_/src/Avalonia.Controls/Grid.cs:line 427
   at Avalonia.Layout.Layoutable.MeasureCore(Size availableSize) in /_/src/Avalonia.Base/Layout/Layoutable.cs:line 532
   at Avalonia.Layout.Layoutable.Measure(Size availableSize) in /_/src/Avalonia.Base/Layout/Layoutable.cs:line 364
   at Avalonia.Controls.Grid.MeasureOverride(Size constraint) in /_/src/Avalonia.Controls/Grid.cs:line 231
   ...

I am using the Elmish way to build a UI so I went ahead and created a shell that holds:

type CurrentPageModel =
    | WelcomeModel of WelcomePage.Model
    | Step1Model of Step1Page.Model
    | ...

type Model = {
    currentModel: CurrentPageModel
    ...
}

type Msg =
    | WelcomeMsg of WelcomePage.Msg
    | Step1Msg of Step1Page.Msg
    | ...

let update (msg: Msg) (model: Model) : Model * Cmd<_> =
    match model.currentModel, msg with
    // --- WelcomePage ---
    | WelcomeModel _, WelcomeMsg WelcomePage.Msg.StartStep1 ->
        let Step1State, Step1Cmd = Step1Page.init
        { model with currentModel = Step1Model Step1State }, Cmd.map Step1Msg Step1Cmd
    | WelcomeModel welcomeState, WelcomeMsg welcomeMsg ->
        let newState, newCmd = WelcomePage.update welcomeMsg welcomeState
        { model with currentModel = WelcomeModel newState }, Cmd.map WelcomeMsg newCmd
    | _, WelcomeMsg _ ->
        model, Cmd.none

    // --- Step1 Page ---
    | Step1Model Step1State, Step1Msg iMsg ->
        let Step1State, Step1Cmd = Step1Page.update iMsg Step1State
        { model with currentModel = Step1Model Step1State }, Cmd.map Step1Msg Step1Cmd
    | _, Step1Msg _ ->
        model, Cmd.none

    // --- Non-Page-Messages ---
    | _, Exit ->
        do (Application.Current.ApplicationLifetime :?> IClassicDesktopStyleApplicationLifetime).Shutdown()
        model, Cmd.none

let view (model: Model) dispatch =
    Grid.create [
        Grid.children [
            match model.currentModel with
            | WelcomeModel model -> (WelcomePage.view model (WelcomeMsg >> dispatch))
            | InspectionModel model -> (InspectionPage.view model (InspectionMsg >> dispatch))
        ]
    ]

The view in WelcomePage uses a Grid to display its content:

let view (model: Model) (dispatch: Msg -> unit) =
    Grid.create [
        Grid.rowDefinitions "*,Auto,*"
        Grid.columnDefinitions "*,Auto,*"
        Grid.children [
            StackPanel.create [
                Grid.row 1
                Grid.column 1
                StackPanel.children [
                    TextBlock.create [
                        TextBlock.text "Welcome"
                    ]
                    Button.create [
                        Button.onClick (fun _ -> dispatch StartStep1)
                        Button.content (
                            TextBlock.create [
                                TextBlock.text "Button 1"
                            ]
                        )
                    ]
                    Button.create [
                        Button.onClick (fun _ -> dispatch SomethingElse)
                        Button.content (
                            TextBlock.create [
                                TextBlock.text "Button 2"
                            ]
                        )
                    ]
                ]
            ]
        ]
    ]    

Clicking the first button (which triggers the shell to replace the current model) results in the aforementioned exception. Removing Grid.column 1 and Grid.row 1 fixes the problem but that does not seem like an actual solution. I would like those controls to be centered 😅

What am I doing wrong? Is the setup with the shell the problem? Or am I just misusing something?

JaggerJo commented 1 year ago

@b0wter could you post a full runnable sample?

A few things:

b0wter commented 1 year ago

By creating a small runnable example I accidentally fixed my problem. I looked at the wrong part of the code. It was not the currently loaded page that caused the problem but the newly loaded page. 😓 Sorry for the ruckus.

Why do you recommend the component system? So far, I've only build functional UIs with Elm.