Populate tree from 3 different data sources and show its content

Posted on

Problem

The task is to build a WinForm app which populates a tree from 3 different data sources and shows its content.

  • 1 ComboBox at top – to choose datasource. (File System, XMLFile, SQLDB)
  • 1 Button next to ComboBox – to select datasource path. (Or Connection String)
  • 1 TreeView Control at left – to show tree.
  • 1 RichTextBox to show content. (in case of file system showing txt files content).

MainForm.cs:

public partial class MainForm : Form
    {
        private TreeProvider treeProvider;
        private Action pathSelectFunction;

        public MainForm()
        {
            InitializeComponent();
            InitializeDataSourceComboBox();
        }

        private void InitializeDataSourceComboBox()
        {
            DataSourceComboBox.DataSource = ConfigurationManager.DataSourceTypes.dataSourceTypes;
            DataSourceComboBox.ValueMember = "Type";
            DataSourceComboBox.DisplayMember = "Name";
        }


        private void DataSourceComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            DataSourceType selItem = (DataSourceType)DataSourceComboBox.SelectedItem;
            switch (selItem.Type)
            {
                case "XMLFile":
                    SelectDataSourcePathButton.Text = "Select XML File";
                    pathSelectFunction = SelectXMLFilePath;
                    break;
                case "FileSystem":
                    SelectDataSourcePathButton.Text = "Select Root Folder";
                    pathSelectFunction = SelectRootFolderPath;
                    break;
                case "SQLDB":
                    SelectDataSourcePathButton.Text = "Specify SQL connection string";               
                    break;
                default:
                    return;
            }
        }


        private void SelectDataSourcePathButton_Click(object sender, EventArgs e)
        {
            pathSelectFunction();
        }

        private void SelectXMLFilePath()
        {
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Filter = "XML files (*.xml)|*.xml";
            DialogResult result =  fileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                treeProvider=new TreeProvider(new XMLTreeSource(fileDialog.FileName));
                populateTreeView();
            }
          }

        private void SelectRootFolderPath()
        {
            FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();

            DialogResult result = folderBrowserDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                treeProvider = new TreeProvider(new FileSystemTreeSource(folderBrowserDialog.SelectedPath));
                populateTreeView();
            }
        }

        private void SpecifySQLConnection()
        {
            //TODO
        }

        private void populateTreeView()
        {
            GetAndAddRootNodesToTreeView(MyTreeView);
        }


        private void TreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            MyTreeNode treeViewNode = (MyTreeNode)e.Node;
            MyTreeNode firstChildNode = (MyTreeNode)treeViewNode.Nodes[0];
            if (firstChildNode.IsVirtual)
            {
                treeViewNode.Nodes.Clear();
                GetAndAddChildNodesToTreeView((TreeView)sender, treeViewNode);
            }
        }
        private void GetAndAddChildNodesToTreeView(TreeView treeView, MyTreeNode nodeToAdd)
        {
            nodeToAdd.Nodes.Clear();
            foreach (var data in treeProvider.GetChildNodes(nodeToAdd.Data))
            {
                MyTreeNode newTreeNode = new MyTreeNode(data);
                newTreeNode.Text = data.Identity;
                if (data.ChildCount > 0)
                {
                    newTreeNode.Nodes.Add(new MyTreeNode(true));
                }
                nodeToAdd.Nodes.Add(newTreeNode);
            }
        }
        private void GetAndAddRootNodesToTreeView(TreeView treeView)
        {
            treeView.Nodes.Clear();
            foreach (var data in treeProvider.GetRootNodes())
            {
                MyTreeNode newTreeNode = new MyTreeNode(data);
                newTreeNode.Text = data.Identity;

                if (data.ChildCount > 0)
                {
                    newTreeNode.Nodes.Add(new MyTreeNode(true));
                }

                treeView.Nodes.Add(newTreeNode);
            }
        }


        private void TreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            MyTreeNode node = (MyTreeNode)e.Node;
            rtbDataViewer.Text = node.Data.Content;
        }


    }

    public class MyTreeNode : TreeNode
    {
        public NodeBase Data { get; set; }

        private bool isVirtual=false;
        public bool IsVirtual
        {
            get { return isVirtual; }
            set
            {
                if (value)
                {
                    Text = "Please Wait";
                }
                isVirtual = value;
            }
        }
        public MyTreeNode(NodeBase data)
        {
            Data = data;
        }
        public MyTreeNode(bool isVirtual = false)
        {
            IsVirtual = isVirtual;
        }

    }

