Showing posts with label silverlight. Show all posts
Showing posts with label silverlight. Show all posts

Thursday, July 11, 2024

F# and OpenSilver v3.0

An update to the previous series of posts.

Having updated to the OpenSilver3.0 .vsix, the process of porting the standard astroclock test app from 2.1 to a newly minted 3.0 base solution was

  1. Add a global.json to keep the dotnet 9.0 pv SDK from being selected by default
  2. Copy the appliction source and resource files from the 2.1 project
  3. Add the extra files to the project - in this case, Computation.fs and textured_paper.png
  4. Adjust the app namespace in the browser and simulator projects
  5. And that's it.

The process was entirely transparent from the app and user experience point of view.

Only the one niggle - an obsolete code warning in the generated browser project persists from previous releases

Severity	Code	Description	Project	File	Line	Suppression State	Details
Warning (active)	CS0618	'WebAssemblyJSRuntime.InvokeUnmarshalled<T0, TResult>(string, T0)' is obsolete: 'This method is obsolete. Use JSImportAttribute instead.'	astroclock.opensilver3._0.Browser	D:\Github\astroclock\astroclock.opensilver3.0\astroclock.opensilver3.0\astroclock.opensilver3._0.Browser\Interop\UnmarshalledJavaScriptExecutionHandler.cs	19		

The code lives here.

Now when life quietens down, I shall have to port my other work-in-progress project, that has been languishing for several months now, from 2.1 to 3.0 as well.

Saturday, February 10, 2024

F# and OpenSilver v2.1

An update to the previous series of posts, just three months after the last one.

