public sealed class EventSet {
// The private dictionary used to maintain EventKey -> Delegate mappings
private Dictionary<EventKey, Delegate> m_events =
new Dictionary<EventKey, Delegate>();
// Adds an EventKey -> Delegate mapping if it doesn't exist or
// combines a delegate to an existing EventKey
public void Add(EventKey eventKey, Delegate handler) {
lock (m_events) {
Delegate d;
m_events.TryGetValue(eventKey, out d);
m_events[eventKey] = Delegate.Combine(d, handler);
}
}
// Removes a delegate from an EventKey (if it exists) and
// removes the EventKey -> Delegate mapping the last delegate is removed
public void Remove(EventKey eventKey, Delegate handler) {
lock (m_events) {
// Do not throw an exception if attempting to remove
// a delegate from an EventKey not in the set
Delegate d;
if (m_events.TryGetValue(eventKey, out d)) {
d = Delegate.Remove(d, handler);
// If a delegate remains, set the new head else remove the EventKey
if (d != null) m_events[eventKey] = d;
else m_events.Remove(eventKey);
}
}
}
// Raies the event for the indicated EventKey
public void Raise(EventKey eventKey, Object sender, EventArgs e) {
// Don't throw an exception if the EventKey is not in the set
Delegate d;
lock (m_events) { m_events.TryGetValue(eventKey, out d); }
if (d != null) {
// Because the dictionary can contain several different delegate types,
// it is impossible to construct a type-safe call to the delegate at
// compile time. So, I call the System.Delegate type抯 DynamicInvoke
// method, passing it the callback method抯 parameters as an array of
// objects. Internally, DynamicInvoke will check the type safety of the
// parameters with the callback method being called and call the method.
// If there is a type mismatch, then DynamicInvoke will throw an exception.
d.DynamicInvoke(new Object[] { sender, e });
}
}
}
//////////////////////////////// End of File //////////////////////////////////
using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.ComponentModel;
using System.IO;
using System.Collections;
namespace KellysEZControls
{
/// <summary>
/// Textbox control that has autocomplete
/// </summary>
public class SenseControl2 : TextBox
{
/// <summary>
/// Raised when the user saves the text in the textbox.
/// </summary>
/// <remarks>This can be used to inform other controls
/// that are using the same list to reload.</remarks>
public event EventHandler ChoicesSaved;
/// <summary>
/// Raises the Choices Saved event when the user saves an item to the list.
/// </summary>
/// <param name="e"></param>
[Description("Raises the Choices Saved event when the user saves an item to the list.")]
protected virtual void OnChoicesSaved(System.EventArgs e)
{
if(ChoicesSaved != null)
ChoicesSaved(this, e);
}
#region "Variables"
private ArrayList _values = new ArrayList(); //List that holds the choices.
private bool _update = false; //Flag used to tell the keypress handler if it should autocomplete
private string _choiceFile = ""; //The filename of the file that the list is read from and saved to.
private bool _init = false; //Set to true when the _values is loaded.
#endregion
#region "Constructors"
/// <summary>
/// Constructor
/// </summary>
/// <remarks>Loads the contex menu.</remarks>
public SenseControl2()
{
AddContextMenu();
}
#endregion
#region "Properties"
/// <summary>
/// Sets the list of choices directly
/// </summary>
[Description("Sets the list of choices")]
public string[] Choices
{
get
{
return (string[])_values.ToArray(typeof(string));
}
set
{
_values = new ArrayList(value);
}
}
/// <summary>
/// The filename of the file to get the choices from
/// </summary>
[Description("The filename of the file to get the choices from.")]
public string ChoiceFile
{
get{return _choiceFile;}
set{ _choiceFile = value;}
}
#endregion
#region "Methods"
/// <summary>
/// Causes the control to reload the choice file.
/// </summary>
/// <remarks>This can be used to keep two controls using the same list in sync</remarks>
[Description("Causes the control to reload the choice file.")]
public void ReloadChoices()
{
Reload();
}
#endregion
#region "Private Functions"
/// <summary>
/// Adds the Save context menu
/// </summary>
private void AddContextMenu()
{
ContextMenu saveContextMenu = new ContextMenu();
this.ContextMenu = saveContextMenu;
MenuItem mnuItemNew = new MenuItem("Save",new System.EventHandler(this.Saved_OnClick));
saveContextMenu.MenuItems.Add(mnuItemNew);
}
/// <summary>
/// Searches the list for a match
/// </summary>
/// <returns>True if there is a match in the list</returns>
private bool AlreadyListed()
{
IEnumerator itext = _values.GetEnumerator();
string test = this.Text.ToUpper();
while(itext.MoveNext())
{
string text = (string)itext.Current;
if(text.ToUpper().IndexOf(test) == 0)
return true;
}
return false;
}
/// <summary>
/// Saves the text in the textbox into the file.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks>If the file does not exist, it will be created</remarks>
protected void Saved_OnClick(System.Object sender, System.EventArgs e)
{
string text;
FileStream fs;
StreamWriter sw;
if(this.Text.Trim() == "") return; //Don't save empty strings
bool found = AlreadyListed();
if(File.Exists(_choiceFile)) //if the file already exists add to it.
//we do not want to recreate it in case another control is using it
{
if(found) return; //no sense adding it
try
{
_values.Add(this.Text);
fs = new FileStream(_choiceFile, FileMode.Append);
sw = new StreamWriter(fs);
sw.WriteLine(this.Text);
sw.Close();
fs.Close();
OnChoicesSaved(new System.EventArgs());
}
catch(Exception ee)
{
Debug.WriteLine(ee.Message);
Debug.Assert(false);
throw new Exception("Could not save the value: " + this.Text + " in the control: " + this.Name ,ee);
}
}
else //the file was not found, so create it. Any other control will find this one.
{
if(!found)
_values.Add(this.Text);
try
{
IEnumerator itext = _values.GetEnumerator();
fs = new FileStream(_choiceFile, FileMode.Create);
sw = new StreamWriter(fs);
while(itext.MoveNext())
{
text = (string)itext.Current;
sw.WriteLine(text);
}
sw.Close();
fs.Close();
}
catch(Exception ee)
{
Debug.WriteLine(ee.Message);
Debug.Assert(false);
throw new Exception("Could not save the list to file: " + _choiceFile + " in the control: " + this.Name ,ee);
}
}
}
/// <summary>
/// Reloads the list from the file. If there is no filename, create the default one.
/// </summary>
/// <remarks>The default filename will be the control name wit '.txt' appended.</remarks>
private void Reload()
{
try
{
if(_choiceFile == "")
_choiceFile = this.Name + ".txt";
_values = new ArrayList();
FileStream fs = new FileStream(_choiceFile, FileMode.OpenOrCreate,FileAccess.ReadWrite);
StreamReader sr = new StreamReader(fs);
while(sr.Peek() != -1)
_values.Add(sr.ReadLine());
sr.Close();
fs.Close();
_init = true;
}
catch(Exception ee)
{
Debug.WriteLine(ee.Message);
Debug.Assert(false);
throw new Exception("Could not open the value file for the control: " + this.Name ,ee);
}
}
#endregion
/// <summary>
/// Raises the System.Windows.Forms.Control.TextChanged event.
/// </summary>
/// <param name="e">An System.EventArgs that contains the event data.</param>
protected override void OnTextChanged(EventArgs e)
{
if(_values.Count == 0 && !_init) //Load the values from the file.
{
Reload();
}
string test = this.Text.ToUpper();
if((test == "") || _update)
{
base.OnTextChanged (e);
}
else
{
IEnumerator itext = _values.GetEnumerator();
bool found = false;
while((itext.MoveNext()) && (!found))
{
string text = (string)itext.Current;
if(text.ToUpper().IndexOf(test) == 0)
{
_update = true;
this.Text = text;
this.SelectionStart = test.Length;
this.SelectionLength = text.Length - test.Length;
found = true;
_update = false;
}
}
base.OnTextChanged (e);
}
}