Snap a Window to the Screen Edge

Published on April 12, 2008
This article was written back when Visual Studio 6 was new. As such, I cannot guarantee that the content of this article still applies in modern Windows environments.

Snapping a window to the screen edge has long been an interesting effect. Winamp is probably the best application to use as an example, as all of its associated windows can snap to the screen edge (and to each other). So how can you make your application do the same thing? It’s actually not too difficult!

You will find that some "window snap" code snippets require that the "Show Window Contents While Dragging" option be turned on in Windows. Thankfully, the following code doesn’t have that restriction. This article will use a dialog based application as the example, and will refer to the "dialog’s class" as the class to add code to. This same effect can be added to document-view based applications (SDI / MDI), adding the necessary code to the appropriate frame class (e.g., CMainFrame).

Making the window snap to the screen requires that we take control of a few messages that are normally sent by the application to the operating system. We won’t prevent the messages from reaching the OS, but we will be the first to manipulate them. Here’s how to do it:

Override the Default WindowProc Method

First, we need to override the standard WindowProc() message in the dialog’s class. Add this override via ClassWizard, and then add the following line of code to the corresponding method:

OnMessage(this, message, wParam, lParam);

The OnMessage() line we added is how we will "hijack" the messages being sent to the OS, and the return line is the code that actually sends the messages onward to the OS.

Trapping the Necessary Messages

Manually add the OnMessage() method to your dialog’s class (make sure you put the declaration in the .h file). The method should have code similar to the following:

LRESULT CPaperplusDlg::OnMessage(CWnd* wnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch(msg)
    {
        case WM_MOVING : OnMoving(wnd, (LPRECT) lp); break;
        case WM_ENTERSIZEMOVE : OnEnterSizeMove(wnd); break;
    };
    return 0;
}

Handling the Trapped Messages

As you can see, the two messages we need to trap are the WM_MOVING message (which indicates that the user is currently moving the window), and the WM_ENTERSIZEMOVE message (which indicates that the window has just entered the modal loop that deals with the window moving). We will route these two messages through their respective methods (which we will write) shown in the code above; OnMoving() for the moving message, and OnEnterSizeMove() for the other one. To begin, manually add the OnEnterSizeMove() method, which should look like the following:

void CPaperplusDlg::OnEnterSizeMove(CWnd* wnd)
{
    POINT cur_pos;
    CRect rcWindow;
    wnd->GetWindowRect(rcWindow);
    GetCursorPos(&cur_pos);
    cx = cur_pos.x - rcWindow.left;
    cy = cur_pos.y - rcWindow.top;
}

Again, don’t forget to add the declaration of this method to the class .h file. This code first gets the rectangle associated with the window we want to move (in this example the window is our program’s main dialog). It then gets the mouse cursor position, and stores the x and y values in two member variables, cx and cy. Declare two variables in your class .h file that look like this:

int cx;
int cy;

Now that we have that method taken care of, and the mouse coordinate member variables are in place, we can add the OnMoving() method:

BOOL CPaperplusDlg::OnMoving(CWnd* wnd, LPRECT rect)
{
    POINT cur_pos;
    RECT wa;
    GetCursorPos(&cur_pos);
    OffsetRect(rect, cur_pos.x - (rect->left + cx),
               cur_pos.y - (rect->top + cy));
    SystemParametersInfo(SPI_GETWORKAREA, 0, &wa, 0);

    if(isClose(rect->left, wa.left))
        OffsetRect( rect, wa.left - rect->left, 0 );
    else if(isClose(wa.right, rect->right))
        OffsetRect( rect, wa.right - rect->right, 0 );

    if(isClose(rect->top, wa.top))
        OffsetRect(rect, 0, wa.top - rect->top);
    else if(isClose(wa.bottom, rect->bottom))
        OffsetRect(rect, 0, wa.bottom - rect->bottom);

    return TRUE;
}

Before we discuss the previous block of code, there is one point of confusion that should be cleared up. You may have noticed that we removed the values "cx" and "cy" in the OnEnterSizeMove() method and are adding them back in this method. Clearly this seems like a wasted effort, right? Wrong! These calculations are necessary to "un-snap" the window from the edge of the screen. If you play around with the code by commenting out the first OffsetRect() call above, you will notice that the window does some strange stuff.

Testing the Window’s Location

The OnMoving() block of code is a bit more involved. We are getting the cursor position again (as we move the window), and are offsetting the main dialog rectangle appropriately. We then get the rectangle of the "work area" (using the SystemParametersInfo() call), which basically retrieves the rectangle that is defined by the screen edges (and the start bar edge). We then call a method that we have yet to write, isClose(), which basically just tests to see if we’ve gotten close enough to snap to the edge of the screen. Let’s now take a look at that method:

BOOL CPaperplusDlg::isClose(int a, int b)
{
    return abs(a-b) < m_SnapWidth;
}

This method is very simple. All it does is return whether or not the difference in the passed parameters is less than the width that we want to snap by. As you can see in the code example above, I use a variable in my Paper Plus program that allows the user to set that snap distance: m_SnapWidth. But any value can be used here (so you can hard-wire a value in, without allowing the user a choice of a snap distance).

Now you’re done! That wasn’t too bad, was it? Just make sure you have all the function declarations in place, and you should be set to go. Test out the snapping, and see how well it works.

Comments are closed.

Copyright © 2004-2018 Jonah Bishop. Hosted by DreamHost.