组合框

组合框:顾名思义是几个窗体组合而成的.先看下Delphi组合框的TCOMBOBOX类:
TComboBox = class(TCustomComboBox) = class(TCustomCombo) = class(TCustomListControl) = class(TWinControl)
在TCustomComboBox之前的都是DELPHI一手捏造的,到了TCustomComboBox就子类化了,成为名符其实的WINDOW控件.
TCustomListControl这是一个纯虚类,它的存在仅仅是一种设计思想.TCustomCombo类其实也是虚类,它的存在的目的就是让DELPHI与WINDOW控件分离开来.其实只要大家注意,这个设计思想贯穿整个VCL,那么为什么要使用这个设计思想呢?因为WINDOWS是另外一家公司,他的变化不在BORLAND公司的控制之内,把不能控制的变化独立出来,把相同的独立出来大概是设计的核心吧.
限制全局的变化只在一个地方,限制不能控制的变化在最小区域.Delphi的VCL这点还是做得蛮好的.

既然是组合框,那到底是那个窗口组合的呢?答:是一个EDIT,一个LISTBOX,一个小下拉窗体,共三个窗口组合而成的.
其中,"小下拉窗体"是主窗体,是另外两个的父窗体,它的第一子窗体是EDIT,第二个子窗体是LISTBOX.我们看下DELPHI是如何取得两子窗体的.
procedure TCustomComboBox.CreateWnd;
begin
  inherited CreateWnd;
  FDropHandle := Handle;
  ....    
  ChildHandle := GetWindow(Handle, GW_CHILD); 
  ...
  FListHandle := GetWindow(ChildHandle, GW_HWNDNEXT);    //大概是这样的
  ...
end;

下面具体看下Combobox类信息:
static const WCHAR comboboxW[] = {'C','o','m','b','o','B','o','x',0};
const struct builtin_class_descr COMBO_builtin_class =
{
    comboboxW,            /* name */
    CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, /* style  */ //是不是有点似曾相识感觉
    ComboWndProcA,        /* procA */
    ComboWndProcW,        /* procW */
    sizeof(HEADCOMBO *),  /* extra */
    IDC_ARROW,            /* cursor */
    0                     /* brush */
};

可以看combobox也有个窗体扩展数据结构:
/* combo state struct */
typedef struct
{
   HWND           self;             //自身句柄
   HWND           owner;            //拥有者句柄
   UINT           dwStyle;          //类型
   HWND           hWndEdit;         //EDIT句柄
   HWND           hWndLBox;         //LIST句柄
   UINT           wState;           //状态  
   HFONT          hFont;            //字体
   RECT           textRect;         //EDIT的RECT
   RECT           buttonRect;       //BUTTON的RECT
   RECT           droppedRect;      //下拉窗口RECT 
   INT            droppedIndex;     //当前下拉项
   INT            fixedOwnerDrawHeight;              //行高
   INT            droppedWidth;   /* last two are not used unless set */      //行宽
   INT            editHeight;     /* explicitly */   //行高
   LONG           UIState;                           //UI状态
} HEADCOMBO,*LPHEADCOMBO;


在WM_CREATE里调用的是:
          lphc->hWndLBox = CreateWindowExA(lbeExStyle, "ComboLBox", NULL, lbeStyle,
                                           lphc->droppedRect.left,
                                           lphc->droppedRect.top,
                                           lphc->droppedRect.right - lphc->droppedRect.left,
                                           lphc->droppedRect.bottom - lphc->droppedRect.top,
                                           hwnd, (HMENU)ID_CB_LISTBOX,
                                           (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), lphc );
