#pragma title("IP Address Custom Control Implementation")

// Created by Joseph A. Dziedzic, September 1997
// Revised April 1998
// Thanks to Dan Anderson, Kenny Goers, Kevin Lussier, and Doug Miller for their suggestions
// and code enhancements.
// Mail comments to dziedzic@ma.ultranet.com


#include "stdafx.h"
#include "IPAddr.h"

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

// Style bits for the individual edit controls
const int WS_EDIT = WS_CHILD | WS_VISIBLE | ES_CENTER | ES_MULTILINE;
const TCHAR szDialogClass[] = _T("#32770");			// Special window class for dialogs

BOOL CIPAddrCtl::m_bRegistered = Register();		// Register the control during class initialization

/////////////////////////////////////////////////////////////////////////////
// CIPAddrCtl

IMPLEMENT_DYNCREATE(CIPAddrCtl, CWnd)

CIPAddrCtl::CIPAddrCtl()
{
	m_bEnabled = TRUE;								// Window enabled flag (TRUE by default)
	m_bReadOnly = FALSE;							// Read only flag (FALSE by default)
	m_bNoValidate = FALSE;							// Don't do immediate field validation on input
}

CIPAddrCtl::~CIPAddrCtl()
{
}


BEGIN_MESSAGE_MAP(CIPAddrCtl, CWnd)
	//{{AFX_MSG_MAP(CIPAddrCtl)
	ON_WM_CREATE()
	ON_WM_NCDESTROY()
	ON_WM_SIZE()
	ON_WM_SETFOCUS()
	ON_WM_PAINT()
	ON_WM_ENABLE()
	ON_WM_ERASEBKGND()
	ON_MESSAGE(WM_SETFONT, OnSetFont)
	ON_MESSAGE(IPAM_GETADDRESS, OnGetAddress)
	ON_MESSAGE(IPAM_SETADDRESS, OnSetAddress)
	ON_MESSAGE(IPAM_SETREADONLY, OnSetReadOnly)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CIPAddrCtl message handlers

BOOL CIPAddrCtl::Register()
{
	// Register the window class of the control
	WNDCLASS	wc;
	wc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;	// Usual style bits
	wc.lpfnWndProc = IPAddrWndProc;					// Message processing code
	wc.cbClsExtra = 0;								// No extra bytes needed
	wc.cbWndExtra = 0;
	wc.hInstance = NULL;							// No instance handle
	wc.hIcon = NULL;								// No icon
	wc.hCursor = ::LoadCursor(NULL, IDC_IBEAM);		// Use I-beam cursor (like edit control)
	wc.hbrBackground = (HBRUSH) (COLOR_WINDOW+1);	// Use default window color (overriden in OnEraseBkgnd)
	wc.lpszMenuName = NULL;							// No menus
	wc.lpszClassName = _T("IPAddr");				// Class name
	if (!::RegisterClass(&wc))						// If registration failed, subsequent dialogs will fail
	{
		ASSERT(FALSE);
		return FALSE;
	}
	else
		return TRUE;
}

BOOL CIPAddrCtl::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, DWORD dwExStyle/*=0*/)
{
	// Create a window class that has the properties we want
	CString szWndClass = AfxRegisterWndClass(CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW,
		::LoadCursor(NULL, IDC_IBEAM), (HBRUSH) COLOR_WINDOW+1);

	// Create using the extended window style
#if _MSC_VER >= 1100
	// Original VC 5.0 stuff
	return CWnd::CreateEx(dwExStyle, szWndClass, NULL, dwStyle, rect, pParentWnd, nID);
#else
	// Back ported to VC 4.2
	return CWnd::CreateEx(dwExStyle, szWndClass, NULL, dwStyle,
		rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
		pParentWnd->GetSafeHwnd(), (HMENU) nID);
#endif
}

LRESULT CALLBACK IPAddrWndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uiMsg)									// Dispatch on message type
	{
	case WM_NCCREATE:								// On WM_NCCREATE we create a C++ object and attach it to the control
		{
			CIPAddrCtl* pCtl = new CIPAddrCtl;		// Create an instance of the class
			ASSERT(pCtl);							// Better not fail!
			BOOL b = pCtl->SubclassWindow(hWnd);	// Attach the window handle to the new object
			ASSERT(b);								// Better not fail!
			return b;								// Return result to continue/abort window creation
			break;
		}
	default:										// All other messages go through default window processor
		return ::DefWindowProc(hWnd, uiMsg, wParam, lParam);
	}
}

