Traverse tree initializing variables - c#

If you find a more appreapiate way of naming the title of this question please feel free to update it.
I have the Node
public Class Node
{
public List<Node> Children = new List<Node>();
public Node Parent;
public FileSystemInfo Value;
}
to represent a file in a tree.
Now I will like to change that node class for:
class ScanItem
{
public List<ScanItem> Children;
public ScanItem Parent;
public long Size;
public string Name;
public string FullPath;
public bool IsDirectory;
// etc....
}
So my question is how will I be able to cast a Node class object to a ScanItem. I will like to store the size of files and directories not just files. (Node class only stores sizes of files not directories) Therefore I will have to somehow recursively keep track of the sum of the sizes of children in order to achieve what I want.
So far I have placed this constructor on ScanItem:
public ScanItem(Node node)
{
this.Name = node.Name;
this.FullPath = node.Value.FullName;
foreach(var child in node.Children)
{
this.Children.add(new ScanItem(child));
}
}
That will enable me to perform the cast but I am missing to assign the sizes of directories...

To get the size of a directory you can use this extension method
public static class DirectoryInfoEx
{
public static long GetDirectorySize(this DirectoryInfo di)
{
long size = 0;
var fileInfos = di.GetFiles();
foreach (var fi in fileInfos)
{
size += fi.Length;
}
var subDirInfos = di.GetDirectories();
foreach (var subDir in subDirInfos)
{
size += GetDirectorySize(subDir);
}
return size;
}
}
If I got this correctly, you will just need to add two lines of code in the constructor of the ScanItem class
public ScanItem(Node node)
{
this.Name = node.Name;
this.FullPath = node.Value.FullName;
DirectoryInfo di = new DirectoryInfo(this.FullPath); //<<<<<<<
this.Size = di.GetDirectorySize(); //<<<<<<<
foreach(var child in node.Children)
{
this.Children.add(new ScanItem(child));
}
}
You may need to handle some exceptional situations like UnauthorizedAccessException. You can also improve your style by getting rid of public fields, for example by converting them into public auto properties public long Size{get;set;}.

Related

How to hide a specific node on treeview in c# winform? [duplicate]

I'm having a windows form with a tree view control. This tree view has a Root node and 2 child nodes. My requirement is i need to hide the first child node.
Is it possible to make visible false that particular child nod
Yes you could inherit from tree node and create your own behaviour. Like so.
public class RootNode : TreeNode
{
public List<ChildNode> ChildNodes { get; set; }
public RootNode()
{
ChildNodes = new List<ChildNode>();
}
public void PopulateChildren()
{
this.Nodes.Clear();
var visibleNodes =
ChildNodes
.Where(x => x.Visible)
.ToArray();
this.Nodes.AddRange(visibleNodes);
}
//you would use this instead of (Nodes.Add)
public void AddNode(ChildNode node)
{
if (!ChildNodes.Contains(node))
{
node.ParentNode = this;
ChildNodes.Add(node);
PopulateChildren();
}
}
//you would use this instead of (Nodes.Remove)
public void RemoveNode(ChildNode node)
{
if (ChildNodes.Contains(node))
{
node.ParentNode = null;
ChildNodes.Remove(node);
PopulateChildren();
}
}
}
public class ChildNode : TreeNode
{
public RootNode ParentNode { get; set; }
private bool visible;
public bool Visible { get { return visible; } set { visible = value;OnVisibleChanged(): } }
private void OnVisibleChanged()
{
if (ParentNode != null)
{
ParentNode.PopulateChildren();
}
}
}
No, there is no way to make node invisible. You should remove it instead of making invisible. And later you will have to add it back into its original position.
If you are loading a treeview with a sitemap file, then another approach is to do something like this. Here the user's credentials have been read from a DB and written to a cookie.
private void ManageTreeMenu()
{
var value = Utilities.Cookies.GetCookieValue("IsAdmin");
bool.TryParse(value, out var isAdmin);
var dir = Server.MapPath("~");
File.Delete(dir + "Web.sitemap");
if (isAdmin)
File.Copy(dir + "WebAdmin.sitemap", dir + "/Web.sitemap");
else
File.Copy(dir + "WebOper.sitemap", dir + "/Web.sitemap");
}
You'd have to do this again if the user's role was changed in the program. I have only verified this in Visual Studio, not in a deployed web application. Caveat emptor.

