www.pudn.com > SuperOPC-vc7.rar > AddItemDlg.cpp


// AddItemDlg.cpp : 实现文件 
// 
 
#include "stdafx.h" 
#include "SuperOPC.h" 
#include "AddItemDlg.h" 
#include ".\additemdlg.h" 
// Registry sections; 
static LPCTSTR lpszRegSection	= _T("Add Items"); 
static LPCTSTR lpszAutoValidate	= _T("Auto Validate"); 
static LPCTSTR lpszBrowseFlat	= _T("Browse Flat"); 
 
// Default add item list size: 
#define DEF_ITEM_LIST_SIZE		16 
 
// Branch dummy item data: 
#define NULL_ITEM_NAME			_T("_QCNULL_DUMMY") 
#define NULL_ITEM_DATA			0xFFFFFFFF 
 
// Image list indices: 
#define ILI_BRANCH				8 
#define ILI_SELBRANCH			9 
#define ILI_LEAF				3 
 
// Access rights filter combo box indices: 
#define CB_ACCESS_ANY			0 
#define CB_ACCESS_READONLY		1 
#define CB_ACCESS_WRITEONLY		2 
#define CB_ACCESS_READWRITE		4 
 
// CAddItemDlg 对话框 
 
IMPLEMENT_DYNAMIC(CAddItemDlg, CDialog) 
CAddItemDlg::CAddItemDlg(CHotOpcGroup *pGroup, IOPCBrowseServerAddressSpace *pIBrowse, CWnd* pParent /*=NULL*/) 
: CDialog(CAddItemDlg::IDD, pParent) 
, m_strItemID(_T("")) 
{ 
	ASSERT (pGroup != NULL); 
	m_pGroup = pGroup; 
	m_pIBrowse = pIBrowse; 
	m_vtDataType = VT_EMPTY; 
	m_nListIndex = 0; 
	m_nSelIndex = 0; 
	m_bModified = false; 
	m_pIItemMgt = NULL; 
 
	// Load registry settings.  These are settings we may want to use each time 
	// we add some items: 
	m_bAutoValidate = FALSE; 
	m_bBrowseFlat = FALSE; 
 
	// Allocate memory for add item list: 
	try 
	{ 
		m_cItemList.SetSize (DEF_ITEM_LIST_SIZE, DEF_ITEM_LIST_SIZE); 
	} 
	catch (...) 
	{ 
		ASSERT (FALSE); 
	} 
} 
 
CAddItemDlg::~CAddItemDlg() 
{ 
} 
 
void CAddItemDlg::DoDataExchange(CDataExchange* pDX) 
{ 
	CDialog::DoDataExchange(pDX); 
	// Transfer data type combo box index to vartype: 
	CComboBox *pCombo = (CComboBox *)GetDlgItem (IDC_DATATYPE); 
	CListCtrl *pList  = (CListCtrl *)GetDlgItem(IDC_LEAFLIST); 
	pList->ModifyStyle(NULL, LVS_REPORT | LVS_SORTASCENDING ,0); 
	pList->SetExtendedStyle(LVS_EX_FULLROWSELECT); 
 
	// If save and validate, transfer data type combo box index to 
	// vartype member variable: 
	if (pDX->m_bSaveAndValidate) 
	{ 
		CString strType; 
		pCombo->GetLBText (pCombo->GetCurSel (), strType); 
		m_vtDataType = VartypeFromString (strType); 
	} 
 
	// else use current member variable value to make combo box selection: 
	else 
	{ 
		CString strType; 
 
		// Convert data type to combo box entry string: 
		switch (m_vtDataType & ~VT_ARRAY) 
		{ 
		case VT_BOOL:	strType = _T("Boolean");	break; 
		case VT_UI1:	strType = _T("Byte");		break; 
		case VT_I1:		strType = _T("Char");		break; 
		case VT_UI2:	strType = _T("Word");		break; 
		case VT_I2:		strType = _T("Short");		break; 
		case VT_UI4:	strType = _T("DWord");		break; 
		case VT_I4:		strType = _T("Long");		break; 
		case VT_R4:		strType = _T("Float");		break; 
		case VT_R8:		strType = _T("Double");		break; 
		case VT_BSTR:	strType = _T("String");		break; 
		default:		strType = _T("Native");		break; 
		} 
 
		// Update array flag data: 
		if ((m_vtDataType & VT_ARRAY) != 0) 
			strType += _T(" Array"); 
 
		// Select combo box entry: 
		pCombo->SelectString (-1, strType); 
	} 
	DDX_Text(pDX, IDC_ACCESSPATH, m_strAccessPath); 
	DDX_Text(pDX, IDC_ITEMID, m_strItemID); 
} 
 
 
BEGIN_MESSAGE_MAP(CAddItemDlg, CDialog) 
	ON_NOTIFY(TVN_ITEMEXPANDING, IDC_BRANCHLIST, OnTvnItemexpandingBranchlist) 
	ON_NOTIFY(TVN_SELCHANGED, IDC_BRANCHLIST, OnTvnSelchangedBranchlist) 
	ON_CBN_SELCHANGE(IDC_DATATYPE, OnCbnSelchangeDatatype) 
	ON_EN_CHANGE(IDC_ACCESSPATH, OnEnChangeAccesspath) 
	ON_EN_CHANGE(IDC_ITEMID, OnEnChangeItemid) 
	ON_WM_SHOWWINDOW() 
	ON_NOTIFY(NM_DBLCLK, IDC_LEAFLIST, OnNMDblclkLeaflist) 
