Programmatically pre-select using IFileDialog in C++ - c++

I am perplexed on whether IFileDialog has the capability of programmatically selecting an item inside the dialog even with out the user selecting.
Ex.
I was hoping to achieve opening IFileDialog then selecting a default item/folder inside the dialog.
Ex.
By the way, in the picture above. I did manually click/select the folder.
But I was hoping to implement a defaultly selected item inside the IFileDialog.

The procedure suggested by zett42 works. You need to implement your own version of IFileDialogEvents. After hooking with IFileDialog::Advise you can query your way to IShellView and that lets you change the selection.
This example is a little silly because I'm forcing the directory as well to be sure I have a file I can select.
struct MyIFileDialogEvents : public IFileDialogEvents {
bool forcedDir, forcedSel;
MyIFileDialogEvents() : forcedDir(false), forcedSel(false) {}
...
};
STDMETHODIMP MyIFileDialogEvents::OnFolderChanging( IFileDialog *pfd, IShellItem*psiFolder)
{
if (forcedDir) return S_OK; else forcedDir = true;
IShellItem*psiwindir;
HRESULT hr = SHGetKnownFolderItem(FOLDERID_Windows, KF_FLAG_DEFAULT, NULL, IID_IShellItem, (void**) &psiwindir);
if (!hr)
{
hr = pfd->SetFolder(psiwindir); // MSDN says it is OK to change the folder in OnFolderChanging with SetFolder
psiwindir->Release();
}
if (FAILED(hr)) forcedSel = true;
return S_OK;
}
STDMETHODIMP MyIFileDialogEvents::OnFolderChange(IFileDialog *pfd)
{
if (forcedSel || !forcedDir) return S_OK; else forcedSel = true;
IShellItem*psiwindir, *psiexp;
HRESULT hr = SHGetKnownFolderItem(FOLDERID_Windows, KF_FLAG_DEFAULT, NULL, IID_IShellItem, (void**) &psiwindir);
if (!hr)
{
hr = SHCreateItemFromRelativeName(psiwindir, L"Explorer.exe", NULL, IID_IShellItem, (void**) &psiexp);
psiwindir->Release();
if (!hr)
{
IServiceProvider*pSP;
IShellBrowser*pSB;
IShellView*pSV;
if (!pfd->QueryInterface(IID_IServiceProvider, (void**) &pSP))
{
if (!pSP->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void**)&pSB))
{
if (!pSB->QueryActiveShellView(&pSV))
{
PIDLIST_ABSOLUTE pidl;
if (!SHGetIDListFromObject(psiexp, &pidl))
{
pSV->SelectItem(ILFindLastID(pidl), SVSI_SELECT|SVSI_ENSUREVISIBLE|SVSI_FOCUSED|SVSI_DESELECTOTHERS);
CoTaskMemFree(pidl);
}
pSV->Release();
}
pSB->Release();
}
pSP->Release();
}
psiexp->Release();
}
}
return S_OK;
}

Related

How to host a preview handler in a dialog

