Winforms- design pattern to couple treenodes to datastructure - c#

I currently have a data structure that displays file system tree's its basically an object with an array list of like typed objects. I want to display this in a treeview, and need to run some code when the tree view nodes are expanded, collapsed, selected, etc.
My question- is there a design pattern, or method of coupling between the data structure and the treeview that wont require me to search the whole DS tree for the selected node each time the user selects something?
Currently I am searching the data structure for a node with the same Text and Tag property as the selected node each time a relevant tree view event fires. I run into scenarios where if the node is not a leaf I have to go and re-search for its parent node, and I'm concerned about performance with large tree
Keep in mind, the data structure already inherits an object so I cant simply extend the treenode class.
Any help is greatly appreciated

You can still extend TreeNode but using composition to add an extra property that maps to the object that node represents.
You can also move the logic for the child nodes to this tree node rather than have it outside
public class DomainClass { /*...*/ }
public class DomainTreeNode: TreeNode
{
public DomainClass Element { get; private set; }
public DomainTreeNode(DomainClass element): base(element.Name)
{
Element = element;
/* iterate on element's children and add them to the node's
Childs collection ...*/
}
}

Related

Conditionally show WPF Treeview levels

I'm using a TreeView and HierarchicalDataTemplates in WPF and need to use some logic to decide which level of my data structure should be the root node in the tree.
My data structure that the TreeView is bound to is something like this:
|Root
|---|Sublevel1
|------|SubLevel2
|---------|Sublevel3
Based on the role that the user is logged in as, I need to make the root of the tree one of the sublevels, for example:
If I'm an Admin, show the entire tree as listed above.
If I'm a Power User, start the tree at Sublevel1.
If I'm a Normal User, show the tree starting at SubLevel2.
What would be the best approach for doing this? I basically have 4 user roles so I've toyed with the idea of simply creating 4 versions of the tree and selectively showing the correct one based on the user role, but I'm sure there's a better way.
If the tree views essentially display the same data, then do not create four separate tree views. That is redundant, keep it DRY. Populate a single TreeView according to the role that is curretly active.
Let us assume that you have a collection in your view model that contains the hierarchical tree data structure. Usually there is a root node or a collection of nodes of a common node data type, which contains an ObservableCollection of child nodes.
private MyNodeType _root;
This root contains the hierarchical data. Expose a property that represents the top level collection of your tree data structure for binding to ItemsSource of your single TreeView.
private ObservableCollection<MyNodeType> _roles;
public ObservableCollection<MyNodeType> Roles
{
get => _roles;
set
{
if (_roles == value)
return;
_roles = value;
OnPropertyChanged();
}
}
It is important to implement INotifyPropertyChanged, if you can change roles at runtime, because then you would assign a different collection and without raising the PropertyChanged event, the bindings and therefore the associated controls would not update their value.
Now, if you change the role in your application, you can search for matching role in your _root member and directly assign the corresponding child collection to the Roles property, e.g. Sublevel1 (assuming that the child collection property is Children).
Roles = _root.Children;
Of course you could also remove the _root backing property and load, search and assign the right collection on each user change, that depends on your requirements.

TreeView Private variable clearing