END_MESSAGE_MAP() 
 
 
// CAddItemDlg 消息处理程序 
 
BOOL CAddItemDlg::OnInitDialog() 
{ 
	CDialog::OnInitDialog(); 
 
	// Limit the access path to 255 chars: 
	((CEdit *)GetDlgItem (IDC_ACCESSPATH))->LimitText (255); 
 
	// Allow 10 levels of 32 character names plus a tag name of 31 characters: 
	((CEdit *)GetDlgItem (IDC_ITEMID))->LimitText (10*32 + 31); 
	// Get our group's item management interface pointer for the group: 
	m_pIItemMgt = m_pGroup->GetIItemMgt (); 
 
	// Intialize our item browser: 
	InitializeBrowser (); 
 
	// Intialize control status: 
	UpdateStatus (); 
 
 
	return TRUE;  // return TRUE unless you set the focus to a control 
	// 异常: OCX 属性页应返回 FALSE 
} 
// ************************************************************************** 
// AutoIncrementID () 
// 
// Description: 
//	Create a new item ID by incrementing current ID. 
// 
// Parameters: 
//  bool		bDotBitAddress	Set to true if item has "dot bit number" 
//								  address format. 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::AutoIncrementID (bool bDotBitAddress) 
{ 
	static TCHAR szBuffer [MAX_PATH]; 
	static TCHAR *pch = NULL; 
 
	// If the item ID is defined, we will modify it by incrementing: 
	if (!m_strItemID.IsEmpty ()) 
	{ 
		// Make a local copy of the current item ID: 
		lstrcpyn (szBuffer, m_strItemID, sizeof (szBuffer) / sizeof (TCHAR)); 
 
		// Ensure it is null terminated: 
		szBuffer [MAX_PATH - 1] = '\0'; 
 
		// Get a pointer to the last character of the string (search for  
		// NULL terminator, then back up a character): 
		pch = _tcschr (szBuffer, _T('\0')); 
		pch--; 
 
		// If it is a digit we can simply increment this address: 
		if (_istdigit (*pch)) 
		{ 
			int nNumber; 
 
			// Scan backwards from last character until we encounter 
			// a non-digit: 
			while (_istdigit (*pch) && pch != szBuffer) 
				pch--; 
 
			// Obtain the number we are peeling off (go to next character 
			// which will be the first digit, then convert text to right 
			// to an integer): 
			pch++; 
			nNumber = _ttol (pch); 
 
			// Increment the number: 
			nNumber++; 
 
			// If we are dealing with a "dot bit number" address, 
			// make sure we do not increment bit number beyond 15. 
			// Increment bit numbers to 15, then increment offset 
			// and make bit number 0. 
 
			// If bDotBitAddress is set, then character to the left 
			// of current pointer should be the "dot". 
			if (bDotBitAddress && *(pch - 1) == _T('.')) 
			{ 
				// Assume dot specifier is a bit and increment 0 - 15. 
 
				// If nNumber is greater than 15, determine offset, and 
				// increment that instead of the bit number: 
				if (nNumber > 15) 
				{ 
					// Roll offset ahead: 
					pch = _tcsrchr (szBuffer, _T('.')); 
					pch--; 
 
					// Peel back looking for the first non-digit: 
					while (_istdigit (*pch) && pch != szBuffer) 
						pch--; 
 
					// Go to next character and convert to integer.  This will 
					// be the offset since _ttol will not use any characters to  
					// the right of the dot.  Increment the result by one.  This 
					// will be our new offset. 
					pch++; 
					nNumber = _ttol (pch) + 1; 
 
					// Replace old offset and bit number with incremented 
					// offset and bit number "00". 
					_stprintf (pch, _T("%d.00"), nNumber); 
				} 
 
				// Otherwise just replace the bit number with incremeted 
				// bit number: 
				else 
					_stprintf (pch, _T("%02d"), nNumber); 
			} 
 
			// If not a dot bit number address, make sure we do not lose 
			// any leading zeros (as in modbus addresses): 
			else 
			{ 
				// We should currently be pointing at first character of  
				// trailing digit portion of address.  Check to see if 
				// this is a zero. 
				if (*pch == _T('0')) 
				{ 
					// We found a leading zero. 
					TCHAR *pZeros = pch; 
					int cnDigits = 0; 
 
					// Scan forwared until we find a non-zero character. 
					// Count each zero along the way: 
					while (*pZeros++ != _T('\0')) 
						cnDigits++; 
 
					// Replace the trailing digits of the adress with 
					// the new incremented number, keeping the same 
					// number of leading zeros: 
					_stprintf (pch, _T("%0*d"), cnDigits, nNumber); 
				} 
 
				// Else if no leading zeros, just replace the trailing  
				// digits with the new incremented number: 
				else 
					_stprintf (pch, _T("%d"), nNumber); 
			} 
		} 
 
		// else if last character is not a digit, append a good starting offset: 
		else 
			*(pch + 1) = '0'; 
 
		// Update item ID: 
		m_strItemID = szBuffer; 
 
		// Update controls: 
		UpdateData (false); 
 
		// Set modifed flag: 
		m_bModified = true;		 
	} 
} 
 
