Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / Win32

QuickAccent - A Tool for Accents and Symbols

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
29 Oct 2014CPOL8 min read 30.2K   537   19   11
Use QuickAccent to quickly copy accents and symbols to your clipboard. Also read the article to find out about the essentials when writing System Tray based applications

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. 

Image 1

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.

Image 2

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.

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
    //  Enable visual styles and set the text rendering modes.
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
 
    //  Load the settings.
    ApplicationContext.Instance.LoadSettings();
 
    //  Create the context menu.
    CreateContextMenu();
 
    //  Create the tray icon.
    CreateTrayIcon();
 
    //  Create the menu items.
    CreateAccentMenuItems();
 
    //  Set the hotkey.
    UpdateHotkeyState(ApplicationContext.Instance.Settings.UseWindowsHotkey);
 
    //  Run the main window.
    Application.Run();
} 

This code shows the process nicely. Here's what's going on blow-by-blow:

 

  1. Do the normal application startup stuff for WinForms apps (visual styles and setting the text rendering options).
  2. 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.
  3. Now we create a context menu, completely programatically.
  4. Then we create a system tray icon, again programatically. We associated the context menu with it.
  5. Specific to this applications requirements, we then add accents that are defined in the settings file to our context menu.
  6. Again, specific to this application, we register a Windows Hotkey to open the menu,
  7. 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?

 

