DOSKey-like Control






4.45/5 (9 votes)
A DOSKey like Windows control with persistent MRU history


Introduction
This project is a full featured DOSKey control, (like what is built into Windows XP Command Prompt).
We are all familiar with the DOSKey utility found in console applications or from the Command Prompt. The DOSKey control has the handy command history that is navigated via the Up/Down arrow keys. This feature prevents us from having to retype some lengthy commands over and over. Once a command has been entered, it is inserted into the DOSKey command history queue.
Did you know that DOSKey has a history window that will popup when the F7 key is hit? This gives a detailed view of where your commands are and allows you to navigate to the desired command and execute it. Also once the F7 key stroke has popped up the history window, you can hit F9 and enter the indexed selection from the history list and execute that command.
I've taken these features and placed similar functionality into this DOSKey control. I've taken all of the good features and added some enhancements to make a solid control for any command driven application. I've included a test application that simulates some command driven application that receives commands from the DOSKey control and simulates some execution engine.
Features
- Persistent Most Recent Used (MRU) circular command queue (size is configurable).
- Persistent MRU list - Spans application scope (history stored in the registry).
- Command Validation - Only valid commands will be added to the MRU queue (a very nice feature).
- Unneeded or unwanted commands can be deleted from the DOSKey History Command Picker dialog box.
- Commands from the DOSKey History Command Picker may be routed directly to your executor or to the text box for modifications.
- No command duplicates. If the same command is already in the list, it is moved to the Most Recent Used position.
- Hitting the Escape key will reset the DOSKey navigation back to the Most Recent Used item in the history queue.
- Hitting the Enter key when the DOSKey control's edit box is empty will place the Most Recent Used item into the edit box.
Using the Code
The DOSKey control is a DLL that needs to be placed in the same folder as your executable. The demo project has that already done for you. For you to place the DOSKey control in a different project, you should add it to your Toolbox. First compile the DOSKey control, then open up the Toolbox and right click, then select "Choose Item".

Use the browse button in the "Choose Toolbox Items" dialog to navigate to DOSKey.dll, then press the Open button.

This will add DOSKey.dll into the Toolbox so that the control can be dragged from the Toolbox into the Form.

Configuring the DOSKey Control
There are some properties and events that must be defined in order for the DOSKey control to fully function properly. First we'll start off with the properties.

Properties
CtrlWidth
property is controlled by the Form design view and adjusts as the control is stretched to fit into the Form in the desired location.HistoryLength
property is the length of the DOSKey's MRU history queue. The allowed range is set to 1 - 100 by the read only propertiesHistroyLengthMin
andHistoryLengthMax
.HistroyLengthMin
andHistoryLengthMax
: These are read only, but they can be changed in the source code.RegistryCompanyName
andRegistryMruName
define the location in the registry where the MRU data is to be stored. Here is the path used in this example: My Computer\HKEY_CURRENT_USER\Software\ST Howard\DosKeyMRU.
The registry is the best place to store the history since the XML and data file persistence methods are too slow and throw exceptions on rapid command entry.
Now for the events (delegates):

