Elmish.wpf: No checkbox samples

Created on 16 Oct 2019  路  39Comments  路  Source: elmish/Elmish.WPF

I'm attempting to implement Elmish.WPF for the first time ever. My WPF window consists primarily of a TreeView in which each leaf of the tree contains a set of checkboxes. In fact, the only way the user will be able to change the data is by checking/unchecking those checkboxes. I have just discovered that in all the samples in this repo, there is not a single checkbox.

Is there any example code anywhere for how to do MVU for a checkbox? I can probably work it out on my own, but it's always quicker if there's at least one example to follow.

Thanks

Most helpful comment

That pretty much reflects how I argued in that other issue thread yesterday. I feel it's better to use separate repos for samples that aim to demo use of various controls, rather than specific features of Elmish.WPF. These repos can be personal, or we can cooperate if somebody takes the initiative. I won't, at this time, since I have too much to learn first.

I have created a repo where I'll be exploring Elmish.WPF with various standard WPF components used in various ways. My intention is to help others while helping myself. It's easier to get help from you experts if I can show you what I'm trying to do, and this way I can also give something back to the community. I've only just got started, so there isn't much to see today.
This is the address: https://github.com/BentTranberg/ExploreElmishWpf

All 39 comments

Use a TwoWay binding to the IsChecked property.

What @bender2k14 said.

Regarding samples: I'm always open to discussing more samples! 馃檪 We need to strike a balance between guiding new users and not overwhelming them with too many samples essentially showing the same thing. The goal of the samples is to show how to set up the bindings (the central part of the Elmish.WPF API), and there should be samples showing how to use oneWay, twoWay, subModelSeq, etc. We can't have a sample for every single control.

That said, I have now adapted the NewWindow sample to show a checkbox (I replaced one of the Window2 text boxes with a checkbox, since there was no particular reason to have two text boxes and the sample is no worse off with a checkbox instead).

That pretty much reflects how I argued in that other issue thread yesterday. I feel it's better to use separate repos for samples that aim to demo use of various controls, rather than specific features of Elmish.WPF. These repos can be personal, or we can cooperate if somebody takes the initiative. I won't, at this time, since I have too much to learn first.

I have created a repo where I'll be exploring Elmish.WPF with various standard WPF components used in various ways. My intention is to help others while helping myself. It's easier to get help from you experts if I can show you what I'm trying to do, and this way I can also give something back to the community. I've only just got started, so there isn't much to see today.
This is the address: https://github.com/BentTranberg/ExploreElmishWpf

I really appreciate all your prompt help. I think I have the checkbox figured out. But I'm definitely wishing I had a simpler example for a TreeView. I'm sure the SubModelSeq example demonstrates everything I need to do, but I think it has a lot more pieces than necessary. In my case, I plan to build the entire tree before the window is displayed. After that the only changes will be updates to some fields of each leaf. So all the code that deals with dynamically adding and removing nodes/leafs at run-time could be greatly simplified in my case. I'm also wondering if assigning a GUID to each leaf as that example does is the preferred design. In my case, I will probably be serializing the data to a JSON file, and never storing it in a database...in case that matters.

But I'm definitely wishing I had a simpler example for a TreeView.

I recommend cloning Elmish.WPF and simplifying the SubModelSeq sample by removing all the things that you don't care about. Then you should have the sample that you want.

I'm still having a difficult time getting from that example of dynamically building a tree to what I need, which is pre-loading a tree from an existing model in memory (either a fresh one with default values, or a user-customized one deserialized from a JSON file). I've been trying to modify the init function to load a tree with children, but so far I can't get it to load anything but top-level nodes.

Are you using a HierarchicalDataTemplate like the SubModelSeq sample does?

I've been trying to modify the init function to load a tree with children, but so far I can't get it to load anything but top-level nodes.

From your description this sounds like a general F# problem not related to Elmish.WPF. Is that correct? (Not saying we can't help; just trying to clarify what you're struggling with.)

From your description this sounds like a general F# problem not related to Elmish.WPF.

Exactly. I was just typing another comment along those lines. Instead of F#, maybe this is a WPF problem. Make sure you are looking at System.Diagnostics.Trace (which Visual Studio displays in its Output window). That is where WPF logs are sent.

I am modifying the init function in the submodelseq sample, so yes I'm using whatever it is.

I would say it's very specific to Elmish.

I don't understand what the init function has to do with this.

Are you able to display all your tree data in WPF _without_ using Elmish.WPF?

Yes, I had a C# project that displayed it in WPF, but it did not have any code to enable the user to make changes. So, I am trying to implement it using Elmish.WPF. See my C# and XAML code at https://gist.github.com/ScottHutchinson/842efd4b1919647189c841f34e5b2a10 which my give you a better idea of what I'm trying to do.

I guessed that the init function would be where I would load the model into the view when the window is displayed. My guess could be completely wrong.

The init function simply returns your initial model/state. It has nothing to do with the view.

I strongly recommend checking out the recommended resources in the readme. They will help you familiarize yourself with core Elmish concepts. 馃憤

Sorry, that's what I meant to say...that the init function would initialize and return the model. I assumed that the rest of the sample code would then load that model into the view (binding it to the WPF TreeView control). I will read the documentation as you suggested.

I've been trying to modify the init function to load a tree with children, but so far I can't get it to load anything but top-level nodes.

If by "load" you mean "load into memory" or "load into the model", then this has nothing to do with Elmish.WPF; it's general F#.

