[ Avalonia ] [ 0.9.11 ] How to use ManagedSystemDialogs?

I have to use file dialogs but CoreRt doesn’t support them yet. Starting from 0.9 version Avalonia provides managed dialogs. I’d expect the only part that I have to change is AppBuilder:

.UseManagedSystemDialogs<AppBuilder>()

but, nope, when I tried to run app I got InvalidOperationException:

Call from invalid thread`

MCVE:

.fsproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Avalonia.Desktop" Version="0.9.11" />
  </ItemGroup>

</Project>

.fs

open Avalonia.Controls
open System

type MainWindow () as self = 
    inherit Window ()
    
    let txb = TextBox(IsReadOnly=true)
    let btn = Button(Content="Select folder")
    do btn.Click.Add(fun _ ->
       let path =
           async {
               let ofg = OpenFolderDialog()
               return! ofg.ShowAsync(self) |> Async.AwaitTask
           } |> Async.RunSynchronously
       txb.Text <- path
    )

    let sp = StackPanel()
    
    do sp.Children.Add(txb)
    do sp.Children.Add(btn)
    
    do self.Content <- sp

open Avalonia
open Avalonia.Controls.ApplicationLifetimes
open Avalonia.Markup.Xaml.Styling

type App() =
    inherit Application()

    override x.Initialize() =
        x.Styles.AddRange [ 
            new StyleInclude(baseUri=null, Source = Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"))
            new StyleInclude(baseUri=null, Source = Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"))
        ]

    override x.OnFrameworkInitializationCompleted() =
        match x.ApplicationLifetime with
        | :? IClassicDesktopStyleApplicationLifetime as desktop ->
             desktop.MainWindow <- new MainWindow()
        | _ -> ()

        base.OnFrameworkInitializationCompleted()

open Avalonia.Dialogs

[<CompiledName "BuildAvaloniaApp">] 
let buildAvaloniaApp () = 
    AppBuilder.Configure<App>()
              .UsePlatformDetect()
              .UseManagedSystemDialogs<AppBuilder>()

[<STAThread>][<EntryPoint>]
let main argv =
    buildAvaloniaApp().StartWithClassicDesktopLifetime(argv)

I tried to use Avalonia.Threading.Dispatcher.UIThread.InvokeAsync, Async.SwitchToContext, even totally replace Async with Task. No matter what I tried I wasn’t able to see selected path.

The issue with Async.RunSynchronously function. Use Async.StartImmediate

do btn.Click.Add(fun _ ->
    async {
        let ofg = OpenFolderDialog()
        let! path = ofg.ShowAsync(self) |> Async.AwaitTask
        txb.Text <- path
    } |> Async.StartImmediate
)

or Async.StartWithContinuations instead:

let setPath path = txb.Text <- path
do btn.Click.Add(fun _ ->
    Async.StartWithContinuations(async {
        let ofg = OpenFolderDialog()
        return! ofg.ShowAsync(self) |> Async.AwaitTask
    }, setPath, ignore, ignore)
)

Don’t forget to replace ignore functions with something appropriate in a real code :slight_smile:


Thanks VisualMelon for the tips.

1 Like