-
-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SubModelSelectItem] - Cascading Listboxes : how to do it in an ItemsControl? #618
Comments
Nope. 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
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 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;
Regarding my specific case
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.. |
It is used for |
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 |
@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. |
@YkTru see https://github.com/xperiandri/Elmish.Uno/tree/submodel-selected-item-cascading 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 |
@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)
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. |
• 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 |
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:
Note: I still depends on the 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. |
The C# code has logic that adds to and removes from the tree. Is that behavior shown in the video in your OP? |
That's ultimately the "complete" goal, as I mentioned in my initial post:
I probably should have included an "Add Child" button in the F# example shown in the video. Or not as it is should be a MWE In the first version my focus was on getting the selection behavior right using I'll work on an F# version of the C# version today without buttons. |
You closed as complete. Did you solve your problem? |
OMG no! I'm working on it right now |
All is working, I guess; the WPF behavior did the trick + accessing "CurrentModel" from ElmisWPF's ViewModels module (godbless chatgpt-o1 on that one) : Video_2024-11-18_131122.mp4I'll have to refactor everything eventually and make it safer + tests, Q1: @TysonMN - Do you not recommend to use Hedgehog, and stick with FsCheck/XUnit exclusively? Q3 @LyndonGingerich @marner2 @xperiandri Do you think it would be valuable to turn this into a fully-featured sample by incorporating serialization, validation, asynchronous web/REST requests, make it as a treeview and other similar functionality showcasing all ElmishWPF bindings helpers + WPF interactions (behaviors/attached prop/dp) in a single solution? This is the kind of resource I wish I had when I started learning Elmish.WPF about a year ago. Q4: @marner2 : I agree with @xperiandri on adding a CurrentModel+InitialModel props for easier/idiomatic property access from ElmishWPF's ViewModels I had to make an helper which I'm sure should be improved: let getCurrentModel<'model, 'msg> (viewModel: obj) : 'model option =
let vmType = viewModel.GetType()
match vmType.GetInterface("IViewModel`2") with
| null ->
printfn "The object does not implement IViewModel<'model, 'msg>."
None
| _ ->
match vmType.GetProperty("CurrentModel") with
| null ->
printfn "The object does not have a CurrentModel property."
None
| prop ->
match prop.GetValue(viewModel) with
| :? 'model as model -> Some model
| _ ->
printfn
"The CurrentModel property could not be cast to the expected type."
None |
Q1: Either is fine, but F# Hedgehog is not currently being actively maintained. |
Here's a working example of hardcoded cascading ListBoxes using
subModelSelectedItem
:Video_2024-11-06_030927.mp4
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😓
The text was updated successfully, but these errors were encountered: