Introduction
When I first began developing, I was especially annoyed by the lack of several symbols (such as © and ☺) on the keyboard. This application presents the solution I eventually created for this problem.
When I wrote this, I first created a visual interface and then plugged code into the background to make it work – thus I am presenting this article with this format.
Goals for the Application
The basic goals for the application were as follows:
- Try to replicate operating system look-and-feel.
- Implement hands-on-keyboard functionality. This means registering a HotKey for the application on the keyboard,
and using the number keys on the keyboard to select a symbol, close the application, and insert the symbol into your document, code, etc.
- Allow the user to customize the symbols.
- Automatically run the application when the user logs in so that one is spared the pain of starting it all the time.
- Keep a notification icon in the tasktray to allow the user to close the application.
This is the summary of a single use of the application:
Creating the Visual Interface
Command Links and emulating OP look-and-feel
These controls can be found in the Microsoft API Code Pack. Simply download them and place them in your toolbox. The gray bar along the bottom of the application consists of two panels with dock set to bottom. The colors were obtained by using Paint's 'pick a color' tool on a screenshot of IE9's download dialog.
The Settings dialog
In the settings dialog, the user can change what's in their symbol collection and the order they are in. They can also set the application to start when they login.
The code here is pretty straightforward. Perhaps the only part that can be commented on is moving the symbols up and down:
private void btnUp_Click(object sender, EventArgs e)
{
if (lstCollection.SelectedIndex != 0 && lstCollection.SelectedIndex != -1)
{
int point = lstCollection.SelectedIndex;
char s = (char)lstCollection.SelectedItem;
lstCollection.Items.RemoveAt(point);
lstCollection.Items.Insert(point - 1, s);
lstCollection.SelectedIndex = point - 1;
}
}
private void btnDown_Click(object sender, EventArgs e)
{
if (lstCollection.SelectedIndex != lstCollection.Items.Count - 1 &&
lstCollection.SelectedIndex != -1)
{
int point = lstCollection.SelectedIndex;
char s = (char)lstCollection.SelectedItem;
lstCollection.Items.RemoveAt(point);
lstCollection.Items.Insert(point + 1, s);
lstCollection.SelectedIndex = point + 1;
}
}
The Tasktray Icon
This is fairly basic. This code is in the form's constructor.
icon.Icon = Properties.Resources.Symbol;
icon.Visible = true;
icon.ContextMenu = new ContextMenu();
icon.ContextMenu.MenuItems.Add("Show Dialog", ShwDlg);
icon.ContextMenu.MenuItems.Add("About", Abt);
icon.ContextMenu.MenuItems.Add("-");
icon.ContextMenu.MenuItems.Add("Exit", Exit);
icon.MouseUp += new MouseEventHandler(icon_MouseUp);
Note how a line break can be created by inserting '-' as a menu item. A word of warning about using
NotifyIcon
is that when you close the application,
the icon may hover around in your task-tray until you mouseover. This issue can be resolved by this override:
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing && !AppClosingDown)
{
e.Cancel = true;
btnCancel_Click(this, e);
}
else
{
icon.Visible = false;
icon.Dispose();
}
base.OnFormClosing(e);
}
Also, note how the form rejects the close call and hides instead, if the user clicks the form's close button.
Loading the RadioButtons and handling the Main Form
Loading the radio buttons is pretty straightforward. Here is the basic code run in the form's constructor. The radio buttons were
preexistent from the Designer.
int foundsofar = 0;
foreach (Control c in this.Controls)
{
if (c.ToString().Contains("System.Windows.Forms.RadioButton"))
{
c.Click += new EventHandler(c_Click);
c.Text = Properties.Settings.Default.Symbols[foundsofar].ToString();
if (c.Text == "π")
c.Font = new Font("Arial Rounded MT Bold", c.Font.Size);
++foundsofar;
}
}
Then, the code for c_Click
is this:
void c_Click(object sender, EventArgs e)
{
tmAnim.Start();
}
And the timer's tick event is this:
private void tmAnim_Tick(object sender, EventArgs e)
{
if (this.Height != 318)
{
this.Top -= 5;
this.Height += 10;
}
else
{
tmAnim.Stop();
}
}
Thus, when the user clicks any of the radio buttons, the form slides out and looks like the picture above.
Creating Action
That's the visual interface covered! Now I will summarize how the application actually whirrs.
Starting when the User logs in
This requires some Registry editing. This article was my inspiration.
First, you will need these values:
private const string RegistryPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
private const string KeyName = "JohnsonSymbolInserter";
You can change KeyName
to whatever you wish, of course. Here is the basic code summary:
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath);
bool isRunningOnLogin = (rk.GetValue(KeyName) != null);
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath, true);
rk.SetValue(KeyName, Application.ExecutablePath);
RegistryKey rk = Registry.CurrentUser.OpenSubKey(registryPath, true);
rk.DeleteValue(KeyName);
RegistryKey rk = Registry.CurrentUser.OpenSubKey(registryPath, true);
if (rk.GetValue(KeyName) == null)
rk.SetValue(KeyName, Application.ExecutablePath);
else
rk.DeleteValue(KeyName);
Registering a HotKey
This article (again!) was my source for implementing
HotKeys. Basically, we need these external calls:
[DllImport("user32.dll")]
private static extern int RegisterHotKey(IntPtr hwnd, int id, uint mod, Keys k);
[DllImport("user32.dll")]
private static extern int UnregisterHotKey(IntPtr hwnd, int id);
And we will also need these constants (you can also register HotKeys for Ctrl, Alt, etc.,
see here
for more info on RegisterHotKey
).
private const int ID = 0xFDAE;
private const int WindowsKey = 0x0008;
You then override OnHandleCreated
, OnHandleDestroyed
, and
WndProc
as below:
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
try
{
RegisterHotKey(this.Handle, ID, WindowsKey, Keys.S);
}
catch
{
MessageBox.Show("The application failed to register the Windows+S HotKey. " +
"The application will now exit.", "Register Failed",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
Application.Exit();
}
}
protected override void OnHandleDestroyed(EventArgs e)
{
base.OnHandleDestroyed(e);
try
{
UnregisterHotKey(this.Handle, ID);
}
catch
{
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0312)
{
this.ShowForm();
}
base.WndProc(ref m);
}
Sending Symbols - Method One
To insert a symbol is the main objective of this application... how is it done?
The easiest method to implement is to copy your selected symbol to the clipboard, like this.
this.HideForm();
string selected = "";
foreach (Control c in this.Controls)
{
if (c.ToString().Contains("System.Windows.Forms.RadioButton"))
{
if (((RadioButton)c).Checked)
{
selected = c.Text;
((RadioButton)c).Checked = false;
}
}
}
Clipboard.SetText(selected);
Sending Symbols - Method Two
However, having to paste the symbol yourself all the time becomes tiresome. I know this because I was initially using a much-less-polished version of this application;
pulling it up one time, I decided I would re-write it and publish it here on CodeProject.
So, I've also used SendKeys
in this
application.
SendKeys
can be used to send strings between applications as keyboard input. For example,
SendKeys.Send("Hello World!");
In this example, the application with current focus would receive simulated keyboard input as if someone had just typed instantaneously "Hello World!".
The advantage of simulated keyboard input is that the keyboard input can be anything, even a symbol. This method is slightly more work to implement - it requires
a few more external calls - but the result is far easier to use.
Because SendKeys
sends keystrokes to the currently active window, if
SendKeys
is called while our application is focused, the symbol will simply be sent to our application.
I used these external calls to solve this issue.
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hwnd);
When the form is shown, the application grabs the previously active application, as you can see in ShowForm()
:
private void ShowForm()
{
previousApp = GetForegroundWindow();
this.Opacity = 1;
this.Show();
SetForegroundWindow(this.Handle);
if (!btnCancel.Focused)
btnCancel.Focus();
}
When the user clicks the "Insert Now" button, this is what happens...
this.HideForm();
string selected = "";
foreach (Control c in this.Controls)
{
if (c.ToString().Contains("System.Windows.Forms.RadioButton"))
{
if (((RadioButton)c).Checked)
{
selected = c.Text;
((RadioButton)c).Checked = false;
}
}
}
try
{
SetForegroundWindow(previousApp);
SendKeys.Send(selected);
}
catch
{
MessageBox.Show("The symbol could not be sent to your document.");
this.ShowForm();
}
...and you should be able to see how the process works. Another look at the flowchart diagram should help explain:
Method Three - An Improvement on Method Two
I promised that you would be able to insert symbols without your fingers leaving the keyboard. Implementing this would save
having to stop typing, use your mouse/fingerpad, and then resume typing.
Because there are ten symbols available, we can use the number keys to select our desired symbol, with 0
representing 10.
When this application displays itself, the control which automatically receives focus is the Cancel button (take a look
at the above code for ShowForm()
). All we need to do is add a KeyUp
event handler to this control:
private void btnCancel_KeyUp(object sender, KeyEventArgs e)
{
int j = 0;
if (e.KeyData.ToString().Length == 2)
{
if (int.TryParse(e.KeyData.ToString().Substring(1, 1), out j))
{
if (j == 0)
j = 10;
this.HideForm();
try
{
SetForegroundWindow(previousApp);
SendKeys.Send(Properties.Settings.Default.Symbols[j - 1].ToString());
}
catch
{
MessageBox.Show("The symbol could not be sent to your document.");
this.ShowForm();
}
}
}
}
Voila! This final method produces the most convenient user experience of all the three I presented.
Conclusion
I hope you enjoyed this article and that at least a few people enjoy using it. Happy Typing! If you have any comments or suggestions, feel free to post below.
History
- 8/07/2013: More article polishing.
- 9/03/2012:
Made a few polishes to the article text.
- 4/08/2012: Realized that the "Edit Symbol" dialog was displaying its icon in the taskbar. Removed that.
- 31/07/2012: Fixed a bug where the cancel button was not being
focused. Also fixed a bug where the radio buttons were not being cleared.
- 30/07/2012: Initial public release.