Elmish.WPF
Elmish.WPF copied to clipboard
Use the samples' project structure for the Getting Started instructions?
The current "Getting started" section in the readme presents itself as a summary of the SingleCounter sample, but the instructions demonstrate a different project structure. Specifically, the F# project is the startup project, whereas in all our samples, the C# project has been the startup project.
I think we should change the "Getting started" section to reflect the current sample project structure, which IIRC is in many ways better than the opposite.
@TysonMN what do you think?
@cmeeren For simple projects, the Views in the C# project could be used as the startup project. However, in my case, where I have an adorner in C# being called by the F# project, using the C# project as startup would result in a circular reference. So it seems that I, at least, am forced to use the F# project as startup -- so I appreciate the instructions as is. (Unless you can tell me another way of doing it).
...in my case, where I have an adorner in C# being called by the F# project, using the C# project as startup would result in a circular reference. So it seems that I, at least, am forced to use the F# project as startup -- so I appreciate the instructions as is. (Unless you can tell me another way of doing it).
You are not forced to have an F# project as the startup project. You can definitely add a layer of indirection. I have done this in my application at work.
Here is a simplified version of...
...my ProgramFSharp.fs
file...
type IWpfData =
abstract member InitializeWpf : Unit -> Unit
abstract member MainWindow : System.Windows.Window
abstract member SkElement : SkiaSharp.Views.WPF.SKElement
abstract member EnqueueSnackbarMessage : string -> unit
let main (wpfData: IWpfData) =
let config = ...
let initialCmds = ...
wpfData.InitializeWpf ()
let model = App.init config
let init = fun () -> model, initialCmds
let update = (App.update, App.generate) |> Func2.product2
let bindings = ViewModel.App.bindings model wpfData.SkElement
let bindCmd = Impure.App.bindCmd wpfData.SkElement.InvalidateVisual wpfData.EnqueueSnackbarMessage
WpfProgram.mkProgramWithCmdMsg init update bindings bindCmd
|> WpfProgram.runWindow
wpfData.MainWindow
...and my ProgramCSharp.cs
file
public class WpfData : ProgramFSharp.IWpfData {
private MainWindow _mainWindow;
public void InitializeWpf() {
var mainWindow = new MainWindow();
_mainWindow = mainWindow;
InitializeApplication(mainWindow);
}
private static void InitializeApplication(MainWindow mainWindow) =>
_ = new Application() {
MainWindow = mainWindow,
Resources = new DefaultResourceDicitionaryContainer().Resources
};
public Window MainWindow => _mainWindow;
public SKElement SkElement => DrawScreenBody.SkElementStatic;
public async void EnqueueSnackbarMessage(string message) {
try {
await _mainWindow.SnackbarLoadedCompletion;
await _mainWindow.SnackbarVisibleCompletion;
await _mainWindow.WindowStateNotMinimizedCompletion;
_mainWindow.Dispatcher.Invoke(() => _mainWindow.Snackbar.MessageQueue.Enqueue(message));
} catch (Exception e) {
Log.Error(e, "An exception was thrown while enqueuing a massage to the snackbar");
}
}
}
public static class ProgramCSharp {
[STAThread] // Assigns to UI components a unique thread
public static int Main() => ProgramFSharp.main(new WpfData());
}
To make for a simpler example, I removed configuration, logging, initial command creation, and a comment from the F# code and lengthy comments (explaining "why") in the C# code.