可见,LISTBOX并不真正WINDOWS的LISTBOX,而是专门的"ComboLBox"的类.
        if( CB_GETTYPE(lphc) != CBS_SIMPLE )
        {
              /* Now do the trick with parent */
          SetParent(lphc->hWndLBox, HWND_DESKTOP);
              ...
如果不是CBS_SIMPLE类型的话,那个LBOX的父窗口是桌面.


         lphc->hWndEdit = CreateWindowExA(0, "Edit", NULL, lbeStyle,
                                                   lphc->textRect.left, lphc->textRect.top,
                                                   lphc->textRect.right - lphc->textRect.left,
                                                   lphc->textRect.bottom - lphc->textRect.top,
                                                   hwnd, (HMENU)ID_CB_EDIT,
                                                   (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), NULL );
编辑框倒是WINDOWS通用的EDIT.

static LRESULT ComboWndProc_common( HWND hwnd, UINT message,
                                    WPARAM wParam, LPARAM lParam, BOOL unicode )
{
      LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongPtrW( hwnd, 0 );

      TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
            hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );

      if( lphc || message == WM_NCCREATE )
      switch(message)
      {

    /* System messages */

         case WM_NCCREATE:     //非客户区创建消息
    {
        LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
                       ((LPCREATESTRUCTA)lParam)->style;
                return COMBO_NCCreate(hwnd, style);
    }
         case WM_NCDESTROY:  //非客户区释放消息
        COMBO_NCDestroy(lphc);
        break;/* -> DefWindowProc */

         case WM_CREATE:   //创建初始化消息
    {
        HWND hwndParent;
        LONG style;
        if(unicode)
        {
            hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
            style = ((LPCREATESTRUCTW)lParam)->style;
        }
        else
        {
            hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
            style = ((LPCREATESTRUCTA)lParam)->style;
        }
                return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);  //,就是在此创建的EDIT与COMBOLBOXR的.
    }

        case WM_PRINTCLIENT:
        /* Fallthrough */
         case WM_PAINT:
        /* wParam may contain a valid HDC! */
        return  COMBO_Paint(lphc, (HDC)wParam);           //画图函数,因为是多窗体,所以要画三个窗口    

    case WM_ERASEBKGND:                                       //擦除背景已经在WM_PAINT里做好了,所以不用了
                /* do all painting in WM_PAINT like Windows does */
                return 1;

    case WM_GETDLGCODE:                                         //接收键盘的按键
    {
        LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;              //方向键和字符键
        if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))      //如果是WM_KEYDOWN消息发出的消息
        {
           int vk = (int)((LPMSG)lParam)->wParam;

           if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
               result |= DLGC_WANTMESSAGE;
        }
        return  result;
    } 
    case WM_WINDOWPOSCHANGING:            //正在修改位置,发送WM_SIZING消息
            return  COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
    case WM_WINDOWPOSCHANGED:                   //修改完位置,发送WM_SIZE消息
        /* SetWindowPos can be called on a Combobox to resize its Listbox.
         * In that case, the Combobox itself will not be resized, so we won't
         * get a WM_SIZE. Since we still want to update the Listbox, we have to
         * do it here.
         */
        /* we should not force repainting on WM_WINDOWPOSCHANGED, it breaks
         * Z-order based painting.
         */
        /* fall through */
    case WM_SIZE:                       //不用说了,对COMBOBOX本身来说,因为大小不能改变,所以WM_SIZE已经失去了意义,但是对于COMBOLBOX来说,WM_SIZE是有意义的,在SIMPLI情况下.
            if( lphc->hWndLBox &&
          !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc, message == WM_SIZE );
        return  TRUE;
    case WM_SETFONT:                   //设置字体
        COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
        return  TRUE;
    case WM_GETFONT:                   //取得字体
        return  (LRESULT)lphc->hFont;
    case WM_SETFOCUS:                   //设置焦点
        if( lphc->wState & CBF_EDIT )
            SetFocus( lphc->hWndEdit );
        else
            COMBO_SetFocus( lphc );
        return  TRUE;
    case WM_KILLFOCUS:        //失去焦点
            {
                HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
        if( !hwndFocus ||
            (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
            COMBO_KillFocus( lphc );  //如果在COMBOLBOX上,则关闭COMBOLBOX
        return  TRUE;
            }
    case WM_COMMAND:                  //向父窗口发送命令消息
        return  COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );//
    case WM_GETTEXT:                      //取得TEXT
            return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
                           : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
    case WM_SETTEXT:
    case WM_GETTEXTLENGTH:
    case WM_CLEAR:                        //...
                if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
                {
                    int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
                    if (j == -1) return 0;
                    return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
                                     SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
                }
        else if( lphc->wState & CBF_EDIT )
        {
            LRESULT ret;
            lphc->wState |= CBF_NOEDITNOTIFY;
            ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
                    SendMessageA(lphc->hWndEdit, message, wParam, lParam);
            lphc->wState &= ~CBF_NOEDITNOTIFY;
            return ret;
        }
        else return CB_ERR;
    case WM_CUT:
        case WM_PASTE:
    case WM_COPY:                     //剪贴板三消息合并在一起
        if( lphc->wState & CBF_EDIT )
        {
            return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
                     SendMessageA(lphc->hWndEdit, message, wParam, lParam);
        }
        else return  CB_ERR;

    case WM_DRAWITEM:
    case WM_DELETEITEM:
    case WM_COMPAREITEM:
    case WM_MEASUREITEM:                           //确定项数据,注意和LIST比较下,有点不同.
        return COMBO_ItemOp(lphc, message, lParam);
    case WM_ENABLE:                  //可用处理消息
        if( lphc->wState & CBF_EDIT )
            EnableWindow( lphc->hWndEdit, (BOOL)wParam );
        EnableWindow( lphc->hWndLBox, (BOOL)wParam );

        /* Force the control to repaint when the enabled state changes. */
        InvalidateRect(lphc->self, NULL, TRUE);
        return  TRUE;
    case WM_SETREDRAW:                //重画消息
        if( wParam )
            lphc->wState &= ~CBF_NOREDRAW;
        else
            lphc->wState |= CBF_NOREDRAW;

        if( lphc->wState & CBF_EDIT )
            SendMessageW(lphc->hWndEdit, message, wParam, lParam);
        SendMessageW(lphc->hWndLBox, message, wParam, lParam);
        return  0;
    case WM_SYSKEYDOWN:                        //系统按键
        if( KEYDATA_ALT & HIWORD(lParam) )
            if( wParam == VK_UP || wParam == VK_DOWN )
            COMBO_FlipListbox( lphc, FALSE, FALSE );
                return  0;

    case WM_CHAR:
    case WM_IME_CHAR:
    case WM_KEYDOWN:                
    {
        HWND hwndTarget;

        if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
             (lphc->wState & CBF_DROPPED))
        {
           CBRollUp( lphc, wParam == VK_RETURN, FALSE );
           return TRUE;
        }
               else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
               {
                  COMBO_FlipListbox( lphc, FALSE, FALSE );
                  return TRUE;
               }

        if( lphc->wState & CBF_EDIT )
            hwndTarget = lphc->hWndEdit;
        else
            hwndTarget = lphc->hWndLBox;

        return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
                 SendMessageA(hwndTarget, message, wParam, lParam);
    }
    case WM_LBUTTONDOWN:
        if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
        if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
        return  TRUE;
    case WM_LBUTTONUP:
        COMBO_LButtonUp( lphc );
        return  TRUE;
    case WM_MOUSEMOVE:
        if( lphc->wState & CBF_CAPTURE )
            COMBO_MouseMove( lphc, wParam, lParam );
        return  TRUE;

        case WM_MOUSEWHEEL:
                if (wParam & (MK_SHIFT | MK_CONTROL))
                    return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
                     DefWindowProcA(hwnd, message, wParam, lParam);

                if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
                if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
                return TRUE;

    /* Combo messages */自己单独的消息
    case CB_ADDSTRING:             //添加字符串
        if( unicode )
                {
                    if( lphc->dwStyle & CBS_LOWERCASE )
                        CharLowerW((LPWSTR)lParam);
                    else if( lphc->dwStyle & CBS_UPPERCASE )
                        CharUpperW((LPWSTR)lParam);
                    return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
                }
                else /* unlike the unicode version, the ansi version does not overwrite
                        the string if converting case */
                {
                    char *string = NULL;
                    LRESULT ret;
                    if( lphc->dwStyle & CBS_LOWERCASE )
                    {
                        string = strdupA((LPSTR)lParam);
                        CharLowerA(string);
                    }

                    else if( lphc->dwStyle & CBS_UPPERCASE )
                    {
                        string = strdupA((LPSTR)lParam);
                        CharUpperA(string);
                    }

                    ret = SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, string ? (LPARAM)string : lParam);
                    HeapFree(GetProcessHeap(), 0, string);
                    return ret;
                }

    case CB_INSERTSTRING:       //插入字符串
        if( unicode )
                {
                    if( lphc->dwStyle & CBS_LOWERCASE )
                        CharLowerW((LPWSTR)lParam);
                    else if( lphc->dwStyle & CBS_UPPERCASE )
                        CharUpperW((LPWSTR)lParam);
                    return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
                }
                else
                {
                    if( lphc->dwStyle & CBS_LOWERCASE )
                        CharLowerA((LPSTR)lParam);
                    else if( lphc->dwStyle & CBS_UPPERCASE )
                        CharUpperA((LPSTR)lParam);

                    return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
                }

    case CB_DELETESTRING:           //删除字符串
        return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
                 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);

    case CB_SELECTSTRING:            //选中字符串
        return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);

    case CB_FINDSTRING:             //查找字符串
        return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
                 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);

    case CB_FINDSTRINGEXACT:        //扩展查找字符串
        return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
                 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);

    case CB_SETITEMHEIGHT:            //设置项高
        return  COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);

    case CB_GETITEMHEIGHT:             //取项高
        if( (INT)wParam >= 0 )    /* listbox item */
                    return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
                return  CBGetTextAreaHeight(hwnd, lphc);

    case CB_RESETCONTENT:             //重置内容
        SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
                if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
        {
            static const WCHAR empty_stringW[] = { 0 };
                    SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
        }
                else
                    InvalidateRect(lphc->self, NULL, TRUE);
        return  TRUE;
    case CB_INITSTORAGE:                    //初始化存储
        return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
    case CB_GETHORIZONTALEXTENT:
        return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
    case CB_SETHORIZONTALEXTENT:
        return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
    case CB_GETTOPINDEX:           //当前页首行
        return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
    case CB_GETLOCALE:
        return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
    case CB_SETLOCALE:
        return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
    case CB_GETDROPPEDWIDTH:
        if( lphc->droppedWidth )
                    return  lphc->droppedWidth;
        return  lphc->droppedRect.right - lphc->droppedRect.left;
    case CB_SETDROPPEDWIDTH:         
        if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
            (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
        return  CB_ERR;

    case CB_GETDROPPEDCONTROLRECT:
        if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
        return  CB_OKAY;

    case CB_GETDROPPEDSTATE:
        return  (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;

    case CB_DIR:
        return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
                 SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);

    case CB_SHOWDROPDOWN:       //显示下拉窗口
        if( CB_GETTYPE(lphc) != CBS_SIMPLE )
        {
            if( wParam )
            {
            if( !(lphc->wState & CBF_DROPPED) )
                CBDropDown( lphc );
            }
            else
            if( lphc->wState & CBF_DROPPED )
                    CBRollUp( lphc, FALSE, TRUE );
        }
        return  TRUE;

    case CB_GETCOUNT:     
        return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);

    case CB_GETCURSEL:
        return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);

    case CB_SETCURSEL:
        lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
            if( lParam >= 0 )
                SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);

        /* no LBN_SELCHANGE in this case, update manually */
        if( lphc->wState & CBF_EDIT )
            CBUpdateEdit( lphc, (INT)wParam );
        else
            InvalidateRect(lphc->self, &lphc->textRect, TRUE);
        lphc->wState &= ~CBF_SELCHANGE;
            return  lParam;

    case CB_GETLBTEXT:               
        return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
                 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);

    case CB_GETLBTEXTLEN:
                return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
                                 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);

    case CB_GETITEMDATA:
        return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);

    case CB_SETITEMDATA:
        return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);

    case CB_GETEDITSEL:
        /* Edit checks passed parameters itself */
        if( lphc->wState & CBF_EDIT )
            return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
        return  CB_ERR;

    case CB_SETEDITSEL:
        if( lphc->wState & CBF_EDIT )
                    return SendMessageW(lphc->hWndEdit, EM_SETSEL,
              (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
        return  CB_ERR;

    case CB_SETEXTENDEDUI:
                if( CB_GETTYPE(lphc) == CBS_SIMPLE )
                    return  CB_ERR;
        if( wParam )
            lphc->wState |= CBF_EUI;
        else lphc->wState &= ~CBF_EUI;
        return  CB_OKAY;

    case CB_GETEXTENDEDUI:
        return  (lphc->wState & CBF_EUI) ? TRUE : FALSE;
    case CB_GETCOMBOBOXINFO:
        return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
    case CB_LIMITTEXT:
        if( lphc->wState & CBF_EDIT )
            return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);

    case WM_UPDATEUISTATE:              //更新UI
        if (unicode)
            DefWindowProcW(lphc->self, message, wParam, lParam);
        else
            DefWindowProcA(lphc->self, message, wParam, lParam);

        if (COMBO_update_uistate(lphc))
        {
           /* redraw text */
           if( !(lphc->wState & CBF_EDIT) )
                NtUserInvalidateRect(lphc->self, &lphc->textRect, TRUE);
        }
        break;

    default:
        if (message >= WM_USER)
            WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n",
            message - WM_USER, wParam, lParam );
        break;
      }
      return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
                       DefWindowProcA(hwnd, message, wParam, lParam);
}

