v2 beta feedback mega-thread #29
Replies: 34 comments 151 replies
-
Here is the This is now using the standard Elmish |
Beta Was this translation helpful? Give feedback.
-
A prerelease v2.0.0-beta is available here: |
Beta Was this translation helpful? Give feedback.
-
I updated the SampleProject to use Avalonia Compiled Bindings tonight and it works great. It creates a Build error if the VM is missing a property. Here is the commit that implements it: One of the changes required was that Avalonia doesn’t seem to like having the ViewModel embedded within a module (it causes the path to fail for some reason). |
Beta Was this translation helpful? Give feedback.
-
Another nice benefit to the v2 approach of utilizing ReactiveUI is that it makes it very easy to create viewmodels using the |
Beta Was this translation helpful? Give feedback.
-
I'll work on this over the next few days as I am able to. |
Beta Was this translation helpful? Give feedback.
-
How do you handle binding of option types? For example when binding to the selected item of a list box. |
Beta Was this translation helpful? Give feedback.
-
I'm thinking of removing the shorter member this.Count = this.BindModel(fun m -> m.Count) You would always use this overload: member this.Count = this.BindModel(nameof this.Count, fun m -> m.Count) The reason is that the first overload is supposed to be a convenient shortcut that both retrieves the value from the member this.Count = this.BindModel(fun m -> $"***{m.Count}***") -- as this will break because it will fail when it tries to parse out the vm property name. If there was only the longer overload, you could use a projection at any time and it wouldn't matter because it would always have the vm property name supplied. The convenience of being able to "sometimes" use shorter overload to "type less" is outweighed by the potential "foot gun" of getting a runtime error. Also, Copilot kind of makes the extra typing a moot point anyways as it is really good at boiler plate like this. |
Beta Was this translation helpful? Give feedback.
-
Sharing models conceptCurrently, For example, if you had a parent view/VM that starts an Elmish loop that has a child view/VM that wants to bind to the parent's Elmish loop. In that case, I image that This would essentially be very similar to the classic, monolithic Elmish loop, minus the boilerplate of binding everything together. |
Beta Was this translation helpful? Give feedback.
-
Playing catch up here but looks really nice - I like simplicity - proof is always in the elegance of the final code for real world applications and this passes the sniff test. I still have work to do getting the web/wasm demo running - enough things changed in Avalonia that I had to update the template but it's still not quite right. When I'm done with that, I'll pull all this in (the desktop branch should still work fine). Looking forward to trying this on some of my projects! |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Looking forward to seeing that beta. Can only see beta 9 on nuget at present. |
Beta Was this translation helpful? Give feedback.
-
I'm experimenting with a few different ways of sharing a single model with multiple views that is pretty exciting. MainViewModel.fsnamespace AvaloniaExample.ViewModels
open Elmish.Avalonia
open Elmish
module Main =
type Model =
{
View: View
}
and View =
| CounterView
| ChartView
| AboutView
| FilePickerView
type Msg =
| SetView of View
| Terminate
let init() =
{
View = CounterView
}
let update (msg: Msg) (model: Model) =
match msg with
| SetView view ->
{ View = view }
| Terminate ->
model
let subscriptions (model: Model) : Sub<Msg> =
let messageBusSub (dispatch: Msg -> unit) =
Messaging.bus.Subscribe(fun msg ->
match msg with
| Messaging.GlobalMsg.GoHome ->
dispatch (SetView CounterView)
)
[
[ nameof messageBusSub ], messageBusSub
]
open Main
type MainViewModel() =
inherit ReactiveElmishViewModel<Model, Msg>(init())
member this.ContentVM =
this.Bind (fun m ->
match m.View with
| CounterView -> new CounterViewModel()
| AboutView -> new AboutViewModel()
| ChartView -> new ChartViewModel()
| FilePickerView -> new FilePickerViewModel()
: IElmishViewModel)
member this.ShowChart() = this.Dispatch (SetView ChartView)
member this.ShowCounter() = this.Dispatch (SetView CounterView)
member this.ShowAbout() = this.Dispatch (SetView AboutView)
member this.ShowFilePicker() = this.Dispatch (SetView FilePickerView)
override this.StartElmishLoop(view: Avalonia.Controls.Control) =
Program.mkAvaloniaSimple init update
|> Program.withErrorHandler (fun (_, ex) -> printfn "Error: %s" ex.Message)
|> Program.withConsoleTrace
|> Program.withSubscription subscriptions
|> Program.terminateOnViewUnloaded this Terminate
|> Program.runView this view
static member DesignVM = new MainViewModel() |
Beta Was this translation helpful? Give feedback.
-
FYI - the sample app when built from v2-beta-IReactiveElmishViewModel is a bit wonky. I've noticed the counter view gets a list of "Set View" Actions. And when navigating between views using the top bar eventually the Chart (and File Picker) don't render any longer - it's just a blank screen. I'm still sniffing around but I thought I'd post this back while it's fresh on my eyes. |
Beta Was this translation helpful? Give feedback.
-
@JordanMarr Like what you are developing in the v2-beta-ReactiveViewModel branch, the ElmishStore concept and the fact that the ViewModel can share one or create their own is great! Allows for great flexibility. |
Beta Was this translation helpful? Give feedback.
-
I moved over my views/VMs over to the Example in a fork of your repo and built it out on my own local branch. The map is still behaving oddlly but running five subscriptions to pull data out from a DB seems to be really smooth - as long as I'm running the DB on another machine. 😁 |
Beta Was this translation helpful? Give feedback.
-
good point - I’ve had surprisingly bad experiences with the Elmish pattern (more in React world) doing trivial UI tasks like accepting a barcode scanned input. The flood of updates killed the event loop and bad things happened. I haven’t done the equivalent yet in AvalonialandAm 11/21/23 um 9:34 PM schrieb TeaDrivenDev ***@***.***>:
Another aspect that may or may not need specific support on the library side: throttling or otherwise Rx transforming input from bindings before it's dispatched. Think search/filter fields where you only want to have something happen once the user stops typing for half a second and the like.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
v2.0.0-gamma.6 is released.
|
Beta Was this translation helpful? Give feedback.
-
@JordanMarr With the changes you've made to the way the file picker is called, there's now no example anymore that shows how to use commands. There should probably be something that demonstrates that. Related: How would I run code when the application is closed, like e.g. saving settings on shutdown? I tried issuing a command when handling the termination message, but that appears to be too late, as the breakpoint inside that isn't hit when closing the window. |
Beta Was this translation helpful? Give feedback.
-
Currently, the CompositionRoot base class is using reflection to scan the assembly for view models. |
Beta Was this translation helpful? Give feedback.
-
Two things:
You should end up with this: type ChartViewModel() as this =
inherit ReactiveElmishViewModel()
let app = App.app
let local =
Program.mkAvaloniaSimple init update
|> Program.withErrorHandler (fun (_, ex) -> printfn $"Error: %s{ex.Message}")
|> Program.withSubscription subscriptions
|> Program.mkStoreWithTerminate this Terminate
member this.Series = local.Model.Series
member this.Actions = this.BindSourceList(local, _.Actions)
member this.AddItem() = local.Dispatch AddItem
member this.RemoveItem() = local.Dispatch RemoveItem
member this.UpdateItem() = local.Dispatch UpdateItem
member this.ReplaceItem() = local.Dispatch ReplaceItem
member this.Reset() = local.Dispatch Reset
member this.IsAutoUpdateChecked
with get () = this.Bind (local, _.IsAutoUpdateChecked)
and set value = local.Dispatch (SetIsAutoUpdateChecked value)
member this.XAxes = this.Bind (local, fun _ -> XAxes)
member this.Ok() = app.Dispatch App.GoHome
static member DesignVM = new ChartViewModel() |
Beta Was this translation helpful? Give feedback.
-
v2.0.0-gamma.8
namespace AvaloniaExample.ViewModels
open Elmish.Avalonia
open App
type MainViewModel() =
inherit ReactiveElmishViewModel()
member this.ContentView =
this.BindOnChanged (app, _.View, fun m ->
match m.View with
| CounterView -> this.GetView<CounterViewModel>()
| AboutView -> this.GetView<AboutViewModel>()
| ChartView -> this.GetView<ChartViewModel>()
| FilePickerView -> this.GetView<FilePickerViewModel>()
)
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() |
Beta Was this translation helpful? Give feedback.
-
I started a thread for README feedback here: |
Beta Was this translation helpful? Give feedback.
-
v2.0.0-gamma.9
type MainViewModel(root: CompositionRoot) =
inherit ReactiveElmishViewModel()
member this.ContentView =
this.BindOnChanged (app, _.View, fun m ->
match m.View with
| CounterView -> root.GetView<CounterViewModel>()
| AboutView -> root.GetView<AboutViewModel>()
| ChartView -> root.GetView<ChartViewModel>()
| FilePickerView -> root.GetView<FilePickerViewModel>()
)
|
Beta Was this translation helpful? Give feedback.
-
Big changes!After realizing that this solution is way better than what I was doing on my current WPF project (C#), I wanted to utilize this library there too.
This is my C# code calling an F# global app store: public ICommand IncrementCmd => new RelayCommand(Increment);
public ICommand DecrementCmd => new RelayCommand(Decrement);
public int Count => Bind(_store, App.bind(m => m.Counter));
public void Increment() => _store.Dispatch(App.Msg.Increment);
public void Decrement() => _store.Dispatch(App.Msg.Decrement); It works great! I think that F# might be appealing to all the CSharpers out there that have heard F# is great for modeling and would like to try their hand at discriminated unions! |
Beta Was this translation helpful? Give feedback.
-
ReactiveElmish.Avalonia v1.0.0-beta.1 is now published to NuGet!If you ever decide you want to use the base |
Beta Was this translation helpful? Give feedback.
-
There is a transform extension method called TransformWithInlineUpdate for IObservable<IChangeSet<TDestination, TKey>>, which instead of recreating the transformed object each time, it matches the key and updates if exists. It is in the ObservableCacheEx.cs if you are looking in the source. |
Beta Was this translation helpful? Give feedback.
-
v1.0.0-beta.3
Look at the samples to see the slightly different syntax to call It's not perfect, but maybe a slight improvement. I'm sure there will be more changes around the |
Beta Was this translation helpful? Give feedback.
-
This is awesome! I love the changes. I have a question about binding. I'm building a generic radial slider as a templated control, which is then being used by an Elmish view, with a value binding from the model. This is working out to the control, and the control is properly triggering property changed events, but the two way binding isn't working. I have a workaround by adding a handler to the property changed event in the code behind that calls an elmish command to update the model, but this is a bit clunky. I had rather assumed that the two way binding would pick up this event and change the model, but I am uncertain if that is true? It does not seem to be the case, which leads to the question of if there is a "proper" way to handle this? For reference, the control is invoked from the elmish view:
and the workaround:
this works, but is far from ideal. Any thoughts, or obvious mistakes? |
Beta Was this translation helpful? Give feedback.
-
I think I am ready to merge to the main branch and release as v1 tomorrow. |
Beta Was this translation helpful? Give feedback.
-
I got pretty motivated today and ended up rethinking the way the library works.
Design Goals
DictionaryViewModel
brought over from Elmish.WPF was not able to take advantage of this and had to rely on reflection-based bindings.DictionaryViewModel
was not able to bind toDataGrid
row columns. The workarounds on the Elmish.WPF side were pretty cumbersome, imo. Having more typical view models resolves this issue.Program
module as much as possible as opposed to customAvaloniaProgram
module fns.Changes:
ReactiveElmishViewModel
class.Avalonia.ReactiveUI
.The old bindings (i.e.Binding.oneWay
) still exist, but they are now only used for theVidewModel.designInstance
. (OnlyBinding.oneWay
is used now -- no need to bind commands as this is only used for the design preview.)designVM
.Model
and the view model properties are now sync'd behind the scenes using Rx and the newBindModel
method.This is the end result so far. I will probably post a beta tonight or tomorrow. Let me know what you think.
CounterViewModel
Notes
ReactiveElmishViewModel
inherits fromAvalonia.ReactiveUI
, your Commands (Increment
,Decrement
andReset
) can be expressed as simple methods without needing to manually wrap them in anICommand
.Beta Was this translation helpful? Give feedback.
All reactions