How could I implement the tree correctly using XAML?
Hello! I've started a XAML .NET application
https://github.com/PerikiyoXD/RWTree/
The problem I'm facing is that I'm creating chunks of data in classes that are going to represent a tree.
Now I just create
TreeItem
instances using this approach:
Middleware/RenderWare/Stream/Chunks/ClumpStructChunk.cs
I know this is not the best approach and I'd like to have suggestions on how could I implement this
The end product should be something like this (Side by side comparison, left is RWTree, right is RWAnalyze which I'm trying to replicate)GitHub
GitHub - PerikiyoXD/RWTree: RWTree is a specialized application cra...
RWTree is a specialized application crafted for visualizing the internal structure of RenderWare binary streams. - GitHub - PerikiyoXD/RWTree: RWTree is a specialized application crafted for visual...
38 Replies
A must have feature is the icon on the tree, and to be fair, I'd like to know how to make it modular enought so I can extend it properly
(If you need any reference please look at the repo, thank you!)
(sorry for the lack of development in the statement but I have little time today, tomorrow I will answer the doubts and see the suggestions)
XAML .net is not an application type, afaik. Do you mean WPF?
Yep
My bad
So, I have a Base Class "Chunk" that will have derived classes of it "AtomicChunk", "ClumpChunk"...
And the data is there. It's structured in a tree by itself (As ClumpChunk has ClumpStructChunk, FrameListChunk etc...)
The thing is that I created an Interface that has a function that Chunks need to implement
So far, I create the TreeItems manually
But I think this isn't the correct way to do this
Here is an example
https://wpf-tutorial.com/treeview-control/treeview-data-binding-multiple-templates/
Your class design is also a bit odd. The models should not have anything to do with the UI.
As a rule of thumb: If you use a UI type in your ViewModels and below then you have left the MVVM pattern (which is all about separation)
Also you should avoid bidirectional parent-child relations. Let the child know about the parent or the paerent about the child. It will simplify your design.
Does the data like ClumpChunk and ClumpStructChunk represent a hierarchy of items, as in, it could contain children of different or even the same type containing the children list?
Not very farmiliar with render ware's stuff, cool that people are still using it though lol
Yes, it's just like a tree, the chunks inside are defined by the chunk type
RenderWare game modder here 🚀
And you want to replecate that entire tree in the view?
Exactly
The idea is, I have a parser that will read the binary and will just generate a representation using objects, in this case, Chunk derived classes
Then, in paralell, I want to represent that structure using the data I've parsed
I thought I could just hack together a "TreeItem generation routine"
I assume the tree can be modified by the user right?
As in adding/removing nodes from the tree
Yes, it would be modified by removing or adding items, basically crafting one youself from scratch
But I don't need that functionality right away
If that's already added on the first step would be awesome
So far I need to understand how DataBinding works and how could I represent a "TreeItem" with a "Chunk"
Right now is a hack as I said
This goes heavily against MVVM but it's the easiest solution tbh, what you could do is create NodeAdded and NodeRemoved events in the classes that have that hierarchial feature, and then you could create custom type for those models (extending TreeViewItem)
And then those custom tree view item classes would contain a method like AttachModel and DetatchModel, and in attach you would hook event handlers to the model and also generate all child tree view items based on the model's children (and you'd need some sort of factory method to create the custom tree view item class based on the model; i would just use a Dictionary<Type, Func<ChunkTreeViewItem>> for simplicity)
You would have a control called something like RootTreeView, which contains a reference to the root chunk model, and when that property changes, it generates its children in the same way as the tree view items, and calls the AttachModel/DetatchModel for them. The AttachModel would also create the child tree items too so it's recursive
So right now, I have a Model and a View, The custom TreeItem would be the ViewModel
If that's what you want then yeah
I'm trying to locate myself between your advice and what I already have
Is this assertion right?
I decided to avoid view models because it just adds extra complexity, like having to replicate all model types as view models
I don't mind having a bit of boilerplate tbh, MVVM isn't my favourite anyways
But using view models, you wouldn't have to nessesarily create the custom UI controls as you coud just stick with the general TreeViewItem and use binding
I have an example if the code would be easier to understand. The part of the app is called the ResourceManager, which manages a hierarchy of Resource nodes, which can be folders or items, and there are custom item types that extend the base item type
https://github.com/AngryCarrot789/FramePFXNewFrameworkTests/tree/master/FramePFX/Editors/Controls/Resources
Those are the UI parts in that folder
And then the models are in this folder
https://github.com/AngryCarrot789/FramePFXNewFrameworkTests/tree/master/FramePFX/Editors/ResourceManaging
The folder model contains the Added/Removed/Moved (for efficiency over remove then add) events, and the view listens to them and creates/removes/moves the UI components accordingly
Oh i haven't actually pushed the tree UI parts to it yet lmfao
Maybe the model side would help though. You have to do all the binding yourself, either by making models implement
INotifyPropertyChanged
, or by handling individual events which is more tedious but in some cases can be so much better
And then the icons you were talking about, in your control templates for the custom tree items, you could just have an Image called soemthing like PART_IconImage
, and in OnApplyTemplate
you would fetch it and use that to update the icon based on however you determine the iconI'm having a look to that code
I can't really remember I did the whole attach/detach thing instead of just making it a dependency property, it might have just been for performance but I can't really remember
In other parts of the app I also have OnAdding, OnAdded, OnRemoving, OnRemoved instead which get called before and after actually adding/removing the controls into/from the items collection, but I only do that just as a guarentee that the model object isn't null when the object is actually in the list, just in case inserting the UI item causes some method to get called which didn't expect the model to be null
I understand the Event-oriented part
I guess most of the heavy lifting is already there using WPF events?
Heavy lifting?
Most of the events*
Well, I need to learn WPF in depth first, because it seems that I need to create "Components" and then bind them with the Models
My approach I see it's "Data binding via Code-behind"
I see now that my Models would need implemeting INotifyPropertyChanged as you said
Binding or attaching, is just associating the control with that particular model. In my app I update the model object for the root object (
ResourceExplorerListControl
), and in its property changed callback, I then remove event handlers for the old model and clear the items, and add handlers for the new model and add all of the model's items. Because in my case I just have a single control type (ResourceListItemControl
), that add phase just consists of creating a new instance of that control, calling OnAdding, then inserting that control into the current control's item list, and then calling OnAdded, if that makes sense
If you wanted to, you could just use a single custom class that extends TreeViewItem, but then update its Header
property with a custom control based on your model type, which is probably better that loads of custom TreeViewItem types
But yeah INotifyPropertyChanged would make things easier, but at that point you're basically treating the model as a view model too which I guess is fine
If you decide to do that approach with a model and view model in one, then instead of a custom control for the tree item content, you could just use data templates and bind to properties in your model (making sure to set the DataContext of the tree view items accordingly obviously)I agree, I could just have a custom type that will have an Icon, and a Title, no more
Then styles to handle selection and maybe some other cases
https://github.com/PerikiyoXD/RWTree/blob/459c62d298b5adeeef09b34664826beff3041c15/MainWindow.xaml.cs#L14
That's the Data per-se
Then I do this: https://github.com/PerikiyoXD/RWTree/blob/459c62d298b5adeeef09b34664826beff3041c15/MainWindow.xaml.cs#L84
Then DFF does call the hierarchy to fetch the nodes from the children:
That's the dynamic aspect here
How would I start? Implementing my own TreeViewItem with a "ChunkTreeViewItem" type?
That's what I'd do
In your main window's LoadFile is where I would replace the code with this:
Do I need to derive from a base class and attach Interfaces?
And then in
CustomTreeViewItem
I would do something like this:
Something along those lines if that makes sense
That's what I do and it seems to work nicelyI see
If the model doesn't have children then you don't add anything to
this.Items
Now I gotta find out how to do the XAML part
But then actually updating things like the headers, icons... that's where you could either do
this.DataContext = item
in OnAddingToParent
(or OnAddedToParent
, at this point it depends on whatever one works lol) and then bind in the XAML code assuming your models implement INotifyPropertyChanged
Or, you add event handlers in stuff like OnAddedToParent
and remove the handlers in OnRemovingFromParent
to then update the header when a model's header changed event is fired
Binding is probably simpler for your case though
But your models will start looking pretty much like view models, unless you're fine with thatIn the future I want to extract the "Chunk" stuff to a library so it can be reused later
I'd probably not bind the data to the view directly
The event route might be the better options in that case
Right now the data classes are just implementing IBinaryReadWrite:
Then each chunk derived type implements that and the properties:
In that sense the models are straightforward
And I'd not mix the ViewModel here for cleanness sake
I do similar stuff in my code and decided that Reading is part of the object construction phase and therefore has no reason to fire events for data changing
I'd prefer not binding the Read to the Ctor as I'd need to have empty defaults
That's by design
I just meant that, say a model was attached to a tree item in the view, calling Read on that model might just corrupt the UI a bit
But could allow that though, Read could fire the according events
Read only happens when you load the file
Save only happens when you save the file
In this case, the tree will be reloaded
As the state just changed
Same when I manipulate any property I guess
With events you can avoid it and I think that's the point you're telling