COMBO_Paint函数主要画EDIT内容的,如果是OWNER_DRAW则发送DRAWITEM消息,否则调用EXTTEXTOUT输出字符.并且画按钮调用:
/***********************************************************************
 *           CBPaintButton
 *
 * Paint combo button (normal, pressed, and disabled states).
 */
static void CBPaintButton( LPHEADCOMBO lphc, HDC hdc, RECT rectButton)
{
    UINT buttonState = DFCS_SCROLLCOMBOBOX;

    if( lphc->wState & CBF_NOREDRAW )
      return;


    if (lphc->wState & CBF_BUTTONDOWN)
    buttonState |= DFCS_PUSHED;

    if (CB_DISABLED(lphc))
    buttonState |= DFCS_INACTIVE;

    DrawFrameControl(hdc, &rectButton, DFC_SCROLL, buttonState);  //注意在WINODWS内部经常调用此函数
}


BOOL DrawFrameControl(
  HDC hdc,     // handle to device context
  LPRECT lprc, // bounding rectangle
  UINT uType,  // frame-control type
  UINT uState  // frame-control state
)
这里WINDOWS提供画各种控件的通用函数.比如:
procedure TForm1.Button1Click(Sender: TObject);
var
  ARect: TRect;
begin
  ARect := Bounds(100,100,50,50);
  DrawFrameControl(Canvas.Handle,ARect,DFC_CAPTION,DFCS_HOT or DFCS_FLAT)