I'm attempting to host a file preview handler within a dialog. I've set up an event sink for selection changes in Explorer. When the selection changes, I feed the selected shell item to the dialog, which in turn feeds it to a function that prepares the preview frame.
In general, it successfully loads the correct handler and displays the contents of the file, but for certain file types (namely, Excel and Word files), it runs into various problems like focus-loss or flashing. Here's a demo of the Excel preview handler messing up focus (and by mess up, I mean it wrongfully steals the focus from Explorer, which I'd like to maintain focus):
Word files may load successfully once, but they'll subsequently fail, especially if Word is opened.
As for the code:
For starters, here's my function for obtaining the preview handler from the file extension. This seems to work fine:
HRESULT PreviewHandlerFromExt(LPCWSTR pszExt, IPreviewHandler** ppph)
{
WCHAR szCLSID[CLSID_LEN] = { 0 };
DWORD cchOut = CLSID_LEN;
HRESULT hr = AssocQueryString( ASSOCF_INIT_DEFAULTTOSTAR,
ASSOCSTR_SHELLEXTENSION,
pszExt,
L"{8895b1c6-b41f-4c1c-a562-0d564250836f}",
szCLSID,
&cchOut );
if (FAILED(hr))
{
return hr;
}
CLSID clsid;
hr = CLSIDFromString(szCLSID, &clsid);
if (FAILED(hr))
{
return hr;
}
CComPtr<IUnknown> punk;
hr = punk.CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER);
if (FAILED(hr))
{
return hr;
}
CComPtr<IPreviewHandler> pPrevHandler;
hr = punk->QueryInterface(&pPrevHandler);
if (FAILED(hr) || !pPrevHandler)
{
return hr;
}
return pPrevHandler.CopyTo(ppph);
}
And now here's the function in my dialog that prepares the preview, given a shell item (m_pPreviewHandler is the active preview handler, IDC_PREVIEWFRAME is a placeholder in the dialog for the preview pane, and m_mapExtsToPreviewHandlers is just a map for storing preview handlers as the user comes across them):
void CMyDialog::ShowPreview(IShellItem* pShItem)
{
HRESULT hr;
if (m_pPreviewHandler)
{
m_pPreviewHandler->Unload();
m_pPreviewHandler.Release();
}
CComHeapPtr<WCHAR> pszPath;
hr = pShItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
if (FAILED(hr))
{
return;
}
LPWSTR pszExt = CharLower(PathFindExtension(pszPath));
auto it = m_mapExtsToPreviewHandlers.find(pszExt);
if (it == m_mapExtsToPreviewHandlers.end())
{
hr = PreviewHandlerFromExt(pszExt, &m_pPreviewHandler);
if (FAILED(hr) || !m_pPreviewHandler)
{
return;
}
m_mapExtsToPreviewHandlers[pszExt] = m_pPreviewHandler;
}
else
{
m_pPreviewHandler = m_mapExtsToPreviewHandlers[pszExt];
}
CComPtr<IInitializeWithFile> pInitWithFile;
hr = m_pPreviewHandler->QueryInterface(&pInitWithFile);
if (SUCCEEDED(hr))
{
hr = pInitWithFile->Initialize(pszPath, STGM_READ);
if (FAILED(hr))
{
return;
}
}
else
{
CComPtr<IInitializeWithStream> pInitWithStream;
hr = m_pPreviewHandler->QueryInterface(&pInitWithStream);
if (SUCCEEDED(hr))
{
CComPtr<IStream> pStream;
hr = SHCreateStreamOnFile(pszPath, STGM_READ, &pStream);
if (FAILED(hr) || !pStream)
{
return;
}
hr = pInitWithStream->Initialize(pStream, STGM_READ);
if (FAILED(hr))
{
return;
}
}
}
CWindow wndPreviewFrame( GetDlgItem(IDC_PREVIEWFRAME) );
CRect rectPreviewFrame;
wndPreviewFrame.GetClientRect(&rectPreviewFrame);
hr = m_pPreviewHandler->SetWindow(wndPreviewFrame, &rectPreviewFrame);
if (FAILED(hr))
{
return;
}
hr = m_pPreviewHandler->DoPreview();
if (FAILED(hr))
{
return;
}
hr = m_pPreviewHandler->SetRect(&rectPreviewFrame);
if (FAILED(hr))
{
return;
}
}
Does anyone know what I'm doing wrong or what might fix these focus problems?
I've also tried placing LockSetForegroundWindow at various places, but no lock.
Also, this is what the dialog resource looks like:

cannot get the path for the virtual folder on windows 7 C++(shell namespace extension related)

