Skip to content

Scroll Window Into View

A Win32 technique that animates a window sliding up into view from below the desktop edge, driven by a worker thread.

Published Jul 20, 1998

The pre-DWM way to make a window's first appearance feel smooth. The trick: park the window just below the visible work area (positioned at desktopHeight + 1), then run a worker thread that calls MoveWindow in small steps - three pixels every 10 ms here - until it reaches its final bottom-right position. Because the UI thread keeps pumping messages while the animation runs, the rest of the app stays responsive. A good reference for hand-rolled window entrance effects when you cannot use DWM transitions or an animation library.

struct _ScrollWindowIntoView{
    HWND xHandle;
    int nScreenWidth;
    int nScreenHeight;
    int nDesktopWidth;
    int nDesktopHeight;
    int nWindowHeight;
    int nWindowWidth;

    int NewPos;
    int DestPos;

}; _ScrollWindowIntoView gSWIV;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void InitScrollWindowIntoView(HWND xHandle, _ScrollWindowIntoView *SWIV)
{
    RECT rcDesktop;
    RECT rcWindow;

    SWIV->xHandle = xHandle;

    SystemParametersInfo(SPI_GETWORKAREA, 0, &rcDesktop, 0);
    GetWindowRect(SWIV->xHandle, &rcWindow);

    SWIV->nScreenWidth   = GetSystemMetrics(SM_CXSCREEN);
    SWIV->nScreenHeight  = GetSystemMetrics(SM_CYSCREEN);
    SWIV->nDesktopWidth  = rcDesktop.right - rcDesktop.left;
    SWIV->nDesktopHeight = rcDesktop.bottom - rcDesktop.top;
    SWIV->nWindowHeight  = rcWindow.bottom - rcWindow.top;
    SWIV->nWindowWidth   = rcWindow.right - rcWindow.left;

    SWIV->NewPos  = (SWIV->nDesktopHeight) + 1;
    SWIV->DestPos = (SWIV->nDesktopHeight - SWIV->nWindowHeight);

    MoveWindow(SWIV->xHandle, (SWIV->nDesktopWidth - SWIV->nWindowWidth) - 1, SWIV->NewPos, SWIV->nWindowWidth, SWIV->nWindowHeight, TRUE);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

DWORD WINAPI Worker_Thread(LPVOID pvData)
{
    _ScrollWindowIntoView *SWIV = (_ScrollWindowIntoView *)pvData;

    while(SWIV->NewPos > SWIV->DestPos)
    {
        MoveWindow(SWIV->xHandle, (SWIV->nDesktopWidth - SWIV->nWindowWidth) - 1, SWIV->NewPos, SWIV->nWindowWidth, SWIV->nWindowHeight, TRUE);
        SWIV->NewPos = (SWIV->NewPos - 3);
        Sleep(10);
    }

    MoveWindow(SWIV->xHandle, (SWIV->nDesktopWidth - SWIV->nWindowWidth) - 1, SWIV->DestPos, SWIV->nWindowWidth, SWIV->nWindowHeight, TRUE);

    return 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void DoScrollWindowIntoView(_ScrollWindowIntoView *SWIV)
{
    Worker_Thread_Handle = CreateThread(NULL, 0, Worker_Thread, (LPVOID)SWIV, 0, &Worker_Thread_ID);
}

In the dialog procedure you call the two functions from WM_INITDIALOG:

if(xMessage == WM_INITDIALOG)
{
    // ... set title, icon, wire up controls ...

    InitScrollWindowIntoView(xHandle, &gSWIV);

    DoScrollWindowIntoView(&gSWIV);

    return TRUE;
}

Tune the feel by changing the step size (- 3) and the Sleep(10) interval; note SPI_GETWORKAREA is what makes it respect the taskbar, since raw screen metrics would slide the window underneath it.

An unhandled error has occurred. Reload 🗙