Thursday, March 04, 2021

F# and XAML and OpenSilver

Emboldened by yesterday's success, what happens when we try to emulate the next steps?

First off, when pasting the F# code into the project, we see that System.Windows.Application.LoadComponent isn't present. Adding the XAML file has that subsumed by the OpenSilver environment, to appear as a <Page/> element in the project file that generates C# code that also fails at compile time. That latter can be switched off by modifying the package reference to look like

    <PackageReference Include="OpenSilver" Version="1.0.0-alpha-007">
      <ExcludeAssets>build; native; contentfiles; analyzers</ExcludeAssets>
    </PackageReference>

but we still don't have the runtime-generated model that System.Windows.Application.LoadComponent would give us.

Without that support, we can still use the hybrid approach of the early F# Silverlight days.

In this case, start with a new OpenSilver project and add an F# netstandard2.0 library as a dependencyand add the modified package reference to the new F# library, to allow access to the GUI types. The C# project gets the project-related XAML file, with the necessary modifications for porting to OpenSilver, and the F# code now, rather than subclassing a UI element, works by composition and delegation, much as for the F#/Silverlight3.0 template for VS2008.

The F# type, rather than subclassing a UI type now takes one as a construction argument, and the C# initializer, having constructed the main control and initialized the UI tree can simply pass that object to the F# type constructor, storing the instance as a member variable. Any event handlers can then delegate to the F# object as well.

  public partial class MainPage : Page
  {
    private Astroclock.AstroPage carry;

    public MainPage()
    {
      this.InitializeComponent();

      var temp = new Astroclock.AstroPage(this);
      temp.Begin();
      carry = temp;
    }
  ...

At this point, attempting a build fails in the C# layer with

2>MSBUILD : error : C#/XAML for HTML5: BeforeXamlPreprocessor (pass 1) failed: System.IO.FileNotFoundException: Could not load file or assembly 'FSharp.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.

so we go back and add

    <OtherFlags>--standalone</OtherFlags>  

to the top-level Property Group on the F# project, and suddenly things start working.

For an example at this stage, I've ported the full applet to OpenSilver here. The other significant change to mention is that rather than just being a UserControl embedded into HTML, this is now a Page with the static HTML text included as TextBlock elements instead, but that is a generic OpenSilver behaviour I've just gone with the flow on, not something F# specific. Nor, I suspect, are the not quite circular circles...

Of course what we really want is a reimplementation of the Silverlight System.Windows.Application.LoadComponent. It's just a pity that that dives into native code in agcore.dll to do the interesting bits.

Also, alas, as currently implemented the browser version doesn't update even though the simulator does. Maybe if the updates were an event driven thing, like the button in the previous example?

Update: Events did the trick -- removing the sleepy BackgroundWorkers from the F# code, and instead adding a DispatcherTimer to fire the relevant UpdateXxx methods once a second unblocked the browser behaviour. The timer can be in either the C# or the F# layer. Again, I suspect that this is generic OpenSilver/Blazor behaviour, not anything F# specific.

Next -- A more LoadComponent-like approach with other benefits.

No comments :