checkcombobox.cpp

来自「管理项目进度工具的原代码」· C++ 代码 · 共 557 行

CPP
557
字号
// checkcombobox.cpp : implementation file
//

#include "stdafx.h"
#include "checkcombobox.h"
#include "misc.h"
#include "dlgunits.h"
#include "themed.h"
#include "autoflag.h"
#include "misc.h"
#include "dialoghelper.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CCheckComboBox

CCheckComboBox::CCheckComboBox(BOOL bAllowDelete, BOOL bCaseSensitive)
	: CAutoComboBox(bAllowDelete, bCaseSensitive)
{
	m_bItemHeightSet = FALSE;
	m_bDrawing = TRUE;
}

CCheckComboBox::~CCheckComboBox()
{
}


BEGIN_MESSAGE_MAP(CCheckComboBox, CAutoComboBox)
	//{{AFX_MSG_MAP(CCheckComboBox)
	ON_WM_CTLCOLOR()
	ON_WM_CREATE()
	ON_WM_KEYDOWN()
	//}}AFX_MSG_MAP
	ON_CONTROL_REFLECT_EX(CBN_EDITCHANGE, OnEditchange)
	ON_CONTROL_REFLECT_EX(CBN_DROPDOWN, OnDropdown)
//	ON_CONTROL_REFLECT_EX(CBN_CLOSEUP, OnCloseUp)
	ON_CONTROL(LBN_SELCHANGE, 1000, OnLBSelChange)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCheckComboBox message handlers

void CCheckComboBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	HDC hdc = lpDrawItemStruct->hDC;

	CRect rcBitmap = lpDrawItemStruct->rcItem;
	CRect rcText   = lpDrawItemStruct->rcItem;
	int nItem = (int)lpDrawItemStruct->itemID;

	CString sText;

	// 0 - No check, 1 - Empty check, 2 - Checked
	int nCheck = 0;

	// Check if we are drawing the static portion of the combobox
	if (nItem < 0) 
   {
		// Get the text
		sText = m_sText;

		// Don't draw any boxes on this item
		nCheck = 0;
	}
	else // Otherwise it is one of the items
	{
		GetLBText(lpDrawItemStruct->itemID, sText);
		nCheck = 1 + (GetItemData(lpDrawItemStruct->itemID) != 0);

		TEXTMETRIC metrics;
		GetTextMetrics(hdc, &metrics);

		rcBitmap.left    = 0;
		rcBitmap.right   = rcBitmap.left + metrics.tmHeight + metrics.tmExternalLeading + 6;
		rcBitmap.top    += 1;
		rcBitmap.bottom -= 1;

		rcText.left = rcBitmap.right;
	}

	if (nCheck > 0) 
	{
		SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
		SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));

		UINT nState = DFCS_BUTTONCHECK;

		if (nCheck > 1)
			nState |= DFCS_CHECKED;

		// Draw the checkmark using DrawFrameControl
		CThemed::DrawFrameControl((CWnd*)this, CDC::FromHandle(hdc), rcBitmap, DFC_BUTTON, nState);
	}
	
	if (nCheck > 0 || IsType(CBS_DROPDOWNLIST))
		DrawItemText(hdc, nItem, rcText, sText, lpDrawItemStruct->itemState);
}

void CCheckComboBox::RefreshMaxDropWidth()
{
   int nMaxWidth = CDialogHelper::CalcMaxTextWidth(*this, 0, TRUE) + 18;
   SetDroppedWidth(nMaxWidth);
}

void CCheckComboBox::DrawItemText(HDC hdc, int nItem, CRect rText, const CString& sText, UINT nState)
{
	UNREFERENCED_PARAMETER(nItem);

	if (nState & ODS_SELECTED) 
	{
		SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
		SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
	}
	else 
	{
		SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
		SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
	}
	
	// Erase and draw
	ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rText, 0, 0, 0);
	
	rText.DeflateRect(2, 0);
	DrawText(hdc, sText, sText.GetLength(), &rText, DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS);
	rText.InflateRect(2, 0);
	
	if ((nState & (ODS_FOCUS|ODS_SELECTED)) == (ODS_FOCUS|ODS_SELECTED))
		DrawFocusRect(hdc, &rText);
}

void CCheckComboBox::CheckAll(BOOL bCheck)
{
	int nCount = GetCount();

	for (int i = 0; i < nCount; i++)
		SetCheck(i, bCheck);
}

void CCheckComboBox::RecalcText(BOOL bUpdate, BOOL bNotify)
{
	CStringArray aItems;
	GetChecked(aItems);
	
	CString sText = Misc::FormatArray(aItems);
	
	// update edit field if necessary
	if (bUpdate)
	{
		if (!IsType(CBS_DROPDOWNLIST))
		{
			GetDlgItem(1001)->SetWindowText(sText);
			
			// notify parent
			if (bNotify && m_sText != sText)
				NotifyParent(CBN_EDITCHANGE);
		}
		else
			SetWindowText(sText);
	}
	
	m_sText = sText;
}