end;

COMBO_Command主是处理EDIT和COMBOLBOX的通知消息.

再看如何取得GETTEXT消息:
static LRESULT COMBO_GetTextW( LPHEADCOMBO lphc, INT count, LPWSTR buf )
{
    INT length;

    if( lphc->wState & CBF_EDIT )
        return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );  //直接取EDIT的TEXT

    /* get it from the listbox */

    if (!count || !buf) return 0;         //如果不EDIT风格,则从COMBOLBOX里取数据
    if( lphc->hWndLBox )
    {
        INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
        if (idx == LB_ERR) goto error;
        length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
        if (length == LB_ERR) goto error;

        /* 'length' is without the terminating character */
        if (length >= count)
        {
            LPWSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
            if (!lpBuffer) goto error;
            length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);

            /* truncate if buffer is too short */
            if (length != LB_ERR)
            {
                lstrcpynW( buf, lpBuffer, count );
                length = count;
            }
            HeapFree( GetProcessHeap(), 0, lpBuffer );
        }
        else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);

        if (length == LB_ERR) return 0;
        return length;
    }

 error:  /* error - truncate string, return zero */
    buf[0] = 0;
    return 0;
}


再看剪贴板消息,他直接把它传送给EDIT.
case WM_CUT:
case WM_PASTE:
case WM_COPY:
        if( lphc->wState & CBF_EDIT )
        {
            return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
                     SendMessageA(lphc->hWndEdit, message, wParam, lParam);
        }
        else return  CB_ERR;