If you instead mean "load into the UI", then the difficulties may indeed lie in the bindings (but the init function itself isn't relevant in that case).

Which of these interpretations (if any) are correct?

I assumed that the rest of the sample code would then load that model into the view (binding it to the WPF TreeView control).

You would also have to set up the correct bindings. I assume your tree model is not exactly like the sample's tree model, so therefore you'd need to change the bindings (and probably a lot else in the code) to match your model type. A lot of that should be forced by the compiler, though.

Wow, I apparently cannot write English. I seem to be confusing everyone. All I am trying to do at the moment, is modify the SubModelSeq sample to be more like the use case I need. I should not need to change the bindings to do that, as far as I know. I'm just trying to get the init function to return a model with more branches/children than the example, which starts with only one root node. Once I do that, I would think that model would be displayed in the WPF window, but so far that is not happening, except for top-level nodes.

Ah, I see. Could you post the init code you think should work, but which is not working?

The stack overflow is probably because you reuse the root ID as the child ID (Counter.createChild(root.Data.Id)), making the root its own child:

https://gist.github.com/ScottHutchinson/6e1fd8a18cdb2cbfe8cadf2651330bec#file-program-fs-L96-L98

IDs must be unique.

Oh, I thought that was the parent ID, that would make it a child of the node with that ID. I'll try something different.

@bender2k14 is the one who refactored that sample to use a tree type, so he's the expert, but AFAIK the child/parent relationship comes directly from the tree hierarchy (which is the point of the tree type in the first place).

OK. I finally got a child to appear in the view as I expected (using the code below). So I should be able to get it to do what I want now.

  let init () =
    let root = [ Counter.create() |> Tree.asLeaf ] |> asDummyRoot
    let c = Counter.create() |> Tree.asLeaf
    let gc = Counter.create() |> Tree.asLeaf
    let c' = { c with Children = gc :: c.Children }
    let root' = { root with Children = c' :: root.Children }
    { SomeGlobalState = false; DummyRoot = root' }

Glad to see that you figured out your problem. Sorry that we didn't understand what you were struggling with.

One way that you could have figured out the parent-child relationship is to run the sample without modifications, add children through the UI, set a breakpoint in update, click some button in the UI, and then inspect the model.

Part of what I struggled with, and am still struggling with is that when I run the init function above, c' is displayed at the same level in the hierarchy as root, even though the code cons it to root.Children.

Also, it's difficult to tell what's going on when the only way to identify each node is by a GUID, so I eventually added a CounterName to the Counter and the view. Counter.create "child1" etc.

Part of what I struggled with, and am still struggling with is that when I run the init function above, c' is displayed at the same level in the hierarchy as root, even though the code cons it to root.Children.

I think you are confused by what is meant by "dummy". The (dummy)root is _not_ (itself) displayed in the UI. The children of the (dummy)root, including c' _are_ displayed in the UI. Therefore, it is definitely the case that (dummy)root and c' are _not_ displayed at the same level of the hierarchy.

You can search for information about the use of dummy nodes in data structures. Here is one result that I think does a good job of explaining dummy nodes.

Also, it's difficult to tell what's going on when the only way to identify each node is by a GUID, so I eventually added a CounterName to the Counter and the view. Counter.create "child1" etc.

Oh, sure. That is a good idea.

The (dummy)root is not (itself) displayed in the UI.

To be clearer, the data of the (dummy)root is not displayed in the UI but its children (and their data) are.

root and c' are not displayed at the same level of the hierarchy
I guess you did not run my code, or you would see what I mean.

I did read that comment, but it does not fully explain the behavior I am seeing.

I guess you did not run my code, or you would see what I mean.

I think I have only seen gists and secret file links from you, but I could be mistaken.

Part of what I struggled with, and am still struggling with is that when I run the init function above, c' is displayed at the same level in the hierarchy as root, even though the code cons it to root.Children.

Can you share a link to a repo (that I can clone) containing a branch (that I can checkout) that exhibits this behavior?

The code I mean is at https://github.com/elmish/Elmish.WPF/issues/153#issuecomment-543978444
I was having trouble yesterday creating a branch in VSCode.
Maybe I should just pretend the dummy node is not there, and not add any children to it? Instead I should add my own root and add children to it?

The code I mean is at #153 (comment)

Ok, that is easy.

I stand by my previous statement. The data of the root node is _not_ displayed in the UI. Count the calls to Counter.create(). There are four:

  • three explicitly in your version of init and
  • one inside asDummyRoot.

Then look at the UI. There are just three counters displayed in the UI.

Maybe this variable naming will make things clearer.

let init () =
  let child1 = Counter.create() |> Tree.asLeaf
  let root = [ child1 ] |> asDummyRoot
  let child2 = Counter.create() |> Tree.asLeaf
  let grandChild = Counter.create() |> Tree.asLeaf
  let child2' = { child2 with Children = grandChild :: child2.Children }
  let root' = { root with Children = child2' :: root.Children }
  { SomeGlobalState = false; DummyRoot = root' }

The data of (dummy)root is _not_ displayed in the UI. Instead, the data of the following nodes are displayed in the UI:

  • child1,
  • child2', and
  • grandchild.

I think you're right. I think I mistakenly named your child1 as "root", so I thought the root was displayed, but it was a child of root.

I think I mistakenly named your 'child1' as 'root'...

No. Your root is my root. You did not give a name to my child1'. Your c is my child2.

Yeah, ok. Let's just say I was confused, and now I've confused you yet again with my imprecise language (re variable name vs. a display name property for the Counter type).

Thank you so much for all your help. I'm fairly optimistic that Elmish.WPF will serve my needs well. But much work still to be done.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TysonMN picture TysonMN  路  10Comments

TysonMN picture TysonMN  路  7Comments

awaynemd picture awaynemd  路  5Comments

dsyme picture dsyme  路  10Comments

cmeeren picture cmeeren  路  13Comments