browser icon
You are using an insecure version of your web browser. Please update your browser!
Using an outdated browser makes your computer unsafe. For a safer, faster, more enjoyable user experience, please update your browser today or try a newer browser.

SDK实现分隔条

Posted by on 2004 年 01 月 14 日

你可以任意转载本文,但请在转载后的文章中注明作者和原始链接。
媒体约稿请联系 titilima_AT_163.com(把“_AT_”换成“@”)。

Windows中的分隔条是一种被广泛使用的控件,绝大多数Explorer式样的应用程序都使用了这种控件。然而却很少有相关的资料对它的完整实现进行介绍,于是我自己实现了一个,希望对SDK的爱好者们有所帮助。

事实上,分隔条也是一个很普通的窗口,它也拥有自己的窗口类、自己的窗口过程——就像所有的预定义控件一样。也就是说,要创建一个分隔条,也需要进行窗口类的注册和窗口的创建。以下的示例代码示范了如何注册一个分隔条的窗口类:

WNDCLASS wc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; // 1
wc.hCursor = LoadCursor( NULL, IDC_SIZEWE ); // 2
wc.hIcon = NULL;
wc.hInstance = hInstance;
wc.lpfnWndProc = (WNDPROC)ProcSplitter; // 3
wc.lpszClassName = "MySplitter"; // 4
wc.lpszMenuName = NULL;
wc.style = 0;
RegisterClass(&wc);

这段代码相信大家已经很熟悉了,所以在此我只简要说明四点:1、分隔条的背景颜色,这里我取默认的对话框背景色;2、分隔条的鼠标指针,这里我取水平的调整大小指针;3、这是分隔条的窗口过程,所有的秘密都在这个回调函数之中;4、分隔条的窗口类名称,你可以随便取一个你喜欢的名字。

在成功地注册窗口类之后,就可以创建分隔条了。以下是我的示例界面,它由一个树形视图、一个分隔条、一个列表视图以及一个状态栏组成,下文的所有代码都是以这个界面为基础的。

在编写分隔条的窗口过程之前,我先来处理对话框的WM_SIZE消息作为分隔条窗口过程的一个热身。代码如下(你会发现在整个的代码中我没有对hTree、hStatus、hSplitter以及hList做任何的声明,那是因为对于这个简单的示例,我将所有的这些东西都声明为了全局变量):

case WM_SIZE:
    {
        HDWP hdwp;
        RECT rect, rectStatus, rectTree;
        hdwp = BeginDeferWindowPos( 4 );
        GetClientRect( hDlg, &rect );
        GetClientRect( hStatus, &rectStatus );
        GetWindowRect( hTree, &rectTree );
        DeferWindowPos( hdwp, hStatus, NULL, 0, rect.bottom - rectStatus.bottom, rect.right,
           rectStatus.bottom, SWP_NOZORDER );
        DeferWindowPos( hdwp, hTree, NULL, 0, 0, rectTree.right - rectTree.left,
           rect.bottom - rectStatus.bottom, SWP_NOMOVE | SWP_NOZORDER );
        DeferWindowPos( hdwp, hSplitter, NULL, rectTree.right - rectTree.left, 0, 2,
           rect.bottom - rectStatus.bottom, SWP_NOZORDER );
        DeferWindowPos( hdwp, hList, NULL, rectTree.right - rectTree.left + 2, 0,
           rect.right - rectTree.right + rectTree.left - 2, rect.bottom - rectStatus.bottom, SWP_NOZORDER );
        EndDeferWindowPos( hdwp );
    }
    break;

你肯定已经注意到了,这段代码的大部分篇幅都是在和矩形做游戏。的确是这样,因为调整窗口大小的过程就是一个改变各个子窗口的位置和大小的过程。这个过程用语言叙述就是:1、首先,将状态栏放置在对话框的最下方;2、第二步,不改变树形视图的位置和宽度,重设它的高度;3、不改变分隔条的位置和宽度,重设它的高度;4、使列表视图占满剩余的客户区。

如果你弄懂了上面的代码,那么分隔条的窗口过程也就没有任何难度了:

LRESULT CALLBACK ProcSplitter( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
    switch ( Msg )
    {
    case WM_LBUTTONDOWN:
        {
            SetCapture( hwnd );
        }
        break;
    case WM_LBUTTONUP:
        {
            ReleaseCapture();
        }
        break;
    case WM_MOUSEMOVE:
        {
            if ( ( wParam & MK_LBUTTON ) == MK_LBUTTON && GetCapture() == hwnd )
            {
                HDWP hdwp;
                RECT rect, rectStatus, rectTree;
                hdwp = BeginDeferWindowPos( 3 );
                GetClientRect( GetParent( hwnd ), &rect );
                GetClientRect( hStatus, &rectStatus );
                GetWindowRect( hTree, &rectTree );
                DeferWindowPos( hdwp, hTree, NULL, 0, 0, rectTree.right - rectTree.left + (short)LOWORD( lParam ),
                    rect.bottom - rectStatus.bottom, SWP_NOMOVE | SWP_NOZORDER );
                DeferWindowPos(hdwp, hSplitter, NULL, rectTree.right - rectTree.left + (short)LOWORD( lParam ),
                    0, 0, 0, SWP_NOSIZE | SWP_NOZORDER );
                DeferWindowPos( hdwp, hList, NULL, rectTree.right - rectTree.left + (short)LOWORD( lParam ) + 2,
                    0, rect.right - rectTree.right + rectTree.left - (short)LOWORD( lParam ) - 2,
                    rect.bottom - rectStatus.bottom, SWP_NOZORDER);
                EndDeferWindowPos( hdwp );
            }
        }
        break;
    default:
        return DefWindowProc( hwnd, Msg, wParam, lParam );
    }
    return 0;
}

