A TreeView
on the left which just displays a list of files saved by the application. A DataGridView
on the right to display data from a file clicked in the TreeView
, or to type directly into to create a new file. Files in the application are just Dictionary<string,string>
objects written to disk with a BinaryFormatter
.
IMappingsView.cs
using System;
using System.Data;
using System.Windows.Forms;
namespace Mapper.Interfaces
{
public interface IMappingsView
{
DataTable MappingData { get; set; }
TreeNodeCollection TreeNodes { get; }
TreeNode SelectedNode { get; }
event EventHandler SaveMapping;
event EventHandler NewMapping;
event EventHandler DeleteMapping;
event EventHandler PasteData;
event EventHandler NodeClicked;
string GetFilename();
void ExpandTreeView();
}
}
MappingsView.cs
using Mapper.Interfaces;
using System;
using System.Data;
using System.Windows.Forms;
namespace Mapper
{
public partial class MappingsView : Form, IMappingsView
{
public event EventHandler SaveMapping;
public event EventHandler NewMapping;
public event EventHandler DeleteMapping;
public event EventHandler PasteData;
public event EventHandler NodeClicked;
public DataTable MappingData
{
get { return (DataTable)dataGridView1.DataSource; }
set { dataGridView1.DataSource = value; }
}
public TreeNodeCollection TreeNodes
{
get { return treeView1.Nodes; }
}
public TreeNode SelectedNode
{
get { return treeView1.SelectedNode; }
}
public MappingsView()
{
InitializeComponent();
}
private void buttonNewMapping_Click(object sender, EventArgs e)
{
if (NewMapping != null)
{
NewMapping(this, EventArgs.Empty);
}
}
private void buttonSaveMapping_Click(object sender, EventArgs e)
{
if (SaveMapping != null)
{
SaveMapping(this, EventArgs.Empty);
}
}
private void buttonDeleteMapping_Click(object sender, EventArgs e)
{
if (DeleteMapping != null)
{
DeleteMapping(this, EventArgs.Empty);
}
}
private void buttonPaste_Click(object sender, EventArgs e)
{
if (PasteData != null)
{
PasteData(this, EventArgs.Empty);
}
}
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
if (NodeClicked != null)
{
NodeClicked(this, EventArgs.Empty);
}
}
public string GetFilename()
{
var input = new InputBox();
input.StartPosition = FormStartPosition.CenterParent;
input.ShowDialog();
return input.MappingName;
}
public void ExpandTreeView()
{
treeView1.ExpandAll();
}
}
}
IMappingModel.cs
using System.Collections.Generic;
namespace Mapper.Interfaces
{
public interface IMappingModel
{
Dictionary<string, string> mapping { get; set; }
}
}
MappingModel.cs
using Mapper.Interfaces;
using System.Collections.Generic;
namespace Mapper
{
public class MappingModel : IMappingModel
{
public Dictionary<string, string> mapping { get; set; }
public MappingModel(Dictionary<string, string> mapping)
{
this.mapping = mapping;
}
}
}
MappingPresenter.cs
using Mapper.Interfaces;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace Mapper
{
class MappingPresenter
{
private const string RootDir = @"C:\";
private IMappingsView view;
private bool isNewMapping;
public MappingPresenter(IMappingsView view)
{
this.view = view;
isNewMapping = true;
AssignEventHandlers();
RefreshTreeView();
}
private void AssignEventHandlers()
{
view.DeleteMapping += view_DeleteMapping;
view.NodeClicked += view_NodeClicked;
view.NewMapping += view_NewMapping;
view.SaveMapping += view_SaveMapping;
view.PasteData += view_PasteData;
}
private void RefreshTreeView()
{
var rootDirInfo = new DirectoryInfo(RootDir);
view.TreeNodes.Clear();
view.TreeNodes.Add(CreateDirectoryNode(rootDirInfo));
view.ExpandTreeView();
}
private void ClearDisplay()
{
view.MappingData = null;
}
private DataTable CreateEmptyTable()
{
var dt = new DataTable("MappingData");
dt.Columns.Add("Key", typeof(string));
dt.Columns.Add("Value", typeof(string));
return dt;
}
private TreeNode CreateDirectoryNode(DirectoryInfo directoryInfo)
{
var directoryNode = new TreeNode(directoryInfo.Name);
foreach (var directory in directoryInfo.GetDirectories())
directoryNode.Nodes.Add(CreateDirectoryNode(directory));
foreach (var file in directoryInfo.GetFiles())
directoryNode.Nodes.Add(new TreeNode(file.Name));
return directoryNode;
}
private IMappingModel LoadMapping(string path)
{
var mapping = new Dictionary<string, string>();
using (var fs = File.OpenRead(path))
{
using (var br = new BinaryReader(fs))
{
int count = br.ReadInt32();
for (int i = 0; i < count; i++)
{
string key = br.ReadString();
string val = br.ReadString();
mapping[key] = val;
}
}
}
return new MappingModel(mapping);
}
private void SaveMapping(IMappingModel mappingModel,string path)
{
using (var fs = File.OpenWrite(path))
{
using (var bw = new BinaryWriter(fs))
{
bw.Write(mappingModel.mapping.Count);
foreach (var pair in mappingModel.mapping)
{
bw.Write(pair.Key);
bw.Write(pair.Value);
}
}
}
}
void view_SaveMapping(object sender, EventArgs e)
{
var mapping = new Dictionary<string, string>();
foreach (DataRow row in view.MappingData.Rows)
{
var key = row[0].ToString();
var val = row[1].ToString();
if (!key.Equals(String.Empty) && !val.Equals(String.Empty))
{
mapping.Add(key, val);
}
}
var path = String.Empty;
if (isNewMapping)
{
var filename = view.GetFilename();
if (filename.Equals(String.Empty))
{
filename = String.Format("mapping{0}", DateTime.Now.ToString("MMddhhmmss"));
}
path = Path.Combine(RootDir, String.Format("{0}.bin", filename));
}
else
{
path = view.SelectedMappingPath;
}
SaveMapping(new MappingModel(mapping),path);
RefreshTreeView();
}
void view_NewMapping(object sender, EventArgs e)
{
var dt = CreateEmptyTable();
view.MappingData = dt;
isNewMapping = true;
}
void view_NodeClicked(object sender, EventArgs e)
{
if (view.SelectedNode.Text.EndsWith(".bin"))
{
var path = Path.Combine(RootDir, view.SelectedNode.Text);
IMappingModel mappingModel = LoadMapping(path);
var dt = CreateEmptyTable();
foreach (var pair in mappingModel.mapping)
{
var row = dt.NewRow();
row[0] = pair.Key;
row[1] = pair.Value;
dt.Rows.Add(row);
}
view.MappingData = dt;
isNewMapping = false;
}
}
void view_DeleteMapping(object sender, EventArgs e)
{
if (view.SelectedNode.Text.EndsWith(".bin"))
{
File.Delete(Path.Combine(RootDir, view.SelectedNode.Text));
ClearDisplay();
RefreshTreeView();
}
}
void view_PasteData(object sender, EventArgs e)
{
DataObject o = (DataObject)Clipboard.GetDataObject();
if (o.GetDataPresent(DataFormats.Text))
{
var pastedRows = Regex.Split(o.GetData(DataFormats.Text).ToString().TrimEnd("\r\n".ToCharArray()), "\r\n");
var dt = CreateEmptyTable();
foreach (string pastedRow in pastedRows)
{
var pastedRowCells = pastedRow.Split(new char[] { '\t' });
var row = dt.NewRow();
for (int i = 0; i < pastedRowCells.Length; i++)
{
row[i] = pastedRowCells[i];
}
dt.Rows.Add(row);
}
view.MappingData = dt;
}
}
}
}
Program.cs
using System;
using System.Windows.Forms;
namespace Mapper
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
MappingsView view = new MappingsView();
var presenter = new MappingPresenter(view);
Application.Run(view);
}
}
}
I welcome all feedback/criticisims/suggestions, but I wasn't sure about some specific items:
- Where should constants be stored? The one I have for this application is in MappingPresenter.cs.
Should
MappingModel
know how toSave
andLoad
itself? I had it like this originally but wasn't sure if thePresenter
was supposed to manage that.2a. Should the code in
SaveMapping
andLoadMapping
be in the event handlers instead?- Do I need to have an
IMappingModel
in the Presenter? - What is the correct way to start the application from Program.cs as what I have doesn't seem correct?
- Should methods like
RefreshTreeView()
andClearDisplay()
be public methods on the view? - Is having a propery (e.g.
TreeNode SelectedNode
,TreeNodeCollection TreeNodes
,DataTable MappingData
) the best practices method for getting/setting data from controls on the view? I'm guessing you don't want to expose the whole control in the interface? - Is it wrong to have private members in the Presenter (e.g.
isNewMapping
)? If so, where would you manage something like that? - What is the best practice way for displaying another view? (
InputBox
in this example) Having a public method on theIMappingsView
was the only way I could see to work it for positioning.