// ************************************************************************** 
// UpdateStatus () 
// 
// Description: 
//	Update status of dialog controls based on current item selected. 
// 
// Parameters: 
//  none 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::UpdateStatus () 
{ 
	// Determine the focus so that if we disable the focused control 
	// we can move it. 
	HWND hWnd = ::GetFocus (); 
 
	// Enable the next button if the selected item has an item ID defined: 
 
 
	// Update the browse controls: 
	m_pBranchList->EnableWindow (m_pIBrowse != NULL); 
	m_pLeafList->EnableWindow (m_pIBrowse != NULL); 
 
 
	// Set the focus to the first control if the previous control with the 
	// focus just lost it: 
	if (hWnd && !::IsWindowEnabled (hWnd)) 
		GetDlgItem (IDC_ITEMID)->SetFocus (); 
} 
 
// ************************************************************************** 
// InitializeBrowser () 
// 
// Description: 
//	Initialize things related to the browser. 
// 
// Parameters: 
//  none 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::InitializeBrowser () 
{ 
	// Get pointers to our browse controls for easy access: 
	m_pBranchList = (CTreeCtrl *) GetDlgItem (IDC_BRANCHLIST); 
	ASSERT (m_pBranchList != NULL); 
 
	m_pLeafList = (CListCtrl *) GetDlgItem (IDC_LEAFLIST); 
	ASSERT (m_pLeafList != NULL); 
 
 
 
	// Insert a column for our leaf item list control: 
	CRect rc; 
	m_pLeafList->GetClientRect (&rc); 
	m_pLeafList->InsertColumn (0, _T(""), LVCFMT_LEFT, rc.Width ());	 
 
 
	// Intialize browse controls if browsing is supported, otherwise 
	// just disable the controls: 
	if (m_pIBrowse != NULL) 
	{ 
		// Create a wait cursor object.  This will cause the wait cursor,  
		// usually an hourglass, to be displayed.  When this object goes 
		// out of scope, its destructor will restore the previous cursor 
		// type. 
		CWaitCursor wc; 
		HRESULT hr; 
 
		// Query the server name space: 
		hr = m_pIBrowse->QueryOrganization (&m_cOpcNameSpace); 
 
		if (SUCCEEDED (hr)) 
		{ 
			// Browse root level: 
			switch (m_cOpcNameSpace) 
			{ 
			case OPC_NS_HIERARCHIAL: 
			case OPC_NS_FLAT: 
				BrowseRootLevel (); 
				break; 
 
				// Did the foundation add a new type? 
			default: 
				ASSERT (FALSE); 
				break; 
			} 
		} 
 
		// We will assume a hierarchial search if the server fails this request: 
		// (This allows us to work with any server that uses the ICONICS toolkit.) 
		else 
		{ 
			TRACE (_T("OTC: Attempting agressive browsing since the server failed on QueryOrganization ()\r\n")); 
 
			m_cOpcNameSpace = OPC_NS_HIERARCHIAL; 
			BrowseRootLevel (); 
		} 
	} 
} 
 
// ************************************************************************** 
// BrowseRootLevel () 
// 
// Description: 
//	Reset the browser to the root object. 
// 
// Parameters: 
//  none 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::BrowseRootLevel () 
{ 
	HRESULT hr = E_FAIL; 
	HTREEITEM hItem = NULL; 
 
	try 
	{ 
		// If hierarchial namespace: 
		if (m_cOpcNameSpace == OPC_NS_HIERARCHIAL) 
		{ 
			// Re-initialize server's browse position to the root: 
			do 
			{ 
				// Use the OPC_BROWSE_UP rather than the OPC_BROWSE_TO which 
				// is only supported in OOPC version 2.0.  We will have to  
				// browse up to root one level at a time.  Function will 
				// fail when we are at root. 
				hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_UP, L"\0"); 
			} while (SUCCEEDED (hr)); 
 
			// Insert our root level item: 
			hItem = m_pBranchList->InsertItem ((m_pGroup->GetParentServer ())->GetServerName(),  
				ILI_BRANCH, ILI_SELBRANCH, TVI_ROOT); 
 
			// Set the item data and add a dummy child branch: 
			m_pBranchList->SetItemData (hItem, DWORD (-1)); 
			AddDummyBranch (hItem); 
		} 
 
		// Else flat namespace: 
		else 
		{ 
			ASSERT (m_cOpcNameSpace == OPC_NS_FLAT); 
 
			// Insert our root level item (there is no need to add any dummy 
			// branches since a flat space will only have one level of leaves) 
			hItem = m_pBranchList->InsertItem ((m_pGroup->GetParentServer ())->GetServerName (),  
				ILI_BRANCH, ILI_SELBRANCH, TVI_ROOT); 
		} 
 
		// Select root item: 
		if (hItem) 
			m_pBranchList->SelectItem (hItem); 
	} 
 
	catch (...) 
	{ 
		m_pIBrowse = NULL; 
		UpdateStatus (); 
	} 
} 
 