/// <summary>
/// Creates the context menu.
/// </summary>
private static void CreateContextMenu()
{
    contextMenu = new HotkeyEnabledContextMenuStrip();
    contextMenu.Name = "Context Menu";
    contextMenu.HotkeyModifier = ModifierKeys.Win;
    contextMenu.Hotkey = Keys.Q;
 
    //  Add the separator.
    contextMenu.Items.Add(new ToolStripSeparator());
 
    //  Add 'Settings'.
    var settingsItem = new ToolStripMenuItem("&Settings");
    settingsItem.Click += settingsItem_Click;
    contextMenu.Items.Add(settingsItem);
 
    //  Add 'Exit'.
    var exitItem = new ToolStripMenuItem("E&xit");
    exitItem.Click += exitItem_Click;
    contextMenu.Items.Add(exitItem);
 
    //  The context menu will also have to wait for keyup/keydown to handle shift functionality.
    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.

/// <summary>
/// Creates the tray icon.
/// </summary>
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.

/// <summary>
/// Shows the settings window.
/// </summary>
private static void ShowSettingsWindow()
{
    //  Is the settings form open? If so, activate it.
    if (settingsForm != null && settingsForm.IsHandleCreated)
    {
        settingsForm.Activate();
        return;
    }
 
    //  Create the settings form.
    settingsForm = new FormQuickAccent();
 
    //  Show the settings form.
    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.

/// <summary>
/// A Hotkey Enabled Context Menu Strip is a Context Menu Strip
/// that can be opened via a Windows Hotkey.
/// </summary>
public class HotkeyEnabledContextMenuStrip : ContextMenuStrip
{
    /// <summary>
    /// Registers the hot key.
    /// </summary>
    /// <param name="hWnd">The h WND.</param>
    /// <param name="id">The id.</param>
    /// <param name="fsModifiers">The fs modifiers.</param>
    /// <param name="vk">The vk.</param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
 
    /// <summary>
    /// Unregisters the hot key.
    /// </summary>
    /// <param name="hWnd">The h WND.</param>
    /// <param name="id">The id.</param>
    /// <returns></returns>
    [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.

/// <summary>
/// An id for our application's hotkey.
/// </summary>
private int hotkeyUniqueId = 123;
 
/// <summary>
/// Registers a hot key in the system.
/// </summary>
/// <param name="modifier">The modifiers that are associated with the hot key.</param>
/// <param name="key">The key itself that is associated with the hot key.</param>
public void RegisterHotKey(ModifierKeys modifier, Keys key)
{
    //  Register the hot key.
    RegisterHotKey(Handle, hotkeyUniqueId, (uint)modifier, (uint)key);
}
 
/// <summary>
/// Unregisters the hotkey.
/// </summary>
public void UnregisterHotkey()
{
    //  Unregister the hotkey.
    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?

/// <summary>
/// The WindowProc.
/// </summary>
/// <param name="m">The Windows <see cref="T:System.Windows.Forms.Message"/> to process.</param>
protected override void WndProc(ref Message m)
{
    //  Call the base.
    base.WndProc(ref m);
 
    //  Have we hit the hotkey?
    if (m.Msg == WM_HOTKEY && HotkeyEnabled)
    {
        //  Open the menu.
        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.

/// <summary>
/// The <see cref="ApplicationContext"/> Singleton class.
/// </summary>
public sealed class ApplicationContext
{
    /// <summary>
    /// The Singleton instace. Declared 'static readonly' to enforce
    /// a single instance only and lazy initialisation.
    /// </summary>
    private static readonly ApplicationContext instance = new ApplicationContext();

    /// <summary>
    /// Initializes a new instance of the <see cref="ApplicationContext"/> class.
    /// Declared private to enforce a single instance only.
    /// </summary>
    private ApplicationContext()
    {
    }

    /// <summary>
    /// Gets the ApplicationContext Singleton Instance.
    /// </summary>
    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:

Image 3

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:

/// <summary>
/// Loads the settings.
/// </summary>
public void LoadSettings()
{
    //  Do we have a settings file?
    if (File.Exists(GetSettingsPath()))
    {
        //  Try and load it.
        try
        {
            using (var stream = new FileStream(GetSettingsPath(), FileMode.Open))
            {
                //  Create a serializer.
                var serializer = new XmlSerializer(typeof(Settings));

                //  Read the settings.
                Settings = (Settings)serializer.Deserialize(stream);
                
            }
        }
        catch (Exception exception)
        {
            //  Trace the exception.
            System.Diagnostics.Trace.WriteLine("Exception loading settings file: " + exception);

            //  Warn the user.
            MessageBox.Show("Failed to load the settings file.", "Error");
        }
    }
    else
    {
        //  We have no settings file - create the default settings.
        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. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.

Comments and Discussions

 
NewsSimilar functionality Pin
DSAlCoda3-Nov-14 8:21
DSAlCoda3-Nov-14 8:21 
GeneralRe: Similar functionality Pin
Dave Kerr3-Nov-14 18:49
mentorDave Kerr3-Nov-14 18:49 
QuestionFailed to load settings file Pin
Dom Sinclair29-Oct-14 21:11
Dom Sinclair29-Oct-14 21:11 
AnswerRe: Failed to load settings file Pin
Tom Brophy12-Nov-14 6:28
Tom Brophy12-Nov-14 6:28 
GeneralRe: Failed to load settings file Pin
Dave Kerr12-Nov-14 22:25
mentorDave Kerr12-Nov-14 22:25 
GeneralRe: Failed to load settings file Pin
Dave Kerr12-Nov-14 22:26
mentorDave Kerr12-Nov-14 22:26 
Questionnot sure what I like more ... Pin
Garth J Lancaster16-Oct-14 20:01
professionalGarth J Lancaster16-Oct-14 20:01 
AnswerRe: not sure what I like more ... Pin
Dave Kerr19-Oct-14 18:26
mentorDave Kerr19-Oct-14 18:26 
GeneralRe: not sure what I like more ... Pin
Garth J Lancaster19-Oct-14 18:35
professionalGarth J Lancaster19-Oct-14 18:35 
GeneralMy vote of 5 Pin
@k@ ?27-Nov-12 6:05
professional@k@ ?27-Nov-12 6:05 
GeneralRe: My vote of 5 Pin
Dave Kerr27-Nov-12 6:18
mentorDave Kerr27-Nov-12 6:18 
Great - glad someone else has found some use for it!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.