// The class factory can improve readability of the code by providing methods that return an operation object of the right type
// This is purely a design decision.
// For the purposes of demonstration you can define this manifest constant to use the class factory method.
#define UseClassFactory
// Readbility can also be improved by defining types for the operations.
// This is purely a design decision.
// For the purposes of demonstration you can define this manifest constant to use the type definitions method.
#define UseTypeDefinitions
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using KWUtils;
namespace CommandDemo
{
#if UseTypeDefinitions
using AddOpType = Operation<string, int>;
using UnAddOpType = Operation<string, int>;
using DeleteOpType = Operation<List<int>, int>;
using UnDeleteOpType = Operation<List<int>, List<string>>;
#endif
public partial class CommandDemoForm : Form
{
#region Data members
/// <summary>
/// We save the names in a file. As this is just a demo the filename is fixed
/// </summary>
private const string fileName = "CommandDemo.txt";
#endregion
#region constructor
/// <summary>
/// Standard form initialiser. Reads in the list of names from the file
/// </summary>
public CommandDemoForm()
{
InitializeComponent();
// Add a handler for the modifiedChanged event
OperationBase.modifiedChanged += new CommandEventHandler(OperationBase_modifiedChanged);
// Read in the list of names if it exists.
try
{
string[] list = File.ReadAllLines(fileName);
foreach (string line in list)
{
namesListBox.Items.Add(line);
}
}
catch (FileNotFoundException)
{
// If the file doesn't exist just leave the list empty
}
}
#endregion
#region Handlers
/// <summary>
/// Handler for the Command ModifoedChanged event. Shows a star in the title bar
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">An <see cref="CommandEventArgs"/> that contains the event data.</param>
void OperationBase_modifiedChanged(object sender, CommandEventArgs e)
{
if (OperationBase.IsModified)
{
this.Text += " *";
}
else
{
this.Text = this.Text.Substring(0, this.Text.Length - 2);
}
}
/// <summary>
/// Handler for the text box text changed event. Enables the Add button if there is some text
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
private void newNameTextBox_TextChanged(object sender, EventArgs e)
{
addButton.Enabled = newNameTextBox.Text.Length > 0;
}
/// <summary>
/// Handler for the add button. Adds the new name to the list
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
private void addButton_Click(object sender, EventArgs e)
{
#if UseClassFactory
// The class factory improves readability of the code by returning an operation object of the right type
AddOperation(newNameTextBox.Text).Do();
#else
new Operation<string, int>("Add", DoAdd, newNameTextBox.Text, 0).Do();
#endif
newNameTextBox.Text = "";
}
/// <summary>
/// Handler for the Save menu item. Saves the contents of the listbox in a file. As this is just a demo the filename is fixed.
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
Save();
// We must reset the command structure to show that the data is no longer modified
OperationBase.IsModified = false;
}
/// <summary>
/// Handler for the keyDown event on the names list box. Handles the delete key to delete items
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">An <see cref="KeyEventArgs"/> that contains the event data.</param>
private void namesListBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete)
{
// We cannot just pass a reference to namesListBox.SelectedItems as the Delete operation
// is also used to redo the operation. Instead we pass a list of indices.
List<int> indices = new List<int>();
foreach (int index in namesListBox.SelectedIndices)
{
indices.Add(index);
}
#if UseClassFactory
// The class factory improves readability of the code by returning an operation object of the right type
DeleteOperation(indices).Do();
#else
new Operation<List<int>, int>("Delete", DoDelete, indices, 0).Do();
#endif
}
}
/// <summary>
/// Handles the edit menu DropDownOpening event. Enables the Undo and Redo items as appropriate
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
private void editToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(OperationBase.UndoName))
{
undoToolStripMenuItem.Text = "Undo";
undoToolStripMenuItem.Enabled = false;
}
else
{
undoToolStripMenuItem.Text = "Undo " + OperationBase.UndoName;
undoToolStripMenuItem.Enabled = true;
}
if (string.IsNullOrEmpty(OperationBase.RedoName))
{
redoToolStripMenuItem.Text = "Redo";
redoToolStripMenuItem.Enabled = false;
}
else
{
redoToolStripMenuItem.Text = "Redo " + OperationBase.RedoName;
redoToolStripMenuItem.Enabled = true;
}
}
/// <summary>
/// Handles the Undo menu item.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void undoToolStripMenuItem_Click(object sender, EventArgs e)
{
OperationBase.Undo();
}
/// <summary>
/// Handles the Redo menu item
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void redoToolStripMenuItem_Click(object sender, EventArgs e)
{
OperationBase.Redo();
}
/// <summary>
/// Handles the form closing event. Check to see if the data has been modified and prompts for a save
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CommandDemoForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (OperationBase.IsModified &&
MessageBox.Show("The data has been changed, do you wish to save?", "Command Demo", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
Save();
}
}
#endregion
#region Operation methods
/// <summary>
/// The Do add operation. Adds the name to the listbox.
/// </summary>
/// <param name="name">The name to be added to the list box</param>
/// <param name="dummy">Not used</param>
/// <returns></returns>
#if UseTypeDefinitions
// Type definitions improve the readability of the code
private UnAddOpType DoAdd(string name, int dummy)
#else
private Operation<string, int> DoAdd(string name, int dummy)
#endif
{
// Add the name. We want to cater for sorted lists so we need to keep the index where it is added
int index = namesListBox.Items.Add(name);
// Return the undo operation. This requires both the name and the index
#if UseClassFactory
// The class factory improves readability of the code by returning an operation object of the right type
return UnAddOperation(name, index);
#else
// Note that the name of the Undo operation is also "Add".
return new Operation<string, int>("Add", UndoAdd, name, index);
#endif
}
/// <summary>
/// The Undo Add operation.
/// </summary>
/// <param name="name">The name that is being removed. This is required to pass back to the Redo operation</param>
/// <param name="index">The index of the item that was added</param>
/// <returns></returns>
#if UseTypeDefinitions
// Type definitions improve the readability of the code
private AddOpType UndoAdd(string name, int index)
#else
private Operation<string, int> UndoAdd(string name, int index)
#endif
{
// Remove the item from the specified index
namesListBox.Items.RemoveAt(index);
// Return the Redo operation, which is the Do operation and takes the name as parameter
#if UseClassFactory
// The class factory improves readability of the code by returning an operation object of the right type
return AddOperation(name);
#else
return new Operation<string, int>("Add", DoAdd, name, 0);
#endif
}
/// <summary>
/// The delete operation. We cater for a multi-select list box so multiple items can be selected
/// This is also used to redo a previous delete, so it shouldn't use the listbox itself,
/// or a reference to the selectedItems member as the items may not be selected when the redo is done.
/// So we pass a list of names and a list of indices.
/// </summary>
/// <param name="indices">A list of the indices of the items to be deleted</param>
/// <param name="dummy">Not used</param>
/// <returns></returns>
#if UseTypeDefinitions
// Type definitions improve the readability of the code
private UnDeleteOpType DoDelete(List<int> indices, int dummy)
#else
private Operation<List<int>, List<string>> DoDelete(List<int> indices, int dummy)
#endif
{
// We need to delete in reverse order, so sort the list
indices.Sort();
// Now delete the items, keeping a list of the names for the Undo
List<string> names = new List<string>();
for (int x = indices.Count - 1; x >= 0; x--)
{
names.Insert(0, namesListBox.Items[indices[x]] as string);
namesListBox.Items.RemoveAt(indices[x]);
}
// Return the undo operation
#if UseClassFactory
// The class factory improves readability of the code by returning an operation object of the right type
return UnDeleteOperation(indices, names);
#else
return new Operation<List<int>, List<string>>("Delete", UndoDelete, indices, names);
#endif
}
/// <summary>
/// The undo delete operation. this also accepts multiple items
/// </summary>
/// <param name="indices">The indices of the items to be put back.</param>
/// <param name="names">The names to be put back</param>
/// <returns></returns>
#if UseTypeDefinitions
// Type definitions improve the readability of the code
private DeleteOpType UndoDelete(List<int> indices, List<string> names)
#else
private Operation<List<int>, int> UndoDelete(List<int> indices, List<string> names)
#endif
{
// We need to put the names back at the given indices.
// Note that the fundamental principle of Do/Undo/Redo is that the data cannot have been changed between
// a Do and an Undo. The list of indices is already sorted, and the items were removed in reverse order,
// so if we replace them in forward order they will go back in their original places.
// Note that if the list is sorted the indices won't make any difference.
for (int x = 0; x < indices.Count; x++)
{
namesListBox.Items.Insert(indices[x], names[x]);
}
// Now we create a redo operation to delete them again
#if UseClassFactory
// The class factory improves readability of the code by returning an operation object of the right type
return DeleteOperation(indices);
#else
return new Operation<List<int>, int>("Delete", DoDelete, indices, 0);
#endif
}
#endregion
#region ClassFactory
// This is a way of simplifying the code by providing a class factory for the various operation classes
// Define UseClassFactory to compile using this
#if UseClassFactory
/// <summary>
/// Make an add operation.
/// </summary>
/// <param name="name">The name to be added</param>
/// <returns>The new Operation object</returns>
#if UseTypeDefinitions
// Type definitions improve the readability of the code
private AddOpType AddOperation(string name)
#else
private Operation<string, int> AddOperation(string name)
#endif
{
return new Operation<string, int>("Add", DoAdd, name, 0);
}
/// <summary>
/// Make an undo add operation.
/// </summary>
/// <param name="name">The name to be un-added</param>
/// <param name="index">The index of the name to be un-added</param>
/// <returns>The new Operation object</returns>
#if UseTypeDefinitions
// Type definitions improve the readability of the code
private UnAddOpType UnAddOperation(string name, int index)
#else
private Operation<string, int> UnAddOperation(string name, int index)
#endif
{
// Note that the undo operation takes the same name as the do operation
return new Operation<string, int>("Add", UndoAdd, name, index);
}
/// <summary>
/// Make a delete operation.
/// </summary>
/// <param name="names">The list of names to be deleted</param>
/// <returns>The new Operation object</returns>
#if UseTypeDefinitions
// Type definitions improve the readability of the code
private DeleteOpType DeleteOperation(List<int> names)
#else
private Operation<List<int>, int> DeleteOperation(List<int> names)
#endif
{
return new Operation<List<int>, int>("Delete", DoDelete, names, 0);
}
/// <summary>
/// Make an undelete operation.
/// </summary>
/// <param name="indices">The indices of the names to be undeleted.</param>
/// <param name="names">The list of names to be deleted</param>
/// <returns>The new Operation object</returns>
#if UseTypeDefinitions
// Type definitions improve the readability of the code
private UnDeleteOpType UnDeleteOperation(List<int> indices, List<string> names)
#else
private Operation<List<int>, List<string>> UnDeleteOperation(List<int> indices, List<string> names)
#endif
{
// Note that the undo operation takes the same name as the do operation
return new Operation<List<int>, List<string>>("Delete", UndoDelete, indices, names);
}
#endif
#endregion
#region private methods
private void Save()
{
StreamWriter streamWriter = File.CreateText(fileName);
foreach (string item in namesListBox.Items)
{
streamWriter.WriteLine(item);
}
streamWriter.Close();
}
#endregion
}
}