I created a private variable
private TreeView currentFullTree;
After that I run through a database and create a nice treeview with parent, child, child nodes. That works fine also. Once the treeview is created I set my private variable to
currentFullTree = treeView1;
That populates perfectly fine and I can even extract each node and re-create the tree exactly how it looked originally from a different function.
Now this is where I'm getting lost. I've created a filter/search function separate from where I created the treeview or re-create the treeview. I want to only display the nodes that match the text provided. Before I perform the search I do a treeView1.Nodes.Clear(); so it looks nice and isn't cluttery.
When I do this, it also empties out my private variable I created! I'm not entirely sure why it is doing this. I was thinking since it is just a TreeView variable I would be able to store my treeview in there and access it as I please. I'm not sure if it's something silly I'm overlooking, but any help is appreciated.
Thanks,
Tim
Your currentFullTree and treeView1 are pointing to the same object because those variables are of reference type.
Either you have to keep copy of whole tree or just tree nodes.
private TreeView currentFullTree;
private List<TreeNode> treeNodes;
and before clearing tree just add them to the list:
treeNodes.AddRange(treeView1.Nodes.OfType<TreeNode>());
treeView1.Nodes.Clear();
and when you want to show full tree (original) then add nodes from list back to tree view.
Your variable is only a reference to an object.
You need to clone it to be able to have different treeview (one for reference and one for display).
see https://msdn.microsoft.com/en-us/library/ms173109.aspx
this may also be usefull for cloning the tree https://msdn.microsoft.com/en-us/library/system.windows.forms.treenode.clone%28v=vs.110%29.aspx
but be aware of this:
Remarks
The tree structure from the tree node being cloned and below is
copied. Any child tree nodes assigned to the TreeNode being cloned are
included in the new tree node and subtree.
The Clone method performs a shallow copy of the node. If the value of
the Tag property is a reference type, both the original and cloned
copy will point to the same single instance of the Tag value.

Adding father after the child to Treeview

There is an existing TreeView with a single item. Lets say that tree consist from some object that has it own id, and parentId. Now i would like to add to tree parent item, that child reference to it by its parentId.
The idea is to handle input with a large amount of nodes that is not always sorted by father first.
How can i repopulate my tree with maximum efficiency ?
Take a look here : http://www.c-sharpcorner.com/uploadfile/9f4ff8/add-root-node-child-node-to-a-treeview-selected-node-at-runtime-and-rename-the-selected-node/
There is a method "Add Root node"
There is a method "Add child node"
I think you are ok.

Pattern / Methodology For Dynamic ContextMenu Based on Collection of Objects

Background
I have a TreeView that follows the MVVM design pattern and supports multiple selection by recording TreeViewItem selections in a List. As it stands there are several types of TreeViewItems available for the user to select.
They are:
Two Root nodes of type WorldFolder and MyDataFodler which can contain child Folder types
Child Folder nodes of types LocationFolder, PersonFolder, CollectionFolder
Child Item nodes of type LocationItem, PersonItem
CollectionFolder can contain child nodes of Folder types
In all this works very well with very little code and supports collections of Locations and People and furthermore Collections within Collections.
Problem / Question
My top level view-model keeps track of the selection state of TreeViewItems and the current selection may be a combination of Item, Folder or even Root type nodes. Depending on the user's selection I want to create a dynamic ContextMenu. So far this works! When I select several LocationItem and/or PersonItem type nodes my view-model generates a custom ContextMenu. The problem is the complexity! My view-model is quickly turning into dozens of if/else if/else statements to capture all the possible permutations!
For example:
if (_selectedItems.All(item => item is PersonItem)) // Only people selected
{
// Create ContextMenu based on only PersonItems
}
else if( _selectedItems.All(item => item is LocationItem)) // Only Locations
{
// Create ContextMenu based only on LocationItems
}
...
Is there a better way to handle all the possible permutations of user choices and generate my ContextMenus more efficiently?
* Sorry about the code formatting, it's been giving me grief all week *
I can't remember where I read this wise saying:
The best way to work with a TreeView is not not to work with the TreeView
What does this mean? Move the functionality into the tree nodes and keep the tree view as thumb as possible. Unfortunately, by default the tree node does not many events, though, it's easy to redirect the tree view events to the nodes.
Once done, you can override the ContextMenuStrip property in your nodes. The first selected node creates the list of ToolStripItems it wants to handle and asks the tree view which are allowed (e.g. with a FilterMenuItems(desiredItems) method). The tree view asks all selected nodes which of the nodes they would be able to handle. The result is your context menu.
This should work with almost any count of different nodes and keeps the tree (nodes) easy to maintain.
Edit: Dang! Missed the WPF tag, so I cannot asses the available events, since I did not yet work with WPF

C# TreeView design - best way to display a tree structure?

