Platform SDK: Active Directory, ADSI, and Directory Services |
To extend property sheets for directory objects, a property page COM object must implement two shell interfaces: IShellExtInit and IShellPropSheetExt.
A property page COM object is instantiated when the user views the properties for an object of a class for which the property page has been registered. If the COM object was registered in the class's adminPropertyPages property, the property page appears when the user views properties for objects of that class in Active Directory administrative snap-ins. If it was registered in shellPropertyPages, the property page appears in the Windows shell.
The IShellExtInit interface has only one method and it must be implemented:
You should also store the clipboard formats as member variables. The IShellExtInit::Initialize method is a good place to register these formats.
The following code fragment contains an implementation of the IShellExtInit::Initialize method. This method releases the IDataObject pointer if IShellExtInit::Initialize was called before, stores the passed IDataObject pointer in a member variable, and registers the two clipboard formats in a member variable:
STDMETHODIMP CMyDsPropertyPage::Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hRegKey) { // Initialize can be called more than once. if (m_pDataObj) m_pDataObj->Release(); // Keep a pointer to the IDataObject. if (pDataObj) { m_pDataObj = pDataObj; pDataObj->AddRef(); } //Register the clipboard formats to use for getting info about //the selected DS objects. m_cfDsDispSpecOptions = RegisterClipboardFormat(CFSTR_DS_DISPLAY_SPEC_OPTIONS); m_cfDsObjectNames = RegisterClipboardFormat(CFSTR_DSOBJECTNAMES); return NOERROR; }
Note that the IShellExtInit::Initialize method is implemented the same way for context menu extensions.
After IShellExtInit::Initialize returns, the IShellPropSheetExt::AddPages method is called.
You can implement the property page(s) in the class implementing the IShellPropSheetExt and IShellExtInit interfaces. Or you can implement each property page in a class that is completely separate from the class implementing the two shell extension interfaces.
The class that implements the two shell extension interfaces only needs to be instantiated for the creation of the pages, whereas the class for each page lasts as long as the property sheet. By implementing a class for each page, the class can be tailored for manipulating the attributes displayed on its page. If the property pages are implemented as separate classes, the class implementing the property page should be initialized with the appropriate member data such as the IDataObject pointer and any other data about the selected objects required by the property page class.
The IShellPropSheetExt interface has two methods that must be implemented:
Important The IShellPropSheetExt::AddPages method call runs on the caller's main thread. The dialog box procedure runs on a different thread. Therefore, do not reference the IDataObject pointer in the dialog box procedure unless it is first marshalled to this thread.
If the dialog box procedure requires data from the IDataObject, you can avoiding having to marshal the IDataObject pointer by extracting the data that the dialog box procedure needs and storing that data in member variables in the class. This can be done in the IShellExtInit::Initialize or the IShellPropSheetExt::AddPages methods. Then the dialog box procedure can access the class members via the lParam. Typically, the dialog box procedure will need only the ADsPath of the selected objects so that it can then use the ADsPath information to bind and perform operations on those objects.
The following code fragment contains an implementation of the IShellPropSheetExt methods and the dialog box procedure for the single property page added by the IShellPropSheetExt::AddPages method. The property page binds to the selected object and displays some properties for it. For the full implementation of this sample, see the following DSSamplePage sample.
STDMETHODIMP DSSamplePage::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam) { HRESULT hr = S_OK; STGMEDIUM ObjMedium = {TYMED_NULL}; FORMATETC fmte = {g_cfDsObjectNames, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; LPDSOBJECTNAMES pDsObjectNames; PWSTR pwzObjName; PWSTR pwzClass; // Get the path to the DS object from the data object. // Note: This call runs on the caller's main thread. The pages' window // procs run on a different thread, so don't reference the data object // from a winproc unless it is first marshaled on this thread. // For this sample, we extract the data we want and store // it in member variables so that the dialog proc // can access the data without having to marshall // the IDataObject pointer. hr = m_pDataObj->GetData(&fmte, &ObjMedium); if (SUCCEEDED(hr)) { pDsObjectNames = (LPDSOBJECTNAMES)ObjMedium.hGlobal; if (pDsObjectNames->cItems < 1) { hr = E_FAIL; } pwzObjName = (PWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[0].offsetName); pwzClass = (PWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[0].offsetClass); // Save the ADsPath of object m_ObjPath = new WCHAR [wcslen(pwzObjName )+1]; wcscpy(m_ObjPath,pwzObjName); } // Now release the ObjMedium: // If punkForRelease is NULL, the receiver of // the medium is responsible for releasing it; otherwise, // punkForRelease points to the IUnknown on the appropriate // object so its Release method can be called. ReleaseStgMedium(&ObjMedium); PROPSHEETPAGE psp; HPROPSHEETPAGE hpage; HRESULT hres = 0; LPCSHELLEXT lpcsext = this; // // Create a property sheet page object from a dialog box. // // We store a pointer to our class in the psp.lParam, so we // can access our class members from within the DSSamplePageDlgProc. // // If the page needs more instance data, you can append // arbitrary size of data at the end of this structure, // and pass it to the CreatePropSheetPage. In such a case, // the size of entire data structure (including page specific // data) must be stored in the dwSize field. Note that in // general you should NOT need to do this, as you can simply // store a pointer to data in the lParam member. // psp.dwSize = sizeof(psp); // no extra data. psp.dwFlags = PSP_USETITLE | PSP_USECALLBACK; psp.hInstance = g_hInstance; psp.pszTemplate = MAKEINTRESOURCE(IDD_DSSamplePage); psp.hIcon = 0; psp.pszTitle = L"Object Stats"; psp.pfnDlgProc = DSSamplePageDlgProc; psp.pcRefParent = NULL;//&g_cRefThisDll; psp.pfnCallback = DSSamplePageCallback; psp.lParam = (LPARAM)lpcsext; AddRef(); hpage = CreatePropertySheetPage(&psp); if (hpage) { if (!lpfnAddPage(hpage, lParam)) { DestroyPropertySheetPage(hpage); Release(); } } return NOERROR; } STDMETHODIMP DSSamplePage::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE lpfnReplaceWith, LPARAM lParam) { return E_FAIL; } BOOL CALLBACK DSSamplePageDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam) { LPPROPSHEETPAGE psp=(LPPROPSHEETPAGE)GetWindowLong(hDlg, DWL_USER); UINT iIndex=0; LPCSHELLEXT lpcs; switch (uMessage) { // // When the shell or snap-in creates a dialog box for a property sheet page, // it passes the pointer to the PROPSHEETPAGE data structure as // lParam. The dialog procedures of extensions typically store it // in the DWL_USER of the dialog box window. // case WM_INITDIALOG: { SetWindowLong(hDlg, DWL_USER, lParam); BSTR bsResult; psp = (LPPROPSHEETPAGE)lParam; lpcs = (LPCSHELLEXT)psp->lParam; HRESULT hr; IADs* pIADs = NULL; hr = ADsGetObject( lpcs->m_ObjPath, IID_IADs,(void **)&pIADs); if (SUCCEEDED(hr)) { // Retrieves the GUID for this object- The guid uniquely identifies // this directory object. The Guid is globally unique // Also the guid is rename/move safe. The ADsPath below returns the // CURRENT location of the object- The guid remains constant regardless of // name or location of the directory object pIADs->get_GUID(&bsResult); SetWindowText(GetDlgItem(hDlg,IDC_GUID),bsResult); SysFreeString(bsResult); // Retrieves the RDN pIADs->get_Name(&bsResult); SetWindowText(GetDlgItem(hDlg,IDC_NAME),bsResult); SysFreeString(bsResult); // Retrieves the value in the class attribute, that is, group pIADs->get_Class(&bsResult); SetWindowText(GetDlgItem(hDlg,IDC_CLASS),bsResult); SysFreeString(bsResult); // Retrieves the full literal LDAP path for this object. // This may be used to re-bind to this object- though for persistent // storage (and to be 'move\rename' safe) it is suggested that the // guid be used instead of the ADsPath pIADs->get_ADsPath(&bsResult); SetWindowText(GetDlgItem(hDlg,IDC_ADSPATH),bsResult); SysFreeString(bsResult); // Retrieves the LDAP path for the parent\container for this object pIADs->get_Parent(&bsResult); SetWindowText(GetDlgItem(hDlg,IDC_PARENT),bsResult); SysFreeString(bsResult); // Retrieves the LDAP path for the Schema definition of the object returned from /// the IADs::get_Schema() member pIADs->get_Schema(&bsResult); SetWindowText(GetDlgItem(hDlg,IDC_SCHEMA),bsResult); SysFreeString(bsResult); pIADs->Release(); pIADs = NULL; } } break; case WM_DESTROY: RemoveProp(hDlg, L"ID"); break; case WM_NOTIFY: switch (((NMHDR FAR *)lParam)->code) { case PSN_SETACTIVE: break; case PSN_APPLY: //TODO: Apply changes user made here. break; default: break; } break; default: return FALSE; } return TRUE; }