By using Microsoft windows SDK 7.0, explorerDataProvider, I installed a virtual folder on windows 7.
When I open the file browse dialog from an application,
CFileDialog dlg(TRUE, NULL, 0, OFN_ENABLESIZING | OFN_ALLOWMULTISELECT, L"all(*.*)|*.*||", this);
INT_PTR result = dlg.DoModal();
if (result == IDOK)
{
.
.
.
//some actions
}
it can also display the virtual folder:
But when I select the file, like "Zero" can then clicks "Open",
I tried to add breakpoints to
INT_PTR result = dlg.DoModal();
But it seems that this error happens inside DoModal().
I doing some research and revise the code of virtual folder:
HRESULT CFolderViewImplFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, ULONG *rgfInOut)
{
// If SFGAO_FILESYSTEM is returned, GetDisplayNameOf(SHGDN_FORPARSING) on that item MUST
// return a filesystem path.
HRESULT hr = E_INVALIDARG;
DWORD dwAttribs = 0;
dwAttribs |= SFGAO_BROWSABLE;
if (1 == cidl)
{
int nLevel = 0;
hr = _GetLevel(apidl[0], &nLevel);
if (SUCCEEDED(hr))
{
BOOL fIsFolder = FALSE;
hr = _GetFolderness(apidl[0], &fIsFolder);
if (SUCCEEDED(hr))
{
if (fIsFolder)
{
dwAttribs |= SFGAO_FOLDER;
dwAttribs |= SFGAO_FILESYSANCESTOR;
}
else
{
dwAttribs |= SFGAO_SYSTEM;
dwAttribs |= SFGAO_FILESYSTEM;
}
if (nLevel < g_nMaxLevel)
{
dwAttribs |= SFGAO_HASSUBFOLDER;
}
}
}
}
*rgfInOut &= dwAttribs;
return hr;
}
And return the path inGetDisplayNameOf()
HRESULT CFolderViewImplFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF shgdnFlags, STRRET *pName)
{
HRESULT hr = S_OK;
if (shgdnFlags & SHGDN_FORPARSING)
{
WCHAR szDisplayName[MAX_PATH];
if (shgdnFlags & SHGDN_INFOLDER)
{
// This form of the display name needs to be handled by ParseDisplayName.
hr = _GetName(pidl, szDisplayName, ARRAYSIZE(szDisplayName));
}
else
{
PWSTR pszThisFolder = L"Computer\\Jerry";
StringCchCopy(szDisplayName, ARRAYSIZE(szDisplayName), pszThisFolder);
StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), L"\\");
WCHAR szName[MAX_PATH];
hr = _GetName(pidl, szName, ARRAYSIZE(szName));
if (SUCCEEDED(hr))
{
StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), szName);
}
}
if (SUCCEEDED(hr))
{
hr = StringToStrRet(szDisplayName, pName);
}
}
else
{
PWSTR pszName;
hr = _GetName(pidl, &pszName);
if (SUCCEEDED(hr))
{
hr = StringToStrRet(pszName, pName);
CoTaskMemFree(pszName);
}
}
return hr;
}
But it still stats error message of "Path does not exist" in the application.
I add breakpoints in GetDisplayNameOf, but it is never triggered when the application opens a file browse dialog. But if I open a regular folder just by double click "Computer", it will be triggered.
The error message of "Path does not exist" seems cannot be deprecated or covered. Any way that I can revise the virtual folder to return the correct path ?
Update: I tried IFileDialog, I copied the code from the MSDN sample, code, and add breakpoints to see what will happen. However,
// Show the dialog
hr = pfd->Show(NULL);//the breakpoint is added here
if (SUCCEEDED(hr))
{
// Obtain the result once the user clicks
// the 'Open' button.
// The result is an IShellItem object.
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
//do something
}
}
When I click "Open" button, the error message box of "Path does not exist, check the path and try again" still show up. And it never comes out of pfd->Show(NULL) after I click Open" button.
Based on the comments in the sample code, it should come out of hr = pfd->Show(NULL); and reach to the next line.but the error message happens inside the hr = pfd->Show(NULL);.
Update:
In the IFileDialog, I tried
hr = pfd->GetOptions(&dwFlags);
if (SUCCEEDED(hr))
{
hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS);
if (SUCCEEDED(hr))
{
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
// Obtain the result once the user clicks
// the 'Open' button.
// The result is an IShellItem object.
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
// We are just going to print out the
// name of the file for sample sake.
PWSTR pszFilePath = NULL;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH,
&pszFilePath);
if (SUCCEEDED(hr))
{
TaskDialog(NULL,
NULL,
L"CommonFileDialogApp",
pszFilePath,
NULL,
TDCBF_OK_BUTTON,
TD_INFORMATION_ICON,
NULL);
CoTaskMemFree(pszFilePath);
}
psiResult->Release();
}
}
}
I also tried
hr = pfd->SetOptions(dwFlags);//delete FOS_FORCEFILESYSTEM
And tried
DWORD dwFlags = 0;
if (SUCCEEDED(hr))
{
// overwrite options completely
hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS);
I also tried to return the whole name in namespace extension code:
WCHAR szDisplayName[MAX_PATH];
if (shgdnFlags & SHGDN_INFOLDER)
{
// This form of the display name needs to be handled by ParseDisplayName.
hr = _GetName(pidl, szDisplayName, ARRAYSIZE(szDisplayName));
}
else
{
PWSTR pszThisFolder;// = L"Computer\\Jerry";
hr = SHGetNameFromIDList(m_pidl, (shgdnFlags & SHGDN_FORADDRESSBAR) ? SIGDN_DESKTOPABSOLUTEEDITING : SIGDN_DESKTOPABSOLUTEPARSING, &pszThisFolder);
if (SUCCEEDED(hr))
{
StringCchCopy(szDisplayName, ARRAYSIZE(szDisplayName), pszThisFolder);
StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), L"\\");
WCHAR szName[MAX_PATH];
hr = _GetName(pidl, szName, ARRAYSIZE(szName));
if (SUCCEEDED(hr))
{
StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), szName);
}
CoTaskMemFree(pszThisFolder);
}
}
if (SUCCEEDED(hr))
{
hr = StringToStrRet(szDisplayName, pName);
}
But the result is:
Update:
hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE);
This works for me!!
With the help of #RemyLebeau, I did some tests in my code. Finally, I found the working solution for me:
I copied the code from MSDN for IFileDialog: https://msdn.microsoft.com/en-us/library/windows/desktop/bb776913.aspx#file_types
and then revise it a little bit:
HRESULT BasicFileOpen()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents *pfde = NULL;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook up the event handler.
DWORD dwCookie;
hr = pfd->Advise(pfde, &dwCookie);
if (SUCCEEDED(hr))
{
// Set the options on the dialog.
DWORD dwFlags = 0;
if (SUCCEEDED(hr))
{
// the default flag is FOS_PATHMUSTEXIST and FOS_FILEMUSTEXIST, so I set overwrite it with these two flags.
hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE);
if (SUCCEEDED(hr))
{
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
// Obtain the result once the user clicks
// the 'Open' button.
// The result is an IShellItem object.
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
// We are just going to print out the
// name of the file for sample sake.
PWSTR pszFilePath = NULL;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH,
&pszFilePath);
if (SUCCEEDED(hr))
{
TaskDialog(NULL,
NULL,
L"CommonFileDialogApp",
pszFilePath,
NULL,
TDCBF_OK_BUTTON,
TD_INFORMATION_ICON,
NULL);
CoTaskMemFree(pszFilePath);
}
psiResult->Release();
}
}
}
}
// Unhook the event handler.
pfd->Unadvise(dwCookie);
}
pfde->Release();
}
pfd->Release();
}
return hr;
}
The key point is to overwrite the options by SetOptions. Based on the MSDN:https://msdn.microsoft.com/en-us/library/windows/desktop/dn457282(v=vs.85).aspx ,
FOS_PATHMUSTEXIST
The item returned must be in an existing folder. This is a default value.
FOS_FILEMUSTEXIST
The item returned must exist. This is a default value for the Open dialog.
These flags are default values which prevent it returns.
So I set it to :
hr = pfd->SetOptions(dwFlags | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE);
These flags mean:
FOS_NOVALIDATE
Do not check for situations that would prevent an application from opening the selected file, such as sharing violations or access denied errors.
and
FOS_ALLNONSTORAGEITEMS
Enables the user to choose any item in the Shell namespace, not just those with SFGAO_STREAM or SFAGO_FILESYSTEM attributes. This flag cannot be combined with FOS_FORCEFILESYSTEM.
Then it finally works!