// ************************************************************************** 
// AddBranches () 
// 
// Description: 
//	Add branch to tree control. 
// 
// Parameters: 
//  LPENUMSTRING	pIEnumString	Stores the interface pointer 
//	HTREEITEM		hParent			Handle of parent tree item 
//	DWORD			dwData			Item data value to give new branches 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::AddBranches (LPENUMSTRING pIEnumString, HTREEITEM hParent, DWORD dwData) 
{ 
	ASSERT (hParent != NULL); 
 
	ULONG celt = 1;			 
	LPOLESTR rgelt;			 
	ULONG celtFetched = 0;	 
	TCHAR szBuffer [MAX_PATH]; 
 
	// Remove the dummy branch if one exists: 
	RemoveDummyBranch (hParent); 
 
	// Start at the beginning of the list: 
	pIEnumString->Reset ();	 
	pIEnumString->Next (celt, &rgelt, &celtFetched); 
 
	// Add each branch to the browse control: 
	while (celtFetched > 0)  
	{ 
		HTREEITEM hNewItem = NULL; 
 
		// COM requis that all strings be sent in UNICODE format. 
		// Convert if necessary and copy to szBuffer: 
#ifdef _UNICODE 
		lstrcpyn (szBuffer, rgelt, sizeof (szBuffer) / sizeof (TCHAR)); 
#else 
		_wcstombsz (szBuffer, rgelt, sizeof (szBuffer) / sizeof (TCHAR)); 
#endif 
 
		// Insert the branch: 
		hNewItem = m_pBranchList->InsertItem (szBuffer, ILI_BRANCH, ILI_SELBRANCH, hParent); 
		m_pBranchList->SetItemData (hNewItem, dwData);	 
 
		// Always fake each branch into having a sub item in the tree: 
		AddDummyBranch (hNewItem); 
 
		// Free the branch name: 
		CoTaskMemFree (rgelt);	 
 
		// Re-initialize and get the next item: 
		celt = 1; 
		celtFetched = 0; 
		pIEnumString->Next (celt, &rgelt, &celtFetched); 
	} 
} 
 
// ************************************************************************** 
// ExpandBranch () 
// 
// Description: 
//	Called to expand branch. 
// 
// Parameters: 
//  HTREEITEM	hItem		Handle of tree control branch to expand. 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::ExpandBranch (HTREEITEM hItem) 
{ 
	ASSERT (hItem != NULL); 
 
	int nPos; 
	HRESULT hr; 
	LPENUMSTRING pIEnumString; 
 
#ifndef _UNICODE 
	WCHAR szFilter [MAX_PATH]; 
#endif 
 
	// Get the new browse position from the item that was previously 
	// selected: 
	nPos = (int) m_pBranchList->GetItemData (hItem); 
	nPos++; 
	ASSERT (nPos >= 0); 
 
	try 
	{ 
		// Re-intialize the server's position to the root level.  
		do 
		{ 
			// Use the OPC_BROWSE_UP rather than the OPC_BROWSE_TO which 
			// is only supported in OOPC version 2.0.  We will have to  
			// browse up to root one level at a time.  Function will 
			// fail when we are at root. 
			hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_UP, L"\0"); 
		} while (SUCCEEDED (hr)); 
 
		// Now browse down to the new position: 
		CStringArray strBranches; 
		HTREEITEM hParentItem; 
 
		strBranches.SetSize (nPos + 1); 
		hParentItem = hItem; 
 
		for (int i = 0; i <= nPos; i++) 
		{ 
			ASSERT (hItem); 
			strBranches [i] = m_pBranchList->GetItemText (hParentItem); 
			hParentItem = m_pBranchList->GetParentItem (hParentItem); 
		} 
 
		hr = S_OK; 
 
		// > 0 we do not want to include the "Root" item since the 
		// client only uses this branch: 
		while (SUCCEEDED (hr) && nPos-- > 0)	 
		{ 
#ifdef _UNICODE 
			hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_DOWN, strBranches [nPos]); 
#else 
			WCHAR szBranch [MAX_PATH]; 
			_mbstowcsz (szBranch, strBranches [nPos], sizeof (szBranch) / sizeof (WCHAR)); 
			hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_DOWN, szBranch); 
#endif 
		} 
 
		// Browse for root level: 
#ifdef _UNICODE 
		hr = m_pIBrowse->BrowseOPCItemIDs (OPC_BRANCH,				// provide items with children 
			m_strFilterBranch,		// id filtering 
			VT_EMPTY,					// no datatype filtering on a branch 
			0,						// no access filtering on a branch 
			&pIEnumString);			// store the interface pointer here 
#else 
		_mbstowcsz (szFilter, m_strFilterBranch, sizeof (szFilter) / sizeof (WCHAR)); 
		hr = m_pIBrowse->BrowseOPCItemIDs (OPC_BRANCH,				// provide items with children 
			szFilter,					// id filtering 
			VT_EMPTY,					// no datatype filtering on a branch 
			0,						// no access filtering on a branch 
			&pIEnumString);			// store the interface pointer here 
#endif 
 
		// On success add the branches to the root: 
		if (SUCCEEDED (hr) && pIEnumString) 
		{ 
			AddBranches (pIEnumString, hItem, m_pBranchList->GetItemData (hItem) + 1); 
			pIEnumString->Release (); 
		} 
		else 
		{ 
			RemoveDummyBranch (hItem); 
			throw (-1); 
		} 
	} 
 
	catch (...) 
	{ 
		m_pIBrowse = NULL; 
		UpdateStatus (); 
	} 
} 
 