int CIPAddrCtl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)	
		return -1;

	// Save the "no immediate validation on input" style setting
	m_bNoValidate = (lpCreateStruct->style & IPAS_NOVALIDATE);

	// Set the styles for the parent control
	ModifyStyleEx(0, WS_EX_CLIENTEDGE | WS_EX_NOPARENTNOTIFY);

	// Create the four edit controls used to obtain the four parts of the IP address (size
	// of controls gets set during OnSize)
	for (int ii = 0; ii < 4; ii++)
	{
		m_Addr[ii].Create(WS_EDIT, CRect(0,0,0,0), this, IDC_ADDR1 + ii);
		m_Addr[ii].LimitText(3);
		m_Addr[ii].SetParent(this);
	}
	
	return 0;
}

void CIPAddrCtl::OnNcDestroy() 
{
	CWnd::OnNcDestroy();
	
	// Make sure the window was destroyed
	ASSERT(NULL == m_hWnd);

	// Destroy this object since it won't be destroyed otherwise
	delete this;
}

void CIPAddrCtl::OnSize(UINT nType, int cx, int cy) 
{
	CWnd::OnSize(nType, cx, cy);

	// Get the width of a "." drawn in the control
	CDC*	pDC = GetDC();
	CSize	szDot = pDC->GetTextExtent(_T("."), 1);
	int nDotWidth = szDot.cx;
	ReleaseDC(pDC);	

	// Based on the size of the parent window, compute the width & height of the edit
	// controls.  Leave room for the three "." which will be drawn on the parent window
	// to separate the four parts of the IP address.
	CRect	rcClient;
	GetClientRect(&rcClient);
	int nEditWidth = (rcClient.Width() - (3 * nDotWidth)) / 4;
	int nEditHeight = rcClient.Height();
	int cyEdge = ::GetSystemMetrics(SM_CYEDGE);

	// Compute rectangles for the edit controls, then move the controls into place
	CRect rect = CRect(0, cyEdge, nEditWidth, nEditHeight);
	for (int ii = 0; ii < 4; ii++)
	{
		m_rcAddr[ii] = rect;
		m_Addr[ii].MoveWindow(rect);
		rect.OffsetRect(nEditWidth + nDotWidth, 0);
	}

	rect = CRect(nEditWidth, 0, nEditWidth + nDotWidth, nEditHeight);
	for (ii = 0; ii < 3; ii++)
	{
		m_rcDot[ii] = rect;
		rect.OffsetRect(nEditWidth + nDotWidth, 0);
	}
}

void CIPAddrCtl::OnSetFocus(CWnd* pOldWnd) 
{
	CWnd::OnSetFocus(pOldWnd);
	
	m_Addr[0].SetFocus();							// Set focus to first edit control
	m_Addr[0].SetSel(0, -1);						// Select entire contents
}

