### Build and Install Workloads with F# Script Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/src/Samples/AvaloniaXPlatExample/README.md Use this F# script to install the necessary workloads and build the entire solution. Ensure you are in the 'src' folder before running. ```fsharp dotnet fsi build.fsx ``` -------------------------------- ### Create Avalonia Store with Commands using Program.mkAvaloniaProgram Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Use `Program.mkAvaloniaProgram` when init and update functions return `'Model * Cmd<'Msg>` tuples to handle side effects like HTTP calls or timers. This example demonstrates dispatching a command on initialization and adding a todo item. ```fsharp open Elmish open ReactiveElmish.Avalonia open ReactiveElmish module TodoApp = type Model = { Todos: Map; IsLoading: bool } type Msg = | LoadTodos | SetTodos of Map | AddTodo of string let init () = { Todos = Map.empty; IsLoading = false }, Cmd.ofMsg LoadTodos // Dispatch LoadTodos immediately on init let update (msg: Msg) (model: Model) = match msg with | LoadTodos -> { model with IsLoading = true }, Cmd.OfAsync.perform (fun () -> async { return Map [Guid.NewGuid(), "Buy milk"] }) () SetTodos | SetTodos todos -> { model with Todos = todos; IsLoading = false }, Cmd.none | AddTodo text -> { model with Todos = model.Todos.Add(Guid.NewGuid(), text) }, Cmd.none let store = Program.mkAvaloniaProgram TodoApp.init TodoApp.update |> Program.mkStore store.Dispatch (TodoApp.AddTodo "Walk the dog") printfn $"Todo count: {store.Model.Todos.Count}" // Todo count: 2 ``` -------------------------------- ### Build Web Application Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/src/Samples/AvaloniaXPlatExample/README.md Compile and run the web version of the application. After execution, follow the provided instructions to open the application in your browser. ```bash dotnet run --project AvaloniaXPlatExample.Web ``` -------------------------------- ### Initialize Avalonia Application in F# Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Set up the main application window and initialize Avalonia controls. This snippet shows how to launch the main view using the `AppCompositionRoot`. ```F# namespace AvaloniaExample open Avalonia open Avalonia.Controls open Avalonia.Markup.Xaml open Avalonia.Controls.ApplicationLifetimes type App() = inherit Application() override this.Initialize() = // Initialize Avalonia controls from NuGet packages: let _ = typeof AvaloniaXamlLoader.Load(this) override this.OnFrameworkInitializationCompleted() = match this.ApplicationLifetime with | :? IClassicDesktopStyleApplicationLifetime as desktop -> let appRoot = AppCompositionRoot() desktop.MainWindow <- appRoot.GetView() :?> Window | _ -> // leave this here for design view re-renders () base.OnFrameworkInitializationCompleted() ``` -------------------------------- ### Create Simple Avalonia Store with Program.mkAvaloniaSimple Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Use `Program.mkAvaloniaSimple` for stores where init and update functions return plain models without commands. Dispatches are automatically marshalled to the Avalonia UI thread. Includes optional error handling and console tracing. ```fsharp open Elmish open ReactiveElmish.Avalonia open ReactiveElmish module Counter = type Model = { Count: int } type Msg = | Increment | Decrement | Reset let init () = { Count = 0 } let update (msg: Msg) (model: Model) = match msg with | Increment -> { Count = model.Count + 1 } | Decrement -> { Count = model.Count - 1 } | Reset -> { Count = 0 } // Create and start the store — the Elmish loop begins immediately. let store: IStore = Program.mkAvaloniaSimple Counter.init Counter.update |> Program.withErrorHandler (fun (_, ex) -> printfn $"Error: {ex.Message}") |> Program.withConsoleTrace |> Program.mkStore // Read current model printfn $"Count = {store.Model.Count}" // Count = 0 // Dispatch a message store.Dispatch Counter.Increment printfn $"Count = {store.Model.Count}" // Count = 1 ``` -------------------------------- ### Initialize SourceCache from Collection Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Shows how to initialize a SourceCache directly from an existing collection of items, using a key selector function. This is useful for pre-populating the cache with data. ```fsharp // Initialize from an existing collection: let preloaded = SourceCache.createFrom _.Id [r1; r2] ``` -------------------------------- ### Build Desktop Application Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/src/Samples/AvaloniaXPlatExample/README.md Compile and run the desktop version of the application. This command targets the specific desktop project within the solution. ```bash dotnet run --project AvaloniaXPlatExample.Desktop ``` -------------------------------- ### Initialize and Update Model with SourceCache Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Initializes a `SourceCache` with a key selector and demonstrates updating it by adding, updating, or removing files. Use `SourceCache.addOrUpdate` for adding/modifying and `SourceCache.removeKey` for deletion. ```F# let init () = { FileQueue = SourceCache.create _.FullName } let update message model = | QueueFile path -> let file = mkFile path { model with FileQueue = model.FileQueue |> SourceCache.addOrUpdate file } | UpdateFileStatus (file, progress, moveFileStatus) -> let updatedFile = { file with Progress = progress; Status = moveFileStatus } { model with FileQueue = model.FileQueue |> SourceCache.addOrUpdate updatedFile } | RemoveFile file -> { model with FileQueue = model.FileQueue |> SourceCache.removeKey file.FullName} ``` -------------------------------- ### Create Elmish Store with mkAvaloniaProgram Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Creates an Elmish store using `Program.mkAvaloniaProgram`, which expects `init` and `update` functions to return a `Model * Cmd` tuple. ```F# let store = Program.mkAvaloniaProgram init update |> Program.mkStore ``` -------------------------------- ### Create and Mutate SourceCache Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Illustrates creating a SourceCache with a specified key selector and performing operations like adding, updating, and removing items. This is suitable for scenarios requiring unique item identification and efficient updates. ```fsharp type Record = { Id: Guid; Name: string } let cache: SourceCache = SourceCache.create _.Id let r1 = { Id = Guid.NewGuid(); Name = "Alice" } let r2 = { Id = Guid.NewGuid(); Name = "Bob" } cache |> SourceCache.addOrUpdate r1 |> SourceCache.addOrUpdate r2 |> SourceCache.addOrUpdateRange [{ r1 with Name = "Alice Updated" }] |> SourceCache.removeKey r2.Id |> ignore printfn $"Cache count: {cache.Count}" // Cache count: 1 ``` -------------------------------- ### Create Elmish Store with mkAvaloniaSimple Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Creates an Elmish store using `Program.mkAvaloniaSimple`, suitable for `init` and `update` functions that return only a `Model`. ```F# let store = Program.mkAvaloniaSimple init update |> Program.mkStore ``` -------------------------------- ### AboutViewModel Accessing Global App Store Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Demonstrates how an AboutViewModel can access the global `App` store to dispatch navigation messages. ```F# namespace AvaloniaExample.ViewModels open ReactiveElmish open App type AboutViewModel() = inherit ReactiveElmishViewModel() member this.Version = "v1.0" member this.Ok() = app.Dispatch GoHome static member DesignVM = new AboutViewModel() ``` -------------------------------- ### Create and Mutate SourceList Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Demonstrates creating a SourceList and performing common mutations like adding and removing items using a pipeline style. This pattern is useful within Elmish update functions. ```fsharp open System open DynamicData open ReactiveElmish // --- SourceList helpers --- let log: SourceList = SourceList.create () log |> SourceList.add "First entry" |> SourceList.add "Second entry" |> SourceList.addRange ["Third"; "Fourth"] |> SourceList.remove "First entry" |> ignore printfn $"Items: {log.Count}" // Items: 3 // Inside an Elmish update function (pipeline returns the same list): // { model with Logs = model.Logs |> SourceList.add newEntry } ``` -------------------------------- ### Configure AppCompositionRoot in F# Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Register services and views with their lifetimes in the composition root. Use `RegisterServices` for DI and `RegisterViews` to map ViewModels to Views with specified lifetimes (Transient or Singleton). ```F# namespace AvaloniaExample open ReactiveElmish.Avalonia open Microsoft.Extensions.DependencyInjection open AvaloniaExample.ViewModels open AvaloniaExample.Views type AppCompositionRoot() = inherit CompositionRoot() let mainView = MainView() override this.RegisterServices services = base.RegisterServices(services) // Auto-registers view models .AddSingleton(FileService(mainView)) // Add any additional services override this.RegisterViews() = Map [ VM.Key(), View.Singleton(mainView) VM.Key(), View.Singleton() VM.Key(), View.Singleton() VM.Key(), View.Singleton() VM.Key(), View.Singleton() ] ``` -------------------------------- ### Create Elmish Store with Subscriptions Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Configures an Elmish store to include defined subscriptions, enabling reactive message dispatching based on intervals or model state. ```F# let store = Program.mkAvaloniaSimple init update |> Program.withSubscription subscriptions |> Program.mkStore ``` -------------------------------- ### FilePickerViewModel with Local and Global Stores Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Shows a FilePickerViewModel that manages its own local store for file path and also accesses the global App store for navigation. ```F# namespace AvaloniaExample.ViewModels open ReactiveElmish.Avalonia open ReactiveElmish open Elmish open AvaloniaExample module FilePicker = type Model = { FilePath: string option } type Msg = | SetFilePath of string option let init () = { FilePath = None } let update (msg: Msg) (model: Model) = match msg with | SetFilePath path -> { FilePath = path } open FilePicker type FilePickerViewModel(fileSvc: FileService) = inherit ReactiveElmishViewModel() let app = App.app let local = Program.mkAvaloniaSimple init update |> Program.mkStore member this.FilePath = this.Bind(local, _.FilePath >> Option.defaultValue "Not Set") member this.Ok() = app.Dispatch App.GoHome member this.PickFile() = task { let! path = fileSvc.TryPickFile() local.Dispatch(SetFilePath path) } static member DesignVM = new FilePickerViewModel(Design.stub) ``` -------------------------------- ### Model Definition with SourceCache Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Defines a model containing a `SourceCache` for managing files with unique keys. The `SourceCache.create` method requires a key selector function. ```F# type Model = { FileQueue: SourceCache } ``` -------------------------------- ### Update Model with SourceList Actions Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Demonstrates how to update the model's `Actions` SourceList by adding or clearing items. Use `SourceList.add` for individual items and `SourceList.clear` followed by `SourceList.add` for resetting. ```F# let update (msg: Msg) (model: Model) = match msg with | Increment -> { Count = model.Count + 1 Actions = model.Actions |> SourceList.add { Description = "Incremented"; Timestamp = DateTime.Now } } | Decrement -> { Count = model.Count - 1 Actions = model.Actions |> SourceList.add { Description = "Decremented"; Timestamp = DateTime.Now } } | Reset -> { Count = 0 Actions = model.Actions |> SourceList.clear |> SourceList.add { Description = "Reset"; Timestamp = DateTime.Now } } ``` -------------------------------- ### Create Elmish Store with Termination Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Creates an Elmish store with termination capabilities, using `mkStoreWithTerminate`. This pattern is useful for disposing subscriptions when a view is unloaded, particularly with 'Transient' views. ```F# let local = Program.mkAvaloniaSimple init update |> Program.withErrorHandler (fun (_, ex) -> printfn $"Error: {ex.Message}") |> Program.mkStoreWithTerminate this Terminate ``` -------------------------------- ### Registering Reactive Elmish Subscriptions Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Use `Program.withSubscription` to register Elmish subscriptions that can be conditionally enabled or disabled based on the model's state. Each subscription requires a key and returns an `IDisposable`. ```fsharp open System open System.Reactive.Linq open Elmish open ReactiveElmish.Avalonia module Clock = type Model = { Time: DateTime; IsRunning: bool } type Msg = | Tick of DateTime | StartStop let init () = { Time = DateTime.Now; IsRunning = true } let update msg model = match msg with | Tick t -> { model with Time = t } | StartStop -> { model with IsRunning = not model.IsRunning } let subscriptions (model: Model) : Sub = let clockSub (dispatch: Msg -> unit) = Observable .Interval(TimeSpan.FromSeconds 1.0) .Subscribe(fun _ -> dispatch (Tick DateTime.Now)) [ if model.IsRunning then [ nameof clockSub ], clockSub ] let store = Program.mkAvaloniaSimple Clock.init Clock.update |> Program.withSubscription Clock.subscriptions |> Program.mkStore // Subscription is created when IsRunning = true, disposed when it becomes false. ``` -------------------------------- ### Initialize ReactiveBindingsCS for INotifyPropertyChanged in C# Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Initialize `ReactiveBindingsCS` for standard `INotifyPropertyChanged` implementations by passing an `OnPropertyChanged` method. This enables Rx bindings for view models. ```C# new ReactiveBindingsCS(this.OnPropertyChanged); ``` -------------------------------- ### Initialize ReactiveBindingsCS for ReactiveObject in C# Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Initialize `ReactiveBindingsCS` for `ReactiveObject` base classes by passing the `RaisePropertyChanged` method. This is used to set up Rx bindings within the view model. ```C# new ReactiveBindingsCS(this.RaisePropertyChanged); ``` -------------------------------- ### Register DI Services and Views in Avalonia Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Inherit from CompositionRoot to register DI services and pair view models with views. Views can be registered as Singleton or Transient. ```fsharp open Microsoft.Extensions.DependencyInjection open ReactiveElmish.Avalonia open AvaloniaExample.ViewModels open AvaloniaExample.Views type AppCompositionRoot private () = inherit CompositionRoot() let mainView = MainView() // pre-create the shell window override this.RegisterServices(services: IServiceCollection) = base.RegisterServices(services) // auto-registers all IReactiveObject VMs as Transient .AddSingleton(FileService(mainView)) // custom singleton service override this.RegisterViews() = Map [ VM.Key(), View.Singleton(mainView) // reuse same instance VM.Key(), View.Transient() // recreate each time VM.Key(), View.Transient() // recreate each time VM.Key(), View.Singleton() VM.Key(), View.Singleton() VM.Key(), View.Singleton() ] static member val Instance = AppCompositionRoot() ``` ```fsharp open Avalonia open Avalonia.Controls open Avalonia.Controls.ApplicationLifetimes type App() = inherit Application() override this.OnFrameworkInitializationCompleted() = match this.ApplicationLifetime with | :? IClassicDesktopStyleApplicationLifetime as desktop -> let root = AppCompositionRoot.Instance desktop.MainWindow <- root.GetView() :?> Window | _ -> () base.OnFrameworkInitializationCompleted() ``` -------------------------------- ### Auto-disposing Store for Transient Views Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Use `mkStoreWithTerminate` for stores associated with transient views. It automatically dispatches a terminate message and disposes subscriptions when the owning ViewModel is unloaded. ```fsharp open Elmish open ReactiveElmish.Avalonia open ReactiveElmish module LiveFeed = type Model = { Items: string list; IsAutoUpdate: bool } type Msg = | AddItem | ToggleAutoUpdate | Terminate let init () = { Items = []; IsAutoUpdate = true } let update msg model = match msg with | AddItem -> { model with Items = $"Item {model.Items.Length + 1}" :: model.Items } | ToggleAutoUpdate -> { model with IsAutoUpdate = not model.IsAutoUpdate } | Terminate -> model // stub — triggers loop termination let subscriptions (model: Model) : Sub = let timerSub (dispatch: Msg -> unit) = Observable .Interval(TimeSpan.FromSeconds 1.0) .Subscribe(fun _ -> if model.IsAutoUpdate then dispatch AddItem) [ if model.IsAutoUpdate then [ nameof timerSub ], timerSub ] open LiveFeed type LiveFeedViewModel() = inherit ReactiveElmishViewModel() // Store auto-terminates (and disposes the timer subscription) when this VM is disposed. let local = Program.mkAvaloniaSimple init update |> Program.withSubscription subscriptions |> Program.withErrorHandler (fun (_, ex) -> eprintfn $"Error: {ex.Message}") |> Program.mkStoreWithTerminate this Terminate member this.Items = this.BindList(local, _.Items) member this.IsAutoUpdate = this.Bind(local, _.IsAutoUpdate) member this.Toggle() = local.Dispatch ToggleAutoUpdate static member DesignVM = new LiveFeedViewModel() ``` -------------------------------- ### Define App Store Model and Messages Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Defines the data model and messages for the global application store, used for managing view states. ```F# module App open System open Elmish open ReactiveElmish.Avalonia open ReactiveElmish type Model = { View: View } and View = | CounterView | ChartView | AboutView | FilePickerView type Msg = | SetView of View | GoHome let init () = { View = CounterView } let update (msg: Msg) (model: Model) = match msg with | SetView view -> { View = view } | GoHome -> { View = CounterView } let app = Program.mkAvaloniaSimple init update |> Program.withErrorHandler (fun (_, ex) -> printfn $"Error: {ex.Message}") |> Program.withConsoleTrace |> Program.mkStore ``` -------------------------------- ### C# Interop Adapter for ReactiveElmish Bindings Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Wraps ReactiveElmishViewModel for use from C# view models by replacing F# function types with Func<> and Action<> delegates. Initialize with a RaisePropertyChanged callback. ```csharp using ReactiveElmish; using ReactiveUI; // Elmish model and store defined in F#: // module Counter ... (same as F# examples above) public class CounterViewModelCS : ReactiveObject, IDisposable { private readonly IStore _store; private readonly ReactiveBindingsCS _rx; public CounterViewModelCS() { _store = CounterModule.CreateStore(); // F# factory // Pass RaisePropertyChanged so ReactiveUI INPC notifications work. _rx = new ReactiveBindingsCS(this.RaisePropertyChanged); } // Scalar binding — PropertyChanged raised when Count changes. public int Count => _rx.Bind(_store, m => m.Count); // Projected binding — derived boolean. public bool IsResetEnabled => _rx.Bind(_store, m => m.Count != 0); // List binding with transform and sort. public ReadOnlyObservableCollection Actions => _rx.BindList(_store, m => m.Actions, map: a => new ActionVM(a), sortBy: a => (IComparable)a.Timestamp); public void Increment() => _store.Dispatch(Counter.Msg.Increment); public void Decrement() => _store.Dispatch(Counter.Msg.Decrement); public void Reset() => _store.Dispatch(Counter.Msg.Reset); public void Dispose() => _rx.Dispose(); } ``` -------------------------------- ### Stub Terminate Message for Store Termination Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md A stub `Terminate` message is required for `mkStoreWithTerminate`. It does not need to perform any action but must exist to trigger loop termination. ```F# let update (msg: Msg) (model: Model) = // ... | Terminate -> model // This is just a stub Msg that needs to exist -- it doesn't need to do anything. ``` -------------------------------- ### BindList with Item Mapping Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Binds a collection in the model to a DynamicData.SourceList, automatically handling diffing and updates. An optional 'map' function can transform items as they are added to the SourceList. ```F# type CounterViewModel() = inherit ReactiveElmishViewModel() let local = Program.mkAvaloniaSimple init update |> Program.mkStore member this.Actions = this.BindList(local, _.Actions, map = fun a -> { a with Description = $"** {a.Description} **" }) ``` -------------------------------- ### Bind Simple Model Property Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Binds a ViewModel property to a simple property of the model. Ensure the ViewModel inherits from ReactiveElmishViewModel and the store is initialized. ```F# type CounterViewModel() = inherit ReactiveElmishViewModel() let local = Program.mkAvaloniaSimple init update |> Program.mkStore member this.Count = this.Bind(local, _.Count) ``` -------------------------------- ### Bind a DynamicData SourceList directly with BindSourceList Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Use BindSourceList to connect an existing ISourceList<'T> directly to a ReadOnlyObservableCollection, bypassing diffing. This is more performant than BindList but requires using a DynamicData type in the model. ```fsharp open System open DynamicData open ReactiveElmish open ReactiveElmish.Avalonia open Elmish module LogViewer = type LogEntry = { Message: string; Timestamp: DateTime } type Model = { Logs: SourceList } type Msg = | AddLog of string | ClearLogs let init () = { Logs = SourceList.create() } let update msg model = match msg with | AddLog text -> { model with Logs = model.Logs |> SourceList.add { Message = text; Timestamp = DateTime.Now } } | ClearLogs -> { model with Logs = model.Logs |> SourceList.removeAll } type LogViewerViewModel() = inherit ReactiveElmishViewModel() let local = Program.mkAvaloniaSimple LogViewer.init LogViewer.update |> Program.mkStore // BindSourceList directly connects to the SourceList — no diffing required. member this.Logs = this.BindSourceList(local.Model.Logs) member this.AddLog msg = local.Dispatch(LogViewer.AddLog msg) member this.Clear () = local.Dispatch LogViewer.ClearLogs static member DesignVM = new LogViewerViewModel() ``` -------------------------------- ### Lightweight Reactive Store with Rx Stream Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt A simplified reactive store that does not use an Elmish loop. Model updates are applied via an Update function and pushed as an Rx stream. Useful for C# view models or simple state containers that do not require the full MVU cycle. ```fsharp open ReactiveElmish // Define the model type AppSettings = { Theme: string; FontSize: int; ShowToolbar: bool } // Create the store with an initial value let settingsStore = new ReactiveStore({ Theme = "Light"; FontSize = 14; ShowToolbar = true }) // Subscribe to changes settingsStore.Observable.Subscribe(fun s -> printfn $"Settings changed: Theme={s.Theme}, FontSize={s.FontSize}" ) |> ignore // Update using F# lambda settingsStore.Update(fun s -> { s with Theme = "Dark" }) // Output: Settings changed: Theme=Dark, FontSize=14 // Update using C# Func delegate (interop) settingsStore.Update(System.Func(fun s -> { s with FontSize = 16 })) // Output: Settings changed: Theme=Dark, FontSize=16 printfn $"Current: {settingsStore.Model}" // Current: { Theme = "Dark"; FontSize = 16; ShowToolbar = true } ``` -------------------------------- ### Bind DynamicData SourceCache to Avalonia Collection Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Connects an IObservableCache directly to a ReadOnlyObservableCollection with optional mapping, filtering, and sorting. Ideal for high-frequency item updates identified by a key, offering zero diffing overhead. ```fsharp open System open DynamicData open ReactiveElmish open ReactiveElmish.Avalonia open Elmish module FileQueue = type Status = | Pending | InProgress | Done type FileItem = { FullName: string; Progress: int; Status: Status } type Model = { FileQueue: SourceCache } type Msg = | QueueFile of path: string | UpdateProgress of path: string * pct: int | RemoveFile of path: string let init () = { FileQueue = SourceCache.create _.FullName } let update msg model = match msg with | QueueFile path -> { model with FileQueue = model.FileQueue |> SourceCache.addOrUpdate { FullName = path; Progress = 0; Status = Pending } } | UpdateProgress (path, pct) -> let updated = { FullName = path; Progress = pct; Status = if pct >= 100 then Done else InProgress } { model with FileQueue = model.FileQueue |> SourceCache.addOrUpdate updated } | RemoveFile path -> { model with FileQueue = model.FileQueue |> SourceCache.removeKey path } type FileQueueViewModel() = inherit ReactiveElmishViewModel() let local = Program.mkAvaloniaSimple FileQueue.init FileQueue.update |> Program.mkStore // BindSourceCache — filter to show only in-progress files, sorted by name. member this.ActiveFiles = this.BindSourceCache( local.Model.FileQueue, filter = System.Func<_,_>(fun f -> f.Status <> FileQueue.Done), sortBy = System.Func<_,_>(fun f -> f.FullName :> IComparable) ) member this.QueueFile path = local.Dispatch(FileQueue.QueueFile path) member this.UpdateProgress p n = local.Dispatch(FileQueue.UpdateProgress(p, n)) member this.RemoveFile path = local.Dispatch(FileQueue.RemoveFile path) static member DesignVM = new FileQueueViewModel() ``` -------------------------------- ### Bind SourceList to ViewModel Property Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Binds a `DynamicData` `SourceList` from the model to a view model property using `BindSourceList`. This enables list add and remove notifications to the view. ```F# type CounterViewModel() = inherit ReactiveElmishViewModel() let local = Program.mkAvaloniaSimple init update |> Program.mkStore member this.Actions = this.BindSourceList(local.Model.Actions) ``` -------------------------------- ### Bind SourceCache to ViewModel Property Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Binds a `DynamicData` `SourceCache` from the model to a view model property using `BindSourceCache`. This is suitable for lists where items have unique keys. ```F# type MainWindowViewModel() as this = inherit ReactiveElmishViewModel() member this.FileQueue = this.BindSourceCache(store.Model.FileQueue) ``` -------------------------------- ### BindList for Observable List Management in ViewModels Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Utilize `BindList` to bind an F# list or sequence to the view. It efficiently diffs the list, applies minimal add/remove operations to a `SourceList`, and exposes it as a `ReadOnlyObservableCollection`. Optional `map` and `sortBy` parameters allow item transformation and sorting. ```fsharp open System open ReactiveElmish open ReactiveElmish.Avalonia open Elmish module Counter = type Action = { Description: string; Timestamp: DateTime } type Model = { Count: int; Actions: Action list } type Msg = | Increment | Decrement | Reset let init () = { Count = 0 Actions = [{ Description = "Initialized"; Timestamp = DateTime.Now }] } let update msg model = let add desc = model.Actions @ [{ Description = desc; Timestamp = DateTime.Now }] match msg with | Increment -> { Count = model.Count + 1; Actions = add "Incremented" } | Decrement -> { Count = model.Count - 1; Actions = add "Decremented" } | Reset -> { Count = 0; Actions = [{ Description = "Reset"; Timestamp = DateTime.Now }] } // Lightweight display wrapper type ActionVM(action: Counter.Action) = inherit ReactiveElmishViewModel() member _.Timestamp = action.Timestamp member _.Description = $"** {action.Description} **" type CounterViewModel() = inherit ReactiveElmishViewModel() let local = Program.mkAvaloniaSimple Counter.init Counter.update |> Program.mkStore member this.Count = this.Bind(local, _.Count) // BindList diffs the list and wraps each item in ActionVM, sorted by Timestamp. member this.Actions = this.BindList(local, _.Actions, map = fun a -> ActionVM(a), sortBy = _.Timestamp) member this.Increment() = local.Dispatch Counter.Increment member this.Decrement() = local.Dispatch Counter.Decrement member this.Reset() = local.Dispatch Counter.Reset static member DesignVM = new CounterViewModel() ``` -------------------------------- ### Define Elmish Subscriptions Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Defines a function that returns Elmish subscriptions. Subscriptions can dispatch messages and are conditionally enabled based on the model's state. ```F# let subscriptions (model: Model) : Sub = let autoUpdateSub (dispatch: Msg -> unit) = Observable .Interval(TimeSpan.FromSeconds(1)) .Subscribe(fun _ -> dispatch AddItem ) [ if model.IsAutoUpdateChecked then [ nameof autoUpdateSub ], autoUpdateSub ] ``` -------------------------------- ### BindOnChanged for Efficient ViewModel Projections Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Use `BindOnChanged` to separate change detection from value projection. The projection is only re-evaluated when the change detector returns a new value, preventing redundant computations for costly operations like creating child view models. ```fsharp open ReactiveElmish open ReactiveElmish.Avalonia open App // global app store module type MainViewModel(root: CompositionRoot) = inherit ReactiveElmishViewModel() // BindOnChanged: only re-creates the view when _.View changes on the model. // Using plain Bind here would call the modelProjection twice per update. member this.ContentView = this.BindOnChanged( app, _.View, // cheap change-detector fun m -> // expensive projection — called once per change match m.View with | CounterView -> root.GetView() | AboutView -> root.GetView() | ChartView -> root.GetView() | FilePickerView -> root.GetView() | TodoListView -> root.GetView() ) member this.ShowCounter() = app.Dispatch(SetView CounterView) member this.ShowAbout() = app.Dispatch(SetView AboutView) member this.ShowChart() = app.Dispatch(SetView ChartView) member this.ShowFilePicker() = app.Dispatch(SetView FilePickerView) member this.ShowTodoList() = app.Dispatch(SetView TodoListView) static member DesignVM = new MainViewModel(Design.stub) ``` -------------------------------- ### Binding Scalar Model Properties to ViewModel Properties Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Use `ReactiveElmishViewModel.Bind` to subscribe to store observables and automatically raise `PropertyChanged` when projected values change. It internally uses `DistinctUntilChanged` and returns the current value on read. ```fsharp open ReactiveElmish open ReactiveElmish.Avalonia open Elmish module Counter = type Model = { Count: int; Label: string } type Msg = | Increment | Decrement | Reset let init () = { Count = 0; Label = "Start" } let update msg model = match msg with | Increment -> { Count = model.Count + 1; Label = "Incremented" } | Decrement -> { Count = model.Count - 1; Label = "Decremented" } | Reset -> { Count = 0; Label = "Reset" } type CounterViewModel() = inherit ReactiveElmishViewModel() let local = Program.mkAvaloniaSimple Counter.init Counter.update |> Program.mkStore // Simple property binding — raises PropertyChanged only when Count changes. member this.Count = this.Bind(local, _.Count) // Projected binding — derived value, raises PropertyChanged when Count changes. member this.IsResetEnabled = this.Bind(local, fun m -> m.Count <> 0) // String projection with formatting member this.Label = this.Bind(local, _.Label) member this.Increment() = local.Dispatch Counter.Increment member this.Decrement() = local.Dispatch Counter.Decrement member this.Reset() = local.Dispatch Counter.Reset // Exposes a design-time instance for Avalonia XAML previewer static member DesignVM = new CounterViewModel() ``` -------------------------------- ### Bind a Map<'Key, 'Value> to the view with BindKeyedList Source: https://context7.com/jordanmarr/reactiveelmish.avalonia/llms.txt Use BindKeyedList to diff a Map<'Key, 'Value> from the model by key. It adds new items, removes deleted items, and optionally updates existing ones, returning an ObservableCollection<'Mapped>. This is suitable when items have stable identifiers. ```fsharp open System open ReactiveElmish open ReactiveElmish.Avalonia open Elmish module TodoApp = type Todo = { Id: Guid; Description: string; Completed: bool } type Model = { Todos: Map } type Msg = | AddTodo | RemoveTodo of Guid | UpdateTodo of Todo | Clear let init () = { Todos = Map.empty }, Cmd.ofMsg AddTodo let update msg model = match msg with | AddTodo -> let n = model.Todos.Count + 1 let todo = { Id = Guid.NewGuid(); Description = $"Todo {n:D2}"; Completed = false } { Todos = model.Todos.Add(todo.Id, todo) }, Cmd.none | RemoveTodo id -> { Todos = model.Todos.Remove id }, Cmd.none | UpdateTodo todo -> { Todos = model.Todos.Add(todo.Id, todo) }, Cmd.none | Clear -> { Todos = Map.empty }, Cmd.none type TodoViewModel(store: IStore, todo: TodoApp.Todo) = inherit ReactiveElmishViewModel() member _.Id = todo.Id member this.Description with get () = todo.Description and set value = store.Dispatch(TodoApp.UpdateTodo { todo with Description = value }) member this.Completed with get () = todo.Completed and set value = store.Dispatch(TodoApp.UpdateTodo { todo with Completed = value }) member this.Remove() = store.Dispatch(TodoApp.RemoveTodo todo.Id) type TodoListViewModel() = inherit ReactiveElmishViewModel() let store = Program.mkAvaloniaProgram TodoApp.init TodoApp.update |> Program.mkStore // BindKeyedList keys items by Guid — only changed items are touched. member this.Todos = this.BindKeyedList(store, _.Todos, map = fun todo -> TodoViewModel(store, todo), getKey = fun vm -> vm.Id // update = fun todo vm -> vm.Update(todo) // optional in-place update // sortBy = fun vm -> vm.Completed // optional sorting ) member this.AddTodo() = store.Dispatch TodoApp.AddTodo member this.Clear() = store.Dispatch TodoApp.Clear static member DesignVM = new TodoListViewModel() ``` -------------------------------- ### BindKeyedList for Identifiable Items Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Binds a Map<'Key, 'Value> to an ObservableCollection, using a 'getKey' function to identify items. An optional 'update' function can be provided for more complex item updates, though it's generally more efficient to manage state within the mapped item. ```F# type TodoListViewModel() = inherit ReactiveElmishViewModel() let store = Program.mkAvaloniaProgram init update |> Program.mkStore member this.Todos = this.BindKeyedList(store, _.Todos , map = fun todo -> new TodoViewModel(store, todo) , getKey = fun todoVM -> todoVM.Id //, update = fun todo todoVM -> todoVM.Update(todo) // Optional //, sortBy = fun todo -> todo.Completed // Optional ) ``` -------------------------------- ### Bind Model Projection Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Binds a ViewModel property to a projection of the model, allowing for computed values. The projection is evaluated based on the model's state. ```F# type CounterViewModel() = inherit ReactiveElmishViewModel() let local = Program.mkAvaloniaSimple init update |> Program.mkStore member this.IsResetEnabled = this.Bind(local, fun m -> m.Count <> 0) ``` -------------------------------- ### BindOnChanged for Efficient Updates Source: https://github.com/jordanmarr/reactiveelmish.avalonia/blob/main/README.md Binds a ViewModel property to a model projection, but only re-evaluates the projection when the specified 'onChanged' value changes. This is useful for expensive projections to avoid redundant calculations. ```F# namespace AvaloniaExample.ViewModels open ReactiveElmish.Avalonia open ReactiveElmish open App type MainViewModel(root: CompositionRoot) = inherit ReactiveElmishViewModel() member this.ContentView = this.BindOnChanged (app, _.View, fun m -> match m.View with | CounterView -> root.GetView() | AboutView -> root.GetView() | ChartView -> root.GetView() | FilePickerView -> root.GetView() ) member this.ShowChart() = app.Dispatch(SetView ChartView) member this.ShowCounter() = app.Dispatch(SetView CounterView) member this.ShowAbout() = app.Dispatch(SetView AboutView) member this.ShowFilePicker() = app.Dispatch(SetView FilePickerView) static member DesignVM = new MainViewModel(Design.stub) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.