F# and XAML and OpenSilver ctd.
Last time, we concluded that what we really want is a reimplementation of the Silverlight System.Windows.Application.LoadComponent
, whereas what we have at the moment is generated C# code built from the XAML.
In practice, those generated InitializeComponent
methods are a compile-time version of what the runtime component load would give us. This leads to a next step that takes us in the direction of what our ideal would be, and is in any case closer to the initial manual code-only approach.
Start with a new OpenSilver application, and add an F# netstandard2.0
library like last time. This time, however, make this assembly depend on the primary OpenSilver project, and the .Simulation
and .Browser
projects depend on the F# library. Add OpenSilver to the F# library project, as a nuget package, but this time there's no need to add any ExcludeAssets
qualifier, nor to static link the F# core library.
The F# library now contains the Page
(or UserControl
) and Application
classes that you would want to write, but their XAML goes into the C# project that it builds against. In the XAML project, remove everything in the initial classes after the // Enter construction logic here...
comments. In the .Simulation
project, point the debug parameters at the F# library, and in the .Browser
project's RunApplication
method, construct the F# Application
type. This is important, otherwise nothing will show when you try to run the application!
We can now go one of two ways to invoke the InitializeComponent
methods into the F# code.
First, we can treat the generated InitializeComponent
code as almost the static utility LoadComponent
method we would desire; in which case, in the F# library, the Application
leeches from the corresponding generated code by
member this.InitializeComponent() = let prototype = Inversion.App() this.Resources <- prototype.Resources
and the Page
similarly
member this.InitializeComponent() = let prototype = Inversion.MainPage() prototype.InitializeComponent() this.Content <- prototype.Content let hack = typeof.GetField("_nameScopeDictionary", System.Reflection.BindingFlags.Instance ||| System.Reflection.BindingFlags.NonPublic) hack |> Option.ofObj |> Option.map ((fun f -> f.GetValue prototype) >> Option.ofObj) |> Option.flatten |> Option.map(fun o -> o :?> System.Collections.Generic.Dictionary<string, Object>) |> Option.iter (fun dict -> dict |> Seq.iter (fun kvp ->>this.RegisterName(kvp.Key, kvp.Value)))
takes the work from its pre-processed XAML, here using an over-engineered construction to get at the private name registration dictionary while avoiding any hint of NRE. These methods should then be called from the respective F# type constructors.
Alternatively, we remove the sealed
qualifier from the generated Application
, mark it and the MainPage
class abstract
, and let the F# types directly subclass the skeleton types in the C# library; this case needs no F# level InitializeComponent
methods as the code we're interested in already gets called during the base class constructor call.
If this starts with the same application code as previous -- here my original Silverlight ephemeris clock -- we get the same visual appearance as with the previous approach, so no extra pictures in this post.