// Protected function called by the edit control (friend class) when it receives a
// character which should be processed by the parent
void CIPAddrCtl::OnChildChar(UINT nChar, UINT nRepCnt, UINT nFlags, CIPAddrEdit& child)
{
	switch (nChar)
	{
	case '.':										// Dot means advance to next edit control (if in first 3)
	case VK_RIGHT:									// Ditto for right arrow at end of text
	case ' ':										// Ditto for space
		{
		UINT nIDC = child.GetDlgCtrlID();			// Get control ID of the edit control
		if (nIDC < IDC_ADDR4)						// Move focus to appropriate edit control and select entire contents
		{
			m_Addr[nIDC - IDC_ADDR1 + 1].SetFocus();
			if (VK_RIGHT != nChar)					// Re-select text unless arrow key entered
				m_Addr[nIDC - IDC_ADDR1 + 1].SetSel(0, -1);
		}		
		break;
		}

	case VK_LEFT:									// Left arrow means move to previous edit control (if in last 3)
		{
		UINT nIDC = child.GetDlgCtrlID();			// Get control ID of the edit control
		if (nIDC > IDC_ADDR1)						// Move focus to appropriate edit control
			m_Addr[nIDC - IDC_ADDR1 - 1].SetFocus();
		break;
		}

	case VK_TAB:									// Tab moves between controls in the dialog
		{
		CWnd*	pWnd;
		SHORT nShift = ::GetKeyState(VK_SHIFT);		// Get state of shift key
		if (nShift < 0)
			pWnd = GetParent()->GetNextDlgTabItem(this, TRUE);
		else
			pWnd = GetParent()->GetNextDlgTabItem(this, FALSE);
		if (NULL != pWnd)							// If there's a control, set focus to it
			pWnd->SetFocus();
		break;
		}

	case VK_RETURN:									// Return implies default pushbutton press
		{
		DWORD dw = ((CDialog*) GetParent())->GetDefID();	// Get ID of default pushbutton
		if (DC_HASDEFID == HIWORD(dw))				// If there is a default pushbutton, simulate pressing it
		{
			CWnd* pWnd = GetParent()->GetDlgItem(LOWORD(dw));	// Get the control
			WPARAM wp = MAKEWPARAM(LOWORD(dw), BN_CLICKED);		// Build wParam for WM_COMMAND
			GetParent()->SendMessage(WM_COMMAND, wp, (LPARAM) pWnd->m_hWnd);	// Fake like button was pressed
		}
		}
		break;

	case '-':										// "Field full" indication
		// Validate the contents for proper values (unless suppressed)
		if (!m_bNoValidate)							// If not suppressing immediate validation
		{
			CString		szText;

			child.GetWindowText(szText);			// Get text from edit control
			int n = _ttoi(szText);					// Get numeric value from edit control
			if (n < 0 || n > 255)					// If out of range, notify parent
			{
				szText.Format(_T("%d is not a valid entry.  Please specify a value between 0 and 255 for this field."), n);
				MessageBox(szText, _T("Error"), MB_OK | MB_ICONEXCLAMATION);
				child.SetFocus();					// Set focus to offending field
				child.SetSel(0, -1);				// Select all text
				return;
			}
		}

		// Advance to next field
		OnChildChar('.', 0, nFlags, child);
		break;

	default:
		TRACE(_T("Unexpected call to CIPAddrCtl::OnChildChar!\n"));
	}
}

void CIPAddrCtl::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	// Save mode and set to transparent (so background remains)
	int nOldMode = dc.SetBkMode(TRANSPARENT);

	// If disabled, set text color to COLOR_GRAYTEXT, else use COLOR_WINDOWTEXT
	COLORREF crText;
	if (m_bEnabled)
		crText = ::GetSysColor(COLOR_WINDOWTEXT);
	else
		crText = ::GetSysColor(COLOR_GRAYTEXT);
	COLORREF crOldText = dc.SetTextColor(crText);

	// Draw the three "." which separate the four edit controls
	for (int ii = 0; ii < 3; ii++)
		dc.DrawText(_T("."), 1, m_rcDot[ii], DT_CENTER | DT_SINGLELINE | DT_BOTTOM);

	// Restore old mode and color
	dc.SetBkMode(nOldMode);
	dc.SetTextColor(crOldText);

	// Do not call CWnd::OnPaint() for painting messages
}

BOOL CIPAddrCtl::OnEraseBkgnd(CDC* pDC) 
{
	CRect	rcClient;
	GetClientRect(&rcClient);

	if (m_bEnabled && !m_bReadOnly)
		::FillRect(pDC->m_hDC, rcClient, (HBRUSH) (COLOR_WINDOW+1));
	else
		::FillRect(pDC->m_hDC, rcClient, (HBRUSH) (COLOR_BTNFACE+1));

	return TRUE;
}

void CIPAddrCtl::OnEnable(BOOL bEnable) 
{
	CWnd::OnEnable(bEnable);
	
	// Nothing to do unless the window state has changed
	if (bEnable != m_bEnabled)
	{
		// Save new state
		m_bEnabled = bEnable;

		// Adjust child controls appropriately
		for (int ii = 0; ii < 4; ii++)
			m_Addr[ii].EnableWindow(bEnable);
	}

	Invalidate();
}

