|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionThe program presented here allows you to minimize any open Window to the system tray with just one click. After reading this article you will know how to call unmanaged code from a .NET application using P/Invoke. BackgroundAll the functionality of this application is achieved using Windows API functions so you should be familiar with basic Basic P/InvokeP/Invoke or Platform Invoke allows you to call unmanaged functions from managed code. To do that you need to declare a method and specify the name of the DLL file which contains the method using [DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
How the Program WorksWhen you run the application it hides the main form and an icon is shown in the system tray. When you right-click the icon a list of all visible Windows is displayed. If you click one of them then that Window will disappear and a new icon will appear in the system tray. The icon is the same as the icon of the executable that launched the Window. Clicking on the icon will bring the Window back. Apart from this there are two commands available to minimize all Windows to tray or to show all minimized Windows. Both commands have hotkeys associated with them and they can be changed from the options Window. To open the options Window just double-click the main icon. Other options include adding the program to the start-up program list and ignoring Windows without a title. Code Behind the ApplicationEnumerating WindowsWhen you right-click the icon a list of all visible Windows appears. To enumerate all Windows you should call //This function call EnumWindows and passes delegate to the callback function
private void getwindows()
{
winapi.EnumWindowsProc callback = new winapi.EnumWindowsProc(enumwindows);
winapi.EnumWindows(callback, 0);
}
private bool enumwindows(IntPtr hWnd, int lParam)
{
//Ignore invisible window
if (!winapi.IsWindowVisible(hWnd))
return true;
StringBuilder title = new StringBuilder(256);
winapi.GetWindowText(hWnd, title, 256);
if (string.IsNullOrEmpty(title.ToString())&& set.IgnoreTitle)
{
return true;
}
//Ignore statusbar and add other windows to the list
if (title.Length != 0 || (title.Length == 0 & hWnd != winapi.statusbar))
{
windows.Add(new window(hWnd, title.ToString(), winapi.IsIconic(hWnd),
winapi.IsZoomed(hWnd)));
}
return true;
}
Hiding the Window...When a user clicks on one of the Windows we need to hide it. private void showwindow(window wnd, bool hide)
{
winapi.ShowWindow(wnd.handle, state(wnd, hide));
}
private int state(window wd, bool hide)
{
if (hide)
{
return winapi.SW_HIDE;
}
if (wd.isminimzed)
{
return winapi.SW_MINIMIZE;
}
if (wd.ismaximized)
{
return winapi.SW_MAXIMIZE;
}
return winapi.SW_SHOW;
}
... and Displaying IconThe Window is hidden so we need to retrieve the icon of the executable file that created this Window. To achieve this we need to get the path of the executable first. These are the required steps:
These steps were suggested by Mark Salsbery here: retrieve executable path by hwnd. All these functions are private string pathfromhwnd(IntPtr hwnd)
{
uint dwProcessId;
//Get the process id
winapi.GetWindowThreadProcessId(hwnd, out dwProcessId);
//Get the handle of the process
IntPtr hProcess = winapi.OpenProcess(
winapi.ProcessAccessFlags.VMRead | winapi.ProcessAccessFlags.QueryInformation,
false, dwProcessId);
//Get the executable path
StringBuilder path = new StringBuilder(1024);
winapi.GetModuleFileNameEx(hProcess, IntPtr.Zero, path, 1024);
winapi.CloseHandle(hProcess);
return path.ToString();
}
To retrieve the path to the executable file you can also use Now when we have the path to the executable, we can extract the icon that is displayed by explorer using the private Icon Iconfrompath(string path)
{
System.Drawing.Icon icon = null;
if (System.IO.File.Exists(path))
{
//Retrieve SHFILEINFO type variable
winapi.SHFILEINFO info = new winapi.SHFILEINFO();
winapi.SHGetFileInfo(path, 0, ref info, (uint)Marshal.SizeOf(info),
winapi.SHGFI_ICON | winapi.SHGFI_SMALLICON);
//Create icon and destroy the handle
System.Drawing.Icon temp = System.Drawing.Icon.FromHandle(info.hIcon);
icon = (System.Drawing.Icon)temp.Clone();
winapi.DestroyIcon(temp.Handle);
}
return icon;
}
At this point we have the necessary icon so we just create and show The ClickWhen an icon is clicked we need to display the corresponding Window. First check if there exists a Window with the specified handle and then call void tray_Click(object sender, EventArgs e)
{
NotifyIcon tray = sender as NotifyIcon;
window wnd = tray.Tag as window;
if (winapi.IsWindow(wnd.handle))
{
showwindow(wnd, false);
}
else
MessageBox.Show("Window does not exist");
//Don't forget to remove event handler.
//Otherwise GC won't collect the notifyicon.
tray.Click -= new EventHandler(tray_Click);
tray.Dispose();
}
HotkeysThis program allows to customize hotkeys for two commands: 'all to tray' and 'show all'. To add hotkey functionality to your application, first you need to register it.
if (mod > 0 && key > 0)
{
winapi.RegisterHotKey(this.Handle, 1729, mod, 64 + key);
}
To handle the protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
//We are interested only in WM_HOTKEY message
case winapi.WM_HOTKEY:
//m.WParam is the ID we used when registering the hotkey
ProcessHotkey(m.WParam);
break;
}
//Pass message to base class
base.WndProc(ref m);
}
private void ProcessHotkey(IntPtr wparam)
{
if (wparam.ToInt32() == 1729)
{
alltray_Click(null, null);
}
if (wparam.ToInt32() == 1730)
{
showall();
}
}
When the application exits or hotkey changes we don't need the existing hotkey any more so we should unregister it. It is easier then registering the hotkey and is achieved by calling if (mod > 0 && key > 0)
{
winapi.UnregisterHotKey(this.Handle, 1729);
}
Managing Start-upYou can add the program to start-up from the options Window. To add program to start-up you need to navigate to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run key, create a new string and set its value equal to the application's path. Removing the program from start-up is easier: you just remove the value. The code snippet below shows how to do it: private void startup(bool add)
{
isinstartup = add;
RegistryKey key = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\Windows\CurrentVersion\Run", true);
if (add)
{
//Surround path with " " to make sure that there are no problems
//if path contains spaces.
key.SetValue("Tray minimizer", "\"" + Application.ExecutablePath + "\"");
}
else
key.DeleteValue("Tray minimizer");
key.Close();
}
Making the Application Single-instanceIf you try to launch the second instance of the application you will get a message box saying that it is already running. This is achieved using static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Mutex mt = null;
//Try to open existing mutex
try
{
mt = Mutex.OpenExisting("Tray minimizer");
}
catch (WaitHandleCannotBeOpenedException)
{
}
if (mt == null)
{
//If the mutex doesn't exist create it and launch the application.
mt = new Mutex(true, "Tray minimizer");
Application.Run(new Form1());
//Tell GC not to destroy mutex until the application is running and
//release the mutex when application exits.
GC.KeepAlive(mt);
mt.ReleaseMutex();
}
else
{
//The mutex existed so exit
mt.Close();
MessageBox.Show("Application already running");
Application.Exit();
}
}
Points of InterestThis application shows how powerful ReferencesHistory
|
||||||||||||||||||||||