How to create shortcut for virtual folder in C++ on windows 7?

The platform I am using is windows 7. I need to create shortcut for the virtual folder on windows 7.
I use windows 7 SDK sample to create a virtual folder under Computer:
The sample project name is called ExplorerDataProvider, it defines the CLSID for the IShellFolder class:
// add classes supported by this module here
const CLASS_OBJECT_INIT c_rgClassObjectInit[] =
{
{ &CLSID_FolderViewImpl, CFolderViewImplFolder_CreateInstance },
{ &CLSID_FolderViewImplContextMenu,CFolderViewImplContextMenu_CreateInstance },
};
The definition for CFolderViewImplFolder_CreateInstance is:
HRESULT CFolderViewImplFolder_CreateInstance(REFIID riid, void **ppv)
{
*ppv = NULL;
CFolderViewImplFolder* pFolderViewImplShellFolder = new (std::nothrow) CFolderViewImplFolder(0);
HRESULT hr = pFolderViewImplShellFolder ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pFolderViewImplShellFolder->QueryInterface(riid, ppv);
pFolderViewImplShellFolder->Release();
}
return hr;
}
And the CFolderViewImplFolder implement IShellFolder2 amd IPersistFolder2.
I found a similar code which is used to create shortcut for printer here:
https://www.codeproject.com/Articles/596642/Creating-a-shortcut-programmatically-in-Cplusplus
and in
https://msdn.microsoft.com/en-us/library/aa969393.aspx#Shellink_Item_Identifiers
Once you have the class identifier for IShellFolder, you can call the CoCreateInstance function to retrieve the address of the interface. Then you can call the interface to enumerate the objects in the folder and retrieve the address of the item identifier for the object that you are searching for. Finally, you can use the address in a call to the IShellLink::SetIDList member function to create a shortcut to the object.
I revised
hr = SHGetMalloc(&pMalloc);
hr = SHGetDesktopFolder( &pDesktopFolder );
hr = SHGetSpecialFolderLocation( NULL, CSIDL_PRINTERS, &netItemIdLst );
hr = pDesktopFolder->BindToObject( netItemIdLst, NULL, IID_IShellFolder, (void **)&pPrinterFolder );
to
// testFolder is the CLSID for the virtual folder implementation
hr = CoCreateInstance(testFolder, NULL, CLSCTX_INPROC_SERVER, IID_IShellFolder, (LPVOID*)&pVirtualFolder);
or
hr = CoCreateInstance(testFolder, NULL, CLSCTX_INPROC_SERVER, IID_IShellFolder2, (LPVOID*)&pVirtualFolder);
But the pVirtualFolder is still NULL, and it prints that "cannot find the corresponding interface".
Is there anything wrong with CoCreateInstance when I use it ? Or I shouldn't use this solution ? Any sample code for it ?
To create a shortcut, you can use the official documentation. Here is a sample code that creates a shortcut for a children of "This PC" (aka: ComputerFolder)
int main()
{
CoInitialize(NULL);
// I've used my Apple's iCloud as an example (name is in french)
// it's a virtual folder, a shell namespace extension
HRESULT hr = CreateComputerChildShortCut(L"Photos iCloud", L"c:\\temp\\my icloud");
printf("hr:0x%08X\n", hr);
CoUninitialize();
return 0;
}
HRESULT CreateComputerChildShortCut(LPWSTR childDisplayName, LPWSTR path)
{
// get My Computer folder's ShellItem (there are other ways for this...)
CComPtr<IShellItem> folder;
HRESULT hr = SHCreateItemInKnownFolder(FOLDERID_ComputerFolder, 0, NULL, IID_PPV_ARGS(&folder));
if (FAILED(hr)) return hr;
// enumerate children
CComPtr<IEnumShellItems> items;
hr = folder->BindToHandler(NULL, BHID_EnumItems, IID_PPV_ARGS(&items));
if (FAILED(hr)) return hr;
for (CComPtr<IShellItem> item; items->Next(1, &item, NULL) == S_OK; item.Release())
{
// get parsing path (if's often useful)
CComHeapPtr<wchar_t> parsingPath;
item->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &parsingPath);
wprintf(L"Path: %s\n", parsingPath);
// get display name
CComHeapPtr<wchar_t> displayName;
item->GetDisplayName(SIGDN_NORMALDISPLAY, &displayName);
wprintf(L" Name: %s\n", displayName);
if (!lstrcmpi(childDisplayName, displayName))
{
// get pidl
// it's the unambiguous way of referencing a shell thing
CComHeapPtr<ITEMIDLIST> pidl;
hr = SHGetIDListFromObject(item, &pidl);
if (FAILED(hr)) return hr;
// create an instance of the standard Shell's folder shortcut creator
CComPtr<IShellLink> link;
hr = link.CoCreateInstance(CLSID_FolderShortcut);
if (FAILED(hr)) return hr;
// just use the pidl
hr = link->SetIDList(pidl);
if (FAILED(hr)) return hr;
CComPtr<IPersistFile> file;
hr = link->QueryInterface(&file);
if (FAILED(hr)) return hr;
// save the shortcut (we could also change other IShellLink parameters)
hr = file->Save(path, FALSE);
if (FAILED(hr)) return hr;
break;
}
}
return S_OK;
}
Of course, if you know an absolute parsing path or an absolute pidl, you don't have to enumerate anything, this was just for demonstration purposes.