LONG CIPAddrCtl::OnSetFont(UINT wParam, LONG lParam)
{
	// Note: font passed on to children, but we don't
	// use it, the system font is much nicer for printing
	// the dots, since they show up much better

	for (int ii = 0; ii < 4; ii++)
		m_Addr[ii].SendMessage(WM_SETFONT, wParam, lParam);

	return 0;
}

LONG CIPAddrCtl::OnGetAddress(UINT wParam, LONG lParam)
{
	BOOL bStatus;
	int	i, nAddr[4], nInError = 0;

	BOOL bPrintErrors = (BOOL) wParam;				// Cast wParam as a flag
	IPA_ADDR* lpIPAddr = (IPA_ADDR*) lParam;		// Cast lParam as an IPA_ADDR structure
	if (NULL == lpIPAddr)							// If it's a bad pointer, return an error
		return FALSE;
	memset(lpIPAddr, 0, sizeof(IPA_ADDR));			// Zero out the returned data

	// Parse the fields and return an error indication if something bad was detected
	for (i = 0; i < 4; i++)
	{
		bStatus = ParseAddressPart(m_Addr[i], nAddr[i]);
		if (!bStatus)								// If it failed to parse, quit now
		{
			nInError = i + 1;						// Remember which address part was in error
			break;									// Break out of for loop
		}
	}
	if (!bStatus)									// Error detected during parse?
	{
		lpIPAddr->nInError = nInError;				// Show where it occurred
		if (bPrintErrors)							// If they want us to print error messages
		{
			CString	szText;
			if (nAddr[i] < 0)
				szText = _T("Missing value in IP address");
			else
				szText.Format(_T("%d is not a valid entry.  Please specify a value between 0 and 255 for this field."), nAddr[i]);
			MessageBox(szText, _T("Error"), MB_OK | MB_ICONEXCLAMATION);
		}
		m_Addr[i].SetFocus();						// Set focus to offending byte
		m_Addr[i].SetSel(0, -1);					// Select entire contents
		return FALSE;								// Return an error
	}

	lpIPAddr->nAddr1 = nAddr[0];					// Return the pieces to the caller
	lpIPAddr->nAddr2 = nAddr[1];
	lpIPAddr->nAddr3 = nAddr[2];
	lpIPAddr->nAddr4 = nAddr[3];
	lpIPAddr->nInError = 0;							// No error to report
	return TRUE;									// Return success
}

LONG CIPAddrCtl::OnSetAddress(UINT wParam, LONG lParam)
{
	CString		szText;

	IPA_ADDR* lpIPAddr = (IPA_ADDR*) lParam;		// Cast lParam as an IPA_ADDR structure

	// Format their data and load the edit controls
	szText.Format(_T("%u"), lpIPAddr->nAddr1);
	m_Addr[0].SetWindowText(szText);
	szText.Format(_T("%u"), lpIPAddr->nAddr2);
	m_Addr[1].SetWindowText(szText);
	szText.Format(_T("%u"), lpIPAddr->nAddr3);
	m_Addr[2].SetWindowText(szText);
	szText.Format(_T("%u"), lpIPAddr->nAddr4);
	m_Addr[3].SetWindowText(szText);
	return TRUE;
}

LONG CIPAddrCtl::OnSetReadOnly(UINT wParam, LONG lParam)
{
	m_bReadOnly = (BOOL) wParam;

	for (int ii = 0; ii < 4; ii++)
		m_Addr[ii].SetReadOnly(m_bReadOnly);

	Invalidate();
	return TRUE;
}

BOOL CIPAddrCtl::ParseAddressPart(CEdit& edit, int& n)
{
	CString		szText;

	edit.GetWindowText(szText);						// Get text from edit control
	if (szText.IsEmpty())							// Empty text is an error
	{
		n = -1;										// Return bogus value
		return FALSE;								// Return parse failure to caller
	}

	n = _ttoi(szText);								// Grab a decimal value from edit text
	if (n < 0 || n > 255)							// If it is out of range, return an error
		return FALSE;

	return TRUE;									// Looks acceptable, return success
}

CIPAddrEdit* CIPAddrCtl::GetEditControl(int nIndex)
{
	if (nIndex < 1 || nIndex > 4)
		return NULL;
	return &m_Addr[nIndex - 1];
}