int CCheckComboBox::SelectString(int nStartAfter, LPCTSTR lpszString)
{
   CStringArray aItems;

   if (Misc::ParseIntoArray(lpszString, aItems) > 1) // multiple items
   {
      SetChecked(aItems);
      return GetCurSel();
   }
   
   // else
   return CAutoComboBox::SelectString(nStartAfter, lpszString);
}

int CCheckComboBox::SetCheck(int nIndex, BOOL bFlag)
{
	int nResult = SetItemData(nIndex, bFlag);
	
	if (nResult < 0)
		return nResult;
	
	// update text
	RecalcText();
	
	// Redraw the window
	CComboBox::Invalidate(FALSE);
	
	return nResult;
}

BOOL CCheckComboBox::GetCheck(int nIndex) const
{
	return GetItemData(nIndex);
}

void CCheckComboBox::ParseText()
{
	if (IsType(CBS_DROPDOWNLIST))
		return;

	CString sEditText;
	GetDlgItem(1001)->GetWindowText(sEditText);
	
	// clear existing checks first but don't update window
	int nCount = GetCount();
	
	for (int i = 0; i < nCount; i++)
		SetItemData(i, 0);
	
	// now parse the text and set the check states
	CStringArray aText;

	if (Misc::ParseIntoArray(sEditText, aText))
	{
		int nText = aText.GetSize();

		while (nText--)
		{
			int nIndex = FindStringExact(-1, aText[nText]);
			
			if (nIndex != CB_ERR)
				SetItemData(nIndex, 1);
		}
	}

	if (GetDroppedState())
		ScGetCWnd()->Invalidate();
	
	// Redraw the window
	CComboBox::Invalidate(FALSE);
}

// this handles messages destined for the dropped listbox
LRESULT CCheckComboBox::ScWindowProc(HWND hRealWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch (msg) 
	{
	// Make the combobox always return -1 as the current selection for
	// combos without an edit potion when drawing. This
	// causes the lpDrawItemStruct->itemID in DrawItem() to be -1
	// when the always-visible-portion of the combo is drawn
	case LB_GETCURSEL: 
		if (IsType(CBS_DROPDOWNLIST) && m_bDrawing)
			return -1;
		break;
				
	case WM_CHAR: // sent by the edit control
		if (wp == VK_SPACE) 
		{
			ASSERT (GetDroppedState());

			// Get the current selection
			INT nIndex = GetCurSel();
			
			CRect rcItem;
			::SendMessage(hRealWnd, LB_GETITEMRECT, nIndex, (LONG)(VOID *)&rcItem);
			::InvalidateRect(hRealWnd, rcItem, FALSE);
			
			// Invert the check mark
			SetCheck(nIndex, !GetCheck(nIndex));
			
			// Notify that selection has changed
			if (IsType(CBS_DROPDOWNLIST))
				NotifyParent(CBN_SELCHANGE);

			return 0;
		}
		break;
	
	case WM_LBUTTONDOWN: 
		{
			CPoint pt(lp);
			int nItem = GetCount();

			while (nItem--)
			{
				CRect rItem;
				::SendMessage(hRealWnd, LB_GETITEMRECT, nItem, (LPARAM)(LPRECT)rItem);

				if (rItem.PtInRect(pt))
				{
					// toggle check state
					::InvalidateRect(hRealWnd, rItem, FALSE);
					SetCheck(nItem, !GetCheck(nItem));
					
					// Notify that selection has changed
					if (IsType(CBS_DROPDOWNLIST))
						NotifyParent(CBN_SELCHANGE);

					return 0;
				}
			}
		}
		// Do the default handling now (such as close the popup
		// window when clicked outside)
		break;
		
	case WM_LBUTTONUP: 
		{
			// Don't do anything here. This causes the combobox popup
			// windows to remain open after a selection has been made
			if (IsType(CBS_SIMPLE))
				return 0;
			else
			{
				LRESULT lr = CSubclasser::ScWindowProc(hRealWnd, msg, wp, lp);
				SetCheck(0, GetCheck(0));
				return lr;
			}
		}
		break;
	}

	return CSubclasser::ScWindowProc(hRealWnd, msg, wp, lp);
}

int CCheckComboBox::GetCurSel() const
{
	CAutoFlag af(m_bDrawing, FALSE);

	return CComboBox::GetCurSel();
}

BOOL CCheckComboBox::IsType(UINT nComboType)
{
	return ((CWnd::GetStyle() & 0xf) == nComboType);
}

int CCheckComboBox::AddUniqueItem(const CString& sItem)
{
	if (sItem.Find(Misc::GetListSeparator()) != -1)
	{
		CStringArray aText;
		
		Misc::ParseIntoArray(sItem, aText);
		return CAutoComboBox::AddUniqueItems(aText);
	}
	
	// else add single item and mark as selected
	int nIndex = CAutoComboBox::AddUniqueItem(sItem);

	if (nIndex != CB_ERR)
		SetItemData(nIndex, 1);

	return nIndex;
}