再看WM_SYSKEYDOWN:
BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
{
   if( lphc->wState & CBF_DROPPED )   如果弹出下拉窗口时,则关闭
   {
       CBRollUp( lphc, ok, bRedrawButton );
       return FALSE;
   }

   CBDropDown( lphc );      //如果没有弹出,则弹出
   return TRUE;
}

弹出下拉窗口
static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
{
   HWND    hWnd = lphc->self;

   TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
     lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED));

   CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );      //通知选中某项

   if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )        //不是CBS_SIMPLE风格的话
   { 

       if( lphc->wState & CBF_DROPPED )
       {
       RECT    rect;

       lphc->wState &= ~CBF_DROPPED;
       ShowWindow( lphc->hWndLBox, SW_HIDE );      

           if(GetCapture() == lphc->hWndLBox)
           {
               ReleaseCapture();                //释放鼠标
           }

       if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
       {
           rect = lphc->buttonRect;
       }
       else
           {
           if( bButton )
           {
         UnionRect( &rect,           //计算下拉窗口RECT
                &lphc->buttonRect,
                &lphc->textRect);
           }
           else
         rect = lphc->textRect;

           bButton = TRUE;
       }

       if( bButton && !(lphc->wState & CBF_NOREDRAW) )
           RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |        //画下拉窗口
                   RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
       CB_NOTIFY( lphc, CBN_CLOSEUP );      //通知COMBOBOX已经下拉
       }
   }
}

static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
{
   HWND hWnd = lphc->self;
   UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID );

   TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );

   switch( msg )
   {
   case WM_DELETEITEM:          //删除某项
       {
           DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
           lpIS->CtlType  = ODT_COMBOBOX;
           lpIS->CtlID    = id;
           lpIS->hwndItem = hWnd;
           break;
       }
   case WM_DRAWITEM:          //画某项
       {
           DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
           lpIS->CtlType  = ODT_COMBOBOX;
           lpIS->CtlID    = id;
           lpIS->hwndItem = hWnd;
           break;
       }
   case WM_COMPAREITEM:       //比如某项数据
       {
           COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
           lpIS->CtlType  = ODT_COMBOBOX;
           lpIS->CtlID    = id;
           lpIS->hwndItem = hWnd;
           break;
       }
   case WM_MEASUREITEM:       //画之前确定某项数据
       {
           MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
           lpIS->CtlType  = ODT_COMBOBOX;
           lpIS->CtlID    = id;
           break;
       }
   }
   return SendMessageW(lphc->owner, msg, id, lParam);//注意这里,直接上传给COMBOBOX
}

----------Henry 20100711

你可能感兴趣的:(组合框)