/////////////////////////////////////////////////////////////////////////////
// CIPAddrEdit

CIPAddrEdit::CIPAddrEdit()
{
}

CIPAddrEdit::~CIPAddrEdit()
{
}

void CIPAddrEdit::SetParent(CIPAddrCtl* pParent)
{
	m_pParent = pParent;							// Save pointer to parent control
}


BEGIN_MESSAGE_MAP(CIPAddrEdit, CEdit)
	//{{AFX_MSG_MAP(CIPAddrEdit)
	ON_WM_CHAR()
	ON_WM_KEYDOWN()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CIPAddrEdit message handlers

void CIPAddrEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	// Logic for this function:
	// Tab and dot are forwarded to the parent.  Tab (or shift-tab) operate
	// just like expected (focus moves to next control after the parent).
	// Dot or space causes the parent to set focus to the next child edit (if
	// focus is currently set to one of the first three edit controls).
	// Numerics (0..9) and control characters are forwarded to the standard
	// CEdit OnChar method; all other characters are dropped.
	if (VK_TAB == nChar ||
		'.' == nChar ||
		' ' == nChar ||
		VK_RETURN == nChar)
		m_pParent->OnChildChar(nChar, nRepCnt, nFlags, *this);
	else if (('0' <= nChar && '9'>= nChar) || iscntrl(nChar))
	{
		CEdit::OnChar(nChar, nRepCnt, nFlags);

		// Automatically advance to next child control if 3 characters were entered;
		// use "-" to indicate field was full to OnChildChar
		if (3 == GetWindowTextLength())
			m_pParent->OnChildChar('-', 0, nFlags, *this);
	}
	else
		::MessageBeep(0xFFFFFFFF);
}

void CIPAddrEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	// Handle the left and right arrow keys.  If the left arrow key is pressed
	// with the caret at the left of the input text, shift focus to the previous
	// control (if in edit controls 2-4).  Likewise for the right arrow key.
	// This is done by calling the parent's OnChildChar method.
	// If not left or right arrow, or not at beginning or end, call default
	// OnKeyDown processor so key down gets passed to edit control.
	if (VK_LEFT == nChar || VK_RIGHT == nChar)
	{
		CPoint ptCaret = GetCaretPos();
		int nCharPos = LOWORD(CharFromPos(ptCaret));
		if ((VK_LEFT == nChar && nCharPos == 0) ||
			(VK_RIGHT == nChar && nCharPos == GetWindowTextLength()))
			m_pParent->OnChildChar(nChar, nRepCnt, nFlags, *this);
	}
	else if (VK_ESCAPE == nChar)
	{
		// Must handle VK_ESCAPE specially.
		// In a normal dialog, VK_ESCAPE gets handled by the dialog box window process,
		// and the edit control never sees the character.  When the edit control lives
		// on a property page, this doesn't happen.  If VK_ESCAPE is not handled here
		// then when the escape key is pressed the entire control disappears from the
		// dialog!  If the parent of the control is a dialog, and the dialog's parent
		// is also a dialog, that's a sign that the control is on a property page.
		TCHAR	cClass1[12];
		TCHAR	cClass2[12];

		// Clear out window class name arrays
		memset(cClass1, 0, sizeof(cClass1));
		memset(cClass2, 0, sizeof(cClass2));

		// Get parent and parent's parent window pointers
		CWnd* pDialog = m_pParent->GetParent();		// Get parent of the IP address edit control
		CWnd* pDialogParent = pDialog->GetParent();	// Get its parent

		// Get class names of the windows that own the IP address edit control and its parent
		if (NULL != pDialog)
			::GetClassName(pDialog->m_hWnd, cClass1, sizeof(cClass1)/sizeof(TCHAR));
		if (NULL != pDialogParent)
			::GetClassName(pDialogParent->m_hWnd, cClass2, sizeof(cClass2)/sizeof(TCHAR));

		// If parent is a dialog, and parent's parent is a dialog, simulate Cancel button press
		if (0 == _tcscmp(cClass1, szDialogClass) && 0 == _tcscmp(cClass2, szDialogClass))
		{
			CWnd* pCancel = pDialogParent->GetDlgItem(IDCANCEL);	// Get the Cancel button
			pDialogParent->SendMessage(WM_COMMAND,
				MAKEWPARAM(IDCANCEL, BN_CLICKED),
				(LPARAM) (NULL != pCancel->m_hWnd ? pCancel->m_hWnd : NULL));
			return;
		}
	}
	CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}

