// 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 } }
By viewing downloads associated with this article you agree to the Terms of use and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
Skills that self-taught computer programmers lack