Introduction
This article presents a simple class to make window snap to a desktop working area edges, just like the popular Winamp program. The work area is the portion of the screen not obscured by the system taskbar or by application desktop toolbars (as stated in MSDN). To temporarily disable snapping while dragging the window, press and hold the snap modifier key (shift key by default).
This article is reusing the code from the article Make it snappy by Peter Hesselberg, so it is recommended to read it first. There are similar articles here on Codeproject, WTL Snapping Splitter Window and Snappable tool windows that auto-hide, but they are dealing with particular problems while this article presents a general method.
The Code
The code uses plain Win32 API. The download package includes Dev-C++ (GCC) and Win32/WTL VC2005 projects (made with VC2005 Express + Platform SDK 2003 R2 + WTL 8.0).
Snapping distance and snap modifier keys are stored in snap_Margin
and snap_ModifierKey
class public members. By default, snapping distance is set to a window caption bar height, and no-snap key is Shift key. Working area size is detected in WM_ENTERSIZEMOVE handler, so that window snaps properly if desktop size changes. Functions are declared virtual
so you can easily do your own window position processing.
Using the Code with WTL
Following the usual WTL practice, the code is expanded to a template class. Add CSnapWindow
to CMainFrame
inheritance list and chain the message map:
#include "../Snapwindow.h"
class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,
public CMessageFilter, public CIdleHandler, public CSnapWindow<CMainFrame>
...
BEGIN_MSG_MAP(CMainFrame)
...
CHAIN_MSG_MAP(CSnapWindow<CMainFrame>)
END_MSG_MAP()
Using the Code with Win32
Make a class instance and add class members as main window's message handlers:
#include "SnapWindow.h"
CSnapWindow snapHandler;
...
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage (0);
break;
case WM_MOVING:
return snapHandler.OnSnapMoving(hwnd, message, wParam, lParam);
break;
case WM_ENTERSIZEMOVE:
return snapHandler.OnSnapEnterSizeMove(hwnd, message, wParam, lParam);
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
Source Code
Full source code of the class is given here so you can start using it immediately:
#ifndef _0C46500C_084D_44ac_8C26_37E38BED2714_
#define _0C46500C_084D_44ac_8C26_37E38BED2714_
#pragma once
#ifdef __ATLBASE_H__
template <class T> class CSnapWindow
#else
class CSnapWindow
#endif
{
public:
int snap_Margin, snap_ModifierKey;
#ifdef __ATLBASE_H__
BEGIN_MSG_MAP(CSnapWindow<T>)
MESSAGE_HANDLER(WM_MOVING, OnSnapMoving)
MESSAGE_HANDLER(WM_ENTERSIZEMOVE, OnSnapEnterSizeMove)
END_MSG_MAP()
#endif
#ifdef __ATLBASE_H__
virtual LRESULT OnSnapEnterSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam,
BOOL& bHandled)
#else
virtual LRESULT OnSnapEnterSizeMove(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
#endif
{
snap_cur_pos.x=0;
snap_cur_pos.y=0;
snap_rcWindow.bottom=0;
snap_rcWindow.left=0;
snap_rcWindow.right=0;
snap_rcWindow.top=0;
#ifdef __ATLBASE_H__
T* pT = static_cast<T*>(this);
GetWindowRect(pT->m_hWnd, &snap_rcWindow );
#else
GetWindowRect(hWnd, &snap_rcWindow );
#endif
GetCursorPos( &snap_cur_pos );
snap_x = snap_cur_pos.x - snap_rcWindow.left;
snap_y = snap_cur_pos.y - snap_rcWindow.top;
return 0;
}
#ifdef __ATLBASE_H__
virtual LRESULT OnSnapMoving(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
#else
virtual LRESULT OnSnapMoving(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
#endif
{
if (GetAsyncKeyState(snap_ModifierKey) < 0) return FALSE;
snap_prc = (LPRECT)lParam;
snap_cur_pos.x=0;
snap_cur_pos.y=0;
snap_rcWindow.bottom=0;
snap_rcWindow.left=0;
snap_rcWindow.right=0;
snap_rcWindow.top=0;
GetCursorPos( &snap_cur_pos );
OffsetRect( snap_prc,
snap_cur_pos.x - (snap_prc->left + snap_x) ,
snap_cur_pos.y - (snap_prc->top + snap_y) );
SystemParametersInfo( SPI_GETWORKAREA, 0, &snap_wa, 0 );
if (isSnapClose( snap_prc->left, snap_wa.left ))
{OffsetRect( snap_prc, snap_wa.left - snap_prc->left, 0);}
else
if (isSnapClose( snap_wa.right, snap_prc->right ))
{OffsetRect( snap_prc, snap_wa.right - snap_prc->right, 0);}
if (isSnapClose( snap_prc->top, snap_wa.top ))
{OffsetRect( snap_prc, 0, snap_wa.top - snap_prc->top );}
else
if (isSnapClose( snap_wa.bottom, snap_prc->bottom ))
{OffsetRect( snap_prc, 0, snap_wa.bottom - snap_prc->bottom );}
return TRUE;
}
virtual BOOL isSnapClose( int a, int b ) { return (abs( a - b ) < snap_Margin);}
CSnapWindow()
{
snap_ModifierKey=VK_SHIFT;
NONCLIENTMETRICS ncm = { 0 };
ncm.cbSize = sizeof ncm;
SystemParametersInfo( SPI_GETNONCLIENTMETRICS, 0, &ncm, 0 );
snap_Margin=ncm.iCaptionHeight;
}
private:
POINT snap_cur_pos;
RECT snap_rcWindow, snap_wa, *snap_prc;
int snap_x, snap_y;
};
#endif//_0C46500C_084D_44ac_8C26_37E38BED2714_
Issues/Suggestions
Please post any issues/bugs/suggestions in this article's message board.