How to start multiple windows from F# using Elmish.WPF? - f#

I am a newbie to Elmish.WPF and F#. In studying the tutorial on NewWindow/NewWindow.Views, the authors have assigned the following code from C# :
using System;
using Elmish.WPF.Samples.NewWindow;
using static Elmish.WPF.Samples.NewWindow.Program;
namespace NewWindow.Views {
public static class Program {
[STAThread]
public static void Main() =>
main(new MainWindow(), () => new Window1(), () => new Window2());
}
}
That is calling the main method in the F# NewWindow.Views project:
let main mainWindow (createWindow1: Func<#Window>) (createWindow2: Func<#Window>) =
let createWindow1 () = createWindow1.Invoke()
let createWindow2 () =
let window = createWindow2.Invoke()
window.Owner <- mainWindow
window
let bindings = App.mainBindings createWindow1 createWindow2
Program.mkSimpleWpf App.init App.update bindings
|> Program.withConsoleTrace
|> Program.runWindowWithConfig
{ ElmConfig.Default with LogConsole = true; Measure = true }
mainWindow
How can the F# module main routine be changed so as to use it directly as the EntryPoint and avoid it being a function? That is, I would like the F# module to have direct control over the windows via Elmish. Something along the lines as below but with the invocation of the subordinate windows self-contained:
/// This is the application's entry point. It hands things off to Elmish.WPF
[<EntryPoint; STAThread>]
let main _ =
Program.mkSimpleWpf init update bindings
|> Program.runWindow (MainWindow())
In short, I would like the C# Views to have no knowledge of the F# project.
Can this be done with Elmish.wpf?
Any help (especially sample code :) ) would be most helpful.

I am one of the maintainers of Elmish.WPF. Until very recently, all the samples used their F# project as the entry point. If you clone the repo and check out this commit, then you can inspect those samples and see how to achieve your goal.
Going forward, I created this issue to consider including at least one sample with an F# project as its entry point.
In the future, feel free to ask any of your Elmish.WPF questions by opening an issue in our GitHub.

Related

Using log4net in F# as a singleton separate class

