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 toComboBox
– 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
- 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.
-
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.
- Naming nitpicks:
XMLTreeSource
should beXmlTreeSource
;GetXMLDOM
should beGetXmlDom
;SQLTreeSource
should beSqlTreeSource
;- etc.
This is to fall in line with the best-practices of PascalCasing
.
-
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
ofTable; DROP TABLE Customers;
? Not good things, I suggest.) -
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.