Building a simple Launchy clone in WPF (Part 3/3: Tying things together)
This part is all about some finishing touches. At this point I’ll assume you’ve finished Part 1 and Part 2, so we’ll go ahead and dive right in! 🙂
1. Window startup location
- To make the window start up in the center of the screen, use Blend to set the
WindowStartupLocation
property of theWindow
toCenterScreen
.
This is a good place to run your program and test out the changes.
2. Transparency and Effects
As you’ve surely noticed by now, there is an ugly white background behind our background gradient rectangle. Let’s take care of that and a couple other things.
- Select the Window and set the
Background
toNo Brush
. - Check the box for
AllowsTransparency
. - Check the box for
Topmost
. - Uncheck the box for
ShowInTaskbar
. - Select the top level [Grid] and find the RenderTransform properties section. Set the
Translate Y
to have a value of-4
. This shifts everything up so the drop shadow doesn’t clip. - Select the [Popup] and check the box for
AllowsTransparency
and set thePopupAnimation
toSlide
.
This is a good place to run your program and test out the changes.
3. Window dragging
- Use Blend to set up an event handler for the MouseLeftButtonDown event of Window. Use this event to initiate the window drag:
1 2 3 4 | private void Window_MouseLeftButtonDown( object sender, MouseButtonEventArgs e ) { DragMove( ); } |
This is a good place to run your program and test out the changes.
4. A context menu to exit
In order for a context menu for an item in a window, that item needs to be the top most hit-testable object. In the case of our clone, this can quickly lead to a confused developer. The item we want to add a context menu for (the Rectangle) appears to be the top most item! However, sitting quietly (and transparently) on top of it we have a Border, an Image, and a TextBlock.
4.1. Creating the menu in Blend
- Uncheck the
IsHitTestVisible
property of theborder
,activeItemIcon
, andactiveItemTitle
objects. - Find the
ContextMenu
property for the Rectangle and click New. - Find the
Items (Collection)
property listed under the context menu and click “…” - Click the down arrow to the right of Add another item and select MenuItem.
- Check the box for the
IsEnabled
property of the MenuItem, set theHeader
toExit
, and set theToolTip
toShuts down the Launchy clone
, then click OK. - In the Objects and Timeline pane, right click the Rectangle and select View XAML. Find the line that reads:
<MenuItem IsEnabled="True" ToolTip="Shuts down the Launchy clone" Header="Exit"/> |
and replace it with
<MenuItem IsEnabled="True" ToolTip="Shuts down the Launchy clone" Header="Exit" Click="menu_Exit"/> |
4.2. Handling the menu action in code
In Visual Studio, add a function to your Window1 class to handle the click.
1 2 3 4 | private void menu_Exit( object sender, RoutedEventArgs e ) { Application.Current.Shutdown( ); } |
This is a good place to run your program and test out the changes.
5. Registering the system hotkey
5.1. The ManagedWinapi helper library
The ManagedWinapi includes a convenient wrapper around the functions required for supporting global hotkeys.
- Download either the source or binary distribution of ManagedWinapi. If you download the source distribution, you need to add the project to the Launchy Clone solution.
- Add a reference to the Launchy Clone project: reference the ManagedWinapi project (source distribution) or Browse to add a reference to the downloaded dll (binary distribution).
- Add another reference to the Launchy Clone project: reference the .NET assembly System.Windows.Forms (required for the Keys enumeration).
5.2. Enabling the hotkey
I chose to implement the default hotkey for our clone as WinKey+Space instead of Ctrl+Space (the Launchy default) so they don’t interfere. Open App.xaml.cs file and add the following to the App class (note that the function OnStartup is modified from before.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | private ManagedWinapi.Hotkey hotkey; protected override void OnStartup( StartupEventArgs e ) { cache = new Cache( ); hotkey = new ManagedWinapi.Hotkey( ); hotkey.WindowsKey = true; hotkey.KeyCode = System.Windows.Forms.Keys.Space; hotkey.HotkeyPressed += new EventHandler( hotkey_HotkeyPressed ); try { hotkey.Enabled = true; } catch ( ManagedWinapi.HotkeyAlreadyInUseException ) { System.Windows.MessageBox.Show( "Could not register hotkey (already in use).", "Error", MessageBoxButton.OK, MessageBoxImage.Error ); } base.OnStartup( e ); } void hotkey_HotkeyPressed( object sender, EventArgs e ) { new Window1( ).ShowDialog( ); } protected override void OnExit( ExitEventArgs e ) { hotkey.Dispose( ); base.OnExit( e ); } |
5.3. Correcting the application’s ShutdownMode
Right now, when the main window of our application closes, it exits. We want the window to close, but leave the application running in the background waiting for our hotkey.
- Open App.xaml and change the
ShutdownMode
toOnExplicitShutdown
- Remove the
StartupUri
attribute (completely). This caused our clone to launch silently, waiting for the hotkey (just like Launchy does). - To prevent many copies of the program to start running without a way to close them, update the OnStartup function again to handle errors setting up the hotkey:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | protected override void OnStartup( StartupEventArgs e ) { cache = new Cache( ); hotkey = new ManagedWinapi.Hotkey( ); hotkey.WindowsKey = true; hotkey.KeyCode = System.Windows.Forms.Keys.Space; hotkey.HotkeyPressed += new EventHandler( hotkey_HotkeyPressed ); try { hotkey.Enabled = true; } catch ( ManagedWinapi.HotkeyAlreadyInUseException ) { System.Windows.MessageBox.Show( "Could not register hotkey (already in use).", "Error", MessageBoxButton.OK, MessageBoxImage.Error ); // if the hotkey is not enabled, don't let the application run without a visible window ShutdownMode = ShutdownMode.OnLastWindowClose; } base.OnStartup( e ); if ( ShutdownMode == ShutdownMode.OnLastWindowClose ) { // if our application is not set to run in the background, show a window immediately new Window1( ).ShowDialog( ); } } |
This is a good place to run your program and test out the changes.
6. Conclusion
Well I hope you learned a bit about using WPF in this example. I like this example because it really highlights the ability of WPF to allow you to make a clean, useful application in less time than ever before.
Oh, and one final sidenote: the application and libraries (aside from the .NET framework of course) end up only ~100kb. 🙂
Hmm. I really like this three part article. How come you stopped blogging? Keep up this work, and your blog may become more read.
August 28th, 2007 at 12:28 pmHow to i trap the click on the x button that closes an application? What is the source code for this in a WPF window? Thanks
September 21st, 2007 at 6:08 pmHi,
Great tutorial. I am writing an application that does something similar, but I’m having a problem where sometimes my window appears but doesn’t actually become the foreground window and gain focus. Any idea where I could be going wrong?
November 23rd, 2007 at 8:09 pm[…] Windows system hotkey in wpf: http://blog.280z28.org/arch… […]
August 15th, 2008 at 9:59 am