#pragma once #include "ListTypes.h" #include class CListCombo : public CWindowImpl { public: CListCombo() : m_wndEditCtrl(this, 1) { m_nItem = NULL_ITEM; m_nSubItem = NULL_SUBITEM; m_nFlags = ITEM_FLAGS_NONE; m_nExitChar = 0; m_bMouseOver = FALSE; m_bActivate = FALSE; } ~CListCombo() { } protected: int m_nItem; int m_nSubItem; UINT m_nFlags; TCHAR m_nExitChar; BOOL m_bMouseOver; BOOL m_bActivate; BOOL m_bSwappedButtons; CRect m_rcStatic; CRect m_rcButton; COLORREF m_rgbStaticBackground; COLORREF m_rgbStaticText; CFont m_fntComboFont; CContainedWindowT m_wndEditCtrl; public: BOOL Create(HWND hWndParent, int nItem, int nSubItem, CRect & rcRect, UINT nFlags, LPCTSTR lpszItemText, CListArray & aComboList) { m_nItem = nItem; m_nSubItem = nSubItem; m_nFlags = nFlags; m_nExitChar = 0; m_bActivate = FALSE; m_bSwappedButtons = GetSystemMetrics(SM_SWAPBUTTON); m_rgbStaticBackground = GetSysColor(COLOR_HIGHLIGHT); m_rgbStaticText = GetSysColor(COLOR_HIGHLIGHTTEXT); // Destroy old combo control if (IsWindow()) DestroyWindow(); DWORD dwStyle = WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | CBS_AUTOHSCROLL | CBS_SORT; if (nFlags & ITEM_FLAGS_COMBO_EDIT) dwStyle |= CBS_DROPDOWN; else dwStyle |= CBS_DROPDOWNLIST; if (nFlags & ITEM_FLAGS_EDIT_UPPER) dwStyle |= CBS_UPPERCASE; // Create combo control CRect Area(((dwStyle & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST) ? rcRect.left + 3 : rcRect.left + 1, rcRect.top, rcRect.right, rcRect.bottom + (6 * rcRect.Height())); if (CWindowImpl::Create(hWndParent, Area, nullptr, dwStyle) == nullptr) return FALSE; // Get system message font CLogFont logFont; logFont.SetMessageBoxFont(); if (!m_fntComboFont.IsNull()) m_fntComboFont.DeleteObject(); if (m_fntComboFont.CreateFontIndirect(&logFont) == nullptr) return FALSE; SetFont(m_fntComboFont, FALSE); // Subclass edit control to capture keyboard input HWND hEditControl = GetWindow(GW_CHILD); if (hEditControl != nullptr) m_wndEditCtrl.SubclassWindow(hEditControl); for (int nComboItem = 0; nComboItem < aComboList.GetSize(); nComboItem++) AddString(aComboList[nComboItem].c_str()); if ((dwStyle & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST) { int nIndex = FindStringExact(-1, lpszItemText); if (nIndex != CB_ERR) SetCurSel(nIndex); } else { SetWindowText(lpszItemText); SetEditSel(0, -1); } // Set static edit height SetItemHeight(-1, rcRect.Height() - 6); COMBOBOXINFO infoComboBox = {sizeof(COMBOBOXINFO)}; if (!::GetComboBoxInfo(m_hWnd, &infoComboBox)) return FALSE; // Store combo box details for painting m_rcStatic = infoComboBox.rcItem; m_rcButton = infoComboBox.rcButton; m_rcButton.DeflateRect(0, 1); m_rcButton.OffsetRect(-2, 0); // Show combo control ShowWindow(SW_SHOW); SetFocus(); // Force redraw now RedrawWindow(); return TRUE; } BOOL IsValid(TCHAR nChar) { // Validate number and float input if (!(m_nFlags & (ITEM_FLAGS_EDIT_NUMBER | ITEM_FLAGS_EDIT_FLOAT)) || nChar == VK_BACK) return TRUE; std::wstring strValue; int nValueLength = GetWindowTextLength() + 1; strValue.reserve(nValueLength); GetWindowText((wchar_t *)strValue.c_str(), nValueLength); // Get selected positions DWORD dwSelection = GetEditSel(); int nStartChar = LOWORD(dwSelection); int nEndChar = HIWORD(dwSelection); // Are we changing the sign? if ((m_nFlags & ITEM_FLAGS_EDIT_NEGATIVE) && nChar == _T('-')) { BOOL bNegative = FALSE; if (m_nFlags & ITEM_FLAGS_EDIT_FLOAT) { double dblValue = _wtof(strValue.c_str()); bNegative = (dblValue < 0); strValue = stdstr_f("%lf", -dblValue).ToUTF16(); } else { long lValue = _wtol(strValue.c_str()); bNegative = (lValue < 0); strValue = stdstr_f("%ld", -lValue).ToUTF16(); } SetWindowText(strValue.c_str()); // Restore select position SetEditSel(bNegative ? nStartChar - 1 : nStartChar + 1, bNegative ? nEndChar - 1 : nEndChar + 1); return FALSE; } // Construct new value string using entered character std::wstring strNewValue = strValue.substr(0, nStartChar) + nChar + strValue.substr(nEndChar, strValue.length() - nEndChar); int nGreaterThan = 0; int nLessThan = 0; int nEquals = 0; int nDecimalPoint = 0; int nNegativeIndex = -1; int nGreaterIndex = -1; int nLessIndex = -1; int nEqualIndex = -1; int nDecimalIndex = -1; int nDigitIndex = -1; for (int nCharIndex = 0; nCharIndex < (int)strNewValue.length(); nCharIndex++) { TCHAR nCharValue = (strNewValue.c_str())[nCharIndex]; switch (nCharValue) { case _T('-'): nNegativeIndex = nCharIndex; break; case _T('>'): if (!(m_nFlags & ITEM_FLAGS_EDIT_OPERATOR)) return FALSE; nGreaterIndex = nCharIndex; nGreaterThan++; break; case _T('<'): if (!(m_nFlags & ITEM_FLAGS_EDIT_OPERATOR)) return FALSE; nLessIndex = nCharIndex; nLessThan++; break; case _T('='): if (!(m_nFlags & ITEM_FLAGS_EDIT_OPERATOR)) return FALSE; nEqualIndex = nCharIndex; nEquals++; break; case _T('.'): if (!(m_nFlags & ITEM_FLAGS_EDIT_FLOAT)) return FALSE; nDecimalIndex = nCharIndex; nDecimalPoint++; break; default: if (!_istdigit(nCharValue)) return FALSE; if (nDigitIndex < 0) nDigitIndex = nCharIndex; break; } // Invalid if text contains more than one '>', '<', '=' or '.' if (nGreaterThan > 1 || nLessThan > 1 || nEquals > 1 || nDecimalPoint > 1) return FALSE; } // Invalid if text contains '=>' or '=<' if (nGreaterIndex != -1 && nEqualIndex != -1 && nGreaterIndex > nEqualIndex) return FALSE; if (nLessIndex != -1 && nEqualIndex != -1 && nLessIndex > nEqualIndex) return FALSE; // Invalid if digits exist before operator if (nDigitIndex != -1 && nGreaterIndex != -1 && nGreaterIndex > nDigitIndex) return FALSE; if (nDigitIndex != -1 && nLessIndex != -1 && nLessIndex > nDigitIndex) return FALSE; if (nDigitIndex != -1 && nEqualIndex != -1 && nEqualIndex > nDigitIndex) return FALSE; if (nDigitIndex != -1 && nNegativeIndex != -1 && nNegativeIndex > nDigitIndex) return FALSE; return TRUE; } BEGIN_MSG_MAP_EX(CListCombo) MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) #if (_WIN32_WINNT >= 0x0400) MSG_WM_MOUSELEAVE(OnMouseLeave) #endif MSG_WM_ERASEBKGND(OnEraseBkgnd) MSG_WM_PAINT(OnPaint) REFLECTED_COMMAND_CODE_HANDLER_EX(CBN_KILLFOCUS, OnKillFocus) MSG_WM_GETDLGCODE(OnGetDlgCode) MSG_WM_CHAR(OnChar) DEFAULT_REFLECTION_HANDLER() ALT_MSG_MAP(1) MSG_WM_GETDLGCODE(OnGetDlgCode) MSG_WM_CHAR(OnChar) END_MSG_MAP() LRESULT OnMouseRange(UINT nMessage, WPARAM /*wParam*/, LPARAM /*lParam*/) { SetMsgHandled(FALSE); if (nMessage == WM_MOUSEMOVE && !m_bMouseOver) { m_bMouseOver = TRUE; TRACKMOUSEEVENT trkMouse; trkMouse.cbSize = sizeof(TRACKMOUSEEVENT); trkMouse.dwFlags = TME_LEAVE; trkMouse.hwndTrack = m_hWnd; // Notify when the mouse leaves button _TrackMouseEvent(&trkMouse); } // Do not show button as pressed when first created m_bActivate = TRUE; InvalidateRect(m_rcButton); return 0; } void OnMouseLeave() { m_bMouseOver = FALSE; InvalidateRect(m_rcButton); } BOOL OnEraseBkgnd(CDCHandle dcErase) { return TRUE; } void OnPaint(HDC) { CPaintDC dcPaint(m_hWnd); CRect rcClient; GetClientRect(rcClient); CMemoryDC dcMemory(dcPaint.m_hDC, rcClient); CRect rcClip; if (dcPaint.GetClipBox(rcClip) == ERROR) return; int nContextState = dcMemory.SaveDC(); // Do not repaint background if drawing button only if (!rcClip.EqualRect(m_rcButton)) { CWindow wndParent(GetParent()); if (wndParent.IsWindow()) { // Draw background from parent CPoint ptOrigin(0); MapWindowPoints(wndParent, &ptOrigin, 1); dcMemory.OffsetWindowOrg(ptOrigin.x, ptOrigin.y, &ptOrigin); wndParent.SendMessage(WM_PAINT, (WPARAM)dcMemory.m_hDC); dcMemory.SetWindowOrg(ptOrigin); } } DWORD dwPoint = GetMessagePos(); CPoint ptMouse(GET_X_LPARAM(dwPoint), GET_Y_LPARAM(dwPoint)); ScreenToClient(&ptMouse); DWORD dwStyle = GetStyle(); BOOL bHotButton = m_bActivate && (((dwStyle & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST) ? rcClient.PtInRect(ptMouse) : m_rcButton.PtInRect(ptMouse)); BOOL bPressed = bHotButton && (GetAsyncKeyState(m_bSwappedButtons ? VK_RBUTTON : VK_LBUTTON) < 0); if ((dwStyle & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST && !rcClip.EqualRect(m_rcButton)) { dcMemory.SetBkColor(m_rgbStaticBackground); dcMemory.ExtTextOut(m_rcStatic.left, m_rcStatic.top, ETO_OPAQUE, m_rcStatic, _T( "" ), 0, nullptr); // Draw static text int nIndex = GetCurSel(); if (nIndex != CB_ERR) { std::wstring strText; int cchLen = GetLBTextLen(nIndex); if (cchLen != CB_ERR) { strText.reserve(cchLen + 1); GetLBText(nIndex, (LPTSTR)strText.c_str()); } if (!strText.empty()) { CRect rcText(m_rcStatic); rcText.OffsetRect(1, 1); dcMemory.SelectFont(m_fntComboFont); dcMemory.SetTextColor(m_rgbStaticText); dcMemory.SetBkMode(TRANSPARENT); dcMemory.DrawText(strText.c_str(), (int)strText.length(), rcText, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER); } } } // Draw drop down button dcMemory.DrawFrameControl(m_rcButton, DFC_SCROLL, DFCS_SCROLLDOWN | (bPressed ? DFCS_FLAT | DFCS_PUSHED : 0)); dcMemory.RestoreDC(nContextState); } void OnKillFocus(UINT /*uCode*/, int /*nCtrlID*/, HWND /*hwndCtrl*/) { SetMsgHandled(FALSE); CWindow wndParent(GetParent()); if (wndParent.IsWindow()) { std::wstring strValue; if ((GetStyle() & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST) { int nIndex = GetCurSel(); if (nIndex != CB_ERR) { int cchLen = GetLBTextLen(nIndex); if (cchLen != CB_ERR) { strValue.reserve(cchLen); GetLBText(nIndex, (LPTSTR)strValue.c_str()); } else { strValue = L""; } } } else { int nValueLength = GetWindowTextLength() + 1; strValue.reserve(nValueLength); GetWindowText((LPTSTR)strValue.c_str(), nValueLength); } CListNotify listNotify; listNotify.m_hdrNotify.hwndFrom = m_hWnd; listNotify.m_hdrNotify.idFrom = GetDlgCtrlID(); listNotify.m_hdrNotify.code = LCN_ENDEDIT; listNotify.m_nItem = m_nItem; listNotify.m_nSubItem = m_nSubItem; listNotify.m_nExitChar = m_nExitChar; listNotify.m_lpszItemText = strValue.c_str(); listNotify.m_lpItemDate = nullptr; // Forward notification to parent FORWARD_WM_NOTIFY(wndParent, listNotify.m_hdrNotify.idFrom, &listNotify.m_hdrNotify, ::SendMessage); } ShowWindow(SW_HIDE); } UINT OnGetDlgCode(LPMSG /*lpMessage*/) { return DLGC_WANTALLKEYS; } void OnChar(TCHAR nChar, UINT /*nRepCnt*/, UINT /*nFlags*/) { switch (nChar) { case VK_TAB: case VK_RETURN: case VK_ESCAPE: { m_nExitChar = nChar; CWindow wndParent(GetParent()); if (wndParent.IsWindow()) wndParent.SetFocus(); } break; default: SetMsgHandled(!IsValid(nChar)); break; } } };