Events
IsDOSKeyValidCommand
(command validation) event, if connected, will be executed before theDOSKeyNewEntry
event. This way only valid commands will be accepted and placed into the command history. If theIsDOSKeyValidCommand
is not connected then all commands are assumed to be valid and are placed into the command history.DOSKeyNewEntry
is the event that gets fired whenever a new command has been entered in the control. This is what the owner of the DOSKey control uses to receive all the commands from the control.
Here is the code for the delegates and events:
//==============================================================================
/// Command Entered Delegate:
/// This delegate is the template used for our DOSKey client to hook into our
/// call to the command entered function (DOSKeyNewEntry). This informs our
/// client that a command has been entered so the aspirate action can take place
/// in our client's application.
///
/// The command that has been entered via the Enter key.
public delegate void DOSKeyHandler(string str);
///
/// Command Entered Event:
/// This event is used to notify the owner that the enter key has been pressed.
/// The owner is expected to take the string as the command entered and implement
/// the desired actions. Typically you'd display the command that is to be
/// executed, execute the command and then display the results of the command.
///
/// NOTE: If the DOSKeyCommandValid() has been defined it will be called to
/// validate that this command is valid before executing this notification to
/// our owner. This way only valid commands will be passed along and added into
/// the MRU queue.
//------------------------------------------------------------------------------
[Category("DOSKey Configuration"),
Description("Sent when the user hits the enter key inside the textBox.")]
public event DOSKeyHandler DOSKeyNewEntry;
//==============================================================================
/// Command Validator Delegate:
/// This delegate is the template used for our DOSKKey client to hook into our
/// call to the command validator function (IsDOSKeyValidCommand). This way we
/// are smart enough to only place valid commands into the MRU command history
/// queue.
///
/// The command to be validated by our owner (valid syntax)
public delegate bool DOSKeyCommandValid(string str);
///
/// Command Validate Event:
/// This event is sent to our owner so it can determine if the command is valid.
/// If valid we'll send the command to our owner via the DOSKeyHandler() event.
/// This is used to prevent invalid or unknown commands being passed to our
/// owner.
//------------------------------------------------------------------------------
[Category("DOSKey Configuration"),
DefaultValue("true"),
Description("Used to receive notification that the command entered has valid syntax.")]
public event DOSKeyCommandValid IsDOSKeyValidCommand;
Here is the implementation using these delegates and events:
//=======================================================================
// This function first tests the validity of the entered command via an
// event to our owner. If the command is valid it gets inserted into the
// command history buffer and calls the owner's delegate, if it has been
// assigned, for execution of the command.
//-----------------------------------------------------------------------
private void InsertCallDelegate(string str)
{
if (IsDOSKeyValidCommand != null)
{
if (IsDOSKeyValidCommand(str) != true)
{
textBox.SelectAll(); // for the overwrite if user wants to
// This command does NOT have valid syntax. Skip adding it into the queue
return;
}
}
// To be here we know that the command has been validated so add it to the queue
InsertMruItem(str); // insert a string into MRU history queue
SaveDosKeyHistoryToRegistry(); // Now that we've added it to the list,
// stash away the updated history list
textBox.Focus(); // make sure we get the focus back
textBox.SelectAll(); // for the overwrite if user wants to
iDosKeyIndex = 0; // start over on the Up/Down arrow counting
if (DOSKeyNewEntry != null)
{ // if the owner of this control has instantiated us, we can call it
DOSKeyNewEntry(str); // call the delegate held within our owner
}
}
Command Validation
The default command validation in this example only allows commands that start off with: "cmd "
, "mem "
, "option "
, and "test "
. There are no other qualifications on the input. Your own data validation should be added which fully qualifies your command set.
str.ToLower();
str.Trim();
if (str.StartsWith("cmd ") || str.StartsWith("option ") ||
str.StartsWith("mem ") || str.StartsWith("test "))
{
// we have a valid command here
return true; // for this example we only allow 4 simple commands that are valid
}
Use of the Ctrl Key with the DOSKey Control
The history dialog box can be popped up by holding the Ctrl key down and then pressing the Up arrow key. This can be easier than hitting just the F7 key to open the history dialog box. The greatest benefit of all is when you've navigated through the history queue for a few entries and haven't found the desired item. Holding down the Ctrl key and pressing the Up arrow will open the history dialog box and have the current item in the history queue selected. This turns out to be a very nice feature when the history queue is large.
The Ctrl key is also used to pull an item from the history dialog box to the DOSKey control's edit window for tweaking. To do this, you hold the Ctrl key down and press the Enter key. This action places the item into the DOSKey's edit box for modifications. This way you're able to modify the command before executing it.
Points of Interest
Using the Registry
Reading the history from the registry.
//============================================================================
// Reads the history list from the registry.
// This controls default setting the entries are:
// DosKey0, DosKey1, DosKey2, ..., DosKey(n-1) where n is the allowed length.
// DosKey0 is the oldest entry
// DosKey(n-1) is the newest entry
//
// Returns:
// 0 = success
// 1 = error - registry key does not exist
// -1 = error - registry key action failed (catch branch)
//----------------------------------------------------------------------------
private int ReadDosKeyHistoryFromRegistry()
{
int iRegCnt = 0;
string strDosKeyItem;
RegistryKey regCurrUser_software = null;
RegistryKey regMRU = null;
try
{
regCurrUser_software = Registry.CurrentUser.OpenSubKey("Software", true);
string strSubKey = strRegCompanyName + "\\" + strRegDosKey;
regMRU = regCurrUser_software.OpenSubKey(strSubKey);
if (regMRU == null)
{
// this key does NOT exist, just return
regCurrUser_software.Close();
return 1; // key does not exist
}
iRegCnt = regMRU.ValueCount;
for (int i = 0; i < iRegCnt; i++)
{
strDosKeyItem = string.Format("{0}{1}", strMruName, i);
String s = (String)regMRU.GetValue(strDosKeyItem,
"null - Oh Crap, someone's read from an empty or corrupted list!");
aList.Insert(0, s);
}
}
catch
{
regCurrUser_software.Close();
regMRU.Close();
return -1;
}
regCurrUser_software.Close();
regMRU.Close();
return 0;
}
Saving the history queue to the registry. It is easier to delete the old key and save a new one instead of sorting the change and then saving.
//===============================================================================
// Writes the history list to the registry.
// In this controls default setting the entries are:
// DosKey0, DosKey1, DosKey2, ..., DosKey(n-1) where n is the allowed length.
// DosKey0 is the oldest entry
// DosKey(n-1) is the newest entry
//
// returns:
// 0 = success
// -1 = error - registry key error (catch branch)
//-------------------------------------------------------------------------------
private int SaveDosKeyHistoryToRegistry()
{
string strDosKeyItem;
RegistryKey regCurrUser_software = null;
RegistryKey regMRU = null;
try
{
regCurrUser_software = Registry.CurrentUser.OpenSubKey("Software", true);
string strSubKey = strRegCompanyName + "\\" + strRegDosKey;
regMRU = regCurrUser_software.CreateSubKey(strSubKey);
regCurrUser_software.DeleteSubKeyTree(strSubKey); // delete the old one
regMRU = regCurrUser_software.CreateSubKey(strSubKey); // create a new one
for (int i = 0; i < aList.Count; i++)
{
strDosKeyItem = string.Format("{0}{1}", strMruName, i);
regMRU.SetValue(strDosKeyItem, (string)aList[(aList.Count - 1) - i],
RegistryValueKind.String);
}
}
catch
{
regCurrUser_software.Close();
regMRU.Close();
return -1;
}
regCurrUser_software.Close();
regMRU.Close();
return 0;
}
Acknowledgements
I'd like to thank Bob Powell for the tips on how to get the DOSKey icon to show up in the Toolbox. He has a nice web site with tips and tricks that you should visit.
Thanks also to Eric Woodruff for his NDoc interface into the Sandcastle XML document builder.
- http://www.sandcastledocs.com (Replaces NDoc for .NET 2.0 assemblies)
- http://www.codeplex.com/SHFB