// EditFuzzyValue.cpp : implementation file // // Copyright (C) 2002-2004 William Braynen // Contact information can be found at www.samdurak.com // // The CEditFuzzyValue MFC class consists of two files: // EditFuzzyValue.cpp and EditFuzzyValue.h. It is a subclass of CEdit // and allows on-the-fly user data input validation in a Windows // edit control. It allows the user to input numbers only between 0 and // 1 inclusively [0,1] and automatically formats the number to the // second decimal. So a typed in '1' magically turns into '1.00', and // '0.5' into '0.50'. Of course the user can also type in '0.51'. // Typing a decimal point with no preceding zero ('.') results in '0.' // // Use this class just as you would a regular CEdit class. // Just replace 'CEdit' in the declaration by 'CEditFuzzyValue'. // And don't forget to #include EditFuzzyValue.h // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "stdafx.h" #include "EditFuzzyValue.h" #include "assert.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CEditFuzzyValue CEditFuzzyValue::CEditFuzzyValue() { m_bChopOffExtraZeros = false; } CEditFuzzyValue::~CEditFuzzyValue() { } BEGIN_MESSAGE_MAP(CEditFuzzyValue, CEdit) //{{AFX_MSG_MAP(CEditFuzzyValue) ON_WM_CHAR() ON_WM_KILLFOCUS() ON_WM_KEYDOWN() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CEditFuzzyValue message handlers void CEditFuzzyValue::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // Might want to put this call to SetLimitText elsewhere, // so it only gets called once. // NOTE: but don't place it in the constructor because the object // instance doesn't exist until the constructor returns, so // you can't make the call just yet. SetLimitText (4); // See what chars are selected because // selected text will get replaced by // the incoming nChar character. int nStartPos, nEndPos; GetSel (nStartPos, nEndPos); // See what text is currently there char text[5]; text[5] = '\0'; GetWindowText (text, 5); CString str (text); if (0 == nStartPos && 1 == nEndPos && '1' == nChar) { // if the zero to the left of the decimal point is being replace by 1, // then make the whole thing a "1.00" (e.g. "0.34" --> "1.00) SetWindowText ("1.00"); return; } else if (nStartPos != nEndPos) // if something is selected { // // SOMETHING WAS SELECTED; WILL BE REPLACING // if ((nChar < '0' || nChar > '9') && ('.' != nChar) && (VK_BACK != nChar) && ('0' != nChar)) { // Ignore everything but numbers, decimal point, // backspace, and delete keys return; } else if ('.' == nChar) { if (0 == nStartPos && nEndPos == str.GetLength()) { SetWindowText ("0."); SetSel (2, 2); // move cursor all the way to the right return; } } if (0 == nStartPos && 1 == nEndPos) { // If replacing the digit to the left of the decimal point, // then ignore everything but 0's and 1's if (nChar > '1') return; } else if (1 == nStartPos && 2 == nEndPos) { // If trying to replace the decimal point, then ignore return; } else if (nStartPos > 1 && ( ! strcmp (text, "1.00"))) { // If it's "1.00" then do not allow replacing anything to the // right of the decimal point return; } else if ('1' == nChar && 0 == nStartPos && nEndPos == str.GetLength()) { // if replacing everything with a '1', then make it a "1.00" SetWindowText ("1.00"); SetSel (4, 4); // move the cursor to the right of the text return; } // Delete selected text str.Delete (nStartPos, nEndPos - nStartPos); // Add the nChar character (unless it's a backspace) int nNewPos = str.GetLength() < nStartPos ? str.GetLength() : nStartPos; if (VK_BACK != nChar) { str.Insert (nStartPos, CString (nChar)); } // Format the new string and set it as our text str = Format (str); SetWindowText (str); SetSel (nEndPos, nEndPos); // move the cursor back to where it was return; // return for ALL cases of select/replace } // end SOMETHING WAS SELECTED // // NOTHING WAS SELECTED // else if (str.Find ('.', nStartPos) >= nStartPos) { // If the decimal point is to the right of us, ignore all keystrokes return; } else if ('.' == nChar) { // '.' --> "0." SetWindowText ("0."); SetSel (2,2); // move the cursor to the right of the text return; } if (VK_BACK == nChar || (nChar >= '0' && nChar <='9')) { // // Process only numbers and backspace and delete keys (and the // decimal point, which was already taken care of above) // if (0 == LineLength()) { if ('1' == nChar) { // If the first char is a '1', then just turn it into "1.00" // since 1 is the maximum value allowed to be entered. SetWindowText ("1.00"); SetSel (4,4); // move the cursor to the right of the text return; } else if ('0' == nChar) { // And turn zeros, if it's the first character entered, into "0." SetWindowText ("0."); SetSel (2,2); // move the cursor to the right of the text return; } else if (nChar > '1' && nChar <= '9') { // Insert a decimal point if the first digit is greater than 1 // because we don't want anything exceeding 1.00 CString msg; msg.Format ("0.%c", nChar); SetWindowText (msg); SetSel (3,3); return; } } if (VK_BACK == nChar) { if ((nStartPos > 1) && ('.' == text[nStartPos - 1])) { // When backspacing, if we're to the right of the decimal point, then // delete both the decimal point AND the digit to the left of it ("0." or 1."). SetWindowText (""); return; } else if ( ! strcmp (text, "1.00")) { // Also treat "1.00" as one character - delete all four when backspacing. SetWindowText (""); return; } } // end custom BACKSPACE behavior // The parent's method will handle anything that we haven't // handled so far; in other words, anything that doesn't // have to behave in a "special" custom-tailored kind of way. CEdit::OnChar(nChar, nRepCnt, nFlags); } // end if (process only backspace and digits) } // end OnChar void CEditFuzzyValue::OnKillFocus(CWnd* pNewWnd) { CEdit::OnKillFocus(pNewWnd); // TODO: Add your message handler code here char text[5]; text[5] = '\0'; GetWindowText (text, 5); SetWindowText( Format(text) ); } BOOL CEditFuzzyValue::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class WPARAM nChar = pMsg->wParam; // virtual-key code if (WM_KEYDOWN == pMsg->message) { // see what text is there char text[5]; text[5] = '\0'; GetWindowText (text, 5); switch (nChar) { case VK_RETURN: { // If the RETURN/ENTER key is pressed, format the text CString formattedText; SetWindowText( formattedText = Format(text) ); // move the cursor all the way to the right int len = formattedText.GetLength(); SetSel (len,len); break; } case VK_DELETE: { // Prevent from deleting the decimal point // unless we're deleting the entire text int nStartPos, nEndPos; GetSel (nStartPos, nEndPos); int len = (int) strlen (text); bool isEntireTextSelected = (0 == nStartPos && len == nEndPos); if ( ! isEntireTextSelected && nStartPos < 2) { return TRUE; } break; } } // end switch } // end if return CEdit::PreTranslateMessage(pMsg); } CString CEditFuzzyValue::Format(CString str) { // If text is null or empty, make it "0" if ( ! str || str.GetLength() <= 0) { str = "0"; } // "." -> "0." else if ('.' == str[0]) { if (str.GetLength() <= 1) str = "0."; else // assuming only text.GetLength() == 2 for now str = CString ("0.") + str[1]; } // Remove all extra whitespace (spaces, tabs) // INSERT CODE HERE else if (str.GetLength() > 1 && '0' == str[0] && (str[1] >= '0' && str[1] <= '9')) { // "03" -> "0.3" str = CString ("0.") + str[1]; } else if (atof (str) > 1) { // "52" -> ".52" str = CString ("0.") + str; } str = FormatExtraZeros (str); float fValue = (float) atof (str); if (fValue > 1) { if (fValue < 10) str.Format ("%.2f", fValue / 10); else str.Format ("%.2f", fValue / 100); str = FormatExtraZeros (str); } return str; } //// // // Pre-condition: // (0 <= atof (text) <= 1) /\ // (text does NOT start with '.') // CString CEditFuzzyValue::FormatExtraZeros (CString text) { // Enforce the pre-condition //if (GetFocus() == this) return text; float fValue = (float) atof (text); if (fValue < 0 || fValue > 1 || '.' == text[0]) { assert (false); return "?"; } if ( ! m_bChopOffExtraZeros ) { text.Format ("%.2f", fValue); return text; } if (0 == fValue) { return "0"; } else if (1 == fValue) { return "1"; } // "0.20" -> "0.2" if (text.GetLength() > 3 && '0' == text[3]) { text.Delete (3); } return text; } float CEditFuzzyValue::GetValue() { char text[5]; text[5] = '\0'; GetWindowText (text, 5); return (float) atof (text); } void CEditFuzzyValue::SetValue(float fValue) { CString msg; msg.Format ("%.2f", fValue); msg = Format (msg); SetWindowText (msg); }