// amsEdit.cpp : implementation file for CEdit-derived classes
// Created by: Alvaro Mendez - 07/17/2000
//
#include "stdafx.h"
#include "amsEdit.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#pragma warning (disable:4355) // disables: 'this': used in base member initializer list
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit
// Constructs the object with the default attributes
CAMSEdit::CAMSEdit() :
m_rgbText(0),
m_uInternalFlags(None)
{
}
// Destroys the object (virtual).
CAMSEdit::~CAMSEdit()
{
}
BEGIN_MESSAGE_MAP(CAMSEdit, CEdit)
//{{AFX_MSG_MAP(CAMSEdit)
ON_WM_KEYDOWN()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
// Returns the control's text.
CString CAMSEdit::GetText() const
{
CString strText;
GetWindowText(strText);
return strText;
}
// Returns the control's text without leading or trailing blanks.
CString CAMSEdit::GetTrimmedText() const
{
CString strText = GetText();
strText.TrimLeft();
strText.TrimRight();
return strText;
}
// Sets the control's text to the given string value.
void CAMSEdit::SetText(const CString& strText)
{
SetWindowText(strText);
}
// Sets the background color to the given rgb.
void CAMSEdit::SetBackgroundColor(COLORREF rgb)
{
m_brushBackground.DeleteObject();
m_brushBackground.CreateSolidBrush(rgb);
Invalidate();
}
// Returns the RGB for the background color.
COLORREF CAMSEdit::GetBackgroundColor() const
{
CAMSEdit* pThis = const_cast<CAMSEdit*>(this);
if (!m_brushBackground.GetSafeHandle())
{
COLORREF rgb = pThis->GetDC()->GetBkColor();
pThis->m_brushBackground.CreateSolidBrush(rgb);
return rgb;
}
LOGBRUSH lb;
pThis->m_brushBackground.GetLogBrush(&lb);
return lb.lbColor;
}
// Sets the text color to the given rgb.
void CAMSEdit::SetTextColor(COLORREF rgb)
{
m_rgbText = rgb;
m_uInternalFlags |= TextColorHasBeenSet;
Invalidate();
}
// Returns the RGB for the text color.
COLORREF CAMSEdit::GetTextColor() const
{
if (!(m_uInternalFlags & TextColorHasBeenSet))
{
CAMSEdit* pThis = const_cast<CAMSEdit*>(this);
pThis->m_rgbText = pThis->GetDC()->GetTextColor();
pThis->m_uInternalFlags |= TextColorHasBeenSet;
}
return m_rgbText;
}
// Returns true if the control is read only
bool CAMSEdit::IsReadOnly() const
{
return !!(GetStyle() & ES_READONLY);
}
// Returns the control's value in a valid format.
CString CAMSEdit::GetValidText() const
{
return GetText();
}
// Redraws the window's text.
void CAMSEdit::Redraw()
{
if (!::IsWindow(m_hWnd))
return;
CString strText = GetValidText();
if (strText != GetText())
SetWindowText(strText);
}
// Returns true if the given character should be entered into the control.
bool CAMSEdit::ShouldEnter(TCHAR c) const
{
return true;
}
// Cuts the current selection into the clipboard.
LONG CAMSEdit::OnCut(UINT, LONG)
{
int nStart, nEnd;
GetSel(nStart, nEnd);
if (nStart < nEnd)
{
SendMessage(WM_COPY); // copy the selection and...
SendMessage(WM_KEYDOWN, VK_DELETE); // delete it
}
return 0;
}
// Clears the current selection.
LONG CAMSEdit::OnClear(UINT wParam, LONG lParam)
{
int nStart, nEnd;
GetSel(nStart, nEnd);
if (nStart < nEnd)
SendMessage(WM_KEYDOWN, VK_DELETE); // delete the selection
return 0;
}
// Pastes the text from the clipboard onto the current selection.
LONG CAMSEdit::OnPaste(UINT, LONG)
{
int nStart, nEnd;
GetSel(nStart, nEnd);
CEdit::Default();
CString strText = GetValidText();
if (strText != GetText())
{
SetWindowText(strText);
SetSel(nStart, nEnd);
}
return 0;
}
// Handles drawing the text and background using the designated colors
BOOL CAMSEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
{
if ((message == WM_CTLCOLOREDIT || message == WM_CTLCOLORSTATIC) && (m_brushBackground.GetSafeHandle() || m_uInternalFlags & TextColorHasBeenSet))
{
CDC* pDC = CDC::FromHandle((HDC)wParam);
if (m_rgbText)
pDC->SetTextColor(m_rgbText);
// Set the text background to the requested background color
pDC->SetBkColor(GetBackgroundColor());
*pLResult = (LRESULT)m_brushBackground.GetSafeHandle();
return TRUE;
}
return CEdit::OnChildNotify(message, wParam, lParam, pLResult);
}
// Handles the WM_SETTEXT message to ensure that text (set via SetWindowText) is valid.
LONG CAMSEdit::OnSetText(UINT wParam, LONG lParam)
{
LONG nResult = CEdit::Default();
CString strText = GetValidText();
if (strText != (LPCTSTR)lParam)
SetWindowText(strText);
return nResult;
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::SelectionSaver
// Constructs the selection saver object for the given edit control.
// It then saves the edit control's current selection.
CAMSEdit::SelectionSaver::SelectionSaver(CEdit* pEdit) :
m_pEdit(pEdit)
{
ASSERT(pEdit);
pEdit->GetSel(m_nStart, m_nEnd);
}
// Constructs the selection saver object for the given edit control.
// It then saves the given nStart and nEnd values.
CAMSEdit::SelectionSaver::SelectionSaver(CEdit* pEdit, int nStart, int nEnd) :
m_pEdit(pEdit),
m_nStart(nStart),
m_nEnd(nEnd)
{
ASSERT(pEdit);
ASSERT(nStart <= nEnd);
}
// Destroys the object and restores the selection to the saved start and end values.
CAMSEdit::SelectionSaver::~SelectionSaver()
{
if (m_pEdit)
m_pEdit->SetSel(m_nStart, m_nEnd, TRUE);
}
// Changes the start and end values to nStart and nEnd respectively.
void CAMSEdit::SelectionSaver::MoveTo(int nStart, int nEnd)
{
ASSERT(nStart <= nEnd);
m_nStart = nStart;
m_nEnd = nEnd;
}
// Changes the start and end values by nStart and nEnd respectively.
void CAMSEdit::SelectionSaver::MoveBy(int nStart, int nEnd)
{
m_nStart += nStart;
m_nEnd += nEnd;
ASSERT(m_nStart <= m_nEnd);
}
// Changes both the start and end values by nPos.
void CAMSEdit::SelectionSaver::MoveBy(int nPos)
{
m_nStart += nPos;
m_nEnd += nPos;
}
// Changes both the start and end values by nPos.
void CAMSEdit::SelectionSaver::operator+=(int nPos)
{
MoveBy(nPos);
}
// Returns the value for the selection's start.
int CAMSEdit::SelectionSaver::GetStart() const
{
return m_nStart;
}
// Returns the value for the selection's end.
int CAMSEdit::SelectionSaver::GetEnd() const
{
return m_nEnd;
}
// Updates the selection's start and end with the current selection.
void CAMSEdit::SelectionSaver::Update()
{
if (m_pEdit)
m_pEdit->GetSel(m_nStart, m_nEnd);
}
// Disables resetting the selection in the destructor
void CAMSEdit::SelectionSaver::Disable()
{
m_pEdit = NULL;
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::Behavior
// Constructs the object from the given control.
CAMSEdit::Behavior::Behavior(CAMSEdit* pEdit) :
m_pEdit(pEdit),
m_uFlags(0)
{
ASSERT(m_pEdit);
}
// Destroys the object (virtual).
CAMSEdit::Behavior::~Behavior()
{
}
// Adds and removes flags from the behavior and then redraws the control
void CAMSEdit::Behavior::ModifyFlags(UINT uAdd, UINT uRemove)
{
UINT uFlags = (m_uFlags & ~uRemove) | uAdd;
if (m_uFlags != uFlags)
{
m_uFlags = uFlags;
_Redraw();
}
}
// Returns the flags
UINT CAMSEdit::Behavior::GetFlags() const
{
return m_uFlags;
}
// Handles the WM_CHAR message by passing it to the control.
void CAMSEdit::Behavior::_OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
m_pEdit->OnChar(uChar, nRepCnt, nFlags);
}
// Handles the WM_KEYDOWN message by passing it to the control.
void CAMSEdit::Behavior::_OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
m_pEdit->OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message by passing it to the control.
void CAMSEdit::Behavior::_OnKillFocus(CWnd* pNewWnd)
{
m_pEdit->OnKillFocus(pNewWnd);
}
// Handles the WM_PASTE message by passing it to the control.
LONG CAMSEdit::Behavior::_OnPaste(UINT wParam, LONG lParam)
{
return m_pEdit->OnPaste(wParam, lParam);
}
// Calls the default handler for the current message
LRESULT CAMSEdit::Behavior::_Default()
{
return m_pEdit->Default();
}
// Redraws the control so that its value is valid
void CAMSEdit::Behavior::_Redraw()
{
m_pEdit->Redraw();
}
// Returns true if the given character should be entered into the control.
bool CAMSEdit::Behavior::_ShouldEnter(TCHAR c) const
{
return m_pEdit->ShouldEnter(c);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::AlphanumericBehavior
// Constructs the object using the given set of strInvalidChars
CAMSEdit::AlphanumericBehavior::AlphanumericBehavior(CAMSEdit* pEdit, int nMaxChars /*= 0*/, const CString& strInvalidChars /*= _T("%'*\"+?><:\\"")*/) :
Behavior(pEdit),
m_nMaxChars(nMaxChars),
m_strInvalidChars(strInvalidChars)
{
ASSERT(m_nMaxChars >= 0);
}
// Sets the characters to be considered invalid for text input.
void CAMSEdit::AlphanumericBehavior::SetInvalidCharacters(const CString& strInvalidChars)
{
if (m_strInvalidChars == strInvalidChars)
return;
m_strInvalidChars = strInvalidChars;
_Redraw();
}
// Returns the characters considered invalid for text input.
const CString& CAMSEdit::AlphanumericBehavior::GetInvalidCharacters() const
{
return m_strInvalidChars;
}
// Sets the maximum number of characters to allow for input.
void CAMSEdit::AlphanumericBehavior::SetMaxCharacters(int nMaxChars)
{
if (m_nMaxChars == nMaxChars)
return;
m_nMaxChars = nMaxChars;
_Redraw();
}
// Returns the characters considered invalid for input.
int CAMSEdit::AlphanumericBehavior::GetMaxCharacters() const
{
return m_nMaxChars;
}
// Returns the control's value in a valid format.
CString CAMSEdit::AlphanumericBehavior::_GetValidText() const
{
CString strText = m_pEdit->GetText();
CString strNewText = strText.Left(m_nMaxChars ? m_nMaxChars : strText.GetLength());
// Remove any invalid characters from the control's text
for (int iPos = strNewText.GetLength() - 1; iPos >= 0; iPos--)
{
if (m_strInvalidChars.Find(strNewText[iPos]) >= 0)
strNewText = strNewText.Left(iPos) + strNewText.Mid(iPos + 1);
}
return strNewText;
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSEdit::AlphanumericBehavior::_OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
// Check to see if it's read only
if (m_pEdit->IsReadOnly())
return;
if (!m_strInvalidChars.IsEmpty())
{
// Check if the character is invalid
if (m_strInvalidChars.Find((TCHAR)uChar) >= 0)
{
MessageBeep(MB_ICONEXCLAMATION);
return;
}
}
TCHAR c = static_cast<TCHAR>(uChar);
// If the number of characters is already at Max, overwrite
CString strText = m_pEdit->GetText();
if (strText.GetLength() == m_nMaxChars && m_nMaxChars && _istprint(c))
{
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
if (nStart < m_nMaxChars && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
return;
}
if (_ShouldEnter(c))
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::MaskedBehavior
// Constructs the object using the given mask.
CAMSEdit::MaskedBehavior::MaskedBehavior(CAMSEdit* pEdit, const CString& strMask /*= _T("")*/) :
Behavior(pEdit),
m_strMask(strMask)
{
m_arraySymbols.Add(Symbol('#', _istdigit)); // default mask symbol
}
// Returns the mask
const CString& CAMSEdit::MaskedBehavior::GetMask() const
{
return m_strMask;
}
// Sets the mask and redraws the control to accomodate it
void CAMSEdit::MaskedBehavior::SetMask(const CString& strMask)
{
if (m_strMask == strMask)
return;
m_strMask = strMask;
_Redraw();
}
// Returns the numeric portion of the control's value as a string
CString CAMSEdit::MaskedBehavior::GetNumericText() const
{
CString strText = m_pEdit->GetText();
CString strResult;
for (int iPos = 0, nLen = strText.GetLength(); iPos < nLen; iPos++)
{
TCHAR c = strText[iPos];
if (_istdigit(c))
strResult += c;
}
return strResult;
}
// Returns the control's value in a valid format.
CString CAMSEdit::MaskedBehavior::_GetValidText() const
{
CString strText = m_pEdit->GetText();
int nMaskLen = m_strMask.GetLength();
// If the mask is empty, allow anything
if (!nMaskLen)
return strText;
CString strNewText;
// Accomodate the text to the mask as much as possible
for (int iPos = 0, iMaskPos = 0, nLen = strText.GetLength(); iPos < nLen; iPos++, iMaskPos++)
{
TCHAR c = strText[iPos];
TCHAR cMask = (iMaskPos < nMaskLen ? m_strMask[iMaskPos] : 0);
// If we've reached the end of the mask, break
if (!cMask)
break;
// Match the character to any of the symbols
for (int iMaskChar = 0, nMaskCharCount = m_arraySymbols.GetSize(); iMaskChar < nMaskCharCount; iMaskChar++)
{
const Symbol& mc = m_arraySymbols[iMaskChar];
// Find the symbol that applies for the given character
if (!mc.Validate(c))
continue;
// Try to add matching characters in the mask until a different symbol is reached
for (; iMaskPos < nMaskLen; iMaskPos++)
{
cMask = m_strMask[iMaskPos];
if (cMask == mc)
{
strNewText += mc.Convert(c);
break;
}
else
{
for (int iMaskChar2 = 0; iMaskChar2 < nMaskCharCount; iMaskChar2++)
{
if (cMask == m_arraySymbols[iMaskChar2])
break;
}
if (iMaskChar2 < nMaskCharCount)
break;
strNewText += cMask;
}
}
break;
}
// If the character was not matched to a symbol, stop
if (iMaskChar == nMaskCharCount)
{
if (c == cMask)
{
// Match the character to any of the symbols
for (iMaskChar = 0, nMaskCharCount = m_arraySymbols.GetSize(); iMaskChar < nMaskCharCount; iMaskChar++)
{
if (cMask == m_arraySymbols[iMaskChar])
break;
}
if (iMaskChar == nMaskCharCount)
{
strNewText += c;
continue;
}
}
break;
}
}
return strNewText;
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSEdit::MaskedBehavior::_OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
// Check to see if it's read only
if (m_pEdit->IsReadOnly())
return;
TCHAR c = static_cast<TCHAR>(uChar);
// If the mask is empty, allow anything
int nMaskLen = m_strMask.GetLength();
if (!nMaskLen)
{
if (_ShouldEnter(c))
Behavior::_OnChar(uChar, nRepCnt, nFlags);
return;
}
// Check that we haven't gone past the mask's length
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
if (nStart >= nMaskLen && c != VK_BACK)
return;
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
// Check for a non-printable character (such as Ctrl+C)
if (!_istprint(c))
{
if (c == VK_BACK && nStart != nLen)
{
m_pEdit->SendMessage(WM_KEYDOWN, VK_LEFT); // move the cursor left
return;
}
// Allow backspace only if the cursor is all the way to the right
if (_ShouldEnter(c))
Behavior::_OnChar(uChar, nRepCnt, nFlags);
return;
}
TCHAR cMask = m_strMask[nStart];
// Check if the mask's character matches with any of the symbols in the array.
for (int iMaskChar = 0, nMaskCharCount = m_arraySymbols.GetSize(); iMaskChar < nMaskCharCount; iMaskChar++)
{
const Symbol& mc = m_arraySymbols[iMaskChar];
if (cMask == mc)
{
if (mc.Validate(c) && _ShouldEnter(c))
{
nEnd = (nEnd == nLen ? nEnd : (nStart + 1));
m_pEdit->SetSel(nStart, nEnd);
m_pEdit->ReplaceSel(CString(mc.Convert(c)), TRUE);
}
return;
}
}
// Check if it's the same character as the mask.
if (cMask == c && _ShouldEnter(c))
{
nEnd = (nEnd == nLen ? nEnd : (nStart + 1));
m_pEdit->SetSel(nStart, nEnd);
m_pEdit->ReplaceSel(CString(c), TRUE);
return;
}
// If it's a valid character, find the next symbol on the mask and add any non-mask characters in between.
for (iMaskChar = 0, nMaskCharCount = m_arraySymbols.GetSize(); iMaskChar < nMaskCharCount; iMaskChar++)
{
const Symbol& mc = m_arraySymbols[iMaskChar];
// See if the character is valid for any other symbols
if (!mc.Validate(c))
continue;
// Find the position of the next symbol
CString strMasks;
for (int iMaskChar2 = 0; iMaskChar2 < nMaskCharCount; iMaskChar2++)
strMasks += m_arraySymbols[iMaskChar2];
CString strMaskPortion = m_strMask.Mid(nStart);
int nMaskPos = strMaskPortion.FindOneOf(strMasks);
// Enter the character if there isn't another symbol before it
if (nMaskPos >= 0 && strMaskPortion[nMaskPos] == mc && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + nMaskPos);
m_pEdit->ReplaceSel(strMaskPortion.Left(nMaskPos), TRUE);
_OnChar(uChar, nRepCnt, nFlags);
}
return;
}
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSEdit::MaskedBehavior::_OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
switch (uChar)
{
case VK_DELETE:
{
// If deleting make sure it's the last character or that
// the selection goes all the way to the end of the text
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
if (nEnd != nLen)
{
if (!(nEnd == nStart && nEnd == nLen - 1))
return;
}
break;
}
}
Behavior::_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Returns a reference to the array of symbols that may be found on the mask.
// This allows adding, editing, or deleting symbols for the mask.
CAMSEdit::MaskedBehavior::SymbolArray& CAMSEdit::MaskedBehavior::GetSymbolArray()
{
return m_arraySymbols;
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::MaskedBehavior::Symbol
// Constructs the object -- needed for CArray
CAMSEdit::MaskedBehavior::Symbol::Symbol() :
m_cSymbol(0),
m_fnValidation(NULL),
m_fnConversion(NULL)
{
}
// Constructs the object with the given character and set of functions
CAMSEdit::MaskedBehavior::Symbol::Symbol(TCHAR cSymbol, ValidationFunction fnValidation, ConversionFunction fnConversion /*= NULL*/) :
m_cSymbol(cSymbol),
m_fnValidation(fnValidation),
m_fnConversion(fnConversion)
{
}
// Destroys the object (virtual).
CAMSEdit::MaskedBehavior::Symbol::~Symbol()
{
}
// Returns true if the given character (usually just entered by the user) is a match for self's symbol.
// This is tested by passing it to the validation function passed in the constructor (if valid).
bool CAMSEdit::MaskedBehavior::Symbol::Validate(TCHAR c) const
{
if (m_fnValidation)
return (m_fnValidation(c) != 0);
return true;
}
// Returns the given character converted as a result of calling the conversion function was passed in the constructor.
// If no conversion function was passed, the character is returned intact.
TCHAR CAMSEdit::MaskedBehavior::Symbol::Convert(TCHAR c) const
{
if (m_fnConversion)
return (TCHAR)m_fnConversion(c);
return c;
}
// Sets the character for the symbol to be used in the mask.
void CAMSEdit::MaskedBehavior::Symbol::Set(TCHAR cSymbol)
{
m_cSymbol = cSymbol;
}
// Returns the character for the symbol to be used in the mask.
TCHAR CAMSEdit::MaskedBehavior::Symbol::Get() const
{
return m_cSymbol;
}
// Returns the character for the symbol to be used in the mask.
CAMSEdit::MaskedBehavior::Symbol::operator TCHAR() const
{
return m_cSymbol;
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::NumericBehavior
// Constructs the object using the given nMaxWholeDigits and nMaxDecimalPlaces.
CAMSEdit::NumericBehavior::NumericBehavior(CAMSEdit* pEdit, int nMaxWholeDigits /*= 9*/, int nMaxDecimalPlaces /*= 4*/) :
Behavior(pEdit),
m_nMaxWholeDigits(nMaxWholeDigits >= 0 ? nMaxWholeDigits : -nMaxWholeDigits),
m_nMaxDecimalPlaces(nMaxDecimalPlaces),
m_bAllowNegative(nMaxWholeDigits >= 0),
m_cNegativeSign('-'),
m_cDecimalPoint('.'),
m_cGroupSeparator(','),
m_nDigitsInGroup(0),
m_dMin(AMS_MIN_NUMBER),
m_dMax(AMS_MAX_NUMBER)
{
ASSERT(m_nMaxWholeDigits > 0); // must have at least 1 digit to the left of the decimal
ASSERT(m_nMaxDecimalPlaces >= 0); // decimal places must be positive
// Get the system's negative sign
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNEGATIVESIGN, NULL, 0))
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNEGATIVESIGN, &m_cNegativeSign, sizeof(m_cNegativeSign));
// Get the system's decimal point
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, NULL, 0))
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, &m_cDecimalPoint, sizeof(m_cDecimalPoint));
// Get the system's group separator
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, NULL, 0))
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, &m_cGroupSeparator, sizeof(m_cGroupSeparator));
// Ensure the separators are not the same
if (m_cDecimalPoint == m_cGroupSeparator)
m_cGroupSeparator = (m_cDecimalPoint == ',' ? '.' : ',');
}
// Sets the maximum number of digits before the decimal point.
// If nMaxWholeDigits is negative, then negative numbers will NOT be allowed.
void CAMSEdit::NumericBehavior::SetMaxWholeDigits(int nMaxWholeDigits)
{
ASSERT(nMaxWholeDigits);
// If nMaxWholeDigits is negative, don't allow negatives
bool bAllowNegative = (nMaxWholeDigits >= 0);
if (nMaxWholeDigits < 0)
nMaxWholeDigits = -nMaxWholeDigits;
if (m_nMaxWholeDigits == nMaxWholeDigits && m_bAllowNegative == bAllowNegative)
return;
m_nMaxWholeDigits = nMaxWholeDigits;
m_bAllowNegative = bAllowNegative;
_Redraw();
}
// Returns the maximum number of digits before the decimal point.
int CAMSEdit::NumericBehavior::GetMaxWholeDigits() const
{
return m_nMaxWholeDigits;
}
// Sets the maximum number of digits after the decimal point.
void CAMSEdit::NumericBehavior::SetMaxDecimalPlaces(int nMaxDecimalPlaces)
{
ASSERT(nMaxDecimalPlaces >= 0);
if (m_nMaxDecimalPlaces == nMaxDecimalPlaces)
return;
m_nMaxDecimalPlaces = nMaxDecimalPlaces;
_Redraw();
}
// Returns the maximum number of digits after the decimal point.
int CAMSEdit::NumericBehavior::GetMaxDecimalPlaces() const
{
return m_nMaxDecimalPlaces;
}
// Sets whether the negative sign is allowed in the number or not.
void CAMSEdit::NumericBehavior::AllowNegative(bool bAllowNegative /*= true*/)
{
if (m_bAllowNegative == bAllowNegative)
return;
m_bAllowNegative = bAllowNegative;
_Redraw();
}
// Returns true if the negative sign is allowed in the number.
bool CAMSEdit::NumericBehavior::IsNegativeAllowed() const
{
return m_bAllowNegative;
}
// Sets the number of digits to be grouped together (if any).
void CAMSEdit::NumericBehavior::SetDigitsInGroup(int nDigitsInGroup)
{
ASSERT(nDigitsInGroup >= 0);
if (m_nDigitsInGroup == nDigitsInGroup)
return;
m_nDigitsInGroup = nDigitsInGroup;
_Redraw();
}
// Returns the number of digits to be grouped together (if any).
int CAMSEdit::NumericBehavior::GetDigitsInGroup() const
{
return m_nDigitsInGroup;
}
// Sets the character to use for the decimal point and the group separator (thousands)
void CAMSEdit::NumericBehavior::SetSeparators(TCHAR cDecimal, TCHAR cGroup)
{
ASSERT(cDecimal);
ASSERT(cGroup);
// If nothing's changing, leave
if (m_cDecimalPoint == cDecimal && m_cGroupSeparator == cGroup)
return;
// Set them
m_cDecimalPoint = cDecimal;
m_cGroupSeparator = cGroup;
// Ensure they're not the same
if (m_cDecimalPoint == m_cGroupSeparator)
m_cGroupSeparator = (m_cDecimalPoint == ',' ? '.' : ',');
_Redraw();
}
// Retrieves the character being used for the decimal point and group separator (thousands).
void CAMSEdit::NumericBehavior::GetSeparators(TCHAR* pcDecimal, TCHAR* pcGroup) const
{
if (pcDecimal)
*pcDecimal = m_cDecimalPoint;
if (pcGroup)
*pcGroup = m_cGroupSeparator;
}
// Sets the text to be automatically inserted in front of the number (such as a currency sign).
void CAMSEdit::NumericBehavior::SetPrefix(const CString& strPrefix)
{
if (m_strPrefix == strPrefix)
return;
m_strPrefix = strPrefix;
_Redraw();
}
// Returns the text to be automatically inserted in front of the number (such as a currency sign).
const CString& CAMSEdit::NumericBehavior::GetPrefix() const
{
return m_strPrefix;
}
// Parses the given strMask to set the control's configuration.
void CAMSEdit::NumericBehavior::SetMask(const CString& strMask)
{
int nDecimalPos = -1;
int nGroupingPos = -1;
int nLen = strMask.GetLength();
m_nMaxWholeDigits = 0;
m_nMaxDecimalPlaces = 0;
m_nDigitsInGroup = 0;
m_bAllowNegative = true;
m_strPrefix = _T("");
for (int iPos = nLen - 1; iPos >= 0; iPos--)
{
TCHAR c = strMask[iPos];
if (c == '#')
{
if (nDecimalPos >= 0)
m_nMaxWholeDigits++;
else
m_nMaxDecimalPlaces++;
}
else if ((c == '.' || c == m_cDecimalPoint) && nDecimalPos < 0)
{
nDecimalPos = iPos;
m_cDecimalPoint = c;
}
else if (c == ',' || c == m_cGroupSeparator)
{
if (!m_nDigitsInGroup)
{
m_nDigitsInGroup = (((nDecimalPos >= 0) ? nDecimalPos : nLen) - iPos) - 1;
m_cGroupSeparator = c;
}
}
else
{
m_strPrefix = strMask.Left(iPos + 1);
break;
}
}
if (nDecimalPos < 0)
{
m_nMaxWholeDigits = m_nMaxDecimalPlaces;
m_nMaxDecimalPlaces = 0;
}
ASSERT(m_nMaxWholeDigits > 0); // must have at least one digit on left side of decimal point
_Redraw();
}
// Gets the mask corresponding to the maximum number than can be entered into the control
CString CAMSEdit::NumericBehavior::GetMask() const
{
CString strMask;
for (int iDigit = 0; iDigit < m_nMaxWholeDigits; iDigit++)
strMask += '0';
if (m_nMaxDecimalPlaces)
strMask += m_cDecimalPoint;
for (iDigit = 0; iDigit < m_nMaxDecimalPlaces; iDigit++)
strMask += '0';
strMask = GetSeparatedText(strMask);
for (int iPos = 0, nLen = strMask.GetLength(); iPos < nLen; iPos++)
{
if (strMask[iPos] == '0')
strMask.SetAt(iPos, '#');
}
return strMask;
}
// Sets the range of allowed values to the given minimum and maximum double values.
void CAMSEdit::NumericBehavior::SetRange(double dMin, double dMax)
{
ASSERT(dMin <= dMax);
m_dMin = dMin;
m_dMax = dMax;
_Redraw();
}
// Retrieves the range of allowed values inside the given set of double pointers.
void CAMSEdit::NumericBehavior::GetRange(double* pdMin, double* pdMax) const
{
if (pdMin)
*pdMin = m_dMin;
if (pdMax)
*pdMax = m_dMax;
}
// Returns the number of group separator characters in the given strText.
int CAMSEdit::NumericBehavior::GetGroupSeparatorCount(const CString& strText) const
{
for (int iPos = 0, nSepCount = 0, nLen = strText.GetLength(); iPos < nLen; iPos++)
{
if (strText[iPos] == m_cGroupSeparator)
nSepCount++;
}
return nSepCount;
}
// Returns the given strText as a numeric string.
CString CAMSEdit::NumericBehavior::GetNumericText(const CString& strText, bool bConvertToNumericSigns /*= false*/) const
{
CString strNewText;
for (int iPos = 0, nLen = strText.GetLength(); iPos < nLen; iPos++)
{
TCHAR c = strText[iPos];
if (_istdigit(c))
strNewText += c;
else if (c == m_cNegativeSign)
strNewText += (bConvertToNumericSigns ? '-' : m_cNegativeSign);
else if (c == m_cDecimalPoint)
strNewText += (bConvertToNumericSigns ? '.' : m_cDecimalPoint);
}
return strNewText;
}
// Returns the current double as a text value.
// If bTrimTrailingZeros is true, any insignificant zeros after the decimal point are removed.
CString CAMSEdit::NumericBehavior::GetDoubleText(double dText, bool bTrimTrailingZeros /*= true*/) const
{
CString strText;
strText.Format(_T("%lf"), dText);
if (bTrimTrailingZeros)
{
strText.TrimRight('0');
strText.TrimRight('.');
}
return strText;
}
// Sets the control's text to the given double value.
// If bTrimTrailingZeros is true, any insignificant zeros after the decimal point are removed.
void CAMSEdit::NumericBehavior::SetDouble(double dText, bool bTrimTrailingZeros /*= true*/)
{
m_pEdit->SetWindowText(GetDoubleText(dText, bTrimTrailingZeros));
}
// Returns the current text as a double value.
double CAMSEdit::NumericBehavior::GetDouble() const
{
return _tcstod(GetNumericText(m_pEdit->GetText(), true), NULL);
}
// Sets the control's text to the given integer value.
void CAMSEdit::NumericBehavior::SetInt(int nText)
{
CString strText;
strText.Format(_T("%d"), nText);
m_pEdit->SetWindowText(strText);
}
// Returns the current text as an integer value.
int CAMSEdit::NumericBehavior::GetInt() const
{
return _ttoi(GetNumericText(m_pEdit->GetText(), true));
}
// Adjusts the location of separators based on the nCurrentSeparatorCount.
void CAMSEdit::NumericBehavior::AdjustSeparators(int nCurrentSeparatorCount)
{
SelectionSaver selection = m_pEdit;
CString strText = _GetValidText();
if (strText.IsEmpty() && selection.GetStart() <= m_strPrefix.GetLength())
m_pEdit->SetWindowText(m_strPrefix.Mid(0, selection.GetStart()));
else if (strText != m_pEdit->GetText())
m_pEdit->SetWindowText(strText);
// Adjust the current selection if separators were added/removed
int nNewSeparatorCount = GetGroupSeparatorCount(strText);
if (nCurrentSeparatorCount != nNewSeparatorCount && selection.GetStart() > m_strPrefix.GetLength())
selection += (nNewSeparatorCount - nCurrentSeparatorCount);
}
// Returns the given text with the group separator characters inserted in the proper places.
CString CAMSEdit::NumericBehavior::GetSeparatedText(const CString& strText) const
{
CString strNumericText = GetNumericText(strText);
CString strNewText = strNumericText;
// Retrieve the number without the decimal point
int nDecimalPos = strNumericText.Find(m_cDecimalPoint);
if (nDecimalPos >= 0)
strNewText = strNewText.Left(nDecimalPos);
if (m_nDigitsInGroup > 0)
{
int nLen = strNewText.GetLength();
BOOL bIsNegative = (!strNewText.IsEmpty() && strNewText[0] == m_cNegativeSign);
// Loop in reverse and stick the separator every m_nDigitsInGroup digits.
for (int iPos = nLen - (m_nDigitsInGroup + 1); iPos >= bIsNegative; iPos -= m_nDigitsInGroup)
strNewText = strNewText.Left(iPos + 1) + m_cGroupSeparator + strNewText.Mid(iPos + 1);
}
// Prepend the prefix, if the number is not empty.
if (!strNewText.IsEmpty() || nDecimalPos >= 0)
{
strNewText = m_strPrefix + strNewText;
if (nDecimalPos >= 0)
strNewText += strNumericText.Mid(nDecimalPos);
}
return strNewText;
}
// Inserts nCount zeros into the given string at the given position.
// If nPos is less than 0, the zeros are appended.
void CAMSEdit::NumericBehavior::InsertZeros(CString* pStrText, int nPos, int nCount)
{
ASSERT(pStrText);
if (nPos < 0 && nCount > 0)
nPos = pStrText->GetLength();
for (int iZero = 0; iZero < nCount; iZero++)
pStrText->Insert(nPos, '0');
}
// Returns the control's value in a valid format.
CString CAMSEdit::NumericBehavior::_GetValidText() const
{
CString strText = m_pEdit->GetText();
CString strNewText;
bool bIsNegative = false;
int nPrefixLen = m_strPrefix.GetLength();
// Remove any invalid characters from the number
for (int iPos = 0, nDecimalPos = -1, nNewLen = 0, nLen = strText.GetLength(); iPos < nLen; iPos++)
{
TCHAR c = strText[iPos];
// Check for a negative sign
if (iPos == nPrefixLen && c == m_cNegativeSign && m_bAllowNegative)
{
strNewText += c;
nNewLen++;
bIsNegative = true;
}
// Check for a digit
else if (_istdigit(c))
{
// Make sure it doesn't go beyond the limits
if (nDecimalPos < 0 && nNewLen == m_nMaxWholeDigits + bIsNegative)
continue;
if (nDecimalPos >= 0 && nNewLen > nDecimalPos + m_nMaxDecimalPlaces)
break;
strNewText += c;
nNewLen++;
}
// Check for a decimal point
else if (c == m_cDecimalPoint && nDecimalPos < 0)
{
if (m_nMaxDecimalPlaces == 0)
break;
strNewText += c;
nDecimalPos = nNewLen;
nNewLen++;
}
}
return GetSeparatedText(strNewText);
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSEdit::NumericBehavior::_OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
// Check to see if it's read only
if (m_pEdit->IsReadOnly())
return;
TCHAR c = static_cast<TCHAR>(uChar);
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
CString strText = m_pEdit->GetText();
CString strNumericText = GetNumericText(strText);
int nDecimalPos = strText.Find(m_cDecimalPoint);
int nNumericDecimalPos = strNumericText.Find(m_cDecimalPoint);
int nLen = strText.GetLength();
int nNumericLen = strNumericText.GetLength();
int nPrefixLen = m_strPrefix.GetLength();
int nSepCount = GetGroupSeparatorCount(strText);
bool bNeedAdjustment = false;
// Check if we're in the prefix's location
if (nStart < nPrefixLen && _istprint(c))
{
TCHAR cPrefix = m_strPrefix[nStart];
// Check if it's the same character as the prefix.
if (cPrefix == c && _ShouldEnter(c))
{
if (nLen > nStart)
{
nEnd = (nEnd == nLen ? nEnd : (nStart + 1));
m_pEdit->SetSel(nStart, nEnd);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
// If it's a part of the number, enter the prefix
else if ((_istdigit(c) || c == m_cNegativeSign || c == m_cDecimalPoint) && _ShouldEnter(c))
{
nEnd = (nEnd == nLen ? nEnd : (nPrefixLen));
m_pEdit->SetSel(nStart, nEnd);
m_pEdit->ReplaceSel(m_strPrefix.Mid(nStart), TRUE);
NumericBehavior::_OnChar(uChar, nRepCnt, nFlags);
}
return;
}
// Check if it's a negative sign
if (c == m_cNegativeSign && m_bAllowNegative)
{
// If it's at the beginning, determine if it should overwritten
if (nStart == nPrefixLen)
{
if (!strNumericText.IsEmpty() && strNumericText[0] == m_cNegativeSign && _ShouldEnter(c))
{
nEnd = (nEnd == nLen ? nEnd : (nStart + 1));
m_pEdit->SetSel(nStart, nEnd);
m_pEdit->ReplaceSel(CString(m_cNegativeSign), TRUE);
return;
}
}
// If we're not at the beginning, toggle the sign
else if (_ShouldEnter(c))
{
if (strNumericText[0] == m_cNegativeSign)
{
m_pEdit->SetSel(nPrefixLen, nPrefixLen + 1);
m_pEdit->ReplaceSel(_T(""), TRUE);
m_pEdit->SetSel(nStart - 1, nEnd - 1);
}
else
{
m_pEdit->SetSel(nPrefixLen, nPrefixLen);
m_pEdit->ReplaceSel(CString(m_cNegativeSign), TRUE);
m_pEdit->SetSel(nStart + 1, nEnd + 1);
}
return;
}
}
// Check if it's a decimal point (only one is allowed).
else if (c == m_cDecimalPoint && m_nMaxDecimalPlaces > 0)
{
if (nDecimalPos >= 0)
{
// Check if we're replacing the decimal point
if (nDecimalPos >= nStart && nDecimalPos < nEnd)
bNeedAdjustment = true;
else
{ // Otherwise, put the caret on it
if (_ShouldEnter(c))
m_pEdit->SetSel(nDecimalPos + 1, nDecimalPos + 1);
return;
}
}
}
// Check if it's a digit
else if (_istdigit(c))
{
// Check if we're on the right of the decimal point.
if (nDecimalPos >= 0 && nDecimalPos < nStart)
{
if (strNumericText.Mid(nNumericDecimalPos + 1).GetLength() == m_nMaxDecimalPlaces)
{
if (nStart <= nDecimalPos + m_nMaxDecimalPlaces && _ShouldEnter(c))
{
nEnd = (nEnd == nLen ? nEnd : (nStart + 1));
m_pEdit->SetSel(nStart, nEnd);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
return;
}
}
// We're on the left side of the decimal point
else
{
bool bIsNegative = (!strNumericText.IsEmpty() && strNumericText[0] == m_cNegativeSign);
// Make sure we can still enter digits.
if (nStart == m_nMaxWholeDigits + bIsNegative + nSepCount + nPrefixLen)
{
if (m_uFlags & AddDecimalAfterMaxWholeDigits && m_nMaxDecimalPlaces > 0)
{
nEnd = (nEnd == nLen ? nEnd : (nStart + 2));
m_pEdit->SetSel(nStart, nEnd);
m_pEdit->ReplaceSel(CString(m_cDecimalPoint) + c, TRUE);
}
return;
}
if (strNumericText.Mid(0, nNumericDecimalPos >= 0 ? nNumericDecimalPos : nNumericLen).GetLength() == m_nMaxWholeDigits + bIsNegative)
{
if (_ShouldEnter(c))
{
if (strText[nStart] == m_cGroupSeparator)
nStart++;
nEnd = (nEnd == nLen ? nEnd : (nStart + 1));
m_pEdit->SetSel(nStart, nEnd);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
return;
}
bNeedAdjustment = true;
}
}
// Check if it's a non-printable character, such as Backspace or Ctrl+C
else if (!_istprint(c))
bNeedAdjustment = true;
else
return;
// Check if the character should be entered
if (!_ShouldEnter(c))
return;
Behavior::_OnChar(uChar, nRepCnt, nFlags);
// If the decimal point was added/removed or a separator needs adding/removing, adjust the text
if (bNeedAdjustment || nNumericDecimalPos != GetNumericText(m_pEdit->GetText()).Find(m_cDecimalPoint))
AdjustSeparators(nSepCount);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSEdit::NumericBehavior::_OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
switch (uChar)
{
case VK_DELETE:
{
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
// If deleting the prefix, don't allow it if there's a number after it.
int nPrefixLen = m_strPrefix.GetLength();
if (nStart < nPrefixLen && nLen > nPrefixLen)
{
if (nEnd == nLen)
break;
return;
}
if (nStart < nLen && strText[nStart] == m_cGroupSeparator && nStart == nEnd)
Behavior::_OnKeyDown(VK_RIGHT, nRepCnt, nFlags);
// Allow the deletion and then adjust the value
int nSepCount = GetGroupSeparatorCount(strText);
Behavior::_OnKeyDown(uChar, nRepCnt, nFlags);
AdjustSeparators(nSepCount);
// If everything on the right was deleted, put the selection on the right
if (nEnd == nLen)
m_pEdit->SetSel(nStart, nStart);
return;
}
}
Behavior::_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
// It's used here to check if zeros need to be added to the value.
void CAMSEdit::NumericBehavior::_OnKillFocus(CWnd* pNewWnd)
{
Behavior::_OnKillFocus(pNewWnd);
// Check if the value is empty and we don't want to touch it
CString strOriginalText = GetNumericText(m_pEdit->GetText(), true);
CString strText = strOriginalText;
int nLen = strText.GetLength();
// Add a leading zero before the decimal point if necessary
if (nLen > 0 && strText[0] == '.')
strText.Insert(0, '0');
else if (!(m_uFlags & OnKillFocus_Max) || (nLen == 0 && m_uFlags & OnKillFocus_DontPadWithZerosIfEmpty))
return;
int nDecimalPos = strText.Find('.');
int nMaxDecimalPlaces = GetMaxDecimalPlaces();
int nMaxWholeDigits = GetMaxWholeDigits();
// Check if we need to pad the number with zeros after the decimal point
if (m_uFlags & OnKillFocus_PadWithZerosAfterDecimal && nMaxDecimalPlaces > 0)
{
if (nDecimalPos < 0)
{
if (nLen == 0 || strText == '-')
{
strText = '0';
nLen = 1;
}
strText += '.';
nDecimalPos = nLen++;
}
InsertZeros(&strText, -1, nMaxDecimalPlaces - (nLen - nDecimalPos - 1));
}
// Check if we need to pad the number with zeros before the decimal point
if (m_uFlags & OnKillFocus_PadWithZerosBeforeDecimal && nMaxWholeDigits > 0)
{
if (nDecimalPos < 0)
nDecimalPos = nLen;
if (nLen && strText[0] == '-')
nDecimalPos--;
InsertZeros(&strText, (nLen ? strText[0] == '-' : -1), nMaxWholeDigits - nDecimalPos);
}
if (strText != strOriginalText)
{
SelectionSaver selection = m_pEdit; // remember the current selection
m_pEdit->SetWindowText(strText);
}
// If it's empty, take action based on the flag
if (strText.IsEmpty())
{
if (m_uFlags & OnKillFocus_Beep_IfEmpty)
MessageBeep(MB_ICONEXCLAMATION);
if (m_uFlags & OnKillFocus_SetValid_IfEmpty)
AdjustWithinRange();
if ((m_uFlags & OnKillFocus_ShowMessage_IfEmpty) == OnKillFocus_ShowMessage_IfEmpty)
ShowErrorMessage();
if (m_uFlags & OnKillFocus_SetFocus_IfEmpty)
m_pEdit->SetFocus();
return;
}
if (!IsValid())
{
if (m_uFlags & OnKillFocus_Beep_IfInvalid)
MessageBeep(MB_ICONEXCLAMATION);
if (m_uFlags & OnKillFocus_SetValid_IfInvalid)
AdjustWithinRange();
if ((m_uFlags & OnKillFocus_ShowMessage_IfInvalid) == OnKillFocus_ShowMessage_IfInvalid)
ShowErrorMessage();
if (m_uFlags & OnKillFocus_SetFocus_IfInvalid)
m_pEdit->SetFocus();
}
}
// Returns true if the control's value is valid and falls within the allowed range.
bool CAMSEdit::NumericBehavior::IsValid() const
{
double dValue = GetDouble();
return (dValue >= m_dMin && dValue <= m_dMax);
}
// Returns true if the control's value is valid and falls within the allowed range.
// If bShowErrorIfNotValid is true, an error message box is shown and the control gets the focus.
bool CAMSEdit::NumericBehavior::CheckIfValid(bool bShowErrorIfNotValid /*= true*/)
{
if (!m_pEdit->IsWindowEnabled())
return true;
bool bValid = IsValid();
if (!bValid && bShowErrorIfNotValid)
{
ShowErrorMessage();
m_pEdit->SetFocus();
}
return bValid;
}
// Shows a message box informing the user to enter a valid value within the allowed range.
void CAMSEdit::NumericBehavior::ShowErrorMessage() const
{
CString strMessage = _T("Please specify a valid numeric value.");
if (m_dMin > AMS_MIN_NUMBER && m_dMax < AMS_MAX_NUMBER)
strMessage = _T("Please specify a numeric value between ") + GetDoubleText(m_dMin) + _T(" and ") + GetDoubleText(m_dMax) + _T(".");
else if (m_dMin > AMS_MIN_NUMBER)
strMessage = _T("Please specify a numeric value greater than or equal to ") + GetDoubleText(m_dMin) + _T(".");
else if (m_dMax < AMS_MAX_NUMBER)
strMessage = _T("Please specify a numeric value less than or equal to ") + GetDoubleText(m_dMax) + _T(".");
AfxMessageBox(strMessage, MB_ICONEXCLAMATION);
}
// Adjusts the control's value to be within the range of allowed numeric values.
void CAMSEdit::NumericBehavior::AdjustWithinRange()
{
// Check if it's already within the range
if (IsValid())
return;
// If it's empty, set it a valid number
if (m_pEdit->GetText().IsEmpty())
m_pEdit->SetWindowText(_T(" "));
else
_Redraw();
// Make it fall within the range
double dValue = GetDouble();
if (dValue < m_dMin)
SetDouble(m_dMin);
else if (dValue > m_dMax)
SetDouble(m_dMax);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::DateBehavior
// Constructs the object with the given control.
CAMSEdit::DateBehavior::DateBehavior(CAMSEdit* pEdit) :
Behavior(pEdit),
m_dateMin(AMS_MIN_OLEDATETIME),
m_dateMax(AMS_MAX_OLEDATETIME),
m_cSep('/')
{
// Get the system's date separator
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, NULL, 0))
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, &m_cSep, sizeof(m_cSep));
// Determine if the day should go before the month
TCHAR szShortDate[MAX_PATH];
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, NULL, 0))
{
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, szShortDate, sizeof(szShortDate));
for (int iPos = 0; szShortDate[iPos]; iPos++)
{
TCHAR c = _totupper(szShortDate[iPos]);
if (c == 'M') // see if the month is first
break;
if (c == 'D') // see if the day is first, and then set the flag
{
m_uFlags |= DayBeforeMonth;
break;
}
}
}
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSEdit::DateBehavior::_OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
// Check to see if it's read only
if (m_pEdit->IsReadOnly())
return;
TCHAR c = static_cast<TCHAR>(uChar);
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
// Check for a non-printable character (such as Ctrl+C)
if (!_istprint(c))
{
if (c == VK_BACK && nStart != nLen)
{
m_pEdit->SendMessage(WM_KEYDOWN, VK_LEFT); // move the cursor left
return;
}
// Allow backspace only if the cursor is all the way to the right
if (_ShouldEnter(c))
Behavior::_OnChar(uChar, nRepCnt, nFlags);
return;
}
// Add the digit depending on its location
switch (nStart)
{
case 0: // FIRST DIGIT
{
if (m_uFlags & DayBeforeMonth)
{
if (IsValidDayDigit(c, 0) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
if (nLen > nStart + 1)
{
if (!IsValidDay(GetDay()))
{
m_pEdit->SetSel(nStart + 1, nStart + 2);
m_pEdit->ReplaceSel(CString(GetMinDayDigit(1)), TRUE);
m_pEdit->SetSel(nStart + 1, nStart + 2);
}
}
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
// Check if we can insert the digit with a leading zero
else if (nLen == nStart && GetMinDayDigit(0) == '0' && IsValidDayDigit(c, 1) && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 2);
m_pEdit->ReplaceSel(CString('0') + c, TRUE);
}
}
else
{
if (IsValidMonthDigit(c, 0) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
if (nLen > nStart + 1)
{
if (!IsValidMonth(GetMonth()))
{
m_pEdit->SetSel(nStart + 1, nStart + 2);
m_pEdit->ReplaceSel(CString(GetMinMonthDigit(1)), TRUE);
m_pEdit->SetSel(nStart + 1, nStart + 2);
}
}
AdjustMaxDay();
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
// Check if we can insert the digit with a leading zero
else if (nLen == nStart && GetMinMonthDigit(0) == '0' && IsValidMonthDigit(c, 1) && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 2);
m_pEdit->ReplaceSel(CString('0') + c, TRUE);
}
}
break;
}
case 1: // SECOND DIGIT
{
if (m_uFlags & DayBeforeMonth)
{
if (IsValidDayDigit(c, 1) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
// Check if it's a slash and the first digit (preceded by a zero) is a valid month
else if (c == m_cSep && nLen == nStart && GetMinDayDigit(0) == '0' && IsValidDay(_ttoi(CString('0') + strText[0])) && _ShouldEnter(c))
{
m_pEdit->SetSel(0, nStart);
m_pEdit->ReplaceSel(CString('0') + strText[0] + c, TRUE);
}
}
else
{
if (IsValidMonthDigit(c, 1) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
if (GetDay() > 0 && AdjustMaxDay())
m_pEdit->SetSel(GetDayStartPosition(), GetDayStartPosition() + 2);
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
// Check if it's a slash and the first digit (preceded by a zero) is a valid month
else if (c == m_cSep && nLen == nStart && GetMinMonthDigit(0) == '0' && IsValidMonth(_ttoi(CString('0') + strText[0])) && _ShouldEnter(c))
{
m_pEdit->SetSel(0, nStart);
m_pEdit->ReplaceSel(CString('0') + strText[0] + c, TRUE);
}
}
break;
}
case 2: // FIRST SLASH
{
int nSlash = 0;
if (c == m_cSep)
nSlash = 1;
else
{
if (m_uFlags & DayBeforeMonth)
nSlash = (IsValidMonthDigit(c, 0) || (nLen == nStart && GetMinMonthDigit(0) == '0' && IsValidMonthDigit(c, 1))) ? 2 : 0;
else
nSlash = (IsValidDayDigit(c, 0) || (nLen == nStart && GetMinDayDigit(0) == '0' && IsValidDayDigit(c, 1))) ? 2 : 0;
}
// If we need the slash, enter it
if (nSlash && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 1, FALSE);
m_pEdit->ReplaceSel(CString(m_cSep), TRUE);
}
// If the slash is to be preceded by a valid digit, "type" it in.
if (nSlash == 2)
keybd_event((BYTE)c, 0, 0, 0);
break;
}
case 3: // THIRD DIGIT
{
if (m_uFlags & DayBeforeMonth)
{
if (IsValidMonthDigit(c, 0) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
if (nLen > nStart + 1)
{
if (!IsValidMonth(GetMonth()))
{
m_pEdit->SetSel(nStart + 1, nStart + 2);
m_pEdit->ReplaceSel(CString(GetMinMonthDigit(1)), TRUE);
m_pEdit->SetSel(nStart + 1, nStart + 2);
}
}
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
AdjustMaxDay();
}
// Check if we can insert the digit with a leading zero
else if (nLen == nStart && GetMinMonthDigit(0) == '0' && IsValidMonthDigit(c, 1) && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 2);
m_pEdit->ReplaceSel(CString('0') + c, TRUE);
AdjustMaxDay();
}
}
else
{
if (IsValidDayDigit(c, 0) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
if (nLen > nStart + 1)
{
if (!IsValidDay(GetDay()))
{
m_pEdit->SetSel(nStart + 1, nStart + 2);
m_pEdit->ReplaceSel(CString(GetMinDayDigit(1)), TRUE);
m_pEdit->SetSel(nStart + 1, nStart + 2);
}
}
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
// Check if we can insert the digit with a leading zero
else if (nLen == nStart && GetMinDayDigit(0) == '0' && IsValidDayDigit(c, 1) && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 2);
m_pEdit->ReplaceSel(CString('0') + c, TRUE);
}
}
break;
}
case 4: // FOURTH DIGIT
{
if (m_uFlags & DayBeforeMonth)
{
if (IsValidMonthDigit(c, 1) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
if (GetDay() > 0 && AdjustMaxDay())
m_pEdit->SetSel(GetDayStartPosition(), GetDayStartPosition() + 2);
}
else
{
Behavior::_OnChar(uChar, nRepCnt, nFlags);
AdjustMaxDay();
}
}
// Check if it's a slash and the first digit (preceded by a zero) is a valid month
else if (c == m_cSep && nLen == nStart && GetMinMonthDigit(0) == '0' && IsValidMonth(_ttoi(CString('0') + strText[3])) && _ShouldEnter(c))
{
m_pEdit->SetSel(3, nStart);
m_pEdit->ReplaceSel(CString('0') + strText[3] + c, TRUE);
}
}
else
{
if (IsValidDayDigit(c, 1) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
// Check if it's a slash and the first digit (preceded by a zero) is a valid month
else if (c == m_cSep && nLen == nStart && GetMinDayDigit(0) == '0' && IsValidDay(_ttoi(CString('0') + strText[3])) && _ShouldEnter(c))
{
m_pEdit->SetSel(3, nStart);
m_pEdit->ReplaceSel(CString('0') + strText[3] + c, TRUE);
}
}
break;
}
case 5: // SECOND SLASH (year's first digit)
{
int nSlash = 0;
if (c == m_cSep)
nSlash = 1;
else
nSlash = (IsValidYearDigit(c, 0) ? 2 : 0);
// If we need the slash, enter it
if (nSlash && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 1, FALSE);
m_pEdit->ReplaceSel(CString(m_cSep), TRUE);
}
// If the slash is to be preceded by a valid digit, "type" it in.
if (nSlash == 2)
keybd_event((BYTE)c, 0, 0, 0);
break;
}
case 6: // YEAR (all 4 digits)
case 7:
case 8:
case 9:
{
if (IsValidYearDigit(c, nStart - GetYearStartPosition()) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1, FALSE);
m_pEdit->ReplaceSel(CString(c), TRUE);
for (; nStart + 1 < nLen && nStart < 9; nStart++)
{
if (!IsValidYearDigit(strText[nStart + 1], nStart - (GetYearStartPosition() - 1)))
{
m_pEdit->SetSel(nStart + 1, 10, FALSE);
CString strPortion;
for (int iPos = nStart + 1; iPos < nLen && iPos < 10; iPos++)
strPortion += GetMinYearDigit(iPos - GetYearStartPosition(), false);
m_pEdit->ReplaceSel(strPortion, TRUE);
m_pEdit->SetSel(nStart + 1, 10, FALSE);
break;
}
}
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
if (IsValidYear(GetYear()))
{
AdjustMaxDay(); // adjust the day first
AdjustMaxMonthAndDay(); // then adjust the month and the day, if necessary
}
}
break;
}
}
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSEdit::DateBehavior::_OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
switch (uChar)
{
case VK_DELETE:
{
// If deleting make sure it's the last character or that
// the selection goes all the way to the end of the text
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
if (nEnd != nLen)
{
if (!(nEnd == nStart && nEnd == nLen - 1))
return;
}
break;
}
case VK_UP:
{
// If pressing the UP arrow, increment the corresponding value.
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
if (nStart >= GetYearStartPosition() && nStart <= GetYearStartPosition() + 4)
{
int nYear = GetYear();
if (nYear >= m_dateMin.GetYear() && nYear < m_dateMax.GetYear())
SetYear(++nYear);
}
else if (nStart >= GetMonthStartPosition() && nStart <= GetMonthStartPosition() + 2)
{
int nMonth = GetMonth();
if (nMonth >= GetMinMonth() && nMonth < GetMaxMonth())
SetMonth(++nMonth);
}
else if (nStart >= GetDayStartPosition() && nStart <= GetDayStartPosition() + 2)
{
int nDay = GetDay();
if (nDay >= GetMinDay() && nDay < GetMaxDay())
SetDay(++nDay);
}
return;
}
case VK_DOWN:
{
// If pressing the DOWN arrow, decrement the corresponding value.
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
if (nStart >= GetYearStartPosition() && nStart <= GetYearStartPosition() + 4)
{
int nYear = GetYear();
if (nYear > m_dateMin.GetYear())
SetYear(--nYear);
}
else if (nStart >= GetMonthStartPosition() && nStart <= GetMonthStartPosition() + 2)
{
int nMonth = GetMonth();
if (nMonth > GetMinMonth())
SetMonth(--nMonth);
}
else if (nStart >= GetDayStartPosition() && nStart <= GetDayStartPosition() + 2)
{
int nDay = GetDay();
if (nDay > GetMinDay())
SetDay(--nDay);
}
return;
}
}
Behavior::_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
// It's used here to check if any action needs to be taken based on the control's value.
void CAMSEdit::DateBehavior::_OnKillFocus(CWnd* pNewWnd)
{
Behavior::_OnKillFocus(pNewWnd);
// Check if any of the OnKillFocus flags is set
if (!(m_uFlags & OnKillFocus_Max))
return;
CString strText = m_pEdit->GetText();
// If it's empty, take action based on the flag
if (strText.IsEmpty())
{
if (m_uFlags & OnKillFocus_Beep_IfEmpty)
MessageBeep(MB_ICONEXCLAMATION);
if (m_uFlags & OnKillFocus_SetValid_IfEmpty)
m_pEdit->SetWindowText(_T(" "));
if ((m_uFlags & OnKillFocus_ShowMessage_IfEmpty) == OnKillFocus_ShowMessage_IfEmpty)
ShowErrorMessage();
if (m_uFlags & OnKillFocus_SetFocus_IfEmpty)
m_pEdit->SetFocus();
return;
}
if (!IsValid())
{
if (m_uFlags & OnKillFocus_Beep_IfInvalid)
MessageBeep(MB_ICONEXCLAMATION);
if (m_uFlags & OnKillFocus_SetValid_IfInvalid)
_Redraw();
if ((m_uFlags & OnKillFocus_ShowMessage_IfInvalid) == OnKillFocus_ShowMessage_IfInvalid)
ShowErrorMessage();
if (m_uFlags & OnKillFocus_SetFocus_IfInvalid)
m_pEdit->SetFocus();
}
}
// Returns the given value as a string with or without leading zeros.
CString CAMSEdit::DateBehavior::GetString(int nValue, bool bTwoDigitWithLeadingZero /*= true*/)
{
CString strValue;
if (bTwoDigitWithLeadingZero)
strValue.Format(_T("%02d"), nValue);
else
strValue.Format(_T("%d"), nValue);
return strValue;
}
// Returns the zero-based position of the month inside the control.
// This is based on whether the month is shown before or after the day.
int CAMSEdit::DateBehavior::GetMonthStartPosition() const
{
return ((m_uFlags & DayBeforeMonth) ? 3 : 0);
}
// Returns the zero-based position of the day inside the control.
// This is based on whether the day is shown before or after the month.
int CAMSEdit::DateBehavior::GetDayStartPosition() const
{
return ((m_uFlags & DayBeforeMonth) ? 0 : 3);
}
// Returns the zero-based position of the year inside the control.
int CAMSEdit::DateBehavior::GetYearStartPosition() const
{
return 6;
}
// Returns the maximum value for the month based on the allowed range.
int CAMSEdit::DateBehavior::GetMaxMonth() const
{
if (GetValidYear() == m_dateMax.GetYear())
return m_dateMax.GetMonth();
return 12;
}
// Returns the minimum value for the month based on the allowed range.
int CAMSEdit::DateBehavior::GetMinMonth() const
{
if (GetValidYear() == m_dateMin.GetYear())
return m_dateMin.GetMonth();
return 1;
}
// Returns the maximum value for the day based on the allowed range.
int CAMSEdit::DateBehavior::GetMaxDay() const
{
int nYear = GetValidYear();
int nMonth = GetValidMonth();
if (nYear == m_dateMax.GetYear() && nMonth == m_dateMax.GetMonth())
return m_dateMax.GetDay();
return GetMaxDayOfMonth(nMonth, nYear);
}
// Returns the minimum value for the day based on the allowed range.
int CAMSEdit::DateBehavior::GetMinDay() const
{
int nYear = GetValidYear();
int nMonth = GetValidMonth();
if (nYear == m_dateMin.GetYear() && nMonth == m_dateMin.GetMonth())
return m_dateMin.GetDay();
return 1;
}
// Returns the maximum value for the day based on the given month and year.
int CAMSEdit::DateBehavior::GetMaxDayOfMonth(int nMonth, int nYear)
{
ASSERT(nMonth >= 1 && nMonth <= 12);
switch (nMonth)
{
case 4:
case 6:
case 9:
case 11:
return 30;
case 2:
return IsLeapYear(nYear) ? 29 : 28;
}
return 31;
}
// Returns the digit at the given position (0 or 1) for the maximum value of the month allowed.
TCHAR CAMSEdit::DateBehavior::GetMaxMonthDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
int nYear = GetValidYear();
int nMaxMonth = m_dateMax.GetMonth();
int nMaxYear = m_dateMax.GetYear();
// First digit
if (nPos == 0)
{
// If the year is at the max, then use the first digit of the max month
if (nYear == nMaxYear)
return GetString(nMaxMonth)[0];
// Otherwise, it's always '1'
return '1';
}
// Second digit
CString strText = m_pEdit->GetText();
TCHAR cFirstDigit = (strText.GetLength() > GetMonthStartPosition()) ? strText[GetMonthStartPosition()] : '0';
ASSERT(cFirstDigit); // must have a valid first digit at this point
// If the year is at the max, then check if the first digits match
if (nYear == nMaxYear && (IsValidYear(GetYear()) || nMaxYear == m_dateMin.GetYear()))
{
// If the first digits match, then use the second digit of the max month
if (GetString(nMaxMonth)[0] == cFirstDigit)
return GetString(nMaxMonth)[1];
// Assuming the logic for the first digit is correct, then it must be '0'
ASSERT(cFirstDigit == '0');
return '9';
}
// Use the first digit to determine the second digit's max
return (cFirstDigit == '1' ? '2' : '9');
}
// Returns the digit at the given position (0 or 1) for the minimum value of the month allowed.
TCHAR CAMSEdit::DateBehavior::GetMinMonthDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
int nYear = GetValidYear();
int nMinMonth = m_dateMin.GetMonth();
int nMinYear = m_dateMin.GetYear();
// First digit
if (nPos == 0)
{
// If the year is at the min, then use the first digit of the min month
if (nYear == nMinYear)
return GetString(nMinMonth)[0];
// Otherwise, it's always '0'
return '0';
}
// Second digit
CString strText = m_pEdit->GetText();
TCHAR cFirstDigit = (strText.GetLength() > GetMonthStartPosition()) ? strText[GetMonthStartPosition()] : '0';
if (!cFirstDigit)
return '1';
// If the year is at the max, then check if the first digits match
if (nYear == nMinYear && (IsValidYear(GetYear()) || nMinYear == m_dateMax.GetYear()))
{
// If the first digits match, then use the second digit of the max month
if (GetString(nMinMonth)[0] == cFirstDigit)
return GetString(nMinMonth)[1];
return '0';
}
// Use the first digit to determine the second digit's min
return (cFirstDigit == '1' ? '0' : '1');
}
// Returns true if the digit at the given position (0 or 1) is within the allowed range for the month.
bool CAMSEdit::DateBehavior::IsValidMonthDigit(TCHAR c, int nPos) const
{
return (c >= GetMinMonthDigit(nPos) && c <= GetMaxMonthDigit(nPos));
}
// Returns true if the given month is valid and falls within the range.
bool CAMSEdit::DateBehavior::IsValidMonth(int nMonth) const
{
int nYear = GetValidYear();
int nDay = GetValidDay();
return IsValid(COleDateTime(nYear, nMonth, nDay, 0, 0, 0));
}
// Returns the digit at the given position (0 or 1) for the maximum value of the day allowed.
TCHAR CAMSEdit::DateBehavior::GetMaxDayDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
int nMonth = GetValidMonth();
int nYear = GetValidYear();
int nMaxDay = m_dateMax.GetDay();
int nMaxMonth = m_dateMax.GetMonth();
int nMaxYear = m_dateMax.GetYear();
// First digit
if (nPos == 0)
{
// If the year and month are at the max, then use the first digit of the max day
if (nYear == nMaxYear && nMonth == nMaxMonth)
return GetString(nMaxDay)[0];
return GetString(GetMaxDayOfMonth(nMonth, nYear))[0];
}
// Second digit
CString strText = m_pEdit->GetText();
TCHAR cFirstDigit = (strText.GetLength() > GetDayStartPosition()) ? strText[GetDayStartPosition()] : '0';
ASSERT(cFirstDigit); // must have a valid first digit at this point
// If the year and month are at the max, then use the second digit of the max day
if (nYear == nMaxYear && nMonth == nMaxMonth && GetString(nMaxDay)[0] == cFirstDigit)
return GetString(nMaxDay)[1];
if (cFirstDigit == '0' ||
cFirstDigit == '1' ||
(cFirstDigit == '2' && nMonth != 2) ||
(nMonth == 2 && !IsValidYear(GetYear())))
return '9';
return GetString(GetMaxDayOfMonth(nMonth, nYear))[1];
}
// Returns the digit at the given position (0 or 1) for the minimum value of the day allowed.
TCHAR CAMSEdit::DateBehavior::GetMinDayDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
int nMonth = GetValidMonth();
int nYear = GetValidYear();
int nMinDay = m_dateMin.GetDay();
int nMinMonth = m_dateMin.GetMonth();
int nMinYear = m_dateMin.GetYear();
// First digit
if (nPos == 0)
{
// If the year and month are at the min, then use the first digit of the min day
if (nYear == nMinYear && nMonth == nMinMonth)
return GetString(nMinDay)[0];
return '0';
}
// Second digit
CString strText = m_pEdit->GetText();
TCHAR cFirstDigit = (strText.GetLength() > GetDayStartPosition()) ? strText[GetDayStartPosition()] : '0';
if (!cFirstDigit) // must have a valid first digit at this point
return '1';
// If the year and month are at the max, then use the first second of the max day
if (nYear == nMinYear && nMonth == nMinMonth && GetString(nMinDay)[0] == cFirstDigit)
return GetString(nMinDay)[1];
// Use the first digit to determine the second digit's min
return (cFirstDigit == '0' ? '1' : '0');
}
// Returns true if the digit at the given position (0 or 1) is within the allowed range for the day.
bool CAMSEdit::DateBehavior::IsValidDayDigit(TCHAR c, int nPos) const
{
return (c >= GetMinDayDigit(nPos) && c <= GetMaxDayDigit(nPos));
}
// Returns true if the given day is valid and falls within the range.
bool CAMSEdit::DateBehavior::IsValidDay(int nDay) const
{
return IsValid(COleDateTime(GetValidYear(), GetValidMonth(), nDay, 0, 0, 0));
}
// Returns true if the given year is valid and falls within the range.
bool CAMSEdit::DateBehavior::IsValidYear(int nYear) const
{
return (nYear >= m_dateMin.GetYear() && nYear <= m_dateMax.GetYear());
}
// Adjusts the month (to the minimum) if not valid; otherwise adjusts the day (to the maximum) if not valid.
bool CAMSEdit::DateBehavior::AdjustMaxMonthAndDay()
{
int nMonth = GetMonth();
if (nMonth && !IsValidMonth(nMonth))
{
SetMonth(GetMinMonth()); // this adjusts the day automatically
return true;
}
return AdjustMaxDay();
}
// Adjusts the day (to the maximum) if not valid.
bool CAMSEdit::DateBehavior::AdjustMaxDay()
{
int nDay = GetDay();
if (nDay && !IsValidDay(nDay))
{
SetDay(GetMaxDay());
return true;
}
return false; // nothing had to be adjusted
}
// Returns the digit at the given position (0 to 3) for the maximum value of the year allowed.
TCHAR CAMSEdit::DateBehavior::GetMaxYearDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 3);
CString strYear = GetString(GetYear(), false);
CString strMaxYear = GetString(m_dateMax.GetYear(), false);
if (nPos == 0 || _ttoi(strMaxYear.Left(nPos)) <= _ttoi(strYear.Left(nPos)))
return strMaxYear[nPos];
return '9';
}
// Returns the digit at the given position (0 to 3) for the minimum value of the year allowed.
// If bValidYear is true, the current year is made sure to be valid.
TCHAR CAMSEdit::DateBehavior::GetMinYearDigit(int nPos, bool bValidYear /*= false*/) const
{
ASSERT(nPos >= 0 && nPos <= 3);
int nYear = GetYear();
if (!IsValidYear(nYear) && bValidYear)
nYear = GetValidYear();
CString strYear = GetString(nYear, false);
CString strMinYear = GetString(m_dateMin.GetYear(), false);
if (nPos == 0 || _ttoi(strMinYear.Left(nPos)) >= _ttoi(strYear.Left(nPos)))
return strMinYear[nPos];
return '0';
}
// Returns true if the digit at the given position (0 to 3) is within the allowed range for the year.
bool CAMSEdit::DateBehavior::IsValidYearDigit(TCHAR c, int nPos) const
{
return (c >= GetMinYearDigit(nPos) && c <= GetMaxYearDigit(nPos));
}
// Returns the month currently shown on the control or 0.
int CAMSEdit::DateBehavior::GetMonth() const
{
CString strText = m_pEdit->GetText();
int nStartPos = GetMonthStartPosition();
if (strText.GetLength() >= nStartPos + 2)
return _ttoi(strText.Mid(nStartPos, nStartPos + 2));
return 0;
}
// Returns the current month as a valid value.
// If it is less than the minimum allowed, the minimum is returned;
// if it is more than the maximum allowed, the maximum is returned.
int CAMSEdit::DateBehavior::GetValidMonth() const
{
int nMonth = GetMonth();
// It it's outside the range, fix it
if (nMonth < GetMinMonth())
nMonth = GetMinMonth();
else if (nMonth > GetMaxMonth())
nMonth = GetMaxMonth();
return nMonth;
}
// Returns the day currently shown on the control or 0.
int CAMSEdit::DateBehavior::GetDay() const
{
CString strText = m_pEdit->GetText();
int nStartPos = GetDayStartPosition();
if (strText.GetLength() >= nStartPos + 2)
return _ttoi(strText.Mid(nStartPos, nStartPos + 2));
return 0;
}
// Returns the current day as a valid value.
// If it is less than the minimum allowed, the minimum is returned;
// if it is more than the maximum allowed, the maximum is returned.
int CAMSEdit::DateBehavior::GetValidDay() const
{
int nDay = GetDay();
// It it's outside the range, fix it
if (nDay < GetMinDay())
nDay = GetMinDay();
else if (nDay > GetMaxDay())
nDay = GetMaxDay();
return nDay;
}
// Returns the year currently shown on the control or 0.
int CAMSEdit::DateBehavior::GetYear() const
{
CString strText = m_pEdit->GetText();
int nSlash = strText.ReverseFind(m_cSep);
if (nSlash > 0)
return _ttoi(strText.Mid(nSlash + 1));
return 0;
}
// Returns the current year as a valid value.
// If it is less than the minimum allowed, the minimum is returned;
// if it is more than the maximum allowed, the maximum is returned.
int CAMSEdit::DateBehavior::GetValidYear() const
{
int nYear = GetYear();
if (nYear < m_dateMin.GetYear())
{
nYear = COleDateTime::GetCurrentTime().GetYear();
if (nYear < m_dateMin.GetYear())
nYear = m_dateMin.GetYear();
}
if (nYear > m_dateMax.GetYear())
nYear = m_dateMax.GetYear();
return nYear;
}
// Sets the control's month to the given value, which must be valid.
void CAMSEdit::DateBehavior::SetMonth(int nMonth)
{
SelectionSaver selection = m_pEdit; // remember the current selection
if (GetMonth() > 0) // see if there's already a month
m_pEdit->SetSel(GetMonthStartPosition(), GetMonthStartPosition() + 3);
CString strText;
strText.Format(_T("%02d%c"), nMonth, m_cSep);
m_pEdit->ReplaceSel(strText, TRUE); // set the month
AdjustMaxDay(); // adjust the day if it's out of range
ASSERT(IsValidMonth(nMonth));
}
// Sets the control's day to the given value, which must be valid.
void CAMSEdit::DateBehavior::SetDay(int nDay)
{
ASSERT(IsValidDay(nDay));
SelectionSaver selection = m_pEdit; // remember the current selection
if (GetDay() > 0) // see if there's already a day
m_pEdit->SetSel(GetDayStartPosition(), GetDayStartPosition() + 3);
CString strText;
strText.Format(_T("%02d%c"), nDay, m_cSep);
m_pEdit->ReplaceSel(strText, TRUE); // set the day
}
// Sets the control's year to the given value, which must be valid.
void CAMSEdit::DateBehavior::SetYear(int nYear)
{
ASSERT(IsValidYear(nYear));
SelectionSaver selection = m_pEdit; // remember the current selection
if (GetYear() > 0) // see if there's already a year
m_pEdit->SetSel(GetYearStartPosition(), GetYearStartPosition() + 4);
CString strText;
strText.Format(_T("%4d"), nYear);
m_pEdit->ReplaceSel(strText, TRUE); // set the year
AdjustMaxMonthAndDay(); // adjust the month and/or day if they're out of range
}
// Returns the date on the control as a CTime object.
CTime CAMSEdit::DateBehavior::GetDate() const
{
return CTime(GetYear(), GetMonth(), GetDay(), 0, 0, 0);
}
// Returns the date on the control as a COleDateTime object.
COleDateTime CAMSEdit::DateBehavior::GetOleDate() const
{
return COleDateTime(GetYear(), GetMonth(), GetDay(), 0, 0, 0);
}
// Sets the month, day, and year on the control to the given values, which must be valid.
void CAMSEdit::DateBehavior::SetDate(int nYear, int nMonth, int nDay)
{
ASSERT(IsValid(COleDateTime(nYear, nMonth, nDay, 0, 0, 0)));
m_pEdit->SetWindowText(GetFormattedDate(nYear, nMonth, nDay));
}
// Sets the month, day, and year on the control based on the given CTime object, which must be valid.
void CAMSEdit::DateBehavior::SetDate(const CTime& date)
{
SetDate(date.GetYear(), date.GetMonth(), date.GetDay());
}
// Sets the month, day, and year on the control based on the given COleDateTime object, which must be valid.
void CAMSEdit::DateBehavior::SetDate(const COleDateTime& date)
{
SetDate(date.GetYear(), date.GetMonth(), date.GetDay());
}
// Sets the month, day, and year on the control to today's date.
void CAMSEdit::DateBehavior::SetDateToToday()
{
SetDate(COleDateTime::GetCurrentTime());
}
// Returns true if the given year is a leap year.
bool CAMSEdit::DateBehavior::IsLeapYear(int nYear)
{
return (nYear & 3) == 0 && (nYear % 100 != 0 || nYear % 400 == 0);
}
// Returns true if the control's date is valid and falls within the allowed range.
bool CAMSEdit::DateBehavior::IsValid() const
{
return IsValid(COleDateTime(GetYear(), GetMonth(), GetDay(), 0, 0, 0));
}
// Returns true if the control's date is valid and falls within the allowed range.
// If bShowErrorIfNotValid is true, an error message box is shown and the control gets the focus.
bool CAMSEdit::DateBehavior::CheckIfValid(bool bShowErrorIfNotValid /*= true*/)
{
if (!m_pEdit->IsWindowEnabled())
return true;
bool bValid = IsValid();
if (!bValid && bShowErrorIfNotValid)
{
ShowErrorMessage();
m_pEdit->SetFocus();
}
return bValid;
}
// Shows a message box informing the user to enter a valid date within the allowed range.
void CAMSEdit::DateBehavior::ShowErrorMessage() const
{
AfxMessageBox(_T("Please specify a date between ") + GetFormattedDate(m_dateMin.GetYear(), m_dateMin.GetMonth(), m_dateMin.GetDay()) + _T(" and ") + GetFormattedDate(m_dateMax.GetYear(), m_dateMax.GetMonth(), m_dateMax.GetDay()) + '.', MB_ICONEXCLAMATION);
}
// Sets the range of allowed date values to the given minimum and maximum CTime values.
void CAMSEdit::DateBehavior::SetRange(const CTime& dateMin, const CTime& dateMax)
{
ASSERT(dateMin >= AMS_MIN_CTIME);
ASSERT(dateMax <= AMS_MAX_CTIME);
ASSERT(dateMin <= dateMax);
m_dateMin.SetDate(dateMin.GetYear(), dateMin.GetMonth(), dateMin.GetDay());
m_dateMax.SetDate(dateMax.GetYear(), dateMax.GetMonth(), dateMax.GetDay());
_Redraw();
}
// Sets the range of allowed date values to the given minimum and maximum COleDateTime values.
void CAMSEdit::DateBehavior::SetRange(const COleDateTime& dateMin, const COleDateTime& dateMax)
{
ASSERT(dateMin >= AMS_MIN_OLEDATETIME);
ASSERT(dateMax <= AMS_MAX_OLEDATETIME);
ASSERT(dateMin <= dateMax);
m_dateMin = dateMin;
m_dateMax = dateMax;
_Redraw();
}
// Retrieves the range of allowed date values inside the given set of CTime pointers.
void CAMSEdit::DateBehavior::GetRange(CTime* pDateMin, CTime* pDateMax) const
{
if (pDateMin)
*pDateMin = CTime(m_dateMin.GetYear(), m_dateMin.GetMonth(), m_dateMin.GetDay(), 0, 0, 0);
if (pDateMax)
*pDateMax = CTime(m_dateMax.GetYear(), m_dateMax.GetMonth(), m_dateMax.GetDay(), 0, 0, 0);
}
// Retrieves the range of allowed date values inside the given set of COleDateTime pointers.
void CAMSEdit::DateBehavior::GetRange(COleDateTime* pDateMin, COleDateTime* pDateMax) const
{
if (pDateMin)
*pDateMin = m_dateMin;
if (pDateMax)
*pDateMax = m_dateMax;
}
// Returns true if the given date is valid and falls within the range.
bool CAMSEdit::DateBehavior::IsValid(const COleDateTime& date, bool bDateOnly /*= true*/) const
{
return (date.GetStatus() == COleDateTime::valid && date >= m_dateMin && date <= m_dateMax);
}
// Sets the character used to separate the month, day, and year values.
void CAMSEdit::DateBehavior::SetSeparator(TCHAR cSep)
{
ASSERT(cSep);
ASSERT(!_istdigit(cSep));
if (m_cSep != cSep)
{
m_cSep = cSep;
_Redraw();
}
}
// Returns the character used to separate the month, day, and year values.
TCHAR CAMSEdit::DateBehavior::GetSeparator() const
{
return m_cSep;
}
// Sets whether the day should be shown before the month or after it.
void CAMSEdit::DateBehavior::ShowDayBeforeMonth(bool bDayBeforeMonth /*= true*/)
{
ModifyFlags(bDayBeforeMonth ? DayBeforeMonth : 0, bDayBeforeMonth ? 0 : DayBeforeMonth);
}
// Returns true if the day will be shown before the month (instead of after it).
bool CAMSEdit::DateBehavior::IsDayShownBeforeMonth() const
{
return (m_uFlags & DayBeforeMonth) ? true : false;
}
// Returns the control's value in a valid format.
CString CAMSEdit::DateBehavior::_GetValidText() const
{
CString strText = m_pEdit->GetText();
if (strText.IsEmpty())
return strText;
if (IsValid())
return GetFormattedDate(GetYear(), GetMonth(), GetDay());
// If the date is empty, try using today
if (GetYear() == 0 && GetMonth() == 0 && GetDay() == 0)
((CAMSEdit::DateBehavior*)this)->SetDateToToday();
int nYear = GetValidYear();
int nMonth = GetValidMonth();
int nDay = GetValidDay();
if (!IsValid(COleDateTime(nYear, nMonth, nDay, 0, 0, 0)))
nMonth = GetMinMonth();
if (!IsValid(COleDateTime(nYear, nMonth, nDay, 0, 0, 0)))
nDay = GetMaxDay();
return GetFormattedDate(nYear, nMonth, nDay);
}
// Formats the given year, month, and day values into a string based on the proper format.
CString CAMSEdit::DateBehavior::GetFormattedDate(int nYear, int nMonth, int nDay) const
{
CString strText;
if (m_uFlags & DayBeforeMonth)
strText.Format(_T("%02d%c%02d%c%4d"), nDay, m_cSep, nMonth, m_cSep, nYear);
else
strText.Format(_T("%02d%c%02d%c%4d"), nMonth, m_cSep, nDay, m_cSep, nYear);
return strText;
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::TimeBehavior
#define AMS_DEFAULT_DATE 1899, 12, 30
// Constructs the object with the given control.
CAMSEdit::TimeBehavior::TimeBehavior(CAMSEdit* pEdit) :
Behavior(pEdit),
m_timeMin(AMS_DEFAULT_DATE, 0, 0, 0),
m_timeMax(AMS_DEFAULT_DATE, 23, 59, 59),
m_cSep(':'),
m_strAM(AMS_AM_SYMBOL),
m_strPM(AMS_PM_SYMBOL),
m_nAMPMLength(m_strAM.GetLength()),
m_nHourStart(0)
{
// Get the system's time separator
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STIME, NULL, 0))
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STIME, &m_cSep, sizeof(m_cSep));
// Determine if it's in 24-hour format
TCHAR szBuffer[3];
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITIME, NULL, 0))
{
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITIME, szBuffer, sizeof(szBuffer));
if (_ttoi(szBuffer))
m_uFlags |= TwentyFourHourFormat;
}
// Get the AM symbol
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_S1159, NULL, 0))
{
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_S1159, szBuffer, sizeof(szBuffer));
m_strAM = szBuffer;
}
// Get the PM symbol
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_S2359, NULL, 0))
{
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_S2359, szBuffer, sizeof(szBuffer));
m_strPM = szBuffer;
}
m_nAMPMLength = m_strAM.GetLength();
// Verify the lengths are the same; otherwise use the default
if (m_nAMPMLength == 0 || m_nAMPMLength != m_strPM.GetLength())
{
m_strAM = AMS_AM_SYMBOL;
m_strPM = AMS_PM_SYMBOL;
m_nAMPMLength = m_strAM.GetLength();
}
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSEdit::TimeBehavior::_OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
// Check to see if it's read only
if (m_pEdit->IsReadOnly())
return;
TCHAR c = static_cast<TCHAR>(uChar);
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
// Check for a non-printable character (such as Ctrl+C)
if (!_istprint(c))
{
if (c == VK_BACK && nStart != nLen)
{
m_pEdit->SendMessage(WM_KEYDOWN, VK_LEFT); // move the cursor left
return;
}
// Allow backspace only if the cursor is all the way to the right
if (_ShouldEnter(c))
Behavior::_OnChar(uChar, nRepCnt, nFlags);
return;
}
// Add the digit depending on its location
if (nStart == m_nHourStart) // FIRST DIGIT
{
if (IsValidHourDigit(c, 0) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
if (nLen > nStart + 1)
{
// If the second digit is no longer valid, correct and select it
if (!IsValidHour(GetHour()))
{
m_pEdit->SetSel(nStart + 1, nStart + 2);
m_pEdit->ReplaceSel(CString(GetMinHourDigit(1)), TRUE);
m_pEdit->SetSel(nStart + 1, nStart + 2);
}
}
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
else if (nLen == nStart && IsValidHourDigit(c, 1) && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 2);
m_pEdit->ReplaceSel(CString('0') + c, TRUE);
}
else
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == m_nHourStart + 1) // SECOND DIGIT
{
if (IsValidHourDigit(c, 1) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
else if (c == m_cSep && nLen == nStart && IsValidHour(_ttoi(CString('0') + strText[m_nHourStart])) && _ShouldEnter(c))
{
m_pEdit->SetSel(m_nHourStart, nStart);
m_pEdit->ReplaceSel(CString('0') + strText[m_nHourStart] + c, TRUE);
}
else
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == m_nHourStart + 2) // FIRST COLON
{
int nColon = 0;
if (c == m_cSep)
nColon = 1;
else
nColon = (IsValidMinuteDigit(c, 0) ? 2 : 0);
// If we need the colon, enter it
if (nColon && _ShouldEnter(c))
{
m_pEdit->SetSel(nStart, nStart + 1, FALSE);
m_pEdit->ReplaceSel(CString(m_cSep), TRUE);
}
// If the colon is to be preceded by a valid digit, "type" it in.
if (nColon == 2)
keybd_event((BYTE)c, 0, 0, 0);
else
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == m_nHourStart + 3) // THIRD DIGIT
{
if (IsValidMinuteDigit(c, 0) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
if (nLen > nStart + 1)
{
if (!IsValidMinute(GetMinute()))
{
m_pEdit->SetSel(nStart + 1, nStart + 2);
m_pEdit->ReplaceSel(CString(GetMinMinuteDigit(1)), TRUE);
m_pEdit->SetSel(nStart + 1, nStart + 2);
}
}
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
else
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == m_nHourStart + 4) // FOURTH DIGIT
{
if (IsValidMinuteDigit(c, 1) && _ShouldEnter(c))
{
if (nLen > nStart)
{
m_pEdit->SetSel(nStart, nStart + 1);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
// Show the AM/PM symbol if we're not showing seconds
if (!IsShowingSeconds())
ShowAMPM();
}
else
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == m_nHourStart + 5) // SECOND COLON OR FIRST SPACE (seconds' first digit or AM/PM)
{
if (IsShowingSeconds())
{
int nColon = 0;
if (c == m_cSep)
nColon = 1;
else
nColon = (IsValidSecondDigit(c, 0) ? 2 : 0);
// If we need the slash, enter it
if (nColon && _ShouldEnter(c))
{
int nReplace = (nStart < nLen && strText[nStart] != ' ');
m_pEdit->SetSel(nStart, nStart + nReplace, FALSE);
m_pEdit->ReplaceSel(CString(m_cSep), TRUE);
}
// If the colon is to be preceded by a valid digit, "type" it in.
if (nColon == 2)
keybd_event((BYTE)c, 0, 0, 0);
}
else if (!IsShowing24HourFormat())
{
if (c == ' ')
{
m_pEdit->SetSel(nStart, nStart + 1, FALSE);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
ShowAMPM();
}
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == m_nHourStart + 6) // FIFTH DIGIT - first digit of seconds or AM/PM
{
if (IsShowingSeconds())
{
if (IsValidSecondDigit(c, 0) && _ShouldEnter(c))
{
if (nLen > nStart)
{
int nReplace = (nStart < nLen && strText[nStart] != ' ');
m_pEdit->SetSel(nStart, nStart + nReplace, FALSE);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
}
}
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == m_nHourStart + 7) // SIXTH DIGIT - second digit of seconds or AM/PM
{
if (IsShowingSeconds())
{
if (IsValidSecondDigit(c, 1) && _ShouldEnter(c))
{
if (nLen > nStart)
{
int nReplace = (nStart < nLen && strText[nStart] != ' ');
m_pEdit->SetSel(nStart, nStart + nReplace, FALSE);
m_pEdit->ReplaceSel(CString(c), TRUE);
}
else
Behavior::_OnChar(uChar, nRepCnt, nFlags);
// Show the AM/PM symbol if we're not in 24-hour format
ShowAMPM();
}
}
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == m_nHourStart + 8) // FIRST SPACE (with seconds showing)
{
if (IsShowingSeconds() && !IsShowing24HourFormat())
{
if (c == ' ')
{
m_pEdit->SetSel(nStart, nStart + 1, FALSE);
m_pEdit->ReplaceSel(CString(c), TRUE);
ShowAMPM();
}
}
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else // AM/PM
ChangeAMPM(c);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSEdit::TimeBehavior::_OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
switch (uChar)
{
case VK_DELETE:
{
// If deleting make sure it's the last character or that
// the selection goes all the way to the end of the text
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
if (nEnd != nLen)
{
if (!(nEnd == nStart && nEnd == nLen - 1))
return;
}
break;
}
case VK_UP:
{
// If pressing the UP arrow, increment the corresponding value.
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
if (nStart >= GetHourStartPosition() && nStart <= GetHourStartPosition() + 2)
{
int nHour = GetHour();
if (nHour >= GetMinHour())
{
// Handle moving up through the noon hour
CString strAMPM = GetAMPM();
if (IsValidAMPM(strAMPM))
{
if (nHour == 11)
{
if (strAMPM == m_strPM) // stop at midnight
return;
SetAMPM(false);
}
else if (nHour == 12)
nHour = 0;
}
if (nHour < GetMaxHour())
SetHour(++nHour);
}
}
else if (nStart >= GetMinuteStartPosition() && nStart <= GetMinuteStartPosition() + 2)
{
int nMinute = GetMinute();
if (nMinute >= GetMinMinute() && nMinute < GetMaxMinute())
SetMinute(++nMinute);
}
else if (nStart >= GetAMPMStartPosition() && nStart <= GetAMPMStartPosition() + m_nAMPMLength)
{
CString strAMPM = GetAMPM();
SetAMPM(!IsValidAMPM(strAMPM) || strAMPM == m_strPM);
}
else if (nStart >= GetSecondStartPosition() && nStart <= GetSecondStartPosition() + 2)
{
int nSecond = GetSecond();
if (nSecond >= GetMinSecond() && nSecond < GetMaxSecond())
SetSecond(++nSecond);
}
return;
}
case VK_DOWN:
{
// If pressing the DOWN arrow, decrement the corresponding value.
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
if (nStart >= GetHourStartPosition() && nStart <= GetHourStartPosition() + 2)
{
int nHour = GetHour();
if (nHour <= GetMaxHour())
{
// Handle moving up through the noon hour
CString strAMPM = GetAMPM();
if (IsValidAMPM(strAMPM))
{
if (nHour == 12)
{
if (strAMPM == m_strAM) // stop at midnight
return;
SetAMPM(true);
}
else if (nHour == 1)
nHour = 13;
}
if (nHour > GetMinHour())
SetHour(--nHour);
}
}
else if (nStart >= GetMinuteStartPosition() && nStart <= GetMinuteStartPosition() + 2)
{
int nMinute = GetMinute();
if (nMinute > GetMinMinute() && nMinute <= GetMaxMinute())
SetMinute(--nMinute);
}
else if (nStart >= GetAMPMStartPosition() && nStart <= GetAMPMStartPosition() + m_nAMPMLength)
{
CString strAMPM = GetAMPM();
SetAMPM(!IsValidAMPM(strAMPM) || strAMPM == m_strPM);
}
else if (nStart >= GetSecondStartPosition() && nStart <= GetSecondStartPosition() + 2)
{
int nSecond = GetSecond();
if (nSecond > GetMinSecond() && nSecond <= GetMaxSecond())
SetSecond(--nSecond);
}
return;
}
}
Behavior::_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
// It's used here to check if any action needs to be taken based on the control's value.
void CAMSDateEdit::TimeBehavior::_OnKillFocus(CWnd* pNewWnd)
{
Behavior::_OnKillFocus(pNewWnd);
// Check if any of the OnKillFocus flags is set
if (!(m_uFlags & OnKillFocus_Max))
return;
CString strText = m_pEdit->GetText();
// If it's empty, take action based on the flag
if (strText.IsEmpty())
{
if (m_uFlags & OnKillFocus_Beep_IfEmpty)
MessageBeep(MB_ICONEXCLAMATION);
if (m_uFlags & OnKillFocus_SetValid_IfEmpty)
AdjustWithinRange();
if ((m_uFlags & OnKillFocus_ShowMessage_IfEmpty) == OnKillFocus_ShowMessage_IfEmpty)
ShowErrorMessage();
if (m_uFlags & OnKillFocus_SetFocus_IfEmpty)
m_pEdit->SetFocus();
return;
}
if (!IsValid())
{
if (m_uFlags & OnKillFocus_Beep_IfInvalid)
MessageBeep(MB_ICONEXCLAMATION);
if (m_uFlags & OnKillFocus_SetValid_IfInvalid)
AdjustWithinRange();
if ((m_uFlags & OnKillFocus_ShowMessage_IfInvalid) == OnKillFocus_ShowMessage_IfInvalid)
ShowErrorMessage();
if (m_uFlags & OnKillFocus_SetFocus_IfInvalid)
m_pEdit->SetFocus();
}
}
// Returns the zero-based position of the hour inside the control.
int CAMSEdit::TimeBehavior::GetHourStartPosition() const
{
return m_nHourStart;
}
// Returns the zero-based position of the minute inside the control.
int CAMSEdit::TimeBehavior::GetMinuteStartPosition() const
{
return m_nHourStart + 3;
}
// Returns the zero-based position of the second inside the control.
int CAMSEdit::TimeBehavior::GetSecondStartPosition() const
{
return m_nHourStart + 6;
}
// Returns the zero-based position of the AM/PM symbols inside the control.
// This is based on whether the seconds are being shown or not.
int CAMSEdit::TimeBehavior::GetAMPMStartPosition() const
{
return m_nHourStart + (IsShowingSeconds() ? 9 : 6);
}
// Returns the maximum value for the hour based on whether we're in 24-Hour format (or b24HourFormat is true) or not.
// Note: This value is not based on the allowed range.
int CAMSEdit::TimeBehavior::GetMaxHour(bool b24HourFormat /*= false*/) const
{
return (b24HourFormat || IsShowing24HourFormat()) ? 23 : 12;
}
// Returns the minimum value for the hour based on whether we're in 24-Hour format (or b24HourFormat is true) or not.
// Note: This value is not based on the allowed range.
int CAMSEdit::TimeBehavior::GetMinHour(bool b24HourFormat /*= false*/) const
{
return (b24HourFormat || IsShowing24HourFormat()) ? 0 : 1;
}
// Returns the maximum value for the minute.
// Note: This value is not based on the allowed range.
int CAMSEdit::TimeBehavior::GetMaxMinute() const
{
return 59;
}
// Returns the minimum value for the minute.
// Note: This value is not based on the allowed range.
int CAMSEdit::TimeBehavior::GetMinMinute() const
{
return 0;
}
// Returns the maximum value for the second.
// Note: This value is not based on the allowed range.
int CAMSEdit::TimeBehavior::GetMaxSecond() const
{
return 59;
}
// Returns the minimum value for the second.
// Note: This value is not based on the allowed range.
int CAMSEdit::TimeBehavior::GetMinSecond() const
{
return 0;
}
// Returns the digit at the given position (0 or 1) for the maximum value of the hour.
TCHAR CAMSEdit::TimeBehavior::GetMaxHourDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
// First digit
if (nPos == 0)
return IsShowing24HourFormat() ? '2' : '1';
// Second digit
CString strText = m_pEdit->GetText();
TCHAR cFirstDigit = (strText.GetLength() > GetHourStartPosition()) ? strText[GetHourStartPosition()] : '0';
ASSERT(cFirstDigit); // must have a valid first digit at this point
// Use the first digit to determine the second digit's max
if (cFirstDigit == '2')
return '3';
if (cFirstDigit == '1' && !IsShowing24HourFormat())
return '2';
return '9';
}
// Returns the digit at the given position (0 or 1) for the minimum value of the hour.
TCHAR CAMSEdit::TimeBehavior::GetMinHourDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
// First digit
if (nPos == 0)
return '0';
// Second digit
CString strText = m_pEdit->GetText();
TCHAR cFirstDigit = (strText.GetLength() > GetHourStartPosition()) ? strText[GetHourStartPosition()] : '0';
ASSERT(cFirstDigit); // must have a valid first digit at this point
// If the first digit is a 0 and we're not in 24-hour format, don't allow 0
if (cFirstDigit == '0' && !IsShowing24HourFormat())
return '1';
// For all other cases it's always 0
return '0';
}
// Returns true if the digit at the given position (0 or 1) is valid for the hour.
bool CAMSEdit::TimeBehavior::IsValidHourDigit(TCHAR c, int nPos) const
{
return (c >= GetMinHourDigit(nPos) && c <= GetMaxHourDigit(nPos));
}
// Returns true if the given hour is valid.
// If b24HourFormat is true, the value is checked from 0 to 23 regardless of the format currently being shown.
bool CAMSEdit::TimeBehavior::IsValidHour(int nHour, bool b24HourFormat /*= false*/) const
{
return (nHour >= GetMinHour(b24HourFormat) && nHour <= GetMaxHour(b24HourFormat));
}
// Returns the digit at the given position (0 or 1) for the maximum value of the minute.
TCHAR CAMSEdit::TimeBehavior::GetMaxMinuteDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
return (nPos == 0 ? '5' : '9');
}
// Returns the digit at the given position (0 or 1) for the maximum value of the minute.
TCHAR CAMSEdit::TimeBehavior::GetMinMinuteDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
return '0';
}
// Returns true if the digit at the given position (0 or 1) is valid for the minute.
bool CAMSEdit::TimeBehavior::IsValidMinuteDigit(TCHAR c, int nPos) const
{
return (c >= GetMinMinuteDigit(nPos) && c <= GetMaxMinuteDigit(nPos));
}
// Returns true if the given minute is valid.
bool CAMSEdit::TimeBehavior::IsValidMinute(int nMinute) const
{
return (nMinute >= GetMinMinute() && nMinute <= GetMaxMinute());
}
// Returns the digit at the given position (0 or 1) for the maximum value of the second.
TCHAR CAMSEdit::TimeBehavior::GetMaxSecondDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
return (nPos == 0 ? '5' : '9');
}
// Returns the digit at the given position (0 or 1) for the maximum value of the second.
TCHAR CAMSEdit::TimeBehavior::GetMinSecondDigit(int nPos) const
{
ASSERT(nPos >= 0 && nPos <= 1);
return '0';
}
// Returns true if the digit at the given position (0 or 1) is valid for the second.
bool CAMSEdit::TimeBehavior::IsValidSecondDigit(TCHAR c, int nPos) const
{
return (c >= GetMinSecondDigit(nPos) && c <= GetMaxSecondDigit(nPos));
}
// Returns true if the given second is valid.
bool CAMSEdit::TimeBehavior::IsValidSecond(int nSecond) const
{
return (nSecond >= GetMinSecond() && nSecond <= GetMaxSecond());
}
// Shows the AM symbol if not in 24-hour format and they're not already shown.
void CAMSEdit::TimeBehavior::ShowAMPM()
{
if (!IsShowing24HourFormat() && !IsValidAMPM(GetAMPM()))
SetAMPM(true);
}
// Sets the AM or PM symbol if not in 24-hour format.
void CAMSEdit::TimeBehavior::SetAMPM(bool bAM)
{
if (IsShowing24HourFormat())
return;
SelectionSaver selection = m_pEdit; // remember the current selection
m_pEdit->SetSel(GetAMPMStartPosition() - 1, GetAMPMStartPosition() + m_nAMPMLength);
CString strText;
strText.Format(_T(" %s"), bAM ? m_strAM : m_strPM);
m_pEdit->ReplaceSel(strText, TRUE); // set the AM/PM
}
// Changes the AM/PM symbol based on the given character (entered by the user).
bool CAMSEdit::TimeBehavior::ChangeAMPM(TCHAR c)
{
if (IsShowing24HourFormat())
return false;
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
int nPos = GetAMPMPosition(strText);
if (nPos == 0)
return false;
int nStart, nEnd;
m_pEdit->GetSel(nStart, nEnd);
CString strAMPM = GetAMPM();
TCHAR cUpper = _totupper(c);
switch (cUpper)
{
case 'A':
case 'P':
SetAMPM(cUpper == 'A');
if (cUpper == (TCHAR)_totupper(m_strAM[0]) || cUpper == (TCHAR)_totupper(m_strPM[0]))
{
// Move the cursor right, if we're in front of the AM/PM symbols
if (nStart == nPos)
m_pEdit->PostMessage(WM_KEYDOWN, VK_RIGHT);
// Move the cursor right twice, if we're in front of the space in front of the AM/PM symbols
if (nStart + 1 == nPos)
{
m_pEdit->PostMessage(WM_KEYDOWN, VK_RIGHT);
m_pEdit->PostMessage(WM_KEYDOWN, VK_RIGHT);
}
}
return true;
default:
// Handle entries after the first character of the AM/PM symbol -- allow the user to enter each character
if (nStart > nPos)
{
// Check if we're adding a character of the AM/PM symbol (after the first one)
if ((nLen == nStart && !IsValidAMPM(strAMPM)) || (nLen == nEnd && nEnd != nStart))
{
const CString& strAMPMToUse = _totupper(strText[nPos]) == _totupper(m_strAM[0]) ? m_strAM : m_strPM;
if (cUpper == (TCHAR)_totupper(strAMPMToUse[nStart - nPos]))
{
m_pEdit->ReplaceSel(strAMPMToUse.Mid(nStart - nPos), TRUE); // set the rest of the AM/PM
m_pEdit->SetSel(nStart, nStart, TRUE); // Reset the selection so that the cursor can be moved
return ChangeAMPM(c); // move the cursor (below)
}
}
// Check if the AM/PM symbol is OK and we just need to move over one
if (nLen > nStart && nEnd == nStart && cUpper == (TCHAR)_totupper(strText[nStart]))
{
m_pEdit->PostMessage(WM_KEYDOWN, VK_RIGHT);
return true;
}
}
}
return false;
}
// Returns the zero-based position of the AM/PM symbol shown on the control.
int CAMSEdit::TimeBehavior::GetAMPMPosition(const CString& strText) const
{
int nPos = strText.Find(' ' + m_strAM);
return ((nPos < 0) ? strText.Find(' ' + m_strPM) : nPos) + 1;
}
// Returns true if the given AM/PM symbol is valid.
bool CAMSEdit::TimeBehavior::IsValidAMPM(const CString& strAMPM) const
{
return (strAMPM == m_strAM || strAMPM == m_strPM);
}
// Returns the hour currently shown on the control or -1.
int CAMSEdit::TimeBehavior::GetHour() const
{
CString strText = m_pEdit->GetText();
int nStartPos = GetHourStartPosition();
if (strText.GetLength() >= nStartPos + 2)
return _ttoi(strText.Mid(nStartPos, nStartPos + 2));
return -1;
}
// Returns the current hour as a valid value.
int CAMSEdit::TimeBehavior::GetValidHour(bool b24HourFormat /*= false*/) const
{
int nHour = GetHour();
// It it's outside the range, fix it
if (nHour < GetMinHour(b24HourFormat))
nHour = GetMinHour(b24HourFormat);
else if (nHour > GetMaxHour(b24HourFormat))
nHour = GetMaxHour(b24HourFormat);
return nHour;
}
// Returns the minute currently shown on the control or -1.
int CAMSEdit::TimeBehavior::GetMinute() const
{
CString strText = m_pEdit->GetText();
int nStartPos = GetMinuteStartPosition();
if (strText.GetLength() >= nStartPos + 2)
return _ttoi(strText.Mid(nStartPos, nStartPos + 2));
return -1;
}
// Returns the current minute as a valid value.
int CAMSEdit::TimeBehavior::GetValidMinute() const
{
int nMinute = GetMinute();
// It it's outside the range, fix it
if (nMinute < GetMinMinute())
nMinute = GetMinMinute();
else if (nMinute > GetMaxMinute())
nMinute = GetMaxMinute();
return nMinute;
}
// Returns the second currently shown on the control or -1.
int CAMSEdit::TimeBehavior::GetSecond() const
{
CString strText = m_pEdit->GetText();
int nStartPos = GetSecondStartPosition();
if (strText.GetLength() >= nStartPos + 2 && _istdigit(strText[nStartPos]) && _istdigit(strText[nStartPos + 1]))
return _ttoi(strText.Mid(nStartPos, nStartPos + 2));
return -1;
}
// Returns the current second as a valid value.
int CAMSEdit::TimeBehavior::GetValidSecond() const
{
int nSecond = GetSecond();
if (nSecond < GetMinSecond())
nSecond = GetMinSecond();
else if (nSecond > GetMaxSecond())
nSecond = GetMaxSecond();
return nSecond;
}
// Returns the AM/PM symbol currently shown on the control or an empty string.
CString CAMSEdit::TimeBehavior::GetAMPM() const
{
CString strText = m_pEdit->GetText();
int nPos = GetAMPMPosition(strText);
if (nPos > 0)
return strText.Mid(nPos);
return _T("");
}
// Returns the current AM/PM symbol as a valid value.
CString CAMSEdit::TimeBehavior::GetValidAMPM() const
{
CString strAMPM = GetAMPM();
if (!IsValidAMPM(strAMPM))
return m_strAM;
return strAMPM;
}
// Sets the control's hour to the given value, which must be valid.
void CAMSEdit::TimeBehavior::SetHour(int nHour)
{
ASSERT(IsValidHour(nHour, false));
SelectionSaver selection = m_pEdit; // remember the current selection
if (GetHour() >= 0) // see if there's already an hour
m_pEdit->SetSel(GetHourStartPosition(), GetHourStartPosition() + 3);
// Convert it to AM/PM hour if necessary
CString strAMPM;
if (!IsShowing24HourFormat() && nHour > 12)
nHour = ConvertToAMPMHour(nHour, &strAMPM);
CString strText;
strText.Format(_T("%02d%c"), nHour, m_cSep);
m_pEdit->ReplaceSel(strText, TRUE); // set the hour
// Change the AM/PM if it's present
if (!strAMPM.IsEmpty() && IsValidAMPM(GetAMPM()))
SetAMPM(strAMPM == m_strAM);
}
// Sets the control's minute to the given value, which must be valid.
void CAMSEdit::TimeBehavior::SetMinute(int nMinute)
{
ASSERT(IsValidMinute(nMinute));
SelectionSaver selection = m_pEdit; // remember the current selection
if (GetMinute() >= 0) // see if there's already a minute
m_pEdit->SetSel(GetMinuteStartPosition(), GetMinuteStartPosition() + 2 + IsShowingSeconds());
CString strText;
strText.Format(_T("%02d"), nMinute);
if (IsShowingSeconds())
strText += m_cSep;
m_pEdit->ReplaceSel(strText, TRUE); // set the minute
// Append the AM/PM if no seconds come after and it's not in 24-hour format
if (!IsShowingSeconds())
ShowAMPM();
}
// Sets the control's second to the given value, which must be valid.
void CAMSEdit::TimeBehavior::SetSecond(int nSecond)
{
ASSERT(IsValidSecond(nSecond));
if (!IsShowingSeconds())
return;
SelectionSaver selection = m_pEdit; // remember the current selection
if (GetSecond() >= 0) // see if there's already a second
m_pEdit->SetSel(GetSecondStartPosition(), GetSecondStartPosition() + 2);
CString strText;
strText.Format(_T("%02d"), nSecond);
m_pEdit->ReplaceSel(strText, TRUE); // set the second
// Append the AM/PM if it's not in 24-hour format
ShowAMPM();
}
// Returns the combination of the given hour and AM/PM symbol converted to 24-hour format.
int CAMSEdit::TimeBehavior::ConvertTo24Hour(int nHour, const CString& strAMPM) const
{
if (strAMPM == m_strPM && nHour >= 1 && nHour <= 11)
nHour += 12;
else if (strAMPM == m_strAM && nHour == 12)
nHour = 0;
return nHour;
}
// Returns the given hour (in 24-hour format) to 12-Hour format and sets the optional
// string pointer to the resulting AM/PM symbol (if not NULL).
int CAMSEdit::TimeBehavior::ConvertToAMPMHour(int nHour, CString* pStrAMPM /*= NULL*/) const
{
CString strAMPM = m_strAM;
if (nHour >= 12)
{
nHour -= 12;
strAMPM = m_strPM;
}
if (nHour == 0)
nHour = 12;
if (pStrAMPM)
*pStrAMPM = strAMPM;
return nHour;
}
// Returns the time on the control as a CTime object.
CTime CAMSEdit::TimeBehavior::GetTime() const
{
if (IsShowing24HourFormat())
return CTime(1970, 1, 1, GetHour(), GetMinute(), GetValidSecond());
return CTime(1970, 1, 1, ConvertTo24Hour(GetHour(), GetAMPM()), GetMinute(), GetValidSecond());
}
// Returns the time on the control as a COleDateTime object.
COleDateTime CAMSEdit::TimeBehavior::GetOleTime() const
{
if (IsShowing24HourFormat())
return COleDateTime(AMS_DEFAULT_DATE, GetHour(), GetMinute(), GetValidSecond());
return COleDateTime(AMS_DEFAULT_DATE, ConvertTo24Hour(GetHour(), GetAMPM()), GetMinute(), GetValidSecond());
}
// Sets the hour, minute, and second on the control to the given values, which must be valid.
void CAMSEdit::TimeBehavior::SetTime(int nHour, int nMinute, int nSecond /*= 0*/)
{
ASSERT(IsValidHour(nHour, true));
ASSERT(IsValidMinute(nMinute));
ASSERT(IsValidSecond(nSecond));
m_pEdit->SetWindowText(GetFormattedTime(nHour, nMinute, nSecond));
}
// Sets the hour, minute, and second on the control based on the given CTime object, which must be valid.
void CAMSEdit::TimeBehavior::SetTime(const CTime& time)
{
SetTime(time.GetHour(), time.GetMinute(), time.GetSecond());
}
// Sets the hour, minute, and second on the control based on the given COleDateTime object, which must be valid.
void CAMSEdit::TimeBehavior::SetTime(const COleDateTime& time)
{
SetTime(time.GetHour(), time.GetMinute(), time.GetSecond());
}
// Sets the hour, minute, and second on the control to the current time.
void CAMSEdit::TimeBehavior::SetTimeToNow()
{
SetTime(CTime::GetCurrentTime());
}
// Returns true if the control's time is valid and falls within allowed range.
bool CAMSEdit::TimeBehavior::IsValid() const
{
return IsValid(true);
}
// Returns true if the control's time is valid.
// If bCheckRangeAlso is true, the time is also checked that it falls within allowed range.
bool CAMSEdit::TimeBehavior::IsValid(bool bCheckRangeAlso) const
{
// Check that we have a valid hour and minute
int nHour = GetHour();
int nMinute = GetMinute();
if (nHour < 0 || nMinute < 0)
return false;
// Check that the seconds are valid if being shown
int nSecond = GetSecond();
bool bShowingSeconds = IsShowingSeconds();
if (bShowingSeconds != (nSecond >= 0))
return false;
// Check the AM/PM portion
CString strAMPM = GetAMPM();
bool b24HourFormat = IsShowing24HourFormat();
if ((b24HourFormat && !strAMPM.IsEmpty()) ||
(!b24HourFormat && (strAMPM != m_strAM && strAMPM != m_strPM)))
return false;
if (!b24HourFormat && strAMPM == m_strPM)
{
nHour += 12;
if (nHour == 24)
nHour = 0;
}
if (!bShowingSeconds)
nSecond = m_timeMin.GetSecond(); // avoids possible problem when checking range below
// Check that it's valid
COleDateTime date(AMS_DEFAULT_DATE, nHour, nMinute, nSecond);
if (date.GetStatus() != COleDateTime::valid)
return false;
// Check the range if desired
if (bCheckRangeAlso)
return IsValid(date, false);
return true;
}
// Returns true if the control's time is valid and falls within the allowed range.
// If bShowErrorIfNotValid is true, an error message box is shown and the control gets the focus.
bool CAMSEdit::TimeBehavior::CheckIfValid(bool bShowErrorIfNotValid /*= true*/)
{
if (!m_pEdit->IsWindowEnabled())
return true;
bool bValid = IsValid();
if (!bValid && bShowErrorIfNotValid)
{
ShowErrorMessage();
m_pEdit->SetFocus();
}
return bValid;
}
// Shows a message box informing the user to enter a valid time within the allowed range.
void CAMSEdit::TimeBehavior::ShowErrorMessage() const
{
AfxMessageBox(_T("Please specify a time between ") + GetFormattedTime(m_timeMin.GetHour(), m_timeMin.GetMinute(), m_timeMin.GetSecond()) + _T(" and ") + GetFormattedTime(m_timeMax.GetHour(), m_timeMax.GetMinute(), m_timeMax.GetSecond() + '.'), MB_ICONEXCLAMATION);
}
// Sets the range of allowed time values to the given minimum and maximum CTime values.
void CAMSEdit::TimeBehavior::SetRange(const CTime& dateMin, const CTime& dateMax)
{
ASSERT(dateMin >= AMS_MIN_CTIME);
ASSERT(dateMax <= AMS_MAX_CTIME);
m_timeMin.SetTime(dateMin.GetHour(), dateMin.GetMinute(), dateMin.GetSecond());
m_timeMax.SetTime(dateMax.GetHour(), dateMax.GetMinute(), dateMin.GetSecond());
ASSERT(m_timeMin <= m_timeMax);
_Redraw();
}
// Sets the range of allowed time values to the given minimum and maximum COleDateTime values.
void CAMSEdit::TimeBehavior::SetRange(const COleDateTime& dateMin, const COleDateTime& dateMax)
{
ASSERT(dateMin >= AMS_MIN_OLEDATETIME);
ASSERT(dateMax <= AMS_MAX_OLEDATETIME);
m_timeMin.SetTime(dateMin.GetHour(), dateMin.GetMinute(), dateMin.GetSecond());
m_timeMax.SetTime(dateMax.GetHour(), dateMax.GetMinute(), dateMin.GetSecond());
ASSERT(m_timeMin <= m_timeMax);
_Redraw();
}
// Retrieves the range of allowed time values inside the given set of CTime pointers.
void CAMSEdit::TimeBehavior::GetRange(CTime* pDateMin, CTime* pDateMax) const
{
if (pDateMin)
*pDateMin = CTime(pDateMin->GetYear(), pDateMin->GetMonth(), pDateMin->GetDay(), m_timeMin.GetHour(), m_timeMin.GetMinute(), m_timeMin.GetSecond());
if (pDateMax)
*pDateMax = CTime(pDateMin->GetYear(), pDateMin->GetMonth(), pDateMin->GetDay(), m_timeMax.GetHour(), m_timeMax.GetMinute(), m_timeMax.GetSecond());
}
// Retrieves the range of allowed time values inside the given set of COleDateTime pointers.
void CAMSEdit::TimeBehavior::GetRange(COleDateTime* pDateMin, COleDateTime* pDateMax) const
{
if (pDateMin)
*pDateMin = COleDateTime(pDateMin->GetYear(), pDateMin->GetMonth(), pDateMin->GetDay(), m_timeMin.GetHour(), m_timeMin.GetMinute(), m_timeMin.GetSecond());
if (pDateMax)
*pDateMax = COleDateTime(pDateMin->GetYear(), pDateMin->GetMonth(), pDateMin->GetDay(), m_timeMax.GetHour(), m_timeMax.GetMinute(), m_timeMax.GetSecond());
}
// Returns true if the given date is valid and falls within the range.
bool CAMSEdit::TimeBehavior::IsValid(const COleDateTime& date, bool bDateOnly /*= true*/) const
{
return (date.GetStatus() == COleDateTime::valid && date >= m_timeMin && date <= m_timeMax);
}
// Sets the character used to separate the hour, minute, and second values.
void CAMSEdit::TimeBehavior::SetSeparator(TCHAR cSep)
{
ASSERT(cSep);
ASSERT(!_istdigit(cSep));
if (m_cSep != cSep)
{
m_cSep = cSep;
_Redraw();
}
}
// Returns the character used to separate the hour, minute, and second values.
TCHAR CAMSEdit::TimeBehavior::GetSeparator() const
{
return m_cSep;
}
// Sets whether the hour should be shown in 24-Hour format or in 12-Hour format with the AM/PM symbols.
// Note: By default, this is based on the user's system configuration.
void CAMSEdit::TimeBehavior::Show24HourFormat(bool bShow24HourFormat /*= true*/)
{
ModifyFlags(bShow24HourFormat ? TwentyFourHourFormat : 0, bShow24HourFormat ? 0 : TwentyFourHourFormat);
}
// Returns true if the hour will be shown in 24-Hour format (instead of 12-Hour format with the AM/PM symbols).
bool CAMSEdit::TimeBehavior::IsShowing24HourFormat() const
{
return (m_uFlags & TwentyFourHourFormat) ? true : false;
}
// Sets whether the seconds will be shown.
void CAMSEdit::TimeBehavior::ShowSeconds(bool bShowSeconds /*= true*/)
{
ModifyFlags(bShowSeconds ? WithSeconds : 0, bShowSeconds ? 0 : WithSeconds);
}
// Returns true if the seconds will be shown.
bool CAMSEdit::TimeBehavior::IsShowingSeconds() const
{
return (m_uFlags & WithSeconds) ? true : false;
}
// Sets the symbols to display for AM and PM, which must each be of the same length.
// Note: By default, these symbols are retrieved from the user's system configuration.
void CAMSEdit::TimeBehavior::SetAMPMSymbols(const CString& strAM, const CString& strPM)
{
ASSERT(strAM.GetLength() == strPM.GetLength()); // make sure they're the same length
m_strAM = strAM;
m_strPM = strPM;
if (m_strAM == _T(""))
m_strAM = AMS_AM_SYMBOL;
if (m_strPM == _T(""))
m_strPM = AMS_PM_SYMBOL;
m_nAMPMLength = m_strAM.GetLength();
_Redraw();
}
// Retrieves the symbols to display for AM and PM into the given string pointers, which must be valid.
void CAMSEdit::TimeBehavior::GetAMPMSymbols(CString* pStrAM, CString* pStrPM) const
{
ASSERT(pStrAM);
ASSERT(pStrPM);
*pStrAM = m_strAM;
*pStrPM = m_strPM;
}
// Returns the control's value in a valid format.
CString CAMSEdit::TimeBehavior::_GetValidText() const
{
CString strText = m_pEdit->GetText();
// If it's empty or has a valid time, return it
if (strText.IsEmpty())
return strText;
if (IsValid(false))
return GetFormattedTime(GetHour(), GetMinute(), GetSecond(), GetAMPM());
// If the hour, minute, and second are invalid, set it to the current time
if (GetHour() < 0 && GetMinute() < 0 && GetSecond() < 0)
{
CTime time = CTime::GetCurrentTime();
return GetFormattedTime(time.GetHour(), time.GetMinute(), time.GetSecond());
}
// Otherwise retrieve the validated time
return GetFormattedTime(GetValidHour(true), GetValidMinute(), GetValidSecond(), GetAMPM());
}
// Formats the given hour, minute, second, and optional AM/PM symbol into a string based on the proper format.
CString CAMSEdit::TimeBehavior::GetFormattedTime(int nHour, int nMinute, int nSecond, const CString& strAMPM /*= _T("")*/) const
{
if (IsShowing24HourFormat())
{
// Handle switching from AM/PM to 24-hour format
if (IsValidAMPM(strAMPM))
nHour = ConvertTo24Hour(nHour, strAMPM);
}
else
{
// Handle switching from 24-hour format to AM/PM
if (!IsValidAMPM(strAMPM))
nHour = ConvertToAMPMHour(nHour, const_cast<CString*>(&strAMPM));
}
CString strText;
if (IsShowingSeconds())
{
if (IsShowing24HourFormat())
strText.Format(_T("%02d%c%02d%c%02d"), nHour, m_cSep, nMinute, m_cSep, nSecond);
else
strText.Format(_T("%02d%c%02d%c%02d %s"), nHour, m_cSep, nMinute, m_cSep, nSecond, strAMPM);
}
else
{
if (IsShowing24HourFormat())
strText.Format(_T("%02d%c%02d"), nHour, m_cSep, nMinute);
else
strText.Format(_T("%02d%c%02d %s"), nHour, m_cSep, nMinute, strAMPM);
}
return strText;
}
// Adjusts the control's value to be within the range of allowed time values.
void CAMSEdit::TimeBehavior::AdjustWithinRange()
{
// Check if it's already within the range
if (IsValid())
return;
// If it's empty, set it to the current time
if (m_pEdit->GetText().IsEmpty())
m_pEdit->SetWindowText(_T(" "));
else
_Redraw();
// Make it fall within the range
COleDateTime date = GetOleTime();
if (date < m_timeMin)
SetTime(m_timeMin);
else if (date > m_timeMax)
SetTime(m_timeMax);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSEdit::DateTimeBehavior
CAMSEdit::DateTimeBehavior::DateTimeBehavior(CAMSEdit* pEdit) :
DateBehavior(pEdit),
TimeBehavior(pEdit),
Behavior(pEdit)
{
m_nHourStart = 11;
}
// Sets the month, day, year, hour, minute, and second on the control to the given values, which must be valid.
void CAMSEdit::DateTimeBehavior::SetDateTime(int nYear, int nMonth, int nDay, int nHour, int nMinute, int nSecond /*= 0*/)
{
if (m_uFlags & DateOnly)
SetDate(nYear, nMonth, nDay);
else if (m_uFlags & TimeOnly)
SetTime(nHour, nMinute, nSecond);
else
{
ASSERT(IsValid(COleDateTime(nYear, nMonth, nDay, nHour, nMinute, nSecond)));
m_pEdit->SetWindowText(GetFormattedDate(nYear, nMonth, nDay) + ' ' + GetFormattedTime(nHour, nMinute, nSecond));
}
}
// Sets the month, day, year, hour, minute, and second on the control to the CTime object, which must be valid.
void CAMSEdit::DateTimeBehavior::SetDateTime(const CTime& dt)
{
SetDateTime(dt.GetYear(), dt.GetMonth(), dt.GetDay(), dt.GetHour(), dt.GetMinute(), dt.GetSecond());
}
// Sets the month, day, year, hour, minute, and second on the control to the COleDateTime object, which must be valid.
void CAMSEdit::DateTimeBehavior::SetDateTime(const COleDateTime& dt)
{
SetDateTime(dt.GetYear(), dt.GetMonth(), dt.GetDay(), dt.GetHour(), dt.GetMinute(), dt.GetSecond());
}
// Sets the month, day, year, hour, minute, and second on the control to the current date and time.
void CAMSEdit::DateTimeBehavior::SetToNow()
{
SetDateTime(COleDateTime::GetCurrentTime());
}
// Returns the date and time on the control as a CTime object.
CTime CAMSEdit::DateTimeBehavior::GetDateTime() const
{
CTime date = GetDate();
CTime time = GetTime();
if (m_uFlags & DateOnly)
return date;
if (m_uFlags & TimeOnly)
return time;
return CTime(date.GetYear(), date.GetMonth(), date.GetDay(), time.GetHour(), time.GetMinute(), time.GetSecond());
}
// Returns the date and time on the control as a COleDateTime object.
COleDateTime CAMSEdit::DateTimeBehavior::GetOleDateTime() const
{
COleDateTime date = GetOleDate();
COleDateTime time = GetOleTime();
if (m_uFlags & DateOnly)
return date;
if (m_uFlags & TimeOnly)
return time;
return COleDateTime(date.GetYear(), date.GetMonth(), date.GetDay(), time.GetHour(), time.GetMinute(), time.GetSecond());
}
// Returns true if the control's date and time is valid and falls within the allowed range.
bool CAMSEdit::DateTimeBehavior::IsValid() const
{
if (m_uFlags & DateOnly)
return DateBehavior::IsValid();
if (m_uFlags & TimeOnly)
return TimeBehavior::IsValid();
return (DateBehavior::IsValid() && TimeBehavior::IsValid());
}
// Sets the range of allowed date values to the given minimum and maximum CTime values.
void CAMSEdit::DateTimeBehavior::SetRange(const CTime& dateMin, const CTime& dateMax)
{
DateBehavior::SetRange(dateMin, dateMax);
TimeBehavior::SetRange(dateMin, dateMax);
}
// Sets the range of allowed date values to the given minimum and maximum COleDateTime values.
void CAMSEdit::DateTimeBehavior::SetRange(const COleDateTime& dateMin, const COleDateTime& dateMax)
{
DateBehavior::SetRange(dateMin, dateMax);
TimeBehavior::SetRange(dateMin, dateMax);
}
// Retrieves the range of allowed date and time values inside the given set of CTime pointers.
void CAMSEdit::DateTimeBehavior::GetRange(CTime* pDateMin, CTime* pDateMax) const
{
DateBehavior::GetRange(pDateMin, pDateMax);
TimeBehavior::GetRange(pDateMin, pDateMax);
}
// Retrieves the range of allowed date and time values inside the given set of COleDateTime pointers.
void CAMSEdit::DateTimeBehavior::GetRange(COleDateTime* pDateMin, COleDateTime* pDateMax) const
{
DateBehavior::GetRange(pDateMin, pDateMax);
TimeBehavior::GetRange(pDateMin, pDateMax);
}
// Returns true if the given date and time is valid and falls within the range.
bool CAMSEdit::DateTimeBehavior::IsValid(const COleDateTime& date, bool bDateOnly /*= true*/) const
{
if (bDateOnly)
return DateBehavior::IsValid(date);
COleDateTime dt(GetYear(), GetMonth(), GetDay(), date.GetHour(), date.GetMinute(), date.GetSecond());
COleDateTime dtMin(m_dateMin.GetYear(), m_dateMin.GetMonth(), m_dateMin.GetDay(), m_timeMin.GetHour(), m_timeMin.GetMinute(), m_timeMin.GetSecond());
COleDateTime dtMax(m_dateMax.GetYear(), m_dateMax.GetMonth(), m_dateMax.GetDay(), m_timeMax.GetHour(), m_timeMax.GetMinute(), m_timeMax.GetSecond());
return (dt.GetStatus() == COleDateTime::valid && dt >= dtMin && dt <= dtMax);
}
// Sets the character used to separate the date or time values, depending on the flag.
void CAMSEdit::DateTimeBehavior::SetSeparator(TCHAR cSep, bool bDate)
{
if (bDate)
DateBehavior::SetSeparator(cSep);
else
TimeBehavior::SetSeparator(cSep);
}
// Returns the character used to separate the date or time values, depending on the flag.
TCHAR CAMSEdit::DateTimeBehavior::GetSeparator(bool bDate) const
{
return (bDate ? DateBehavior::GetSeparator() : TimeBehavior::GetSeparator());
}
// Adds and removes flags from the behavior and then redraws the control
void CAMSEdit::DateTimeBehavior::ModifyFlags(UINT uAdd, UINT uRemove)
{
UINT uFlags = (m_uFlags & ~uRemove) | uAdd;
m_nHourStart = (uFlags & TimeOnly) ? 0 : 11;
Behavior::ModifyFlags(uAdd, uRemove);
}
// Returns the control's value in a valid format.
CString CAMSEdit::DateTimeBehavior::_GetValidText() const
{
// Check if we're showing the date only
CString strDate = DateBehavior::_GetValidText();
if (m_uFlags & DateOnly)
return strDate;
// Check if we're showing the time only
CString strTime = TimeBehavior::_GetValidText();
if (m_uFlags & TimeOnly)
return strTime;
CString strSpace = (!strDate.IsEmpty() && !strTime.IsEmpty() ? _T(" ") : _T(""));
return strDate + strSpace + strTime;
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSEdit::DateTimeBehavior::_OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
// Check to see if it's read only
if (m_pEdit->IsReadOnly())
return;
// Check if we're showing the date or the time only
if (m_uFlags & DateOnly)
{
DateBehavior::_OnChar(uChar, nRepCnt, nFlags);
return;
}
if (m_uFlags & TimeOnly)
{
TimeBehavior::_OnChar(uChar, nRepCnt, nFlags);
return;
}
TCHAR c = static_cast<TCHAR>(uChar);
int nStart, nEnd;
DateBehavior::m_pEdit->GetSel(nStart, nEnd);
CString strText = m_pEdit->GetText();
int nLen = strText.GetLength();
if (nStart >= 0 && nStart <= 9)
{
DateBehavior::_OnChar(uChar, nRepCnt, nFlags);
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else if (nStart == 10)
{
DateBehavior::_OnChar(uChar, nRepCnt, nFlags);
int nSpace = 0;
if (c == ' ')
nSpace = 1;
else
nSpace = (IsValidHourDigit(c, 0) || (IsValidHourDigit(c, 1) && nLen <= 11) ? 2 : 0);
// If we need the space, enter it
if (nSpace && _ShouldEnter(c))
{
DateBehavior::m_pEdit->SetSel(nStart, nStart + 1, FALSE);
DateBehavior::m_pEdit->ReplaceSel(CString(' '), TRUE);
}
// If the space is to be preceded by a valid digit, "type" it in.
if (nSpace == 2)
keybd_event((BYTE)c, 0, 0, 0);
else
ChangeAMPM(c); // allow changing AM/PM (if it's being shown) by pressing A or P
}
else
TimeBehavior::_OnChar(uChar, nRepCnt, nFlags);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSEdit::DateTimeBehavior::_OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
// Check if we're showing the time only
if (m_uFlags & TimeOnly)
{
TimeBehavior::_OnKeyDown(uChar, nRepCnt, nFlags);
return;
}
DateBehavior::_OnKeyDown(uChar, nRepCnt, nFlags);
if ((uChar == VK_UP || uChar == VK_DOWN) && !(m_uFlags & DateOnly))
TimeBehavior::_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
// It's used here to check if any action needs to be taken based on the control's value.
void CAMSEdit::DateTimeBehavior::_OnKillFocus(CWnd* pNewWnd)
{
DateBehavior::_OnKillFocus(pNewWnd);
}
// Shows a message box informing the user to enter a valid date and time within the allowed range.
void CAMSEdit::DateTimeBehavior::ShowErrorMessage() const
{
// Show the message depending on what we're showing
if (m_uFlags & DateOnly)
DateBehavior::ShowErrorMessage();
else if (m_uFlags & TimeOnly)
TimeBehavior::ShowErrorMessage();
else
{
CString strMinDateTime =
GetFormattedDate(m_dateMin.GetYear(), m_dateMin.GetMonth(), m_dateMin.GetDay()) + ' ' +
GetFormattedTime(m_timeMin.GetHour(), m_timeMin.GetMinute(), m_timeMin.GetSecond());
CString strMaxDateTime =
GetFormattedDate(m_dateMax.GetYear(), m_dateMax.GetMonth(), m_dateMax.GetDay()) + ' ' +
GetFormattedTime(m_timeMax.GetHour(), m_timeMax.GetMinute(), m_timeMax.GetSecond());
AfxMessageBox(_T("Please specify a date and time between ") + strMinDateTime + _T(" and ") + strMaxDateTime + '.', MB_ICONEXCLAMATION);
}
}
/////////////////////////////////////////////////////////////////////////////
// CAMSAlphanumericEdit window
// Constructs the object using the given set of strInvalidChars
CAMSAlphanumericEdit::CAMSAlphanumericEdit(int nMaxChars /*= 0*/, const CString& strInvalidChars /*= _T("%'*\"+?><:\\"")*/) :
AlphanumericBehavior(this, nMaxChars, strInvalidChars)
{
}
BEGIN_MESSAGE_MAP(CAMSAlphanumericEdit, CEdit)
//{{AFX_MSG_MAP(CAMSAlphanumericEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
// Returns the control's value in a valid format.
CString CAMSAlphanumericEdit::GetValidText() const
{
return _GetValidText();
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSAlphanumericEdit::OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnChar(uChar, nRepCnt, nFlags);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSMaskedEdit window
// Constructs the object using the given numeric strMask.
CAMSMaskedEdit::CAMSMaskedEdit(const CString& strMask /*= _T("")*/) :
MaskedBehavior(this, strMask)
{
}
BEGIN_MESSAGE_MAP(CAMSMaskedEdit, CEdit)
//{{AFX_MSG_MAP(CAMSMaskedEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
// Returns the control's value in a valid format.
CString CAMSMaskedEdit::GetValidText() const
{
return _GetValidText();
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSMaskedEdit::OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnChar(uChar, nRepCnt, nFlags);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSMaskedEdit::OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnKeyDown(uChar, nRepCnt, nFlags);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSNumericEdit window
// Constructs the object using the given nMaxWholeDigits and nMaxDecimalPlaces.
CAMSNumericEdit::CAMSNumericEdit(int nMaxWholeDigits /*= 9*/, int nMaxDecimalPlaces /*= 4*/) :
NumericBehavior(this, nMaxWholeDigits, nMaxDecimalPlaces)
{
}
BEGIN_MESSAGE_MAP(CAMSNumericEdit, CEdit)
//{{AFX_MSG_MAP(CAMSNumericEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
// Returns the control's value in a valid format.
CString CAMSNumericEdit::GetValidText() const
{
return _GetValidText();
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSNumericEdit::OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnChar(uChar, nRepCnt, nFlags);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSNumericEdit::OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
void CAMSNumericEdit::OnKillFocus(CWnd* pNewWnd)
{
_OnKillFocus(pNewWnd);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSIntegerEdit window
// Constructs the object allowing the negative sign or not.
CAMSIntegerEdit::CAMSIntegerEdit(int nMaxWholeDigits /*= 9*/) :
CAMSNumericEdit(nMaxWholeDigits, 0)
{
}
BEGIN_MESSAGE_MAP(CAMSIntegerEdit, CEdit)
//{{AFX_MSG_MAP(CAMSIntegerEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CAMSNumericEdit window
// Constructs the object using the given numeric strMask.
CAMSCurrencyEdit::CAMSCurrencyEdit()
{
m_nDigitsInGroup = 3;
m_nMaxDecimalPlaces = 2;
m_strPrefix = _T("$");
m_uFlags |= OnKillFocus_PadWithZerosAfterDecimal | OnKillFocus_DontPadWithZerosIfEmpty;
// Get the system's current settings
TCHAR szValue[10];
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SCURRENCY, szValue, 0))
{
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SCURRENCY, szValue, sizeof(szValue));
m_strPrefix = szValue;
}
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONGROUPING, szValue, 0))
{
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONGROUPING, szValue, sizeof(szValue));
m_nDigitsInGroup = _ttoi(szValue);
}
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ICURRDIGITS, szValue, 0))
{
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ICURRDIGITS, szValue, sizeof(szValue));
m_nMaxDecimalPlaces = _ttoi(szValue);
}
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONDECIMALSEP, &m_cDecimalPoint, 0))
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONDECIMALSEP, &m_cDecimalPoint, sizeof(m_cDecimalPoint));
if (::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONTHOUSANDSEP, &m_cGroupSeparator, 0))
::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONTHOUSANDSEP, &m_cGroupSeparator, sizeof(m_cGroupSeparator));
}
BEGIN_MESSAGE_MAP(CAMSCurrencyEdit, CEdit)
//{{AFX_MSG_MAP(CAMSCurrencyEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CAMSDateEdit
CAMSDateEdit::CAMSDateEdit() :
DateBehavior(this),
Behavior(this) // required because DateBehavior derives virtually from Behavior
{
}
BEGIN_MESSAGE_MAP(CAMSDateEdit, CEdit)
//{{AFX_MSG_MAP(CAMSDateEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
// Returns the control's value in a valid format.
CString CAMSDateEdit::GetValidText() const
{
return _GetValidText();
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSDateEdit::OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnChar(uChar, nRepCnt, nFlags);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSDateEdit::OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
void CAMSDateEdit::OnKillFocus(CWnd* pNewWnd)
{
_OnKillFocus(pNewWnd);
}
// Handles the WM_PASTE message to ensure that the text being pasted is a valid date.
LONG CAMSDateEdit::OnPaste(UINT, LONG)
{
return _OnPaste(0, 0);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSTimeEdit
CAMSTimeEdit::CAMSTimeEdit() :
TimeBehavior(this),
Behavior(this) // required because TimeBehavior derives virtually from Behavior
{
}
BEGIN_MESSAGE_MAP(CAMSTimeEdit, CEdit)
//{{AFX_MSG_MAP(CAMSTimeEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
// Returns the control's value in a valid format.
CString CAMSTimeEdit::GetValidText() const
{
return _GetValidText();
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSTimeEdit::OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnChar(uChar, nRepCnt, nFlags);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSTimeEdit::OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
void CAMSTimeEdit::OnKillFocus(CWnd* pNewWnd)
{
_OnKillFocus(pNewWnd);
}
// Handles the WM_PASTE message to ensure that the text being pasted is a valid time.
LONG CAMSTimeEdit::OnPaste(UINT, LONG)
{
return _OnPaste(0, 0);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSDateTimeEdit
CAMSDateTimeEdit::CAMSDateTimeEdit() :
DateTimeBehavior(this),
Behavior(this) // required because DateTimeBehavior derives virtually from Behavior
{
}
BEGIN_MESSAGE_MAP(CAMSDateTimeEdit, CEdit)
//{{AFX_MSG_MAP(CAMSDateTimeEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
// Returns the control's value in a valid format.
CString CAMSDateTimeEdit::GetValidText() const
{
return _GetValidText();
}
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSDateTimeEdit::OnChar(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnChar(uChar, nRepCnt, nFlags);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSDateTimeEdit::OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
_OnKeyDown(uChar, nRepCnt, nFlags);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
void CAMSDateTimeEdit::OnKillFocus(CWnd* pNewWnd)
{
_OnKillFocus(pNewWnd);
}
// Handles the WM_PASTE message to ensure that the text being pasted is a valid date.
LONG CAMSDateTimeEdit::OnPaste(UINT, LONG)
{
return _OnPaste(0, 0);
}
/////////////////////////////////////////////////////////////////////////////
// CAMSMultiMaskedEdit
CAMSMultiMaskedEdit::CAMSMultiMaskedEdit() :
AlphanumericBehavior(this),
NumericBehavior(this),
MaskedBehavior(this),
DateTimeBehavior(this),
Behavior(this),
m_pCurrentBehavior((AlphanumericBehavior*)this)
{
}
// Returns the mask
const CString& CAMSMultiMaskedEdit::GetMask() const
{
return MaskedBehavior::m_strMask;
}
// Sets the mask and thereby the edit box's behavior
void CAMSMultiMaskedEdit::SetMask(const CString& strMask)
{
int nLen = strMask.GetLength();
MaskedBehavior::m_strMask = strMask;
// If it doesn't have numeric place holders then it's alphanumeric
int nPos = strMask.Find('#');
if (nPos < 0)
{
m_pCurrentBehavior = (AlphanumericBehavior*)this;
SetMaxCharacters(nLen);
return;
}
// If it's exactly like the date mask, then it's a date
if (strMask == _T("##/##/#### ##:##:##"))
{
m_pCurrentBehavior = (DateTimeBehavior*)this;
m_pCurrentBehavior->ModifyFlags(DateTimeBehavior::WithSeconds, 0);
return;
}
// If it's exactly like the date mask, then it's a date
else if (strMask == _T("##/##/#### ##:##"))
{
m_pCurrentBehavior = (DateTimeBehavior*)this;
return;
}
// If it's exactly like the date mask, then it's a date
else if (strMask == _T("##/##/####"))
{
m_pCurrentBehavior = (DateTimeBehavior*)this;
m_pCurrentBehavior->ModifyFlags(DateTimeBehavior::DateOnly, 0);
return;
}
// If it's exactly like the time mask with seconds, then it's a time
else if (strMask == _T("##:##:##"))
{
m_pCurrentBehavior = (DateTimeBehavior*)this;
m_pCurrentBehavior->ModifyFlags(DateTimeBehavior::TimeOnly | DateTimeBehavior::WithSeconds, 0);
return;
}
// If it's exactly like the time mask, then it's a time
else if (strMask == _T("##:##"))
{
m_pCurrentBehavior = (DateTimeBehavior*)this;
m_pCurrentBehavior->ModifyFlags(DateTimeBehavior::TimeOnly, 0);
return;
}
// If after the first numeric placeholder, we don't find any foreign characters,
// then it's numeric, otherwise it's masked numeric.
CString strSmallMask = strMask.Mid(nPos + 1);
for (int iPos = 0, nSmallLen = strSmallMask.GetLength(); iPos < nSmallLen; iPos++)
{
TCHAR c = strSmallMask[iPos];
if (c != '#' && c != m_cDecimalPoint && c != m_cGroupSeparator)
{
m_pCurrentBehavior = (MaskedBehavior*)this;
MaskedBehavior::m_strMask = _T("");
MaskedBehavior::SetMask(strMask);
return;
}
}
// Verify that it ends in a number; otherwise it's a masked numeric
if (nSmallLen && strSmallMask[nSmallLen - 1] != '#')
{
m_pCurrentBehavior = (MaskedBehavior*)this;
MaskedBehavior::m_strMask = _T("");
MaskedBehavior::SetMask(strMask);
}
else
{
m_pCurrentBehavior = (NumericBehavior*)this;
NumericBehavior::SetMask(strMask);
}
}
// Returns the control's value in a valid format.
CString CAMSMultiMaskedEdit::GetValidText() const
{
ASSERT(m_pCurrentBehavior);
return m_pCurrentBehavior->_GetValidText();
}
BEGIN_MESSAGE_MAP(CAMSMultiMaskedEdit, CEdit)
//{{AFX_MSG_MAP(CAMSMultiMaskedEdit)
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_CUT, OnCut)
ON_MESSAGE(WM_PASTE, OnPaste)
ON_MESSAGE(WM_CLEAR, OnClear)
ON_MESSAGE(WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()
// Handles the WM_CHAR message, which is called when the user enters a regular character or Backspace
void CAMSMultiMaskedEdit::OnChar(UINT uChar, UINT nRepCnt, UINT c)
{
ASSERT(m_pCurrentBehavior);
m_pCurrentBehavior->_OnChar(uChar, nRepCnt, nRepCnt);
}
// Handles the WM_KEYDOWN message, which is called when the user enters a special character such as Delete or the arrow keys.
void CAMSMultiMaskedEdit::OnKeyDown(UINT uChar, UINT nRepCnt, UINT nFlags)
{
ASSERT(m_pCurrentBehavior);
m_pCurrentBehavior->_OnKeyDown(uChar, nRepCnt, nRepCnt);
}
// Handles the WM_KILLFOCUS message, which is called when the user leaves the control.
void CAMSMultiMaskedEdit::OnKillFocus(CWnd* pNewWnd)
{
ASSERT(m_pCurrentBehavior);
m_pCurrentBehavior->_OnKillFocus(pNewWnd);
}
// Handles the WM_PASTE message to ensure that the text being pasted is properly shown.
LONG CAMSMultiMaskedEdit::OnPaste(UINT wParam, LONG lParam)
{
ASSERT(m_pCurrentBehavior);
return m_pCurrentBehavior->_OnPaste(wParam, lParam);
}