// ************************************************************************** 
// SelectBranch () 
//	 
// Description: 
//	Select a branch in tree control. 
// 
// Parameters: 
//  HTREEITEM	hItem		Handle of tree control's item to select 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::SelectBranch (HTREEITEM hItem) 
{ 
	ASSERT (hItem != NULL); 
 
	int nPos; 
	HRESULT hr; 
	LPENUMSTRING pIEnumString; 
	CStringArray strBranches; 
	HTREEITEM hParentItem; 
 
#ifndef _UNICODE 
	WCHAR szFilter [MAX_PATH]; 
#endif 
 
	// Get the new browse position from the item that was previously 
	// selected: 
	nPos = (int) m_pBranchList->GetItemData (hItem); 
	nPos++; 
	ASSERT (nPos >= 0); 
 
	try 
	{ 
		// Re-intialize the server's position to the root level: 
		do 
		{ 
			// Use the OPC_BROWSE_UP rather than the OPC_BROWSE_TO which 
			// is only supported in OOPC version 2.0.  We will have to  
			// browse up to root one level at a time.  Function will 
			// fail when we are at root. 
			hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_UP, L"\0"); 
		} while (SUCCEEDED (hr)); 
 
		// Now browse down to the new position: 
		strBranches.SetSize (nPos + 1); 
		hParentItem = hItem; 
 
		for (int i = 0; i <= nPos; i++) 
		{ 
			ASSERT (hItem); 
			strBranches [i] = m_pBranchList->GetItemText (hParentItem); 
			hParentItem = m_pBranchList->GetParentItem (hParentItem); 
		} 
 
		hr = S_OK; 
 
		// > 0 we do not want to include the "Root" item since the client 
		// only uses this branch: 
		while (SUCCEEDED (hr) && nPos-- > 0)	 
		{ 
#ifdef _UNICODE 
			hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_DOWN, strBranches [nPos]); 
#else 
			WCHAR szBranch [MAX_PATH]; 
 
			_mbstowcsz (szBranch, strBranches [nPos], sizeof (szBranch) / sizeof (WCHAR)); 
			hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_DOWN, szBranch); 
#endif 
		} 
 
		// Browse for root level: 
#ifdef _UNICODE 
		hr = m_pIBrowse->BrowseOPCItemIDs ( 
			m_bBrowseFlat ? OPC_FLAT : OPC_LEAF,	// provide items with children 
			m_strFilterLeaf,						// item id filtering 
			m_vtFilterType,							// datatype filter 
			m_dwFilterAccessRights,					// access rights filtering 
			&pIEnumString);							// store the interface pointer here 
#else 
		_mbstowcsz (szFilter, m_strFilterLeaf, sizeof (szFilter) / sizeof (WCHAR)); 
 
		hr = m_pIBrowse->BrowseOPCItemIDs ( 
			m_bBrowseFlat ? OPC_FLAT : OPC_LEAF,	// provide items with children 
			szFilter,								// item id filtering 
			m_vtFilterType,							// datatype filter 
			m_dwFilterAccessRights,					// access rights filtering 
			&pIEnumString);							// store the interface pointer here 
#endif 
 
		// On success add the branches to the root: 
		if (SUCCEEDED (hr) && pIEnumString) 
		{ 
			AddLeaves (pIEnumString); 
			pIEnumString->Release (); 
		} 
		else 
			throw (-1); 
	} 
 
	catch (...) 
	{ 
		m_pIBrowse = NULL; 
		UpdateStatus (); 
	} 
} 
 
// ************************************************************************** 
// AddDummyBranch () 
// 
// Description: 
//	Add a dummy branch to tree control. 
// 
// Parameters: 
//  HTREEITEM	hParent		Handle to parent item. 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::AddDummyBranch (HTREEITEM hParent) 
{ 
	ASSERT (hParent != NULL); 
 
	HTREEITEM hDummyItem; 
 
	// Insert a dummy item: 
	hDummyItem = m_pBranchList->InsertItem (NULL_ITEM_NAME, hParent); 
	ASSERT (hDummyItem != NULL); 
 
	m_pBranchList->SetItemData (hDummyItem, NULL_ITEM_DATA); 
} 
 
// ************************************************************************** 
// RemoveDummyBranch () 
// 
// Description: 
//	Remove a dummy branch from tree control. 
// 
// Parameters: 
//  HTREEITEM	hParent		Handle of parent item. 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::RemoveDummyBranch (HTREEITEM hParent) 
{ 
	ASSERT (hParent != NULL); 
 
	HTREEITEM hDummyItem; 
 
	// Get child item: 
	hDummyItem = m_pBranchList->GetChildItem (hParent); 
	while (hDummyItem) 
	{ 
		CString strItem = m_pBranchList->GetItemText (hDummyItem); 
 
		if (strItem.CompareNoCase (NULL_ITEM_NAME) == 0) 
		{ 
			if (m_pBranchList->GetItemData (hDummyItem) == NULL_ITEM_DATA) 
			{ 
				m_pBranchList->DeleteItem (hDummyItem); 
				break; 
			} 
		} 
 
		hDummyItem = m_pBranchList->GetNextSiblingItem (hDummyItem); 
	} 
} 
 
// ************************************************************************** 
// DeleteChildBranches () 
// 
// Description: 
//	Deletes all child branches of specified item in tree control 
// 
// Parameters: 
//  HTREEITEM	hParent		Handle to parent item. 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::DeleteChildBranches (HTREEITEM hParent) 
{ 
	ASSERT (hParent != NULL); 
 
	HTREEITEM hItem; 
	hItem = m_pBranchList->GetChildItem (hParent); 
 
	while (hItem) 
	{ 
		m_pBranchList->DeleteItem (hItem); 
		hItem = m_pBranchList->GetChildItem (hParent); 
	} 
 
	AddDummyBranch (hParent); 
} 
 
