www.pudn.com > NumericEdit_Demo.rar > NumericEdit.cpp
/*
CNumericEdit - A clone of the spinedit control used in Jasc's Paint Shop Pro
Contact : jg@jgsoftware.com
Use the code for anything, just submit suggestions and bug fixes to above address.
4/13/2004 - 1.0 Initial release
4/20/2004 - 1.02 Bug fixes and some enhancements
Fixed bug where the edit would only accept hex digits. Also now allow negative sign "-" to be entered
Added NES_METER style to make the small bar under the edit field optional
Control will now reconfigure itself when changing style bits
Fixed max value bug. The full range is now accesible with the popup
Added NES_LIVEUPDATE style. This style allows the control to notify it's parent during value tracking. Without this style, the parent is updated only at the end of tracking
Demo has been updated to show the new features
Added DDX_NumericEdit() function for DDX support
Now using the active window caption color for the bar colors
Changed hardcoded custom messages to use windows registered messages so as not to cause any conflict with other custom messages
*/
#include "stdafx.h"
#include "NumericEdit.h"
void AFXAPI DDX_NumericEdit(CDataExchange* pDX, int nIDC, int& value)
{
pDX->PrepareCtrl(nIDC);
HWND hWndCtrl;
pDX->m_pDlgWnd->GetDlgItem(nIDC, &hWndCtrl);
if (pDX->m_bSaveAndValidate)
value = (int) ::SendMessage(hWndCtrl, NEM_GETPOS, 0, 0l);
else
::SendMessage(hWndCtrl, NEM_SETPOS, TRUE, value);
}
// CMyEdit
IMPLEMENT_DYNAMIC(CMyEdit, CEdit)
BEGIN_MESSAGE_MAP(CMyEdit, CEdit)
ON_WM_KEYDOWN()
ON_WM_CHAR()
ON_CONTROL_REFLECT(EN_CHANGE, OnEnChange)
ON_WM_CTLCOLOR_REFLECT()
END_MESSAGE_MAP()
// CMyEdit message handlers
//****************************************************************************
// CMyEdit::OnKeyDown - Intercepts pressed keys while focused and passes UP
// and down to the parent.
//****************************************************************************
void CMyEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == VK_UP || nChar == VK_DOWN)
GetParent()->SendMessage(WM_KEYDOWN,(WPARAM)nChar,MAKELPARAM(nRepCnt,nFlags));
else
CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}
//****************************************************************************
// CMyEdit::OnChar - Only allows numbers and backspace
//****************************************************************************
void CMyEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (!_istdigit(nChar) && (nChar != 8) & (nChar != _T('-')))
{
MessageBeep(0);
return;
}
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
//****************************************************************************
// CMyEdit::OnEnChange - Converts current text contents to an int and passes
// it to the parent using a private message. Then it
// determines if it's out of range so that CtlColor()
// knows what background color to return
//****************************************************************************
void CMyEdit::OnEnChange()
{
CString str;
int nMin, nMax;
GetWindowText(str);
int nValue = _tstoi(str);
GetParent()->SendMessage(NEM_GETRANGE,(WPARAM) &nMin,(LPARAM) &nMax);
BOOL fNewOverFlow = (nValue < nMin) || (nValue > nMax);
if (fNewOverFlow != m_fOverFlow)
{
m_fOverFlow = fNewOverFlow;
Invalidate();
}
GetParent()->SendMessage(NEM_SETPOS,0,(LPARAM) nValue);
}
//****************************************************************************
// CMyEdit::CtlColor - Depending on the state of m_bOverflow, causes the edit
// background to either show as default or the error color
//****************************************************************************
HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT /*nCtlColor*/)
{
pDC->SetBkMode(TRANSPARENT);
if (m_fOverFlow)
return m_brError;
else
return NULL;
}
//****************************************************************************
//****************************************************************************
//****************************************************************************
//****************************************************************************
// CMyButton
static const UINT NEM_POPUPSTATECHANGE = ::RegisterWindowMessage(_T("NEM_POPUPSTATECHANGE-{B32E120B-ED89-420a-A757-19A978AFC2A9}"));
static const UINT NEM_SHOWPOPUP = ::RegisterWindowMessage(_T("NEM_SHOWPOPUP-{84252E3C-DBD7-4fb6-A56A-160A0D00816A}"));
#define PUSC_OPENING 0
#define PUSC_CLOSING 1
IMPLEMENT_DYNAMIC(CMyButton, CButton)
BEGIN_MESSAGE_MAP(CMyButton, CButton)
ON_MESSAGE(BM_SETSTYLE,OnBnSetStyle)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
// CMyButton message handlers
//****************************************************************************
// CMyButton::OnBnSetStyle - Prevents the button from presenting itself with
// a black border. A black border is normally used
// to indicate the default button.
//****************************************************************************
LRESULT CMyButton::OnBnSetStyle(WPARAM wParam,LPARAM lParam)
{
return DefWindowProc(BM_SETSTYLE, (wParam & ~BS_DEFPUSHBUTTON), lParam);
}
//****************************************************************************
// CMyButton::OnLButtonDown - Creates and displays a CTrackPopup window. The
// message is NOT forwarded to the base class
// because that would cause the button to capture
// the mouse and we need to capture the mouse in
// the popup window. SetState() is called to redraw
// the button in the pressed state.
//****************************************************************************
void CMyButton::OnLButtonDown(UINT nFlags, CPoint point)
{
CButton::OnLButtonDown(nFlags,point);
GetParent()->SendMessage(NEM_SHOWPOPUP);
SetState(TRUE);
}
//****************************************************************************
//****************************************************************************
//****************************************************************************
//****************************************************************************
// CMyTrackPopup
#define THUMB_WIDTH 6 // Width of the track thumb
IMPLEMENT_DYNAMIC(CMyTrackPopup, CWnd)
BEGIN_MESSAGE_MAP(CMyTrackPopup, CWnd)
ON_WM_LBUTTONUP()
ON_WM_PAINT()
ON_WM_MOUSEMOVE()
ON_WM_SIZE()
END_MESSAGE_MAP()
//****************************************************************************
// CMyTrackPopup::Create - Creates the actual popup window while saving
// nesassery values. Also calculates the x position of
// the window so that the thumb will line up with the
// mouse cursor. Finally call SetCapture()
//****************************************************************************
BOOL CMyTrackPopup::Create(int x,int y,int nMin,int nMax,int nValue,CWnd* pParent)
{
CString sClassName;
m_nMin = nMin;
m_nMax = nMax;
m_nValue = nValue;
m_pNotify = pParent;
int nSBHeight = GetSystemMetrics(SM_CYHSCROLL);
sClassName = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,::LoadCursor(NULL, IDC_SIZEWE),(HBRUSH)0);
if(!CreateEx(0,sClassName,0,WS_POPUP|WS_VISIBLE|WS_DLGFRAME,x,y,200,nSBHeight,pParent->GetSafeHwnd(),(HMENU) 0))
{
TRACE0("Failed to create CNumericEdit popup\n");
return FALSE;
}
// Calculate x position
int nDlgBorderWidth = GetSystemMetrics(SM_CXDLGFRAME);
nValue-= nMin;
x-= (int)((m_dPixelValue * nValue) + 0.5);
x-= nDlgBorderWidth;
SetWindowPos(0,x,y,0,0,SWP_NOSIZE | SWP_NOZORDER);
m_pNotify->SendMessage(NEM_POPUPSTATECHANGE,PUSC_OPENING);
SetCapture();
return TRUE;
}
// CMyButton message handlers
//****************************************************************************
// CMyTrackPopup::OnPaint - Calculates and paints the bar and thumb
//****************************************************************************
void CMyTrackPopup::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect rcRect;
GetClientRect(&rcRect);
int nXPos = (int)((m_dPixelValue * (double)(m_nValue - m_nMin)));
// Draw bar
dc.FillSolidRect(rcRect.left,rcRect.top,nXPos,rcRect.Height(),GetSysColor(COLOR_ACTIVECAPTION));
dc.FillSolidRect(rcRect.left + nXPos + 1,rcRect.top,rcRect.Width() - nXPos,rcRect.Height(),GetSysColor(COLOR_BTNFACE));
// Draw thumb
rcRect.left+= nXPos;
rcRect.right = rcRect.left + THUMB_WIDTH;
dc.FillSolidRect(&rcRect,GetSysColor(COLOR_BTNFACE));
dc.DrawEdge(&rcRect,EDGE_RAISED,BF_RECT);
}
//****************************************************************************
// CMyTrackPopup::OnSize - Calculates the m_dPixelvalue private var.
//****************************************************************************
void CMyTrackPopup::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
m_dPixelValue = (double)(cx - THUMB_WIDTH) / (double)(m_nMax - m_nMin);
}
//****************************************************************************
// CMyTrackPopup::OnLButtonUp - Releases the mouse and sends a closing message
// to the pNotify window. Finally destroyes
// itself and releases it's memory.
//****************************************************************************
void CMyTrackPopup::OnLButtonUp(UINT nFlags, CPoint point)
{
ReleaseCapture();
CWnd::OnLButtonUp(nFlags, point);
m_pNotify->SendMessage(NEM_POPUPSTATECHANGE,PUSC_CLOSING);
DestroyWindow();
delete this;
}
//****************************************************************************
// CMyTrackPopup::OnMouseMove - Calculates new value and sends it to the
// pNotify window. Also invalidates the bar area
// causing a repaint.
//****************************************************************************
void CMyTrackPopup::OnMouseMove(UINT nFlags, CPoint point)
{
CRect rcRect;
GetClientRect(&rcRect);
int nPos = point.x;
if (nPos < 0) nPos = 0;
if (nPos > rcRect.right - THUMB_WIDTH) nPos = rcRect.right - THUMB_WIDTH;
m_nValue = ((int)(((double)nPos) / m_dPixelValue)) + m_nMin;
InvalidateRect(&rcRect);
m_pNotify->SendMessage(NEM_SETPOS,0,(LPARAM)m_nValue);
CWnd::OnMouseMove(nFlags, point);
}
//****************************************************************************
//****************************************************************************
//****************************************************************************
//****************************************************************************
// CNumericEdit
#define BARHEIGHT 3 // Height of the bar
IMPLEMENT_DYNAMIC(CNumericEdit, CWnd)
CNumericEdit::CNumericEdit()
{
m_nMin = 0;
m_nMax = 100;
m_nValue = 0;
m_fCaptured = FALSE;
m_fTracking = FALSE;
}
//****************************************************************************
// CNumericEdit::GetValue -
//****************************************************************************
int CNumericEdit::GetValue()
{
return m_nValue;
}
//****************************************************************************
// CNumericEdit::SetValue -
//****************************************************************************
void CNumericEdit::SetValue(int nValue)
{
static BOOL fInFunction = FALSE;
if (fInFunction) return;
if (nValue != m_nValue)
{
fInFunction = TRUE;
//
CString str;
str.Format(_T("%d"),nValue);
if (nValue < m_nMin) nValue = m_nMin;
if (nValue > m_nMax) nValue = m_nMax;
m_nValue = nValue;
m_wndEdit.SetWindowText(str);
int nLength = str.GetLength();
m_wndEdit.SetSel(nLength,nLength);
m_wndSpinButton.SetPos(m_nValue);
InvalidateRect(&m_rcBar,FALSE);
if (!m_fTracking || (GetStyle() & NES_LIVEUPDATE))
GetParent()->SendMessage(NEN_CHANGED,0,m_nValue);
fInFunction = FALSE;
}
}
//****************************************************************************
// CNumericEdit::GetReadOnly -
//****************************************************************************
BOOL CNumericEdit::GetReadOnly()
{
return (m_wndEdit.GetStyle() & ES_READONLY) > 0;
}
//****************************************************************************
// CNumericEdit::SetReadOnly -
//****************************************************************************
void CNumericEdit::SetReadOnly(BOOL fReadOnly)
{
m_wndEdit.SetReadOnly(fReadOnly);
}
//****************************************************************************
// CNumericEdit::GetRange
//****************************************************************************
void CNumericEdit::GetRange(int& nMin,int& nMax)
{
nMin = m_nMin;
nMax = m_nMax;
}
//****************************************************************************
// CNumericEdit::SetRange -
//****************************************************************************
void CNumericEdit::SetRange(int nMin,int nMax)
{
if (nMin != m_nMin || nMax != m_nMax)
{
m_nMin = nMin;
m_nMax = nMax;
m_wndSpinButton.SetRange32(m_nMin,m_nMax);
if (m_nValue < m_nMin) m_nValue = m_nMin;
if (m_nValue < m_nMin) m_nValue = m_nMin;
CString str;
str.Format(_T("%d"),m_nValue);
m_wndEdit.SetWindowText(str);
int nLength = str.GetLength();
m_wndEdit.SetSel(nLength,nLength);
m_dPixelValue = (double)m_rcBar.Width() / (double)(m_nMax - m_nMin);
InvalidateRect(&m_rcBar);
}
}
//****************************************************************************
// CNumericEdit::ResizeChildren -
//****************************************************************************
void CNumericEdit::ResizeChildren(int cx,int cy)
{
int nWidth = GetSystemMetrics(SM_CXHSCROLL);
int nCurx = cx;
if (GetStyle() & NES_POPUP)
{
nCurx-= nWidth;
m_wndButton.MoveWindow(nCurx,0,nWidth,cy);
}
else
m_wndButton.MoveWindow(0,0,0,0);
if (GetStyle() & NES_SPIN)
{
nCurx-= nWidth;
m_wndSpinButton.MoveWindow(nCurx,0,nWidth,cy);
}
else
m_wndSpinButton.MoveWindow(0,0,0,0);
int nEditHeight;
if (GetStyle() & NES_METER)
nEditHeight= cy - BARHEIGHT - 1;
else
nEditHeight = cy;
m_wndEdit.MoveWindow(0,0,nCurx,nEditHeight);
m_rcBar.left = 0;
m_rcBar.top = cy - BARHEIGHT;
m_rcBar.right = nCurx;
m_rcBar.bottom = cy;
m_dPixelValue = (double)(nCurx) / (double)(m_nMax - m_nMin);
}
BEGIN_MESSAGE_MAP(CNumericEdit, CWnd)
ON_WM_PAINT()
ON_WM_SIZE()
ON_WM_CREATE()
ON_WM_LBUTTONDOWN()
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONUP()
ON_WM_VSCROLL()
ON_WM_SETFOCUS()
ON_WM_KEYDOWN()
ON_WM_STYLECHANGED()
ON_REGISTERED_MESSAGE(NEM_GETPOS,OnGetPos)
ON_REGISTERED_MESSAGE(NEM_SETPOS,OnSetPos)
ON_REGISTERED_MESSAGE(NEM_GETRANGE,OnGetRange)
ON_REGISTERED_MESSAGE(NEM_SETRANGE,OnSetRange)
ON_REGISTERED_MESSAGE(NEM_GETREADONLY,OnGetReadOnly)
ON_REGISTERED_MESSAGE(NEM_SETREADONLY,OnSetReadOnly)
ON_REGISTERED_MESSAGE(NEM_SHOWPOPUP,OnShowPopup)
ON_REGISTERED_MESSAGE(NEM_POPUPSTATECHANGE,OnPopupStateChange)
END_MESSAGE_MAP()
// CNumericEdit message handlers
//****************************************************************************
// CNumericEdit::Create - Creates actual window
//****************************************************************************
BOOL CNumericEdit::Create(DWORD dwStyle,const CRect& rcRect,CWnd* pParent,UINT /*nID*/)
{
CString sClassName;
sClassName = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,::LoadCursor(NULL, IDC_ARROW),(HBRUSH)0);
if(!CreateEx(WS_EX_CLIENTEDGE,sClassName,0,WS_CHILD | dwStyle,
rcRect.left,rcRect.top,rcRect.Width(),rcRect.Height(),pParent->GetSafeHwnd(),(HMENU) 0))
{
TRACE0("Failed to create CNumericEdit\n");
return FALSE;
}
return TRUE;
}
//****************************************************************************
// CNumericEdit::OnPaint - Draws bar and black line above it
//****************************************************************************
void CNumericEdit::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect rcRect;
GetClientRect(&rcRect);
if (GetStyle() & NES_METER)
{
// Draw black line
dc.MoveTo(m_rcBar.left,m_rcBar.top - 1);
dc.LineTo(m_rcBar.right + 1,m_rcBar.top - 1);
int nXPos = (int) (m_dPixelValue * ((double)(m_nValue - m_nMin)));
dc.FillSolidRect(m_rcBar.left,m_rcBar.top,nXPos,m_rcBar.Height(),GetSysColor(COLOR_ACTIVECAPTION));
dc.FillSolidRect(m_rcBar.left + nXPos + 1,m_rcBar.top,m_rcBar.Width() - nXPos,m_rcBar.Height(),GetSysColor(COLOR_BTNFACE));
}
}
//****************************************************************************
// CNumericEdit::OnSize - Moves and sizes contained controls. Calculates bar
// position and size. Calculates m_dPixelValue.
//****************************************************************************
void CNumericEdit::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
ResizeChildren(cx,cy);
}
//****************************************************************************
// CNumericEdit::OnCreate - Creates contained controls.
//****************************************************************************
int CNumericEdit::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1) return -1;
CRect r(0,0,0,0);
m_wndButton.Create(_T("v"),WS_CHILD|WS_VISIBLE,r,this,1);
m_wndEdit.Create(WS_CHILD|WS_VISIBLE,r,this,2);
m_wndEdit.SetWindowText(_T("0"));
m_wndSpinButton.Create(WS_CHILD|WS_VISIBLE|UDS_ARROWKEYS,r,this,3);
m_wndSpinButton.SetRange32(m_nMin,m_nMax);
return 0;
}
//****************************************************************************
// CNumericEdit::OnLButtonDown - Sets new value calculated from clicked
// position then captures mouse.
//****************************************************************************
void CNumericEdit::OnLButtonDown(UINT nFlags, CPoint point)
{
if ((m_rcBar.PtInRect(point)) && (GetStyle() & NES_METER))
{
int nNewValue = (int)((point.x) / m_dPixelValue);
SetValue(nNewValue + m_nMin);
SetCapture();
m_fCaptured = TRUE;
m_fTracking = TRUE;
}
CWnd::OnLButtonDown(nFlags, point);
}
//****************************************************************************
// CNumericEdit::OnMouseMove - Calculates new value.
//****************************************************************************
void CNumericEdit::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_fCaptured)
{
int nPos = point.x;
if (nPos < 0) nPos = 0;
if (nPos > m_rcBar.Width()) nPos = m_rcBar.Width();
int nNewValue = (int)(nPos / m_dPixelValue);
SetValue(nNewValue + m_nMin);
}
CWnd::OnMouseMove(nFlags, point);
}
//****************************************************************************
// CNumericEdit::OnLButtonUp - Releases mouse capture
//****************************************************************************
void CNumericEdit::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_fCaptured)
{
ReleaseCapture();
m_fCaptured = FALSE;
m_fTracking = FALSE;
GetParent()->SendMessage(NEN_CHANGED,0,m_nValue);
}
CWnd::OnLButtonUp(nFlags, point);
}
//****************************************************************************
// CNumericEdit::OnVScroll - Handles WM_VSCROLL messages from contained
// CSpinEditCtrl and sets as new value.
//****************************************************************************
void CNumericEdit::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
SetValue(nPos);
CWnd::OnVScroll(nSBCode, nPos, pScrollBar);
}
//****************************************************************************
// CNumericEdit::OnSetFocus - Passes focus on the contained CEdit
//****************************************************************************
void CNumericEdit::OnSetFocus(CWnd* pOldWnd)
{
CWnd::OnSetFocus(pOldWnd);
m_wndEdit.SetFocus();
}
//****************************************************************************
// CNumericEdit::OnKeyDown - Handles the UP and DOWN messages passed from
// contained CEdit and adjusts value accordingly.
//****************************************************************************
void CNumericEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == VK_UP && m_nValue < m_nMax)
SetValue(m_nValue + 1);
else
if (nChar == VK_DOWN && m_nValue > m_nMin)
SetValue(m_nValue - 1);
else
CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}
//****************************************************************************
// CNumericEdit::OnStyleChanged -
//****************************************************************************
void CNumericEdit::OnStyleChanged(int nWhich, LPSTYLESTRUCT lpStyleStruct)
{
if (nWhich == GWL_STYLE)
{
CRect rcRect;
GetClientRect(&rcRect);
ResizeChildren(rcRect.Width(),rcRect.Height());
InvalidateRect(&m_rcBar);
}
}
//****************************************************************************
// CNumericEdit::OnGetPos - Handles NEM_GETPOS
//****************************************************************************
afx_msg LRESULT CNumericEdit::OnGetPos(WPARAM,LPARAM)
{
return m_nValue;
}
//****************************************************************************
// CNumericEdit::OnSetPos - Handles NEM_SETPOS
//****************************************************************************
afx_msg LRESULT CNumericEdit::OnSetPos(WPARAM /*wParam*/,LPARAM nNewValue)
{
SetValue((int)nNewValue);
return 0;
}
//****************************************************************************
// CNumericEdit::OnGetRange - Handles NEM_GETRANGE
//****************************************************************************
afx_msg LRESULT CNumericEdit::OnGetRange(WPARAM wParam,LPARAM lParam)
{
if (wParam) *((int*)wParam) = m_nMin;
if (lParam) *((int*)lParam) = m_nMax;
return 0;
}
//****************************************************************************
// CNumericEdit::OnSetRange - Handles NEM_SETRANGE
//****************************************************************************
afx_msg LRESULT CNumericEdit::OnSetRange(WPARAM wParam,LPARAM lParam)
{
SetRange((int) wParam,(int) lParam);
return 0;
}
//****************************************************************************
// CNumericEdit::OnGetReadOnly - Handles NEM_GETREADONLY
//****************************************************************************
afx_msg LRESULT CNumericEdit::OnGetReadOnly(WPARAM,LPARAM)
{
return (LRESULT) GetReadOnly();
}
//****************************************************************************
// CNumericEdit::OnSetReadOnly - Handles NEM_SETREADONLY
//****************************************************************************
afx_msg LRESULT CNumericEdit::OnSetReadOnly(WPARAM wParam,LPARAM /*lParam*/)
{
SetReadOnly((BOOL)wParam);
return 0;
}
//****************************************************************************
// CNumericEdit::OnShowPopup -
//****************************************************************************
afx_msg LRESULT CNumericEdit::OnShowPopup(WPARAM /*wParam*/,LPARAM /*lParam*/)
{
POINT point;
::GetCursorPos(&point);
CMyTrackPopup* pTrackPopup = new CMyTrackPopup();
CRect rcRect;
m_wndButton.GetWindowRect(rcRect); // Get button location to determine popup location
pTrackPopup->Create(point.x,rcRect.bottom + 4,m_nMin,m_nMax,m_nValue,this);
return 0;
}
//****************************************************************************
// CNumericEdit::OnPopupStateChange -
//****************************************************************************
afx_msg LRESULT CNumericEdit::OnPopupStateChange(WPARAM wParam,LPARAM /*lParam*/)
{
m_fTracking = wParam == PUSC_OPENING;
if (!m_fTracking)
{
m_wndButton.SetState(FALSE);
GetParent()->SendMessage(NEN_CHANGED,0,m_nValue);
m_wndEdit.SetFocus();
}
return 0;
}