Introduction
I live and work in Belgium, so quite often have to send emails in French. All too often I would be slowed down trying to remember alt key codes for accents or the Euro symbol. I would end up googling them and copying and pasting them into emails.
Then it struck me that it would be very useful to have a program running in the System Tray that would let me copy my most commonly used symbols to the clipboard, ideally just with a few keystrokes.
This article introduces 'QuickAccent' - a tool which does exactly that. I'll show the features of the application and then describe some of the useful segments of source code that could be applied to other projects.
How It Works
Install QuickAccent and then right click on the QuickAccent icon in the tray (which is a little white circle with a capital 'A' with a grave over it. You can also use the Windows + Q hotkey. This will show the QuickAccent menu.
Useful Tip: Press shift while the menu is open and any symbols that have capitalised versions will be shown.
Now just click on an accent - it will be copied to your clipboard. As easy as that!
Customising the Accents
Opening the menu and choosing 'Settings' will open the settings window, allowing you to customise the accents.
You can delete accents, edit them or add new ones.
Interesting Code: Pure Tray Applications
Typically when writing Windows Forms applications, you start with a main form and add components such as menus, system tray icons and so on. In an application like this, we want the program to simply run as an icon in the tray, and only show a window when the user selects the settings.
This means that we need to create the tray icon in the program startup - and not show the main window until we're ready. Even if we're clever and try to set the window to be hidden by default, we'll still have problems with it flickering into visibility when the user starts the application.
So how do we actually do this? It's not too hard - we have to give up the designer for the System Tray Icon menu, but that's not so much of a loss. Let's look at the program startup code.
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ApplicationContext.Instance.LoadSettings();
CreateContextMenu();
CreateTrayIcon();
CreateAccentMenuItems();
UpdateHotkeyState(ApplicationContext.Instance.Settings.UseWindowsHotkey);
Application.Run();
}
This code shows the process nicely. Here's what's going on blow-by-blow:
- Do the normal application startup stuff for WinForms apps (visual styles and setting the text rendering options).
- Load the application settings. These settings are in an XML file - they are managed by the ApplicationContext class, which is a singleton. An instance of this class exists for the lifetime of the application and handles the state of the application.
- Now we create a context menu, completely programatically.
- Then we create a system tray icon, again programatically. We associated the context menu with it.
- Specific to this applications requirements, we then add accents that are defined in the settings file to our context menu.
- Again, specific to this application, we register a Windows Hotkey to open the menu,
- Finally, we use 'Run' to kick off the main message pump and start the application. Without this, the application would terminate after the previous line.
So how does the context menu get created?
private static void CreateContextMenu()
{
contextMenu = new HotkeyEnabledContextMenuStrip();
contextMenu.Name = "Context Menu";
contextMenu.HotkeyModifier = ModifierKeys.Win;
contextMenu.Hotkey = Keys.Q;
contextMenu.Items.Add(new ToolStripSeparator());
var settingsItem = new ToolStripMenuItem("&Settings");
settingsItem.Click += settingsItem_Click;
contextMenu.Items.Add(settingsItem);
var exitItem = new ToolStripMenuItem("E&xit");
exitItem.Click += exitItem_Click;
contextMenu.Items.Add(exitItem);
contextMenu.KeyDown += contextMenu_KeyDown;
contextMenu.KeyUp += contextMenu_KeyUp;
contextMenu.Closed += contextMenu_Closed;
}
Again - there's very little to this. We're programatically creating a context menu and adding a few items to it. We listen for key up/down events so that we can change the menu items' text when the user presses shift. We are also handling the click events of some menu items - keep an eye on the 'settingsItem_Click' handler - we;ll see more about it later.
Notice that the context menu is actually a HotkeyEnabledContextMenu. This is a class derived from context menu, it allows the user to open the menu at any time with a Windows Hotkey.
Next, we can see how the tray icon is created.
private static void CreateTrayIcon()
{
trayIcon = new NotifyIcon();
trayIcon.ContextMenuStrip = contextMenu;
trayIcon.Icon = Properties.Resources.QuickAccentIconSmall;
trayIcon.Visible = true;
trayIcon.Text = "QuickAccent - Quickly copy accents to your clipboard";
trayIcon.MouseDoubleClick += trayIcon_MouseDoubleClick;
}
Nothing out of the ordinary is happening here - but if you are used to creating tray icons in the designer then this code may be unfamiliar to you. We're doing the same as you'd do in a designer - just setting some basic properties.
Remember that we registered a function 'settingsItem_Click' for the Clicked event of the Settings menu item? This is the function it will call.
private static void ShowSettingsWindow()
{
if (settingsForm != null && settingsForm.IsHandleCreated)
{
settingsForm.Activate();
return;
}
settingsForm = new FormQuickAccent();
settingsForm.Show();
}
If the settings window is open, we activate it (bringing it to the foreground). Otherwise, we create the window.
This is essentially all that is required to create a vanilla system tray based application. All of the other bits and pieces we saw hints of are specific to the logic of QuickAccent.
Interesting Code: Windows Hotkeys
Registering Windows Hotkeys is trivial when using the Win32 API from C or C++, but the functionality is not wrapped by the .NET Framework. Fortunately, we can p/invoke the functionality we need.
They key functions are:
RegisterHotKey
UnregisterHotKey
It is also worth checking the documentation on WM_HOTKEY:
WM_HOTKEY
The links are to the MSDN documentation. Before we go and write wrappers for them, we must be aware of one thing - hotkey notifications come in the form of Windows Messages. Again, from a Win32 background this will be familiar and expected, but if you are a .NET developer who hasn't spent much time working with Win32 then this is something to be aware of.
The system will tell you when the hotkey has been hit by sending a Windows Message to a Window. This means that it is not enough to call the function to register the hotkey, we also need to have a message pump running to listen for the message. Once we have received the message, we can pass on that information to whatever element of our program we want.
So as we're going to need a message pump to check for the message, the easiest thing to do will be to associate this functionality with a window of some kind (remember that in Windows lots of things are windows - buttons, controls and almost everything). As we want to show a context menu when the hotkey is pressed, we can actually use the context menu class itself - we can derive from it to create a context menu that registers the hotkey and opens itself when it is fired.
Here's how the class will look.
public class HotkeyEnabledContextMenuStrip : ContextMenuStrip
{
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
So far so good - we have the class definition and we immediately import the two Win32 functions we'll need.
private int hotkeyUniqueId = 123;
public void RegisterHotKey(ModifierKeys modifier, Keys key)
{
RegisterHotKey(Handle, hotkeyUniqueId, (uint)modifier, (uint)key);
}
public void UnregisterHotkey()
{
UnregisterHotKey(Handle, hotkeyUniqueId);
}
Now we offer two functions to consumers of the class - one to register, one to unregister. How do we listen for the event?
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_HOTKEY && HotkeyEnabled)
{
Show();
Focus();
Items.OfType<ToolStripMenuItem>().First().Select();
}
}
This is an override of the Window Proc. The window proc is just a function that receives a windows message and processes it. We always call the base first, to make sure that all normal messages are processed correctly. Then we check to see if we have the WM_HOTKEY message (which we only bother checking for if we have set the 'HotkeyEnabled' flag which is in the class).
If the hotkey has been activated, we can open the menu, focus it, and then move the focus to the first menu item, allowing the user to then move the selection with the keyboard.
That's all there is to Windows Hotkeys - in many cases you'll want to associate them with things other than menus - maybe a main window or something else. In that case, just find an appropriate window and hijack its message pump.
Interesting Code: The Application Context
The final piece of code I believe is of some interest is the ApplicationContext class. This is a singleton - there is one instance of it only and this instance is globally accessible. Why do we have this class?
This class is used because we have disparate parts of the application trying to share some central information, for example the application settings, the list of accents etc. By creating a singleton accessible to all, we can share this core information and offer core functionality such as 'save settings' and 'load settings'.
Here's a segment of the class.
public sealed class ApplicationContext
{
private static readonly ApplicationContext instance = new ApplicationContext();
private ApplicationContext()
{
}
public static ApplicationContext Instance
{
get { return instance; }
}
This is standard boilerplate for a single threaded C# singleton. There's a very good MSDN article on singletons here: http://msdn.microsoft.com/en-us/library/ff650316.aspx, I would thoroughly recommend reading it.
I create singletons using my Apex library - because it allows me to do this:
Inserting a singleton from the Add New Item window. If you want this functionality, just go to apex.codeplex.com and download and install the latest version of the SDK.
Now that we have our singleton, we can put core functions in it, such as the below:
public void LoadSettings()
{
if (File.Exists(GetSettingsPath()))
{
try
{
using (var stream = new FileStream(GetSettingsPath(), FileMode.Open))
{
var serializer = new XmlSerializer(typeof(Settings));
Settings = (Settings)serializer.Deserialize(stream);
}
}
catch (Exception exception)
{
System.Diagnostics.Trace.WriteLine("Exception loading settings file: " + exception);
MessageBox.Show("Failed to load the settings file.", "Error");
}
}
else
{
CreateDefaultSettings();
SaveSettings();
}
}
In reality, the loading of the settings shouldn't need to be controlled by disparate objects - but you will often find that a class like 'ApplicationContext' is a useful place to put core functions while the design of the application is changing. For example, if this function was in the main window, what would we do when the main window is no longer loaded on startup? We'd have to move it to the Program class. But this might not be right either - so keeping these application state and lifecycle functions together can be extremely useful - especially during the early stages of a project.
Final Thoughts
I hope that some people will find this tool useful, or the interesting code segments that have been described. If anyone has suggestions for improvements then please let me know.