Compared to previous iterations, "it just works". The only changes from the original Silverlight code are

  • The XAML is precompiled where it stands in a new F# App project, rather than being loaded at runtime
  • The Y-coordinate related changes in the XAML and F# from 2.0 need to be applied
  • The background image is compiled as Content, and in the XAML, referred to by ImageSource="ms-appx://textured_paper.png", but is otherwise as before (note, I've change the background texture on my website in the last 15 years)

In particular, no messing about with IntermediateOutputPath, or having to create stubs for anything; as I said, "it just works".

Now it's all in one place, I've deployed a live version too.

Wednesday, November 08, 2023

F# and OpenSilver v2.0 (Continued)

An update to the previous series of posts.

Building while NuGetting

While I stand by my assertion that the previous post showed the simplest way to build (by not using NuGet to get the package, but referencing the key assembly the old-fashioned way) here is how to build with OpenSilver 2.0.1 as a NuGet reference. First, add properties

    <SkipXamlPreprocessor>true</SkipXamlPreprocessor>
    <OpenSilverGenerateAssemblyInfo>false</OpenSilverGenerateAssemblyInfo>

then if there isn't already a $(ProjectName).OpenSilver.XamlDesigner.fs file in the root of the project (generated there by the OpenSilver compiler taking some untested path before it was switched off), it should look like this:

// <auto-generated>
//     Generated by the FSharp WriteCodeFragment class.
// </auto-generated>
namespace FSharp

open System
open System.Reflection


[<assembly: System.Runtime.CompilerServices.InternalsVisibleTo("XamlDesignerBackground")>]
do()

added with Compile Action of None, and then see it's copied into the appropriate place by

  <Target Name="FixUp" BeforeTargets="BeforeCompile;CoreCompile">
    <ItemGroup>
        <MisgeneratedFiles Include="$(ProjectName).AssemblyInfo.OpenSilver.XamlDesigner.fs"/>
    </ItemGroup>
    
    <Copy SourceFiles="@(MisgeneratedFiles)"
          DestinationFolder="$(IntermediateOutputPath)"
        />
  </Target>

Publishing less

In the browser project, rather than enabling AOT, which saves time, and not space, have properties

    <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
    <BlazorEnableCompression>false</BlazorEnableCompression>
    <!-- Uncomment to enable AOT compilation when publishing -->
    <!--<RunAOTCompilation>true</RunAOTCompilation>-->

and it doesn't hurt to switch of satellites in the Presentation project, either.

That takes us from

to

Thursday, November 02, 2023

F# and OpenSilver v2.0

An update to the previous series of posts following the v2.0 stable release of OpenSilver.

The good news is that proper F# support in promised in the pipeline, but for now we still have to do some work.

Proceed as before, but in the F# project that inherits from the abstract C# w/XAML, a NuGet based reference to OpenSilver 2.x invokes the XAML preprocessor, and adds a file in the intermediate output directory to the compilation list. I've not found any simpler way to suppress this than by side-stepping NuGet entirely for this project

    <Reference Include="OpenSilver">
      <HintPath>$(NuGetPackageRoot)opensilver\2.0.1\lib\netstandard2.0\OpenSilver.dll</HintPath>
    </Reference>

and having to manage the version.

The other thing I noticed in 2.0 was that the sense of the Y-axis seems to have changed wrt render transforms, e.g. to draw a clock face with a series of lines radiating from a centre of (120,120) like this, rotations incrementing 30 degrees at a time, the negative Y values from 1.1 needed to be positive

        <Line
      Name="t1"
      X1="0" Y1="-108"
      X2="0" Y2="-100"
      Stroke="White"
      StrokeThickness="2">
          <Line.RenderTransform>
            <CompositeTransform Rotation="30" TranslateX="120" TranslateY="120" />
          </Line.RenderTransform>
        </Line>

and for the clock hands, add an extra 180 degrees as well as negating the Y values (that the unlabelled 1 o'clock and 7 o'clock lines have swapped makes no odds, but the hands are important).

The resulting code for running with v2.0 can be found here. The published code comes to ~130Mb, which is a lot chunkier than 25kb of JavaScript needed to do the job, but I guess it could be possible for many apps to share the same library files.

Friday, October 07, 2022

F# and OpenSilver v1.1

An update to the previous series of posts following the v1.1 stable release of OpenSilver.

The recipe given as before just works, when all packages are updated and the browser project build is done with net6.0. One warning is emitted using the latest (6.0.401) SDK, about the IntermediateOutputPath override. The fix is to create a Directory.Build.props in the browser project directory, and move the two lines added to the project previously to the new file

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <IntermediateOutputPath>shorter file path here</IntermediateOutputPath>
    <BaseIntermediateOutputPath>$(IntermediateOutputPath)</BaseIntermediateOutputPath>
  </PropertyGroup>
</Project>

While the System.Windows.Application.LoadComponent API now exists, it requires build-time XAML compilation, and that the named class which it decorates exists in the same assembly, so at this time would only make an adjustment to the C# assembly, replacing the constructor InitializeComponent call with one to LoadComponent, but leaving the same inheritance in the F# layer.

It is possible that the 2.0 release will allow XAML compilation in the F# layer, but on-the-fly compilation seems not to be on the cards.

Also, in language independent mode, the ImageBrush and ImageSource APIs used by the original Silverlight AstroClock are now present. Alas, while that means uncommenting that XAML clause can be done without compile or runtime error, the interesting bits are marked NotImplemented, and live in a "work in progress" source folder, so there is currently no point in doing so.

The resulting code for running with v1.1 can be found here.

Wednesday, October 13, 2021

F# and OpenSilver v1.0

An update to the previous series of posts following the first stable release of OpenSilver.

While the details of which lines of code in the Browser and Simulator projects need to be redirected at the F# types have changed since the earlier posts at alpha-7 release, the principle remains the same -- where the generated code refers to the C#/XAML App type, replace that with the derived F# type. The F# (logic) and C# (just the XAML, reified as abstract base types) are set up exactly as before.

If you're porting an old solution, from a pre-release OpenSilver, unless you've been staying on the bleeding edge, it's simpler to start by creating a new one, copying the F# project and the XAML project from old to overwrite the new, and otherwise starting from scratch, remembering to check in all the as-generated files before making the edits.

At this point, provided that all the repointing to the F# project and types is done correctly, it should all "just work".

There is one gotcha, though, that has appeared in the updates for publishing the Browser WASM since my alpha-7 exploration, in that some potentially very long file paths can now be invoked that point deep under the project's default obj subfolder. If your project is already down a few layers from the drive root it will run foul of the Copy MSBuild task which appears to still labour under the old DOS MAX_PATH of 255 characters. Copying to an overlong path fails by timing out, so breaking the build. In that case add

    <IntermediateOutputPath>shorter file path here</IntermediateOutputPath>
    <BaseIntermediateOutputPath>$(IntermediateOutputPath)</BaseIntermediateOutputPath>

where a suitable up-tree location is specified e.g. starting at the top of the repo, or in a shallow build output directory outside the repo, rather than as a child of a project directory. Note that both values need to be set and aligned, lest defaults be assumed during the process.

The resulting code can be found here.

Thursday, March 04, 2021

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.

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.

Wednesday, March 03, 2021

F# and OpenSilver -- first steps

This set of lab notes turned into a series of 3 4 (with the 1.0 release) posts.


Many years ago, I made a series of posts experimenting with F# (then still in preview) and Silverlight

  • documenting some of the hacks required to get it to work. Now, more than a decade later, WASM is the new in-browser hotness, with many approaches to getting your non-JS code to run in browser.

    One of these is OpenSilver, which is intended as a near as possible drop-in replacement for Silverlight. Like the original, it assumes C# and XAML with code-behind first, but let's see what we can do about that...

    Assuming you have OpenSilver set up, then the equivalent of the first post above is relatively simple. Make a new OpenSilver application solution, then replace the actual application project with a new netstandard2.0 F# library (in the solution and as a project reference in the two helper projects). Add OpenSilver as a nuget package to the F# project, then paste in the code from the same first working example to replace the initial stub code. In the .Browser project, fix the type of the App class -- new SilverLightFSharp.MyApp(); -- and then run whichever helper.

    It should look like this :--

    in the simulator after you click the button.

    Next -- Adding XAML, initial attempts

    Monday, November 08, 2010

    Links for 8-Nov

    Asynchronous programming in .net

    Also from the PDC -- what next for Silverlight?

    Rx Design Guidelines.

    SSL/TLS isn't computationally expensive.

    NuGet -- gems for .net has a new name.

    The Should Assertion Library.

    Sandcastle MSBuild integration.

    Monday, December 14, 2009

    Using a different mscorlib for F# in Visual Studio (Silverlight and probably XNA too)

    It turns out that I was lucky last year when building an F# Silverlight 2 application by hand in Visual Studio -- I didn't make an explicit reference to mscorlib.

    If you explicitly browse to the Silverlight mscorlib assembly to make an explicit reference, the .fsproj file still only gets the basic

        <Reference Include="mscorlib"/>

    which falls back to the full framework version of the assembly when you compile. This behaviour has caused some people to abandon the exercise of getting F# to build against something non-default in Visual Studio.

    However, the way forward is very simple indeed -- simply edit the .fsproj to give an explicit hint path pointing at the mscorlib you really mean:

        <Reference Include="mscorlib">
          <HintPath>C:\Program Files\Reference Assemblies\Microsoft\Framework\Silverlight\v3.0\mscorlib.dll</HintPath>
        </Reference>

    and get the confirmation that this sticks in the command line that echoes to the Output window

    C:\Program Files\FSharp-1.9.7.8\\bin\fsc.exe -o:obj\Debug\Library3.dll -g --debug:full --noframework --define:DEBUG --define:TRACE --optimize- --tailcalls- -r:"C:\Program Files\FSharp-1.9.7.8\Silverlight\2.0\bin\FSharp.Core.dll" -r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\Silverlight\v3.0\mscorlib.dll" --target:library --warn:3 --warnaserror:76 --vserrors --utf8output --fullpaths --flaterrors Module1.fs

    Problem sorted.

    (Generalise as required to any other assemblies which refuse to stay pointed at the platform you want.)

    Save the version number in the output, this advice is unchanged by the February 2010 CTP.

    Saturday, December 05, 2009

    F# and Silverlight 3 addendum

    You can make a permanent fix for the hint path needed to find FSharp.Core.dll by editing the template in C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplatesCache\FSharp\Silverlight\SilverlightLibrary3.zip\SilverlightLibrary.fsproj (and probably the version of the file in C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplates\FSharp\Silverlight\SilverlightLibrary3.zip just to be certain).

    Proof of concept -- my orrery-clock upgraded to Silverlight 3.0 (with all the source bundled into the .xap file) here.

    F# October CTP and Silverlight 3.0

    With the February 2010 CTP, obviously change the version number to 1.9.9.9 instead!

    Following on from last year's compendium for Silverlight 2 --

    There's an official Silverlight 3.0 project template for Visual Studio 2008; while targetted at the May CTP, these can be used with the October CTP by opening up the .fsproj file and amending the file path for the FSharp.Core assembly -- unload project, edit, and change the version in the hint path for the reference to be 1.9.7.8 like

        <Reference Include="FSharp.Core.dll">
          <HintPath>$(ProgramFiles)\fsharp-1.9.7.8\Silverlight\2.0\bin\FSharp.Core.dll</HintPath>
        </Reference>

    and then reload.

    The template is still useful as it automatically picks up all the other Silverlight platform assemblies from their location in the Silverlight reference assemblies folder, saving you having to do that manually from C:\Program Files\Microsoft SDKs\Silverlight\v3.0\Reference Assemblies and seems to provide some other integration in conjunction with the Silverlight tools.

    Unfortunately, none of the samples provided are pure F# -- they all use a C# bootstrap for XAML and the AppManifest packaging. However we can still use last year's recipe with only minimal changes -- just update the RuntimeVersion in AppManifest.xaml to "3.0.40818.0", and the FSharp.Core.dll which you wrap in the .xap file is, of course, the one from the October CTP at C:\Program Files\FSharp-1.9.7.8\Silverlight\2.0\bin\FSharp.Core.dll (not the one from last year!)