I've seen a few posts about implementing log4net using C# and F# - but I am wanting to implement it as a singleton in a separate class ( so I can call from anywhere )
I am loosely following this post. I just think my translation from C# to F# is a bit behind.
I set up the log4net.config and run the following code at the start of my console app
namespace MyNamespace
open System.IO
open log4net.Config
module LoggerConfigure =
let configureLogging() =
FileInfo("log4net.config")
|> XmlConfigurator.Configure
|> ignore
The following ( from the link above ) is C# and I want it to be an F# class that can be called as a singleton.
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
My primary quwation is - I'm a bit stuck converting that to an F# class. How do I do that?. Any ideas you have around the whole concept is appreciated as well.
There may be cases where a more sophisticated handling of singletons is needed, but I believe that standard global let declaration in an F# module would work well enough.
You just need to make sure that the configuration code is run before the log value is accessed, which you can do by making that call as part of the let binding that defines log:
module LoggerConfigure =
let configureLogging() =
FileInfo("log4net.config")
|> XmlConfigurator.Configure
|> ignore
log4net.LogManager.GetLogger
(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
let log = configureLogging()
Or if you prefer to put everything in a single expression:
module LoggerConfigure =
let log =
FileInfo("log4net.config") |> XmlConfigurator.Configure |> ignore
log4net.LogManager.GetLogger
(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)

Type Elmish.WPF main() function reports binding () -> #Window as an error

Why does my version of the Elmish.WPF Sample NewWindow (XAML code and F# Core) emit the error ...
The type 'unit -> 'a' is not compatible with the type 'Window'.
The same line emits the warning...
This construct causes code to be less generic than indicated by its type annotations. The type variable implied by the use of a '#', '_' or other type annotation at or near <line reference to createWindow_Window2 in the let bindings = statement">
Why am I getting this error and warning?
What I am doing is merging the Elmish.WPF Samples SingleCounter (XAML code and F# Core) and NewWindow (XAML code and F# Core) to have the Model, bindings(), and Msg parts in Program.fs instead of App.fs (as it was in the NewWindow sample).
My goal is to make a SimpleCounter able to open a NewWindow.
The XAML code passes Func<Window2> into the F# code here...
let main mainWindow (createWindow2: Func<#Window>)
...I define bindings as...
let bindings = Platform.bindings createWindow_Window2
I am down to the one compiler error I mentioned above (and that also appears in line beginning "let bindings = ..." below)...
let main mainWindow (createWindow_Window2: Func<#Window>) =
let logger =
LoggerConfiguration()
.MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose)
.MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose)
.MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose)
.WriteTo.Console()
.CreateLogger()
let createWindow_Window2 =
let window = createWindow_Window2.Invoke()
window.Owner <- mainWindow
window
let bindings = Platform.bindings createWindow_Window2
WpfProgram.mkProgramWithCmdMsg (fun _ -> m_init, []) update bindings toCmd
|> WpfProgram.withLogger (new SerilogLoggerFactory(logger))
|> WpfProgram.startElmishLoop mainWindow
The top of Program.bindings is defined as ...
let bindings (createWindow_Window2: unit -> #Window) () : Binding<Model, Msg> list = [
"Window_Window2_Show|> Binding.cmd Window_AboutProduct_Show
"Window_Window2" |> Binding.subModelWin(
Window_Window2.get >> WindowState.ofOption,
snd,
Window_Window2ct.mapInOutMsg,
Window_Window2_Module.Window_Window2.bindings,
createWindow_Window2,
isModal = true)
...bindings continue but are not relevant to this question...
... and reports no errors!
The compiler seems to think Platform.bindings is expecting unit -> `a when createWindow_Window2 is correctly typed as unit -> #Window.
You can see the call into Program.fs:main is correctly called from App.xaml.cs with a lambda function returning a Window2 as follows...
private void StartElmish(object sender, EventArgs e)
{
this.Activated -= StartElmish;
Program.main(MainWindow, () => new Window2());
}
So my question is why am I getting this mismatch type error when it seems I am passing the correct types?
Thank you!
I think one issue in your code is that the name createWindow_Window2 is used both as the name of an argument of main, but then later also as a local variable in the function of differnt type. Another issue is that in one place, you try to use a delegate type Func<#Window> but in another place, you have an ordinary F# function Window -> unit.
My advice would be to use Window -> unit everywhere and avoid variable shadowing:
let main mainWindow (createWindow_Window2:Window -> unit) = (* Changed type here *)
let logger = (* omitted *)
let createWindowWithOwner () =
(* Renamed and added '()' so that it is a function *)
let window = createWindow_Window2 () (* Just function call *)
window.Owner <- mainWindow
window
let bindings =
Platform.bindings createWindowWithOwner (* Pass the right function here *)
(* omitted *)
A big thank you to: Brian Berns, Bent Tranberg, and Tomas Petricek for helping me!
The answer to my problem was astonishingly simple to an expert but to a relative novice only yielded after meticulous comparison with the working Elmish.WPF code for NewWindow.
For easy comparison I have pushed up to rfreytag/Elmish.WPF a version of the NewWindow (XAML code and F# Core) sample that compiles and runs (REMEMBER to build the NewWindow sample).
This version of NewWindow (XAML code and F# Core) when compiled shows the error and warning I reported above …
The type 'unit -> 'a' is not compatible with the type 'Window'.
See also C:\Workspace\Elmish.WPF\src\Samples\AnotherNewWindow.Core\Program.fs(78,72)-(78,79).
...and the warning...
This construct causes code to be less generic than indicated by its
type annotations. The type variable implied by the use of a '#', '_'
or other type annotation at or near
'C:\Workspace\Elmish.WPF\src\Samples\AnotherNewWindow.Core\Program.fs(78,72)-(78,79)'
has been constrained to be type 'unit -> 'a'.
To see the precise fix you can compare the working and breaking branches on my copy of Elmish.WPF. Which is that I had forgotten the () following the definition of let createWindow_Window2 = ...
let createWindow_Window2 =
let window = createWindow_Window2.Invoke()
window.Owner <- mainWindow
window
... returns the function () -> Window while the correct ...
let createWindow_Window2 () =
let window = createWindow_Window2.Invoke()
window.Owner <- mainWindow
window
... returns the needed Window.
A newbie looks at unit → ‘a and doesn’t immediately recognize it as a function. And of course, functions are not C# Window objects.
Did not help me that the error message doesn’t follow the F# form of...
expecting a <type sought by context>
but was given a <passed in type>
...which has become familiar from working with the F# compiler.
Not sure why this message diverged from that pattern. Maybe someone can explain that?
The warning is alerting to the less-specific #Window (see # ‘flexible type’ definition) possibly clashing at run-time with the unit → ‘a function.
As usual, learning a new framework means learning the compiler and linker messages.

How to manage multiple windows, usercontrols, and customcontrols with Elmish.wpf and F#?

I am a newbie to F#. I have recently be introduced to Elmish.wpf and the MVU design. The application I am working with is in C# WPF with many WPF usercontrols, customcontrols, and windows. It appears that Elmish.wpf flattens the concept of viewmodels into a single datacontext. (??). Can Elmish.wpf be used with multiple windows, usercontrols, and customcontrols? ("Multiple" here means about 20 windows, usercontrols, and customcontrols.)
If so, is there an example of this?
In looking at the Elmish.wpf webside, it seems that all windows need to be created upon initialization--
let main mainWindow (createWindow1: Func<#Window>) (createWindow2: Func<#Window>) =
let createWindow1 () = createWindow1.Invoke()
let createWindow2 () =
let window = createWindow2.Invoke()
window.Owner <- mainWindow
window
let bindings = App.mainBindings createWindow1 createWindow2
Program.mkSimpleWpf App.init App.update bindings
|> Program.withConsoleTrace
|> Program.runWindowWithConfig
{ ElmConfig.Default with LogConsole = true; Measure = true }
mainWindow
Is there a better way to do this? Am I barking up the wrong tree?
Thanks in advance.
Yes, Elmish.WPF absolutely supports multiple windows and user-defined controls.
Many of the samples in the Elmish.WPF repo demonstrate this one way or another. For example, the NewWindow sample demonstrates how to open new windows, and the SubModel (along with many others) sample demonstrates how to use custom UserControls. The SubModelSeq sample is currently the most complex one, demonstrating arbitrarily deep trees of recursive UserControls.
All of this is also described in the official Elmish.WPF tutorial.
(For future reference, this is the current commit at the time of writing; the samples may have changed since then.)

Can I use System.Timers.Timer in an F# PCL library?

I need to use System.Timers.Timer in an F# PCL library.
I'm currently targeting framework 4.5 and using Profile7 (I used the VS template) and it doesn't allow access to System.Timer.
According to this SO answer it's a known issue and is solved in 4.5.1.
I created a 4.5.1 C# PCL and checked its .csproj. It targets framework 4.6 and uses Profile32.
Is there a way to target the same in an F# project? I naively tried to update the .fsproj with the C# values, but it broke everything. :)
Thanks very much!
The System.Timers.Timer (and System.Threading.Timer) classes don't work in the main F# PCL profiles. Given that normal F# async is supported, you can easily work around this by writing your own "timer" type. For example, the following (while a bit ugly) should mimic the Timer class functionality reasonably well:
type PclTimer(interval, callback) =
let mb = new MailboxProcessor<bool>(fun inbox ->
async {
let stop = ref false
while not !stop do
// Sleep for our interval time
do! Async.Sleep interval
// Timers raise on threadpool threads - mimic that behavior here
do! Async.SwitchToThreadPool()
callback()
// Check for our stop message
let! msg = inbox.TryReceive(1)
stop := defaultArg msg false
})
member __.Start() = mb.Start()
member __.Stop() = mb.Post true

Is there any way to use JavaScript attribute by default?

I just want somehow to say "I want all methods in this project use [JavaScript]"
Manually annotation every method is annoying
F# 3 lets you mark a module with the ReflectedDefinition attribute (aka [JavaScript] in WebSharper) which marks all the methods underneath.
See More About F# 3.0 Language Features:
(Speaking of uncommon attributes, in F# 3.0, the
[< ReflectedDefinition >] attribute can now be placed on modules and
type definitions, as a shorthand way to apply it to each individual
member of the module/type.)
I think Phil's answer is the way to go - when you can mark an entire module or type, it does not add too much noise and it also allows you to distinguish between server-side and client-side code in WebSharper.
Just for the record, the F# compiler is open-source and so someone (who finds this issue important) could easily create branch that would add an additional command line attribute to override the setting. I think this is just a matter of adding the parameter and then setting the default value of the reflect flag in check.fs (here is the source on GitHub).
At the moment, the main F# repository does not accept contributions that add new features (see the discussion here), but it is certainly a good way to send a feature request to the F# team :-)
If you annotate all your code with the JavaScript attribute, the WebSharper compiler will try to translate everything to JavaScript. A rule of thumb in WebSharper development is to separate server-side and client-side code, so you can simply annotate the module/class containing client-side code instead of every function/member if you're targeting .NET 4.5.
namespace Website
open IntelliFactory.WebSharper
module HelloWorld =
module private Server =
[<Rpc>]
let main() = async { return "World" }
[<JavaScript>] // or [<ReflectedDefinition>]
module Client =
open IntelliFactory.WebSharper.Html
let sayHello() =
async {
let! world = Server.main()
JavaScript.Alert <| "Hello " + world
}
let btn =
Button [Text "Click Me"]
|>! OnClick (fun _ _ ->
async {
do! sayHello()
} |> Async.Start)
let main() = Div [btn]
type Control() =
inherit Web.Control()
[<JavaScript>]
override __.Body = Client.main() :> _

Resources