// ************************************************************************** 
// AddLeaves () 
// 
// Description: 
//	Add leaves to tree control. 
// 
// Parameters: 
//  LPENUMSTRING	pIEnumString	Stores the interface pointer 
// 
// Returns: 
//  void 
// ************************************************************************** 
void CAddItemDlg::AddLeaves (LPENUMSTRING pIEnumString) 
{ 
	ULONG celt = 1;			 
	LPOLESTR rgelt;			 
	ULONG celtFetched = 0;	 
	int nIndex = 0; 
 
#ifndef _UNICODE 
	TCHAR szBuffer [MAX_PATH]; 
#endif 
 
	// Delete any leaves that are presently being displayed: 
	m_pLeafList->DeleteAllItems (); 
 
	// Start at the beginning of the list: 
	pIEnumString->Reset ();	 
	pIEnumString->Next (celt, &rgelt, &celtFetched); 
 
	// Add each leaf to the leaf control: 
	while (celtFetched > 0)  
	{ 
		// Insert the leaf: 
#ifdef _UNICODE 
		m_pLeafList->InsertItem (nIndex++, rgelt, ILI_LEAF); 
#else 
		_wcstombsz (szBuffer, rgelt, sizeof (szBuffer) / sizeof (TCHAR)); 
		m_pLeafList->InsertItem (nIndex++, szBuffer, ILI_LEAF); 
#endif 
 
		// Free the branch name: 
		CoTaskMemFree (rgelt);	 
 
		// Re-initialize and get the next item: 
		celt = 1; 
		celtFetched = 0; 
		pIEnumString->Next (celt, &rgelt, &celtFetched); 
	} 
 
	// Select first leaf by default: 
	if (m_pLeafList->GetItemCount ()) 
		m_pLeafList->SetItemState (0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); 
} 
 
void CAddItemDlg::BrowseToRoot () 
{ 
	try 
	{ 
		// Try to browse to root in one step using "OPC_BROWSE_TO" (this 
		// is not supported and will fail for OPC 1.0 servers): 
		HRESULT hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_TO, L""); 
 
		// If that fails, browse to root one level at a time using "OPC_BROWSE_UP". 
		// (Browse up will fail when we are at root.) 
		if (FAILED (hr)) 
		{ 
			do 
			{ 
				hr = m_pIBrowse->ChangeBrowsePosition (OPC_BROWSE_UP, L"\0"); 
			} while (SUCCEEDED (hr)); 
		} 
	} 
 
	catch (...) 
	{ 
	} 
} 
void CAddItemDlg::OnTvnItemexpandingBranchlist(NMHDR *pNMHDR, LRESULT *pResult) 
{ 
	// Cast notification header to tree view notification header: 
	NM_TREEVIEW* pnmtv = (NM_TREEVIEW*) pNMHDR; 
 
	// Expand branch: 
	if (pnmtv->action & TVE_EXPAND) 
	{ 
		ExpandBranch (pnmtv->itemNew.hItem); 
	} 
 
	// else delete child branches on collapse: 
	else if (pnmtv->action & TVE_COLLAPSE) 
	{ 
		DeleteChildBranches (pnmtv->itemNew.hItem); 
	} 
 
	*pResult = 0; 
} 
 
void CAddItemDlg::OnTvnSelchangedBranchlist(NMHDR *pNMHDR, LRESULT *pResult) 
{ 
	NM_TREEVIEW* pnmtv = (NM_TREEVIEW*)pNMHDR; 
 
	// Select the branch: 
	SelectBranch (pnmtv->itemNew.hItem); 
	*pResult = 0; 
} 
 
void CAddItemDlg::OnCancel() 
{ 
	for (int nIndex = 0; nIndex < m_nListIndex; nIndex++) 
	{ 
		// Get next element in list: 
		CHotOpcItem *pItem = (CHotOpcItem *) m_cItemList.GetAt (nIndex); 
		ASSERT (pItem != NULL); 
 
		// Delete it: 
		delete pItem; 
	} 
 
 
	CDialog::OnCancel(); 
} 
 
void CAddItemDlg::OnCbnSelchangeDatatype() 
{ 
	UpdateData (true); 
 
	// We will consider ourselves modified if there is an item ID defined: 
	m_bModified = !m_strItemID.IsEmpty (); 
 
	// Update control status: 
	UpdateStatus (); 
} 
 
void CAddItemDlg::OnEnChangeAccesspath() 
{ 
	// TODO:  如果该控件是 RICHEDIT 控件,则它将不会 
	// 发送该通知,除非重写 CDialog::OnInitDialog() 
	// 函数并调用 CRichEditCtrl().SetEventMask(), 
	// 同时将 ENM_CHANGE 标志“或”运算到掩码中。 
 
	UpdateData (true); 
 
	// We will consider ourselves modified if there is an item ID defined: 
	m_bModified = !m_strItemID.IsEmpty (); 
 
	// Update control status: 
	UpdateStatus (); 
} 
 
void CAddItemDlg::OnEnChangeItemid() 
{ 
	UpdateData (true); 
 
	// We will consider ourselves modified if there is an item ID defined: 
	m_bModified = !m_strItemID.IsEmpty (); 
 
	// Update control status: 
	UpdateStatus (); 
} 
 
void CAddItemDlg::OnOK() 
{ 
	if (OnApplyChange ()) 
	{ 
		// Terminate the list.  Will allow us to process list using 
		// "while (element)" loop if we want: 
		if (m_nListIndex > 0) 
			m_cItemList.SetAtGrow (m_nListIndex, NULL); 
 
 
		CDialog::OnOK(); 
	} 
} 
 
void CAddItemDlg::OnShowWindow(BOOL bShow, UINT nStatus) 
{ 
	CDialog::OnShowWindow(bShow, nStatus); 
 
	// If showing dialog, set focus to item ID edit box: 
	if (bShow) 
		GetDlgItem (IDC_ITEMID)->SetFocus (); 
} 
 