HBRUSH CCheckComboBox::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CAutoComboBox::OnCtlColor(pDC, pWnd, nCtlColor);
	
	// hook list box
	if (nCtlColor == CTLCOLOR_LISTBOX && !ScIsHooked())
		ScHookWindow(pWnd->GetSafeHwnd());
	
	return hbr;
}

void CCheckComboBox::PreSubclassWindow() 
{
	if (!m_bItemHeightSet) 
	{
		m_bItemHeightSet = TRUE;
		
		CDlgUnits dlu(CComboBox::GetParent());
		SetItemHeight(-1, dlu.ToPixelsY(9)); 
	}
	
	CAutoComboBox::PreSubclassWindow();
}

int CCheckComboBox::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CAutoComboBox::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	if (!m_bItemHeightSet) 
	{
		m_bItemHeightSet = TRUE;
		
		CDlgUnits dlu(CComboBox::GetParent());
		SetItemHeight(-1, dlu.ToPixelsY(9)); 
	}
	
	return 0;
}

BOOL CCheckComboBox::OnEditchange() 
{
	// update check states
	if (GetDroppedState())
		ParseText();
	
	return FALSE; // pass to parent
}

BOOL CCheckComboBox::OnDropdown() 
{
	ParseText();
	RecalcText(FALSE); // updates m_sText
	
	return FALSE; // pass to parent
}

/*
BOOL CCheckComboBox::OnCloseUp()
{
	// notify parent of (possible) selection change
	NotifyParent(CBN_SELCHANGE);
	
	return FALSE; // pass to parent
}
*/

// this handles messages destined for the embedded edit field
LRESULT CCheckComboBox::WindowProc(HWND hRealWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch (msg)
	{
	case WM_KEYDOWN:
		if (wp == VK_DOWN && !GetDroppedState())
		{
			ShowDropDown();
			return 0L; // eat
		}
 		else if (wp == VK_RETURN)
 		{
 			HandleReturnKey();
			return 0L; // eat
 		}
//		else if (wp == VK_ESCAPE)
//			ShowDropDown(FALSE);
		break;

	case WM_CHAR: // if CTRL+Space then forward to listbox
		if (wp == VK_SPACE && (GetKeyState(VK_CONTROL) & 0x8000))
		{
			if ((GetDroppedState() && IsType(CBS_DROPDOWN)) || IsType(CBS_SIMPLE))
			{
				ScGetCWnd()->SendMessage(msg, wp, lp);
				return 0;
			}
		}
		break;
	}
	
	return CAutoComboBox::WindowProc(hRealWnd, msg, wp, lp);
}

void CCheckComboBox::HandleReturnKey()
{
	if (!GetDroppedState())
	{
		ParseText();
		CAutoComboBox::HandleReturnKey();
	}
	else
	{
		ShowDropDown(FALSE);

		// notify parent of (possible) selection change
		NotifyParent(CBN_SELCHANGE);
	}
}

CString CCheckComboBox::GetSelectedItem() const
{
	return m_sText;
}

void CCheckComboBox::OnLBSelChange()
{
	// do nothing
}

BOOL CCheckComboBox::DeleteSelectedLBItem()
{
	if (CAutoComboBox::DeleteSelectedLBItem())
	{
		RecalcText();
		return TRUE;
	}

	// else 
	return FALSE;
}

int CCheckComboBox::GetChecked(CStringArray& aItems) const
{
	aItems.RemoveAll();

	int nCount = GetCount();
	
	for (int i = 0; i < nCount; i++)
	{
		if (GetItemData(i))
		{
			CString sItem;
			GetLBText(i, sItem);

			aItems.Add(sItem);
		}
	}	

	return aItems.GetSize();
}

void CCheckComboBox::SetChecked(const CStringArray& aItems)
{
	// clear existing checks first but don't update window
	int nCount = GetCount();
	
	for (int i = 0; i < nCount; i++)
		SetItemData(i, 0);
	
	// now set the check states
	int nItem = aItems.GetSize(), nChecked = 0;
	
	while (nItem--)
	{
		int nIndex = FindStringExact(-1, aItems[nItem]);
		
		if (nIndex != CB_ERR)
		{
			SetItemData(nIndex, 1);
			nChecked++;
		}
	}
	RecalcText(TRUE, FALSE);

	if (GetDroppedState())
		ScGetCWnd()->Invalidate();

	// Redraw the window
	CComboBox::Invalidate(FALSE);
}

void CCheckComboBox::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if (nChar == VK_DOWN && !GetDroppedState())
	{
		ShowDropDown();
		return;
	}
		
	CAutoComboBox::OnKeyDown(nChar, nRepCnt, nFlags);
}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?