Open YkTru opened 1 week ago
Nope.
I've just implemented a similar logic using a BreadcrumbBar
backed by submodelSeq
And that logic looks extremely ugly. I'm going to rewrite it one day.
I see the best option is (anonymous types used for readability)
type Model = {
Items : Item0 list
SelectionModel : {|
SelectedItem : Item0
Items: Item1 list
SelectionModel : {|
SelectedItem : Item1
Items: Item2 list
SelectionModel : Item2 voption
|} voption
|} voption
}
with
interface IModel with
interface IReadonlyList<IModel> with
interface IEnumerable<IModel> with
member model.GetEnumerator() =
(seq {
yield model :> IModel
yield! model.SelectionModel |> ValueOption.toList
yield! model.SelectionModel |> ValueOption.map _.SelectionModel |> ValueOption.toList
}).GetEnumerator()
I feel you..
Ramblings: as I barely understand what is the main issue with recursive subModelSelectedItem
, my intuition is that all level should have a specific, unique subModelSelectedItem
binding, as I did in the hardcoded example:
let selectedLevel0Binding =
Binding.subModelSelectedItem (
"Level0Items_VM",
(fun m -> m.SelectedLevel0),
(fun selectedId model -> SelectLevel0 selectedId)
)
let selectedLevel1Binding =
Binding.subModelSelectedItem (
"Level1Items_VM",
(fun m -> m.SelectedLevel1),
(fun selectedId model -> SelectLevel1 selectedId)
)
let selectedLevel2Binding =
Binding.subModelSelectedItem (
"Level2Items_VM",
(fun m -> m.SelectedLevel2),
(fun selectedId model -> SelectLevel2 selectedId)
)
Correspondingly, all items should have a "Selected option" (and/or "SelectedLevel"?) property (I'm not sure about this though..)
(I realize this might seem a bit out there, but I’m running out of options, and I lack proper understanding of ElmishWPF source code)
Proposition: To create a dynamic tree that allows adding and removing siblings or children, could it be possible to assign each newly added child at any level a unique subModelSelectedItem
binding + SelectedLevel prop? This is what I had to do in the hardcoded example, so I’m wondering if it would be feasible and performant enough in practice?
Issues: From my understanding of Elmish.WPF's limitations, it seems we can't create bindings dynamically. However, is there truly no way to achieve similar functionality? Is it currently impossible to build a tree where selecting any item at any level would update properties in a synchronized property editor? ..I'm quite worried right now..
( @marner2 if you have time) Am I speaking non-sense?
Submodels can be created dynamically in submodels seq
module Bindings =
let private viewModel = Unchecked.defaultof<SelectLocationViewModel>
let pathBinding =
let createViewModel (args : ViewModelArgs<obj, obj>) : IViewModel<obj, obj> =
let modelType = args.InitialModel.GetType ()
if modelType = typeof<Floor.Model> then
Floor.FloorItemViewModel args
elif modelType = typeof<Area.Model> then
Area.AreaItemViewModel args
elif modelType = typeof<Room.Model> then
Room.RoomItemViewModel args
else
failwithf "Unknown model type: %A" modelType
let mapVmMsg (index, msg : obj) : Msg =
match msg with
| :? Floor.Msg as m -> FloorMsg (index, m)
| :? Area.Msg as m -> AreaMsg (index, m)
| :? Room.Msg as m -> RoomMsg (index, m)
| _ -> failwithf "Unknown message type: %A" msg
BindingT.subModelSeq createViewModel (nameof viewModel.Path)
|> Binding.mapModel (fun m -> m.Path)
|> Binding.addLazy (fun m1 m2 -> m1.Path = m2.Path)
|> Binding.mapMsg (fun (i, msg) -> mapVmMsg (i, msg))
type SelectLocationViewModel (args) =
inherit ViewModelBase<Model, Msg> (args)
member _.Path = base.Get (Bindings.pathBinding)
I think I understand the bulk of it;
one question though: how can you access the type SelectLocationViewModel
in let private viewModel = Unchecked.defaultof<SelectLocationViewModel>
since its defined after?
Everytime I tried to access args
, I couldn't get anywhere;
• ie I get no Intellisense (except GetType & ToString; I tried open Elmish.WPF.ViewModelArgs/ViewModel
to no avail
• and I get errors such as: The type 'ViewModelArgs<_,_>' does not define the field, constructor or member 'InitialModel'
Regarding my specific case
build a tree where selecting any item at any level would update properties in a synchronized property editor
How would you approach writing the "SelectedItem" functionality? I'm having difficulty figuring out how to apply recursion in this case, as all my attempts so far haven't been successful, and I don't have a clue on how to debug it.
Regarding the AFAIC "catastrophic scenario": Do you think it might be unfeasible with Elmish.WPF? I've gone through numerous issues, discussions, and topics highlighting the challenges of using subModelSelectedItem
recursively, and, honestly, I'm left with the impression that this issue remains unresolved—at least as far as I understand it.
If I'm (hopefully) mistaken, I believe a sample for this scenario would be incredibly valuable, as tree structures combined with property editors are quite common in complex desktop applications. Honestly, I really hope I've simply missed or misunderstood the solutions suggested in the various discussions.. otherwise I'll have to give up my whole project..
- one question though: how can you access the
type SelectLocationViewModel
inlet private viewModel = Unchecked.defaultof<SelectLocationViewModel>
since its defined after?
It is used for nameof
only
- Everytime I tried to access
args
, I couldn't get anywhere; • ie I get no Intellisense (except GetType & ToString; I triedopen Elmish.WPF.ViewModelArgs/ViewModel
to no avail • and I get errors such as:The type 'ViewModelArgs<_,_>' does not define the field, constructor or member 'InitialModel'
I've added such property in Elmish.Uno
I can barely think straight—I’ve hardly slept in three days trying to figure this out I'm exhausted; If I can’t resolve this issue, my whole project is doomed.. And there’s no way I’m going back to C#+MVVM+Prism and all the nulls/classes/interfaces/services/tests/files explosion madness. It's a shame MS refuse to add proper F#+WPF/MAUI implementation.. what a waste.
Anyway… would a less-than-ideal setup like this actually work? I can’t seem to make it happen, and with my lack of sleep and experience here, I’m struggling to judge it clearly.
• AppY.fs: (all compiles, I add the 'Y' suffix to avoid collisions):
type Model =
{ Items: Parent list
SelectedItem: Guid option
}
// Msg
| UpdateSelectedItem of Guid option
// update
| UpdateSelectedItem itemId ->
{ model with
SelectedItem = itemId
Log = sprintf "Selected Item: %A" itemId },
Cmd.none
// function to call from C#.. if possible/relevant
let dispatchUpdateSelectedItem (dispatch: Msg -> unit) itemId =
dispatch (UpdateSelectedItem itemId)
• MainWindow.cs: And now the terrible, pathetic part (I can't find what to put in dispatch):
using Elmish;
using Elmish.WPF;
namespace TreeView_SelectedItem_Behaviors.WPF;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
Action<AppY.Msg> dispatch = ???
if (e.NewValue is Parent_VM p)
{
AppY.dispatchUpdateSelectedItem(dispatch, p.Id);
}
}
}
.. I really can’t stand interop…
Well, I really hope someone can eventually help with this.. thanks.
Why don't you use submodelSeq Get initial model via reflection for now
@xperiandri Would you be able (if you have time) to share a sample please, ideally as a GitHub repository or a zip file so I can better analyze, with just the essentials (Program file + XAML) to demonstrate exactly your proposed approach? (I'm "conceptually" confused by the recursive multi-level item selection part + the dynamic/static issues between ElmishWPF and WPF)
Or even better (if it's easier), you could submit a PR to the repo I shared earlier, focusing only on adding the specific bindings and XAML modifications (assuming my coding style is clear)?
This would be for sure very helpful. Thank you
@YkTru see https://github.com/xperiandri/Elmish.Uno/tree/submodel-selected-item-cascading SubModelSelectedItem.Cascading.fs
I have not finished UI, only logic
@marner2 I've rebased my code on top of your latest code. So no my changes are more easily pickable.
Pay attention to Fixed wrong initial binding value in static view models base class
commit
@xperiandri Great, thanks! I'll take a closer look at this later in the week when I have time and work on adapting my sample with it.
Questions: (These are just "at first glance" questions/uneasiness)
OO: I noticed a fair amount of OO programming in your approach (e.g., in the Model). Is this mostly a stylistic choice, or is it essential to your design? I guess the Model type/class with its members is primarily/only meant to act as an API, encouraging a particular usage pattern (?).
Is there a particular reason why you don’t use as much of an OO-heavy approach in the other samples in your repo? I’m generally not inclined to use OO extensively in F#—it brings back (bad) memories of my C#/Prism days (though I know that might be more personal than rational). That said, I’m open to rethinking my style if there are specific advantages here.
Reflection:
Why don't you use submodelSeq Get initial model via reflection for now
Is there any risk associated with using reflection to get the InitialModel? Could someone potentially access and manipulate critical fields in the Model this way?
I don't care if it is OO or FP approach, whichever solves the problem better.
Model
property and IEnumerable
implementation can actually be omitted and a function transforming into a list can be used.
And IModel
can be omitted too, you will just expose an obj list
So that sample can be simplified
I don't care if it is OO or FP approach, whichever solves the problem better.
Model
property andIEnumerable
implementation can actually be omitted and a function transforming into a list can be used. AndIModel
can be omitted too, you will just expose anobj list
So that sample can be simplified
• I totally agree: I am just curious why you opted for the OO approach in that sample and what advantages you see in it compared to the FP approach, which, all else being equal, seems easier to read and more concise to me. I’m not very familiar with F# OO, so I may not fully appreciate its benefits (as I mostly learned from Wlaschin, who rarely uses OO in his code snippets)
• Regarding using reflection to obtain the 'Initial Model,' sorry to insist but do you think it's safe enough in this specific case? I've often been advised to avoid reflection except for debugging or plugin-based extensibility, but just like with OO I'm open to revise my beliefs/impressions as well
Yes, it is safe @marner2 maybe add that initial model property in WPF too?
I can barely think straight—I’ve hardly slept in three days trying to figure this out I'm exhausted;
I don't recommend trying to solve a problem like this without sleep. In fact, I love trying to solve a problem like this while in bed trying to fall asleep.
Have you solved this problem with a traditional WPF (i.e. MVVM C#) application? If so, can you share a minimal working example of that?
@TysonMN Here I made this C# implementation this morning (I use the excellent MVVMGen library for cleaner VMs):
https://github.com/YkTru/Cascading-ListBoxes---SubModelSelectedItem/tree/C%23_MVVM
It's a quick and rough draft, so my naming conventions and usage of MVVMGen might not be perfectly consistent throughout.
However, everything is functioning exactly as I intended, ie:
Goal: I’m aiming for an ItemsControl that can dynamically create ListBoxes based on selected items or added children, supporting an n-level depth.
Note: I still depends on the SelectedItem
prop of the ListBox element in the template:
SelectedItem="{Binding SelectedNode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
I therefore still haven't figured out how to achieve the same behaviors in Elmish.WPF (particularly when using subModelItemSelected).
One thing I’m certain of now, though, is that I must use a Level_VM in the ElmishWPF version.
F# Version update: I don’t have time today to work on a corresponding F# version, but if you’d like to share any insights or code samples to guide me as for the bindings part (you can refer to my master branch's Models/VM/Xaml), it would surely be helpful.
Here's a working example of hardcoded cascading ListBoxes using
subModelSelectedItem
:https://github.com/user-attachments/assets/2734db15-dba4-4cd1-ad7c-7c895ede8ff9
GitHub Source: https://github.com/YkTru/Cascading-ListBoxes---SubModelSelectedItem (Some code was quickly assembled and includes GPT-generated segments, but it seems to work as expected.)
Problem: The ListBoxes are currently hardcoded (levels 0-1-2) to demonstrate the intended behavior. However, I need a dynamic, n-depth tree structure.
Goal: I’m aiming for an ItemsControl that can dynamically create ListBoxes based on selected items or added children, supporting an n-level depth.
What I Tried: I experimented a lot with recursive bindings and updates, but none of the attempts were effective enough to showcase here.
I can achieve the intended behavior at level 0, but selecting any child results in the following error:
Help needed: Please feel free to add/replace/implement/adjust the code to complete the ItemsControl (and corresponding App + ViewModels files), which I believe represents the intended functionality:
Community: @awaynemd I know you’ve asked many similar questions before—have you ever found a fully satisfying way to use
subModelSelectedItem
recursively?@xperiandri Do you happen to have any helpers in elmish.UNO that could assist with tasks like this?
@TysonMN (if you have time 😉), I believe I’ve read everything you’ve written about these "issues" in the past concerning recursive uses of
subModelSelectedItem
, but I’m still quite confused. Is there an idiomatic way to use them within an ItemsControl as in my example?@marner2 In your static bindings API revision, is it part of your plan to make a simple helper similar to
subModelSelectedItem
, but with static typing and easy optional recursion?Thank you all so much—I’ve been trying different approaches for the past two weeks, all ending in humbling failures😓