Is there any way to access fields of 'super' class (not inherited)

Title may be misleading as I had some trouble searching and even creating a proper question, so let me give a real problem I'm struggling with:
I have a Graph class. Since graphs need nodes and edges I created two additional classes Node (vertex) and Edge. My structure looks like this:
class Graph
{
List<Node> nodes;
List<Edge> edges;
public Graph( ... ) { /* populate lists */ }
}
class Node { ... }
class Edge { ... }
I wrote some methods for Node class, one is particularly problematic for me. Signature:
public List<Node> GetNeighbours(List<Edge> edges) { ... }
Pretty standard. Given a graph I ask a node: how many neighbours do you have? I need list of edges to resolve it.
How can I refactor this code so that I can use Graph properties/fields inside instead of passing a list of edges every time? Is something like this possible:
public List<Node> GetNeighbours()
{
// ...
foreach(edge in *BASE*.edges) { ... }
}
I know that I can't use the base keyword because I don't want any inheritance here (why would a node have to inherit from graph?!) and nested classes seem not to help me as well (no access to "parent's" fields).
This code is working right now, but I feel it's not elegant and I'd like to experience a proper solution.
Pass a reference to the parent class in the Graph constructor.
Something like:
class Graph
{
private ParentType parent;
public void Graph(ref ParentType parent)
{
this.parent = parent;
}
}
Then, in the GetNeighbours method (assuming the ParentType has an Edges collection property):
public List<Node> GetNeighbours()
{
// ...
foreach(var edge in parent.Edges) { ... }
}
From this description of what you're trying to do:
Given a graph I ask a node: how many neighbours do you have?
Are you sure that this should be a method of a Node? Since Graph contains the Nodes and Edges perhaps this method is better off in Graph.
public List<Node> GetNeighbours(Node node)
{
if(!nodes.Contains(node)
{
return new List<Node>(); //No neighbors. Return an empty list.
}
// Find and return the neighbors. This method is in Graph so it
// has access to all of Graph's internals.
}
My reasoning is that since in a sense Graph is a parent and it contains Nodes, Node does not need to know about Graph. Its purpose (Single Responsibility) is complete without any references to Graph.
I would have a method like Graph.AddNodes() or Graph.AddEdges() on Graph so that this is a central place to make sure that all Nodes (and/or Edges) have the reference that it needs. I'm thinking something like this, depending on the model of Node and Edge for you.
class Graph
{
List<Node> nodes;
List<Edge> edges;
public Graph( ... ) { /* populate lists */ }
public void AddEdges(params Edge[] edges) {
foreach (var edge in edges) {
edge.Node1.Parent = this;
edge.Node2.Parent = this;
}
}
}
class Node {
public Graph Parent { get; set; }
public List<Node> GetNeighbours()
{
var neighbors = new List<Node>();
foreach(var edge in parent.Edges) {
if (edge.Node1 == this && !neighbors.Contains(edge.Node2)) {
neighbors.Add(edge.Node2);
}
else if (edge.Node2 == this && !neighbors.Contains(edge.Node1)) {
neighbors.Add(edge.Node1);
}
}
}
}
class Edge {
public Node Node1 { get; set; }
public Node Node2 { get; set; }
}
Here is an alternative approach. Instead of passing the parent reference, you could make each edge aware of the nodes on each end. And make each node aware of the edges connected to them.
A massive advantage of this is that you do not need enumerate possibly massive amounts of nodes/edges to find what you need. You already have what you need so it is much faster.
Here is quick sample of the approach I described along with some tests:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace GraphModelTest
{
class Program
{
static void Main(string[] args)
{
TestA();
TestB();
TestC();
}
private static void TestC()
{
//A <-> B
//| |
//D <-> C
Node a = new Node("a");
Node b = new Node("b");
Node c = new Node("c");
Node d = new Node("d");
Edge ab = a.ConnectTo(b);
Edge bc = b.ConnectTo(c);
Edge cd = c.ConnectTo(d);
Edge da = d.ConnectTo(a);
Graph g = new Graph();
g.Nodes.Add(a);
g.Nodes.Add(b);
g.Nodes.Add(c);
g.Nodes.Add(d);
g.Edges.Add(ab);
g.Edges.Add(bc);
g.Edges.Add(cd);
g.Edges.Add(da);
Console.WriteLine(g.ToString());
Console.WriteLine("Neighbours of B");
foreach (Node n in b.GetNeighbours())
{
Console.WriteLine(n.ToString());
}
Console.WriteLine("Neighbours of D");
foreach (Node n in d.GetNeighbours())
{
Console.WriteLine(n.ToString());
}
}
private static void TestB()
{
//A <-> B <-> C
Node a = new Node("a");
Node b = new Node("b");
Edge ab = a.ConnectTo(b);
Node c = new Node("c");
Edge bc = b.ConnectTo(c);
Graph g = new Graph();
g.Nodes.Add(a);
g.Nodes.Add(b);
g.Nodes.Add(c);
g.Edges.Add(ab);
g.Edges.Add(bc);
Console.WriteLine(g.ToString());
Console.WriteLine("Neighbours of B");
foreach (Node n in b.GetNeighbours())
{
Console.WriteLine(n.ToString());
}
}
private static void TestA()
{
//A <-> B
Node a = new Node("a");
Node b = new Node("b");
Edge ab = a.ConnectTo(b);
Graph g = new Graph();
g.Nodes.Add(a);
g.Nodes.Add(b);
g.Edges.Add(ab);
Console.WriteLine(g.ToString());
}
}
class Edge
{
public Edge(string name, Node a, Node b)
{
Name = name;
A = a;
B = b;
}
public Node A { get; private set; }
public Node B { get; private set; }
public string Name { get; private set; }
public override string ToString() => $"{Name}";
}
class Node
{
public Node(string name)
{
Name = name;
connectedEdges = new List<Edge>();
}
public string Name { get; private set; }
private ICollection<Edge> connectedEdges;
public IEnumerable<Edge> ConnectedEdges
{
get
{
return connectedEdges.AsEnumerable();
}
}
public void AddConnectedEdge(Edge e)
{
connectedEdges.Add(e);
}
public Edge ConnectTo(Node n)
{
//Create the edge with references to nodes
Edge e = new Edge($"{Name} <-> {n.Name}", this, n);
//Add edge reference to this node
AddConnectedEdge(e);
//Add edge reference to the other node
n.AddConnectedEdge(e);
return e;
}
public IEnumerable<Node> GetNeighbours()
{
foreach (Edge e in ConnectedEdges)
{
//Have to figure which one is not this node
Node node = e.A != this ? e.A : e.B;
yield return node;
}
}
public override string ToString() => $"{Name}";
}
class Graph
{
public Graph()
{
Nodes = new List<Node>();
Edges = new List<Edge>();
}
public ICollection<Node> Nodes { get; set; }
public ICollection<Edge> Edges { get; set; }
public override string ToString()
{
StringBuilder str = new StringBuilder();
str.AppendLine("Graph:");
str.AppendLine("Nodes:");
foreach (Node n in Nodes)
{
str.AppendLine(n.ToString());
}
str.AppendLine("Edges:");
foreach (Edge e in Edges)
{
str.AppendLine(e.ToString());
}
return str.ToString();
}
}
}

C# - XDocument / linq to object

How can i import xml elements to object? My code below doesn't work, it fails at the SetValue and i can't figure out why.
But even then, i suspect that linq has a much cleaner way of doing this but i can't find any examples.
class Printers {
public List<Printer> list = new List<Printer>();
public Printers()
{
var xDoc = XDocument.Load(Properties.Settings.Default.XmlSetupPath).Root;
var xPrinters = xDoc.Element("printers").Elements();
foreach (var xPrinter in xPrinters)
{
var printer = new Printer();
foreach (var xEl in xPrinter.Elements())
{
printer.GetType().GetProperty(xEl.Name.ToString()).SetValue(printer, xEl.Value);
}
}
}
}
class Printer
{
public string name;
public string ip;
public string model;
public string infx86;
public string infx64;
public string location;
public string comment;
}
my XML:
<printers>
<printer>
<name>my Printer</name>
<ip>192.168.100.100</ip>
<model>Brother</model>
<driver>ab</driver>
<infx86>ab\cd.INF</infx86>
<comment>Copycenter</comment>
</printer>
<printer>
<name>my Printer</name>
<foobar>oh no!</foobar>
</printer>
</printers>
I want to
You're asking for properties - but your type only has fields. Either make them properties, like this:
public string name { get; set; }
... or use Type.GetField instead.
In terms of making it prettier, I'd personally add a static FromXElement method to your Printer class, at which point you could have:
list = xDoc.Element("printers")
.Elements()
.Select(Printer.FromXElement)
.ToList();
Or you could write a generic method to create a new instance and populate it via reflection, e.g.
public static T FromXElement<T>(XElement element) where T : class, new()
{
T value = new T();
foreach (var subElement in element.Elements())
{
var field = typeof(T).GetField(subElement.Name.LocalName);
field.SetValue(value, (string) subElement);
}
return value;
}
Then:
list = xDoc.Element("printers")
.Elements()
.Select(XmlReflection.FromXElement<Printer>)
.ToList();

Recursively search nested lists

I've read and searched and I'm yet to figure out an answer to this relatively simple issue.
I have a class:
public class AccessibleTreeItem
{
public string name;
public List<AccessibleTreeItem> children;
public AccessibleTreeItem()
{
children = new List<AccessibleTreeItem>();
}
}
which is populate using a series of functions that don't really matter in this context, but what I'm looking for is a way to search through ALL of the children items in the list, searching for a particular 'name' value, and if found, returning that List.
How is this achieved in the easiest manner, with the least performance hit? Thanks - I've been stumped at this point for days now...
public class AccessibleTreeItem
{
public string name;
public List<AccessibleTreeItem> children;
public AccessibleTreeItem()
{
children = new List<AccessibleTreeItem>();
}
public static AccessibleTreeItem Find(AccessibleTreeItem node, string name)
{
if (node == null)
return null;
if (node.name == name)
return node;
foreach (var child in node.children)
{
var found = Find(child, name);
if (found != null)
return found;
}
return null;
}
}

How to make a child node visible = false in a Treeview Control

I'm having a windows form with a tree view control. This tree view has a Root node and 2 child nodes. My requirement is i need to hide the first child node.
Is it possible to make visible false that particular child nod
Yes you could inherit from tree node and create your own behaviour. Like so.
public class RootNode : TreeNode
{
public List<ChildNode> ChildNodes { get; set; }
public RootNode()
{
ChildNodes = new List<ChildNode>();
}
public void PopulateChildren()
{
this.Nodes.Clear();
var visibleNodes =
ChildNodes
.Where(x => x.Visible)
.ToArray();
this.Nodes.AddRange(visibleNodes);
}
//you would use this instead of (Nodes.Add)
public void AddNode(ChildNode node)
{
if (!ChildNodes.Contains(node))
{
node.ParentNode = this;
ChildNodes.Add(node);
PopulateChildren();
}
}
//you would use this instead of (Nodes.Remove)
public void RemoveNode(ChildNode node)
{
if (ChildNodes.Contains(node))
{
node.ParentNode = null;
ChildNodes.Remove(node);
PopulateChildren();
}
}
}
public class ChildNode : TreeNode
{
public RootNode ParentNode { get; set; }
private bool visible;
public bool Visible { get { return visible; } set { visible = value;OnVisibleChanged(): } }
private void OnVisibleChanged()
{
if (ParentNode != null)
{
ParentNode.PopulateChildren();
}
}
}
No, there is no way to make node invisible. You should remove it instead of making invisible. And later you will have to add it back into its original position.
If you are loading a treeview with a sitemap file, then another approach is to do something like this. Here the user's credentials have been read from a DB and written to a cookie.
private void ManageTreeMenu()
{
var value = Utilities.Cookies.GetCookieValue("IsAdmin");
bool.TryParse(value, out var isAdmin);
var dir = Server.MapPath("~");
File.Delete(dir + "Web.sitemap");
if (isAdmin)
File.Copy(dir + "WebAdmin.sitemap", dir + "/Web.sitemap");
else
File.Copy(dir + "WebOper.sitemap", dir + "/Web.sitemap");
}
You'd have to do this again if the user's role was changed in the program. I have only verified this in Visual Studio, not in a deployed web application. Caveat emptor.

Categories