I'm trying to use a TreeView to display a tree structure of objects. I have a tree of four types of object, Company (the root node), City, Store and Employee.
The interface is designed to add/remove Cities/Stores/Employees, so the TreeView has to update to reflect any changes.
I am wondering on the correct way to get the TreeView to display the tree structure, and receive updates when it changes.
I am thinking that the Company object should have events, such as company.CityAdded and company.CityRemoved, then whatever wrapper I put round the TreeView responds to those events? When the TreeView has been built up, there will be a node per city/store/employee. Each node could then respond to events of the node it represents in the tree.
Is that the right idea? Or is there a better method?
I just wanted to add that if WPF is an option for this, it becomes incredible simple using heirarchtical databinding and observablecollections. It basically does all the event handle stuff for you, and lets you just interact with your business objects.
Sounds like you are on the right path. I had to do similar thing, a few pointers I'd like to share:
Store object reference in TreeNode tag property.
Give each Treenode a unique name that can easily identify an object, for example: object hashcode, Company ID, etc.
This way you can easily find and update TreeNode when object state changes. And when user selects a node, you can grab an object it's representing from the Tag property.
Good luck.
You're on the right lines about the concept of listening to events (it's a standard publisher/subscriber pattern).
For the actual updating of the treeview I tend to have two methods: AddOrUpdateTreeItem, and RemoveTreeItem. The add or update method does what it says, looks for the tree item (based on a path) and updates it or adds it. Of course if the model is being updated on a thread other than the one the form was created on you will need to marshal the call using Control.BeginInvoke().
This approach can be a little slow if you're populating a full tree at form_load or something, so you might have a different method for an initial population, and use the concept I've described here for subsequent updates.
I do the same for listviews, here's a typical example. The main difference when adding a tree item is that you might need to add the parent nodes in order to add the node being requested, which makes it a bit recursive. Give it a try.
private void AddOrUpdateListItem(DomainModelObject item)
{
ListViewItem li = lvwListView.Items[GetKey(item)];
if (li == null)
{
li = new ListViewItem
{
Name = GetKey(item),
Tag = item
};
li.SubItems.Add(new ListViewItem.ListViewSubItem());
li.SubItems.Add(new ListViewItem.ListViewSubItem());
li.SubItems.Add(new ListViewItem.ListViewSubItem());
li.ImageIndex = 0;
lvwListView.Items.Add(li);
}
li.Text = [Itemtext];
li.SubItems[1].Text = [Itemtext];
li.SubItems[2].Text = [Itemtext];
li.SubItems[3].Text = [Itemtext];
}
Here's an example of how BeginInvoke() might be implemented:
public class MyForm : Form
{
...
void data_Changed(object sender, DataChangedEventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new EventHandler<DataChangedEventArgs>(data_Changed), sender, e);
return;
}
AddOrUpdateListItem(e.DataItem);
}
...
}
Instead of ...
Business objects subscribe to UI events
Commands update the UI
Business objects are updated when the UI is updated
... you can also do it the other way around (i.e. commands update the tree of business objects, which results in a corresponding update to the UI).
Part of the key to the publish/subscribe pattern for updates is how to wrap the information of what to do when the event triggers.
When the object representing "Store X" is updated with a new name, and fires an event to announce this has happened, which object consumes the event?
Similarly, when City Y is added, which object should be notified of the creation?
One common approach is to have some kind of large uber-manager class that handles the entire process - it subscribes to all the events and does everything.
A different approach, one I've used to good effect, is to create much simpler wrapper/coordinator objects that handle just one part of the puzzle. Typically, I suffix the name of these classes with "Editor".
So, you could have a CityEditor class whose constructor takes both a City object, and the TreeNode that represents that object. The CityEditor would subscribe to events on both the City object and the TreeNode, and would take care of populating the TreeNode with a caption and selecting an icon.
When the City object updates, the CityEditor responds to the triggered event by updating the TreeNode. When the City object is removed, the CityEditor makes sure the node is removed from the Treeview.
When a new Store object is added to the City, the CityEditor can take care of creating a StoreEditor to coordinate updates at that level. Similarly, when an Employee is added to a Store, an instance of EmployeeEditor handles updates of the Treeview.

Categories