NodeBase.cs:

    public class NodeBase
{
    public string Identity { get; set; }
    public string ParentIdentity { get; set; }
    public string Content { get; set; }
    public int ChildCount { get; set; }

    public NodeBase()
    {

    }



    public NodeBase(string identity, string parentIdentity, string content, int childCount)
    {
        Identity = identity;
        ParentIdentity = parentIdentity;
        Content = content;
        ChildCount = childCount;
    }
}

ITreeSource.cs:

    public interface ITreeSource
{
    IEnumerable<NodeBase> GetRootNodes();
    IEnumerable<NodeBase> GetChildNodes(NodeBase node);
}

TreeSource.cs:

   public class TreeProvider : ITreeSource
{
    private ITreeSource provider { get; set; }

    public TreeProvider(ITreeSource provider)
    {
        this.provider = provider;
    }

    public IEnumerable<NodeBase> GetRootNodes()
    {
        return provider.GetRootNodes();
    }

    public IEnumerable<NodeBase> GetChildNodes(NodeBase node)
    {
        return provider.GetChildNodes(node);
    }

}

FileSystemTreeSource.cs:

  class FileSystemTreeSource : ITreeSource
{
    DirectoryInfo rootDirectory;
    public FileSystemTreeSource(string rootDirectoryPath)
    {
        rootDirectory = new DirectoryInfo(rootDirectoryPath);
    }

    public IEnumerable<NodeBase> GetRootNodes()
    {
        foreach (DirectoryInfo directory in rootDirectory.GetDirectories())
        {
            if (GetDirectoryNode(directory) != null)
            {
                yield return GetDirectoryNode(directory);
            }
        }
        foreach (var file in rootDirectory.GetFiles("*.txt"))
        {
                           if (GetFileNode(file) != null)
            {
                yield return GetFileNode(file);
            }
        }
    }


    public IEnumerable<NodeBase> GetChildNodes(NodeBase node)
    {
        DirectoryInfo currentDirectoryInfo = new DirectoryInfo(node.ParentIdentity + "\" + node.Identity);
        foreach (var directory in currentDirectoryInfo.GetDirectories())
        {
            if (GetDirectoryNode(directory)!=null)
            {
                yield return GetDirectoryNode(directory);
            }
        }
        foreach (var file in currentDirectoryInfo.GetFiles("*.txt"))
        {
            if (GetFileNode(file) != null)
            {
                yield return GetFileNode(file);
            }
        }
    }

    private NodeBase GetDirectoryNode(DirectoryInfo directory)
    {
        NodeBase nodeBase = new NodeBase() ;
        try{
            nodeBase.Identity = directory.Name;
            nodeBase.ParentIdentity = directory.Parent.FullName;
            nodeBase.Content = null;
            nodeBase.ChildCount=directory.GetDirectories().Count();
            nodeBase.ChildCount += directory.GetFiles("*.txt").Count() ;
        }
        catch
        {
            //log
            return null;
        }
        return nodeBase;
    }
    private NodeBase GetFileNode(FileInfo file)
    {
        NodeBase nodeBase = new NodeBase();
        try
        {
            nodeBase.Identity = file.Name;
            nodeBase.ParentIdentity = file.DirectoryName;
            using (StreamReader streamReader = file.OpenText())
            {
                nodeBase.Content = streamReader.ReadToEnd();
            }
            nodeBase.ChildCount = 0;
        }
        catch
        {
            //log
            return null;
        }
        return nodeBase;
    }
}

SQTreeSource.cs:

   class SQLTreeSource :ITreeSource
    {
        string connectionString;
        string tableName;
        string keyColumnName;
        string parentkeyColumnName;
        string contentColumnName;

        public SQLTreeSource(string connectionString,
                                string tableName, 
                                string keyColumnName, 
                                string parentkeyColumnName, 
                                string contentColumnName)
        {
            this.connectionString = connectionString;
            this.tableName = tableName;
            this.keyColumnName = keyColumnName;
            this.parentkeyColumnName = parentkeyColumnName;
            this.contentColumnName = contentColumnName;
        }

       private SqlCommand GetChildNodesCountCommand(SqlConnection connection, int key)
        {
            SqlCommand command = new SqlCommand();
            command.Connection = connection;
            command.CommandText = "SELECT Count(" + keyColumnName + ") FROM " + tableName + " WHERE " + parentkeyColumnName + "=@keyColumnName";
            command.Parameters.AddWithValue("@keyColumnName", key);
            return command;
        }