Windows Embedded Compact 2013 - Initial Application Focus

I have a log in screen when my application starts and would like the cursor to be waiting in the user name text box to type (without clicking). I was able to focus the cursor after the grid loads:
HRESULT MainPage::OnLoaded(__in IXRDependencyObject* pRoot)
{
UNREFERENCED_PARAMETER(pRoot);
HRESULT hr = InitializeComponent();
if (FAILED(hr))
{
goto Error;
}
if (m_pLogin)
{
m_pLogin->AddLoadedEventHandler(CreateDelegate(this, &MainPage::Login_Loaded));
}
Error:
return hr;
} // OnLoaded
HRESULT MainPage::Login_Loaded (IXRDependencyObject* pSender, XRRoutedEventArgs* pArgs)
{
HRESULT hr = S_OK;
m_pUserName->Focus(&pBool);
return hr;
}
This allows me to type but the cursor isn't actually visible.
I have seen other threads explaining that the browser plugin must have focus first, but this is an embedded silverlight application (C++) and not a silverlight webpage (C#). I cannot figure out how to set focus to the application when it starts without using the mouse. Any suggestions?
I was not able to accomplish this without a mouse click - but I used SendInput to synthesize a mouse click so the user doesn't have to.
HRESULT MainPage::OnLoaded(__in IXRDependencyObject* pRoot)
{
UNREFERENCED_PARAMETER(pRoot);
HRESULT hr = InitializeComponent();
if (FAILED(hr))
{
goto Error;
}
if (m_pLogin)
{
m_pLogin->AddLoadedEventHandler(CreateDelegate(this, &MainPage::Login_Loaded));
m_pLogin->AddMouseLeftButtonUpEventHandler(CreateDelegate(this, &MainPage::Login_MouseLeftButtonUp));
}
Error:
return hr;
} // OnLoaded
HRESULT MainPage::Login_Loaded (IXRDependencyObject* pSender, XRRoutedEventArgs* pArgs)
{
HRESULT hr = S_OK;
// execute mouse click
INPUT ip;
ip.type = INPUT_MOUSE;
ip.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;
SendInput(1,&ip,sizeof(INPUT));
ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;
SendInput(1,&ip,sizeof(INPUT));
return hr;
}
HRESULT MainPage::Login_MouseLeftButtonUp (IXRDependencyObject* pSender, XRMouseButtonEventArgs* pArgs)
{
HRESULT hr = S_OK;
m_pUserName->Focus(&pBool);
return hr;
}

How to load & call a VBScript function from within C++?

We have customers asking for VBScript functions to be called when particular actions occur within our product. I've been trying to research the Windows Scripting technologies but I'm having difficulty finding exactly what I need. Hopefully some of you can help.
Our product is a native C++ Windows product. The customer would specify a VBScript file, which we would load, and whenever a particular event occurs, we'd call a particular function in the VBScript and let it do its thing. We may provide objects within the script's namespace for it to access information about our product as well.
I found some information on MSDN about the IActiveScript interface, and some related things, but cannot find any examples of instantiating a COM object that implements this interface for VBScript.
I know that PowerShell would probably be a better option for this these days, but our customers are stuck in a lot of legacy systems and VBScript is what they know.
Any help you can provide (links or otherwise) would be appreciated!
I've put together a "Hello World" IActiveScript C++ ATL console application that:
Define CSimpleScriptSite class
Implement IActiveScriptSite interface (mandatory)
Implement IActiveScriptSiteWindow interface (optional)
Minimum implementation with most functions implemented with a dummy stub
Has no error handling. Consult MSDN IActiveScriptError.
Use CoCreateInstance a new IActiveSite object
Create instances of both VBScript and JScript
Link the IActiveSite to IActiveScriptSite using IActiveSite::SetScriptSite
Call QueryInterface to get an IActiveScriptParse interface
Use IActiveScriptParse to execute VBScript or JScript code
The sample:
Evaluates an expression in JScript
Evaluates an expression in VBScript
Runs a command in VBScript
Code:
#include "stdafx.h"
#include <atlbase.h>
#include <activscp.h>
class CSimpleScriptSite :
public IActiveScriptSite,
public IActiveScriptSiteWindow
{
public:
CSimpleScriptSite() : m_cRefCount(1), m_hWnd(NULL) { }
// IUnknown
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);
// IActiveScriptSite
STDMETHOD(GetLCID)(LCID *plcid){ *plcid = 0; return S_OK; }
STDMETHOD(GetItemInfo)(LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown **ppiunkItem, ITypeInfo **ppti) { return TYPE_E_ELEMENTNOTFOUND; }
STDMETHOD(GetDocVersionString)(BSTR *pbstrVersion) { *pbstrVersion = SysAllocString(L"1.0"); return S_OK; }
STDMETHOD(OnScriptTerminate)(const VARIANT *pvarResult, const EXCEPINFO *pexcepinfo) { return S_OK; }
STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState) { return S_OK; }
STDMETHOD(OnScriptError)(IActiveScriptError *pIActiveScriptError) { return S_OK; }
STDMETHOD(OnEnterScript)(void) { return S_OK; }
STDMETHOD(OnLeaveScript)(void) { return S_OK; }
// IActiveScriptSiteWindow
STDMETHOD(GetWindow)(HWND *phWnd) { *phWnd = m_hWnd; return S_OK; }
STDMETHOD(EnableModeless)(BOOL fEnable) { return S_OK; }
// Miscellaneous
HRESULT SetWindow(HWND hWnd) { m_hWnd = hWnd; return S_OK; }
public:
LONG m_cRefCount;
HWND m_hWnd;
};
STDMETHODIMP_(ULONG) CSimpleScriptSite::AddRef()
{
return InterlockedIncrement(&m_cRefCount);
}
STDMETHODIMP_(ULONG) CSimpleScriptSite::Release()
{
if (!InterlockedDecrement(&m_cRefCount))
{
delete this;
return 0;
}
return m_cRefCount;
}
STDMETHODIMP CSimpleScriptSite::QueryInterface(REFIID riid, void **ppvObject)
{
if (riid == IID_IUnknown || riid == IID_IActiveScriptSiteWindow)
{
*ppvObject = (IActiveScriptSiteWindow *) this;
AddRef();
return NOERROR;
}
if (riid == IID_IActiveScriptSite)
{
*ppvObject = (IActiveScriptSite *) this;
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr = S_OK;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Initialize
CSimpleScriptSite* pScriptSite = new CSimpleScriptSite();
CComPtr<IActiveScript> spJScript;
CComPtr<IActiveScriptParse> spJScriptParse;
hr = spJScript.CoCreateInstance(OLESTR("JScript"));
hr = spJScript->SetScriptSite(pScriptSite);
hr = spJScript->QueryInterface(&spJScriptParse);
hr = spJScriptParse->InitNew();
CComPtr<IActiveScript> spVBScript;
CComPtr<IActiveScriptParse> spVBScriptParse;
hr = spVBScript.CoCreateInstance(OLESTR("VBScript"));
hr = spVBScript->SetScriptSite(pScriptSite);
hr = spVBScript->QueryInterface(&spVBScriptParse);
hr = spVBScriptParse->InitNew();
// Run some scripts
CComVariant result;
EXCEPINFO ei = { };
hr = spJScriptParse->ParseScriptText(OLESTR("(new Date()).getTime()"), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
hr = spVBScriptParse->ParseScriptText(OLESTR("Now"), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
hr = spVBScriptParse->ParseScriptText(OLESTR("MsgBox \"Hello World! The current time is: \" & Now"), NULL, NULL, NULL, 0, 0, 0, &result, &ei);
// Cleanup
spVBScriptParse = NULL;
spVBScript = NULL;
spJScriptParse = NULL;
spJScript = NULL;
pScriptSite->Release();
pScriptSite = NULL;
::CoUninitialize();
return 0;
}
A version of the above code can be found here:
https://github.com/stephenquan/RunScriptDemo
IActiveScript and related interfaces work very well. I use them in my product exactly the same way you have described. Some of out customers write their own VBScript and JScript scripts to analyze and update application data before it gets posted to a database.
You use CoCreateInstance() to instantiate IActiveScript, like you would any other COM object. You would then call its QueryInterface() method to obtain an IActiveScriptParse interface for loading snippets of scripting code, and then you update the IActiveScript's state to execute the code.
You can add custom objects to the script by implementing IDispatch-derived classes and then passing them to the engine using IActiveScript::AddNamedItem() and an IActiveScriptSite::GetItemInfo() callback.
There are examples of IActiveScript usage available on MSDN.