void CAddItemDlg::OnNMDblclkLeaflist(NMHDR *pNMHDR, LRESULT *pResult) 
{ 
	int nSelItem; 
 
	// Get selected item index: 
	nSelItem = m_pLeafList->GetNextItem (-1, LVNI_ALL | LVNI_SELECTED); 
 
	// If index looks good, get properties of selected item and 
	// update all other controls accordingly: 
	if (nSelItem >= 0) 
	{ 
		HRESULT hr; 
		WCHAR szItemID [MAX_PATH]; 
		LPWSTR lpszQualifiedID; 
 
		// COM requires all strings to be in UNICODE format. Convert 
		// item ID if needed, then copy to allocated buffer: 
#ifdef _UNICODE 
		lstrcpyn (szItemID, m_pLeafList->GetItemText (nSelItem, 0), sizeof (szItemID) / sizeof (WCHAR)); 
#else 
		_mbstowcsz (szItemID, m_pLeafList->GetItemText (nSelItem, 0), sizeof (szItemID) / sizeof (WCHAR)); 
#endif 
 
		try 
		{ 
			// If we are using a flat browse space, then we need to browse to 
			// root first: 
			if (m_bBrowseFlat) 
				BrowseToRoot (); 
 
			// User browser to get item's fully qualified ID: 
			hr = m_pIBrowse->GetItemID (szItemID, &lpszQualifiedID); 
 
			// If we succeeded, update controls: 
			if (SUCCEEDED (hr) && lpszQualifiedID) 
			{ 
				// Update selector to end of the list: 
				m_nSelIndex = m_nListIndex; 
 
				// Re-initialize for new item: 
				m_strItemID = lpszQualifiedID; 
				m_strAccessPath.Empty (); 
				UpdateData (false); 
 
				// Apply new dataset: 
				m_bModified = true; 
				OnApplyChange (); 
 
				// Update control state: 
				UpdateStatus (); 
 
				// Free server allocation for qualified item id: 
				CoTaskMemFree (lpszQualifiedID); 
			} 
 
			// If we didn't get qualified ID, issue a trace statement for debugging: 
			else 
			{ 
				TRACE (_T("OTC: Unable to get the qualified item id for %s\r\n"),  
					m_pLeafList->GetItemText (nSelItem, 0)); 
			} 
		} 
 
		// Catch exceptions: 
		catch (...) 
		{ 
			m_pIBrowse = NULL; 
			UpdateStatus (); 
		} 
	} 
 
	*pResult = 0; 
} 
 
bool CAddItemDlg::OnApplyChange () 
{ 
	// No need to apply changes unless we are modified: 
	if (m_bModified) 
	{ 
		CHotOpcItem *pItem = NULL; 
 
		// Make sure member variables have values currently displayed in 
		// associated controls: 
		UpdateData (true); 
 
		// If auto validating, validate item before applying changes. 
		if (m_bAutoValidate) 
		{ 
			// Validate currently selected item: 
			HRESULT hr = Validate (); 
 
			// If item is invalid, then inform user of problem and give him 
			// option of continuing: 
			if (FAILED (hr)) 
			{ 
				// Define a string format for the validation return code: 
				CString strHR; 
				strHR.Format (_T("0x%08X"), hr); 
 
				// Define a string format for the validation return code: 
				CString strFailure; 
				strFailure.FormatMessage ("IDS_VALIDATE_ITEM_FAILED_CONTINUE %s %s", m_strItemID, strHR); 
 
				// Show message bow with error string.  Give user of option of 
				// continuing.  If user clicks "NO", return now without applying 
				// changes: 
				if (AfxMessageBox (strFailure, MB_YESNO | MB_ICONQUESTION) == IDNO) 
					return (false); 
			} 
		} 
 
		// If we make it here, it is OK to apply changes. 
 
		// If selected index is the same as list control index, then we 
		// need to create a new item and add to the list: 
		if (m_nSelIndex == m_nListIndex) 
		{ 
			try  
			{ 
				// Create a new item object and add it to the list: 
				pItem = new CHotOpcItem (m_pGroup); 
				m_cItemList.SetAtGrow (m_nListIndex++, pItem); 
			} 
 
			catch (...) 
			{ 
				ASSERT (FALSE); 
			} 
		} 
 
		// else we are modifying an item already in list: 
		else 
		{ 
			pItem = (CHotOpcItem *) m_cItemList.GetAt (m_nSelIndex); 
			ASSERT (pItem != NULL); 
		} 
 
		// Set item properties: 
		pItem->SetAccessPath (m_strAccessPath); 
		pItem->SetActive (TRUE); 
		pItem->SetDataType (m_vtDataType); 
		pItem->SetItemID (m_strItemID); 
 
		// Set modified flag: 
		m_bModified = false; 
	} 
 
	// Return true to indicate success: 
	return (true); 
} 
 
