Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Snapping Window

4.08/5 (7 votes)
15 Jul 2008CPOL2 min read 1   1.5K  
Simple class to make a window snap to screen edges
Window snaps to screen edges

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:

C++
//WTL example

#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:

C++
//Dev-C++ (GCC) example:

#include "SnapWindow.h"
CSnapWindow snapHandler;

...

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) /* handle the messages */
    {
    case WM_DESTROY:
        PostQuitMessage (0); /* send a WM_QUIT to the message queue */
    break;
/////////////////////////////////////////////
//snap handling
//
    case WM_MOVING:
        return snapHandler.OnSnapMoving(hwnd, message, wParam, lParam);
    break;
    case WM_ENTERSIZEMOVE:
        return snapHandler.OnSnapEnterSizeMove(hwnd, message, wParam, lParam);
    break;
//
/////////////////////////////////////////////
default: /* for messages that we don't deal with */
    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:

C++
#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
            {
                //no snap if modifier key pressed
                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) );

                //working area may change during app lifetime
                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.

License

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