        public int GetChildNodesCount(int key)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                using (SqlCommand comm = GetChildNodesCountCommand(conn, key))
                {
                    var result = comm.ExecuteScalar();
                    return (int)result;
                }
            }
        }
        private SqlCommand GetRootNodesCommand(SqlConnection connection)
        {
            SqlCommand command = new SqlCommand();
            command.Connection = connection;
            command.CommandText = "SELECT * FROM " + tableName + " WHERE " + parentkeyColumnName + " is null";

            return command;
        }
        public IEnumerable<NodeBase> GetRootNodes()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                using (SqlCommand comm = GetRootNodesCommand(conn))
                {
                    var reader = comm.ExecuteReader();
                    while (reader.Read())
                    {
                        yield return new NodeBase(reader[keyColumnName].ToString(),
                                        reader[parentkeyColumnName].ToString(),
                                        reader[contentColumnName].ToString(),
                                        GetChildNodesCount((int)reader[keyColumnName]));
                    }
                }
            }
        }

        private SqlCommand GetChildNodesCommand(SqlConnection connection, string key)
        {
            SqlCommand command = new SqlCommand();
            command.Connection = connection;
            command.CommandText = "SELECT * FROM " + tableName + " WHERE " + parentkeyColumnName + "=@key";
            command.Parameters.AddWithValue("@key", key);
            return command;
        }
        public IEnumerable<NodeBase> GetChildNodes(NodeBase node)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                using (SqlCommand comm = GetChildNodesCommand(conn, node.Identity))
                {
                    var reader = comm.ExecuteReader();
                    while (reader.Read())
                    {
                        yield return new NodeBase(reader[keyColumnName].ToString(), 
                                        reader[parentkeyColumnName].ToString(),
                                        reader[contentColumnName].ToString(),
                                        GetChildNodesCount((int)reader[keyColumnName]));
                    }
                }
            }
        }
    }

XMLTreeSource.cs:

public class XMLTreeSource : ITreeSource
{
    string xmlFilePath;
    XmlDocument xmlDocument;

    public XMLTreeSource(string xmlFilePath)
    {
        this.xmlFilePath = xmlFilePath;
    }

    private XmlDocument GetXMLDOM()
    {
        if (xmlDocument == null)
        {
            this.xmlDocument = new XmlDocument();
            xmlDocument.Load(xmlFilePath);
        }
        return xmlDocument;
    }

    public IEnumerable<NodeBase> GetRootNodes()
    {
        int ChildCount = GetXMLDOM().DocumentElement.ChildNodes.Count;
        yield return new NodeBase(GetXMLDOM().DocumentElement.Name, null, null, ChildCount);
    }
    public IEnumerable<NodeBase> GetChildNodes(NodeBase node)
    {
        XmlNode parentNode = GetXMLDOM().SelectSingleNode(".//" + node.Identity);
        foreach (XmlNode cNode in parentNode)
        {
            int ChildCount = cNode.ChildNodes.Count;
            yield return new NodeBase(cNode.Name, node.Identity, cNode.Value, ChildCount);
        }
    }



}

Solution

  1. The usual whitespace/egyptian brackets mumbo-jumbo.
    NodeBase nodeBase = new NodeBase() ;
    try{
        nodeBase.Identity = directory.Name;
        nodeBase.ParentIdentity = directory.Parent.FullName;

Should be:

    NodeBase nodeBase = new NodeBase();

    try
    {
        nodeBase.Identity = directory.Name;
        nodeBase.ParentIdentity = directory.Parent.FullName;

Etc.

  1. Did you mean for the internal classes?

    class FileSystemTreeSource : ITreeSource
    class SQLTreeSource : ITreeSource
    public class XMLTreeSource : ITreeSource
    

Notice that XMLTreeSource is the only one that is public.

If you did want them to be internal, I suggest adding the internal keyword in front of them to be explicit about it.

  1. Naming nitpicks:
    1. XMLTreeSource should be XmlTreeSource;
    2. GetXMLDOM should be GetXmlDom;
    3. SQLTreeSource should be SqlTreeSource;
    4. etc.

This is to fall in line with the best-practices of PascalCasing.

  1. Your SQL should be parameterized queries instead. As it stands you are very open to SQL injection. (What would happen if I specified a tableName of Table; DROP TABLE Customers;? Not good things, I suggest.)

  2. Usually I tend to favour immutability over mutability.

public string Identity { get; set; }
public string ParentIdentity { get; set; }
public string Content { get; set; }
public int ChildCount { get; set; }

Those set actions should be private.

Unless you absolutely have a need for mutability, you should try to favour immutability. It leaves you less vulnerable to problems with data validation, et al.

Leave a Reply

Your email address will not be published. Required fields are marked *