/////////////////////////////////////////////////////////////////////////////
// DDX routines

void DDX_IPAddr(CDataExchange* pDX, int nIDC, unsigned char* nAddr)
{
	IPA_ADDR x;

	HWND hWndCtrl = pDX->PrepareCtrl(nIDC);
	ASSERT(hWndCtrl);
	CIPAddrCtl* pIPAddrCtl = (CIPAddrCtl*) CWnd::FromHandle(hWndCtrl);
	ASSERT(pIPAddrCtl);

	if (!pDX->m_bSaveAndValidate)
	{
		// Load IP address control with current data
		x.nAddr1 = nAddr[0];						// Copy address bytes over
		x.nAddr2 = nAddr[1];
		x.nAddr3 = nAddr[2];
		x.nAddr4 = nAddr[3];
		pIPAddrCtl->SetAddress(&x);
	}
	else
	{
		// Save contents of IP address control
		BOOL bStatus = pIPAddrCtl->GetAddress(TRUE, &x);
		if (!bStatus)
		{
			// x.nInError contains the 1-based index of the address field that was in error
			pDX->m_hWndLastControl = pIPAddrCtl->GetEditControl(x.nInError)->m_hWnd;	// Set HWND of control to set focus on
			pDX->m_bEditLastControl = TRUE;			// It's an edit control (so Fail selects the text)
			pDX->Fail();							// Fail DDX
		}
		nAddr[0] = x.nAddr1;
		nAddr[1] = x.nAddr2;
		nAddr[2] = x.nAddr3;
		nAddr[3] = x.nAddr4;
	}
}

void DDX_IPAddr(CDataExchange* pDX, int nIDC, CString& szAddr)
{
	IPA_ADDR x;

	HWND hWndCtrl = pDX->PrepareCtrl(nIDC);
	ASSERT(hWndCtrl);
	CIPAddrCtl* pIPAddrCtl = (CIPAddrCtl*) CWnd::FromHandle(hWndCtrl);
	ASSERT(pIPAddrCtl);

	if (!pDX->m_bSaveAndValidate)
	{
		CString copy_szAddr(szAddr) ; //bug fix
	    // Convert string to numbers
	    unsigned char nAddr[4];
	    memset(nAddr, 0, 4);

	   // Get numbers
	    for (int ii = 0; ii < 4; ii++)
	    {
		if (ii < 3)
		{
		    int nIndex = copy_szAddr.Find(_T('.')); // bug fix
		    if (-1 != nIndex)
		    {
			nAddr[ii] = (unsigned char) _ttoi(copy_szAddr.Left(nIndex));//bug fix
			copy_szAddr = copy_szAddr.Mid(nIndex + 1); // bug fix
		    }
		}
		else
		    nAddr[ii] = (unsigned char) _ttoi(copy_szAddr);//bug fix
	    }

		// Load IP address control with current data
		x.nAddr1 = nAddr[0];						// Copy address bytes over
		x.nAddr2 = nAddr[1];
		x.nAddr3 = nAddr[2];
		x.nAddr4 = nAddr[3];
		pIPAddrCtl->SetAddress(&x);
	}
	else
	{
		// Save contents of IP address control
		BOOL bStatus = pIPAddrCtl->GetAddress(TRUE, &x);
		if (!bStatus)
		{
			// x.nInError contains the 1-based index of the address field that was in error
			pDX->m_hWndLastControl = pIPAddrCtl->GetEditControl(x.nInError)->m_hWnd;	// Set HWND of control to set focus on
			pDX->m_bEditLastControl = TRUE;			// It's an edit control (so Fail selects the text)
			pDX->Fail();							// Fail DDX
		}
        szAddr.Format(_T("%d.%d.%d.%d"),
                (unsigned char) x.nAddr1, (unsigned char) x.nAddr2,
                (unsigned char) x.nAddr3, (unsigned char) x.nAddr4);
	}
}