HRESULT CAddItemDlg::Validate (bool *pbDotBitAddress /*= NULL */) 
{ 
	// Assume invalid until proven otherwisw: 
	HRESULT hr = E_FAIL; 
 
	// Make sure data in member variables represents current control settings: 
	UpdateData (true); 
 
	// Allocate memory for our item definition: 
	OPCITEMDEF *pItems = (OPCITEMDEF *) CoTaskMemAlloc (sizeof (OPCITEMDEF)); 
 
	// If allocation succeeds, perform validation: 
	if (pItems) 
	{ 
		// Initialize some variables: 
		HRESULT *pErrors = NULL; 
		OPCITEMRESULT *pResults = NULL; 
		WCHAR *pszItemID = NULL; 
		WCHAR *pszAccessPath = NULL; 
 
		// Check to make sure we have an item ID (debug only): 
		ASSERT (!m_strItemID.IsEmpty ()); 
 
		// Allocate memory for item ID string: 
		pszItemID = (WCHAR *) CoTaskMemAlloc ((m_strItemID.GetLength () + 1) * sizeof (WCHAR)); 
 
		// COM requires us to put strings in UNICODE format. 
#ifdef _UNICODE 
		// This is a UNICODE build so just copy item ID string "as is" 
		// to allocated memory: 
		lstrcpyn (pszItemID, m_strItemID, m_strItemID.GetLength () + 1); 
#else 
		// This is an ANSI build so convert item ID string to UNICODE and 
		// place result in allocated memory: 
		_mbstowcsz (pszItemID, m_strItemID, m_strItemID.GetLength () + 1); 
#endif 
 
		// If an access path is specified, then convert its string to UNICODE 
		// and copy it to allocated memory: 
		if (!m_strAccessPath.IsEmpty ()) 
		{ 
			// First allocate memory for the access path string: 
			pszAccessPath = (WCHAR *) CoTaskMemAlloc ((m_strAccessPath.GetLength () + 1) * sizeof (WCHAR)); 
 
			// Convert if necessary and copy result to allocated memory: 
#ifdef _UNICODE 
			lstrcpyn (pszAccessPath, m_strAccessPath, m_strAccessPath.GetLength () + 1); 
#else 
			_mbstowcsz (pszAccessPath, m_strAccessPath, m_strAccessPath.GetLength () + 1); 
#endif 
		} 
 
		// Fill in item def structure: 
		pItems->bActive = true; 
		pItems->dwBlobSize = 0; 
		pItems->hClient = NULL; 
		pItems->pBlob = NULL; 
		pItems->szAccessPath = pszAccessPath; 
		pItems->szItemID = pszItemID; 
		pItems->vtRequestedDataType = m_vtDataType; 
 
		// Validate the single item, (no blob requested): 
		try 
		{ 
			// Request server to validate item described initem def structure 
			// through the group's item management interface: 
			hr = m_pIItemMgt->ValidateItems (1, pItems, FALSE, &pResults, &pErrors); 
 
			// If server reports that item is valid, we may still have to modify 
			// the data type.  We will also validate "dot bit address" format. 
			if (hr == S_OK) 
			{ 
				// Modify the data type to what the server will use: 
				m_vtDataType = pResults->vtCanonicalDataType; 
 
				// Update the dialog controls to reflect data type change: 
				UpdateData (false); 
 
				// Flag as modified: 
				m_bModified = true; 
 
				// Verify dot bit address setting: 
				if (pbDotBitAddress) 
				{ 
					// Assume false until proven otherwise: 
					*pbDotBitAddress = false; 
 
					// Data type must be VT_BOOL for dot bit address,  It it's not, 
					// fall through with flag set to false: 
					if (m_vtDataType == VT_BOOL) 
					{ 
						// To determine if truly a dot bit address, strip off 
						// bit number and see if its still valid. 
 
						// First free previous results, 
						if (pResults) 
						{ 
							CoTaskMemFree (pResults); 
							pResults = NULL; 
						} 
 
						// and previous errors: 
						if (pErrors) 
						{ 
							CoTaskMemFree (pErrors); 
							pErrors = NULL; 
						} 
 
						// Get position of "dot" character: 
						WCHAR *pwch = wcsrchr (pItems->szItemID, L'.'); 
 
						if (pwch) 
						{ 
							// Replace "dot" with NULL terminator, effectively stripping 
							// off the whole "dot bit number" portion of the address: 
							*pwch = L'\0'; 
 
							// Reset the requested data type to VT_EMPTY.  We are only 
							// interested in seeing if the address is valid for some 
							// unspecified data type. 
							pItems->vtRequestedDataType = VT_EMPTY; 
 
							// Request server to validate the "stripped" address: 
							if (SUCCEEDED (m_pIItemMgt->ValidateItems (1, pItems, FALSE, &pResults, &pErrors))) 
							{ 
								// If the server reports that the "stipped" address is valid, and 
								// that it corresponds to some data type other than VT_BOOL,  
								// then the original address is truely a "dot bit" address: 
								if (pResults->vtCanonicalDataType != VT_EMPTY && pResults->vtCanonicalDataType != VT_BOOL) 
									*pbDotBitAddress = true; 
							} 
						} 
					} 
				} 
			} 
 
			// else if server reports that item is not valid.  Assign item 
			// error for return: 
			else 
			{ 
				hr = pErrors [0]; 
			} 
		} 
 
		// Catch exceptions: 
		catch (...) 
		{ 
			// Probably something wrong with interface pointer, so invalidate it: 
			m_pIItemMgt = NULL; 
 
			// Update dialog control status: 
			UpdateStatus (); 
		} 
 
		// Free allocations (COM requires us to do this): 
		CoTaskMemFree (pItems); 
 
		if (pszItemID) 
			CoTaskMemFree (pszItemID); 
 
		if (pszAccessPath) 
			CoTaskMemFree (pszAccessPath); 
 
		if (pResults) 
			CoTaskMemFree (pResults); 
 
		if (pErrors) 
			CoTaskMemFree (pErrors); 
	} 
 
	// Return validation return code: 
	return (hr); 
}