SetCapture和ReleaseCapture是分别在鼠标左键按下与释放的时候捕获和释放鼠标,这是分隔条的一般要求。这段代码中的核心部分就是在处理鼠标移动的事件,就是当鼠标左键按下并且分隔条捕获鼠标的时候来改变三个相关窗口的位置和宽度。具体的矩形操作与主窗口WM_SIZE的代码原理相似,我就不多说了。我之所以不使用MoveWindow之类的函数来实现改变大小,就是因为这些函数会使窗体的多次重绘而导致整个窗体的闪烁——而事实上我并不希望状态栏也一起闪烁。

上边的分隔条是一种“动态”的分隔条,就是说在移动分隔条的同时窗口的大小也发生了改变。以下我再介绍一种“静态”的分隔条,即在拖动分隔条的时候出现一条竖线,由这一条线来指示分隔条的分隔结果。

如果说动态的分隔条是在和矩形做游戏,那么静态的分隔条就是在和图形做游戏了。首先请大家看我的代码:

LRESULT CALLBACK ProcSplitter( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
    static int x;
    switch ( Msg )
    {
    case WM_LBUTTONDOWN:
        {
            HDC hdc;
            RECT rectTree;
            SetCapture( hwnd );
            GetWindowRect( hTree, &rectTree );
            hdc = GetDC( GetParent( hwnd ) );
            SelectObject( hdc, CreatePen( PS_SOLID, 2, 0 ) );
            SetROP2( hdc, R2_NOTXORPEN );
            x = rectTree.right - rectTree.left;
            MoveToEx( hdc, x, 0, NULL );
            LineTo( hdc, x, rectTree.bottom - rectTree.top );
            ReleaseDC( GetParent( hwnd ), hdc );
        }
        break;
    case WM_LBUTTONUP:
        {
            HDWP hdwp;
            HDC hdc;
            RECT rect, rectTree, rectStatus;
            GetClientRect( GetParent( hwnd ), &rect );
            GetWindowRect( hTree, &rectTree );
            GetClientRect( hStatus, &rectStatus );
            hdc = GetDC( GetParent( hwnd ) );
            SelectObject( hdc, CreatePen( PS_SOLID, 2, 0 ) );
            SetROP2( hdc, R2_NOTXORPEN );
            MoveToEx( hdc, x, 0, NULL );
            LineTo( hdc, x, rectTree.bottom - rectTree.top );
            ReleaseDC( GetParent( hwnd ), hdc );
            ReleaseCapture();
            hdwp = BeginDeferWindowPos( 3 );
            DeferWindowPos( hdwp, hTree, NULL, 0, 0, x, rect.bottom - rectStatus.bottom, SWP_NOMOVE | SWP_NOZORDER );
            DeferWindowPos( hdwp, hSplitter, NULL, x, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER );
            DeferWindowPos( hdwp, hList, NULL, x + 2, 0, rect.right - x - 2, rect.bottom - rectStatus.bottom, SWP_NOZORDER );
            EndDeferWindowPos( hdwp );
        }
        break;
    case WM_MOUSEMOVE:
        {
            if ( ( wParam & MK_LBUTTON ) == MK_LBUTTON && GetCapture() == hwnd )
            {
                HDC hdc;
                RECT rectTree;
                GetWindowRect( hTree, &rectTree );
                hdc = GetDC( GetParent( hwnd ) );
                SelectObject( hdc, CreatePen( PS_SOLID, 2, 0 ) );
                SetROP2( hdc, R2_NOTXORPEN );
                MoveToEx( hdc, x, 0, NULL );
                LineTo( hdc, x, rectTree.bottom - rectTree.top );
                x = rectTree.right - rectTree.left + (short)LOWORD( lParam );
                MoveToEx( hdc, x, 0, NULL );
                LineTo( hdc, x, rectTree.bottom - rectTree.top );
                ReleaseDC( GetParent( hwnd ), hdc );
            }
        }
        break;
    default:
        return DefWindowProc( hwnd, Msg, wParam, lParam );
    }
    return 0;
}

这段代码的核心部分,就是它的画线和擦线部分。在这里我玩弄了一个小把戏,就是利用了SetROP2函数的R2_NOTXORPEN模式:在这个模式下作图,只要在先前画过线的地方再画一道线,那么用户看到的效果就是原来的线被擦除了。这样一来,只需要再使用一个static变量x,就可以完成这个过程了。

当然分隔条的种类有很多,例如VCL中的TSplitter中就包括了不同样式的分隔条,不过效果无非是改变了画线的样式之类,在此我就不多讨论了。

附件:splitter.zip

订阅本站

一条评论

  • At 2006.04.03 10:06, lcyi said:

    这篇文章给我很大的帮助,谢谢小马!!

    (Required)
    (Required, will not be published)