-------------------------update 5th-------------------------------
if (fIsFolder)
{
dwAttribs |= SFGAO_FOLDER;
}
else
{
dwAttribs |= SFGAO_SYSTEM;
dwAttribs |= SFGAO_FILESYSTEM;
}
if (nLevel < g_nMaxLevel)
{
dwAttribs |= SFGAO_HASSUBFOLDER;
dwAttribs |= SFGAO_FILESYSANCESTOR;
}
Now this one works normally in application A, stackoverflow image upload file dialog and in regular folder explorer.
-----------------------------------------------------update 4th-----------------------------------------------------------
After add dwAttribs |= SFGAO_SYSTEM; , now at least for application A, it works as expected in the file dialog. But the folder is still not clickable in the regular folder dialog or for CWFileDialog.
if (fIsFolder)
{
dwAttribs |= SFGAO_FOLDER;
dwAttribs |= SFGAO_FILESYSTEM;
dwAttribs |= SFGAO_FILESYSANCESTOR;
}
else
{
dwAttribs |= SFGAO_SYSTEM;// this line of code works better
dwAttribs |= SFGAO_FILESYSTEM;
}
-----------------------------------update third-----------------------------------------
This is the code in the virtual folder's ISHellFolder:
After I edit the following code to add dwAttribs |= SFGAO_FILESYSTEM;, the folder cannot be browsed when I double click it in the folder view. But it can be opened by left clicking it in the tree view.
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_FILESYSTEM;
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;
}
if (nLevel < g_nMaxLevel)
{
dwAttribs |= SFGAO_HASSUBFOLDER;
}
}
}
}
*rgfInOut &= dwAttribs;
return hr;
}`
Or
DWORD dwAttribs = 0;
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_FILESYSTEM;
dwAttribs |= SFGAO_FILESYSANCESTOR;
}
else
{
dwAttribs |= SFGAO_SYSTEM;
}
if (nLevel < g_nMaxLevel)
{
dwAttribs |= SFGAO_HASSUBFOLDER;
}
}
}
}
*rgfInOut &= dwAttribs;
Other code are the same as in update 2nd.
The weird stuff is, after I edit these code, application A which uses CFileDialog normally( not wrapping it, not set m_bPickNonFileSysFoldersMode to true) can display all virtual folder normally as I expected.
But in all other applications( including the one uses CWFileDilao which set m_bPickNonFileSysFoldersMode to true is still cannot view the virtual folder.
---------------------------------------update 2nd-------------------------
I code a simple class which derives from CFileDialog:
class CWFileDlg : public CFileDialog
{
public:
CWFileDlg(BOOL bOpenFileDialog, // TRUE for FileOpen, FALSE for FileSaveAs
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
CWnd* pParentWnd = NULL,
DWORD dwSize = 0,
BOOL bVistaStyle = TRUE);
~CWFileDlg();
};
CWFileDlg::CWFileDlg(BOOL bOpenFileDialog, // TRUE for FileOpen, FALSE for FileSaveAs
LPCTSTR lpszDefExt,
LPCTSTR lpszFileName,
DWORD dwFlags,
LPCTSTR lpszFilter,
CWnd* pParentWnd,
DWORD dwSize,
BOOL bVistaStyle) : CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd, dwSize, bVistaStyle)
{
m_bPickNonFileSysFoldersMode = TRUE;
}
CWFileDlg::~CWFileDlg()
{
}
Then I call it like:
CWFileDlg dlg(TRUE, NULL, 0, OFN_SHAREAWARE | OFN_ENABLESIZING | OFN_ALLOWMULTISELECT, L"all(*.*)|*.*||", this);
BOOL IsPickNonFileSysFoldersMode = dlg.IsPickNonFileSysFoldersMode();
//the value it gets is 1, which is TRUE
INT_PTR result = dlg.DoModal();
But in the pop up dialog, virtual folder is stil invisible. Tree view for it is also invisible.
------------------------------------------updates----------------------------
protected:
BOOL m_bVistaStyle;
BOOL m_bPickFoldersMode;
BOOL m_bPickNonFileSysFoldersMode;
DWORD m_dwCookie;
void* m_pIFileDialog;
void* m_pIFileDialogCustomize;
The m_bPickNonFileSysFoldersMode is not a public type. How could I set it to TRUE ?
Error 1 error C2248: 'CFileDialog::m_bPickNonFileSysFoldersMode' : cannot access protected member declared in class 'CFileDialog' c:\users\liyuan.liu\documents\dp-dll\testvirtualfolder\browser2\browser2\browser2dlg.cpp 166 1 browser2
By using Microsoft windows sample code, I installed a virtual folder on windows 7 by using shell namespace extension.
The following screenshot is using regular folder browser to open it:
Then I wrote a project which can create a file dialog:
CFileDialog dlg(TRUE, NULL, 0, OFN_SHAREAWARE | OFN_ENABLESIZING | OFN_ALLOWMULTISELECT, L"all(*.*)|*.*||", this);
INT_PTR result = dlg.DoModal();
However, in the file browser, the virtual folder is invisible:
But! When I was trying to upload the screenshot to StackOverflow ( the web browser I am using is chrome), the file dialog which is used to select file can display the tree view ( only the tree view) of the virtual folder:
By googling, it seems that the CFileDialog cannot support to display the virtual folder since it doesn't actually exist in the system. Any solutions to solve that?
I also tried with
bi.pidlRoot = pidlVirtual;
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_RETURNFSANCESTORS | BIF_BROWSEINCLUDEFILES | BIF_BROWSEFILEJUNCTIONS;
LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
Also doesn't work.
Unfortunately, what the file dialog shows is function of the app that opens it. Starting with Vista, it's the IFileDialog interface which is used, and it defines a set of options through the IFileDialog::SetOptions method.
The FOS_FORCEFILESYSTEM flag ensures that returned items are file system items (SFGAO_FILESYSTEM), and that depends on how you wrote your extension. SFGAO_FILESYSTEM should ring a bell, it's one of the flags the IShellFolder::GetAttributesOf method can return. If what you do is really virtual (ie: if you don't return this flag), they won't be shown when IFileDialog is configured like this.
If you test your extension with different apps (notepad, word, excel, browsers, etc.), you'll see that you see it sometimes, and sometimes you don't.
Spelunking into MFC's code (dlgfile.cpp), you will find this:
// We only expect and handle file system paths (for compatibility with GetOpenFileName functionality), so set the
// "force file system" flag which enables GetOpenFileName-like download behavior for non file system paths, unless
// the m_bPickNonFileSysFoldersMode is set to allow picking non-file system folders (like libraries in Windows 7).
dwFlags |= FOS_FORCEFILESYSTEM;
if (m_bPickNonFileSysFoldersMode)
{
dwFlags &= ~FOS_FORCEFILESYSTEM;
}
So, you need to set m_bPickNonFileSysFoldersMode to TRUE for CFileDialog to show your extension. in MFC you must derive from CFileDialog since this member is protected (BTW this is a stupid MFC design decision), for example::
class MyFileDialog : public CFileDialog
{
public:
MyFileDialog(LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL);
};
MyFileDialog::MyFileDialog(LPCTSTR lpszDefExt, LPCTSTR lpszFileName, DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd) : CFileDialog(TRUE, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd)
{
m_bPickNonFileSysFoldersMode = TRUE;
}
Last thing to remember: make sure your virtual folder (namespace extension, etc.) is registered with the same bitness (x86 vs x64) as the app using the CFileDialog.
But it won't fix other apps you don't own that use this flag...
Related
C++/MFC, Windows 11. Visual Studio 2022 17.4.3.
I'm using CFileDialog to allow user to chose a file. By creating a new class derived from CFileDialog, I am being notified whenever the user changes directories (folders).
I implemented this so I could control the filter applied to the list of files in the directory. However, I have been unsuccessful in this. Even if I don't change m_ofn, I get an error.
Here is some sample code:
// Caller
#include "Browsing_test.h"
P brTest(true, NULL, NULL, 0, fileTypes);
brTest.BrowseTest();
// Browsing_test.h
class P : CFileDialog
{
public:
P(BOOL bOpenFileDialog,
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
CWnd *pParentWnd = NULL,
DWORD dwSize = 0,
BOOL bVistaStyle = TRUE) : CFileDialog(bOpenFileDialog,
lpszDefExt,
lpszFileName,
dwFlags,
lpszFilter,
pParentWnd,
dwSize,
bVistaStyle) {};
int BrowseTest(void);
#include "stdafx.h"
#include "Browsing_test.h"
int P::BrowseTest(void)
{
int resultDebug = (int)DoModal();
return resultDebug;
}
void P::OnFolderChange()
{
auto s = GetOFN(); // for modifying m_ofn member of the base class,
// but not used in this sample code
// Add modificatons to m_ofn here
ApplyOFNToShellDialog(); // Gets assert on updating flags
}
Running this code gives an error in dlgfile.cpp (Microsoft code) at line:
hr = (static_cast<IFileDialog*>(m_pIFileDialog))->SetOptions(dwFlags);
which returns hr = E_UNEXPECTED Catastrophic failure. The value of dwFlags was hex 40.
I'm trying to hook winevents like EVENT_SYSTEM_MOVESIZESTART and EVENT_SYSTEM_MOVESIZEEND with all desktop windows.
For this, I used SetWinEventHook().
Here's my sample code.
BOOL DlmSetEventHook()
{
if (hInstance == NULL)
return FALSE;
CoInitialize(NULL);
hWEventHook = SetWinEventHook(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, NULL, WinEventProc, NULL, NULL, WINEVENT_OUTOFCONTEXT );
if ( hWEventHook == NULL )
{
return FALSE;
}
return TRUE;
}
It's portion of Installed Hook.
void DlmRemoveEventHook()
{
WaitForSingleObject(hWEventHook, INFINITE);
UnhookWinEvent(hWEventHook);
CoUninitialize();
}
The above is for removing WinEvent Hook.
And this is HookProc Function.
void CALLBACK WinEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime
)
{
IAccessible* pAcc = NULL;
VARIANT varChild;
HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &pAcc, &varChild);
if ((hr == S_OK) && (pAcc != NULL))
{
if (event == EVENT_SYSTEM_MOVESIZESTART)
{
if (!(::GetWindowLong(hPaneWnd, GWL_STYLE) & WS_VISIBLE))
{
::ShowWindow(hPaneWnd, SW_SHOWNOACTIVATE);
::UpdateWindow(hPaneWnd);
}
}
if (event == EVENT_SYSTEM_MOVESIZEEND)
{
if ((::GetWindowLong(hPaneWnd, GWL_STYLE) & WS_VISIBLE))
{
::ShowWindow(hPaneWnd, SW_HIDE);
}
}
pAcc->Release();
}
}
The above codes are all in DLL. And I compiled DLL with x86 Debug method.
And I, of course, compiled client project with x86 Debug method.
My problem is that this hookproc listens for events which caused on only x64 bit apps.(I use Win10 x64).
It cannot listen for winevents of x86 bit apps.
I only know that 32 bit dll can only injected into 32 bit project and same thing on 64 bit dll.
But I think that it's not related to winevent hook.
What's wrong on my code? And Which things do I never know?
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!
I have a a ListView containing a list of files:
hList = CreateWindowEx(0, WC_LISTVIEW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT, 0, 0, 500, 400, hWnd, (HMENU)ID_LISTVIEW, hInst, NULL);
Let's say it contains a row c:\temp\hello.txt.
How to enable drag and drop of this file from my application's ListView to external applications (such as Windows Explorer) as "Copy"?
The GUI part of the question might be obvious (or not?) via:
case WM_NOTIFY:
{
...
case LVN_BEGINDRAG:
But here this question is about the actual sending of the file to external applications, such as Windows Explorer. How to do this?
Implement IDropSource, IDropSourceNotify (optional) and IDataObject and call DoDragDrop:
If you are developing an application that can act as a data source for an OLE drag-and-drop operation, you must call DoDragDrop when you detect that the user has started an OLE drag-and-drop operation.
The DoDragDrop function enters a loop in which it calls various methods in the IDropSource and IDropTarget interfaces. (For a successful drag-and-drop operation, the application acting as the data source must also implement IDropSource, while the target application must implement IDropTarget.)
SHCreateDataObject can provide a IDataObject instance for you but you often end up having to code your own because the shell provided implementation is not perfect.
IDragSourceHelper can help you to get a fancy drag image.
See also:
Dragging a shell object, part 1: Getting the IDataObject
What a drag: Dragging a virtual file (HGLOBAL edition)
What a drag: Dragging a virtual file (IStream edition)
What a drag: Dragging a virtual file (IStorage edition)
Here is some code that implements all that is required to perform such a ListView file drag&drop. First some includes:
#define CINTERFACE
#define COBJMACROS
#include "ShObjIdl.h"
#include "ShlObj.h"
#include "oleidl.h"
Then this in the WinMain function, to initialize OLE operations.
OleInitialize(NULL);
InitCommonControls();
Then, the IDropSource part:
typedef struct __DSV_TDropSource {
IDropSource This;
IDropSourceVtbl Func;
ULONG RefCnt;
} __DSV_TDropSource;
HRESULT WINAPI __DSV_QueryInterface(IDropSource *This, REFIID riid, void **ppvObject)
{
IUnknown *punk = NULL;
if (riid == IID_IUnknown)
{
punk = (IUnknown*)This;
}
else if (riid == IID_IDropSource)
{
punk = (IUnknown*)This;
}
*ppvObject = punk;
if (punk)
{
IUnknown_AddRef(punk);
return S_OK;
}
else {
return E_NOINTERFACE;
}
}
ULONG WINAPI __DSV_AddRef(IDropSource *This)
{
__DSV_TDropSource *pThis = (__DSV_TDropSource*)This;
return pThis->RefCnt++;
}
ULONG WINAPI __DSV_Release(IDropSource *This)
{
__DSV_TDropSource *pThis = (__DSV_TDropSource*)This;
LONG iRes = (LONG)pThis->RefCnt - 1;
if (iRes < 1) { iRes = 0; }
pThis->RefCnt = iRes;
if (iRes == 0) { free(pThis); }
return iRes;
}
HRESULT WINAPI __DSV_QueryContinueDrag(IDropSource *This, BOOL fEscapePressed, DWORD grfKeyState)
{
if (fEscapePressed) { return DRAGDROP_S_CANCEL; }
if (!(grfKeyState & (MK_LBUTTON | MK_RBUTTON))) { return DRAGDROP_S_DROP; }
return S_OK;
}
HRESULT WINAPI __DSV_GiveFeedback(IDropSource *This, DWORD dwEffect)
{
return DRAGDROP_S_USEDEFAULTCURSORS;
}
IDropSource* CreateDropSource()
{
__DSV_TDropSource *pResu = (__DSV_TDropSource*)malloc(sizeof(__DSV_TDropSource));
if (!pResu) { return 0; }
pResu->This.lpVtbl = &(pResu->Func);
pResu->Func.QueryInterface = __DSV_QueryInterface;
pResu->Func.AddRef = __DSV_AddRef;
pResu->Func.Release = __DSV_Release;
pResu->Func.QueryContinueDrag = __DSV_QueryContinueDrag;
pResu->Func.GiveFeedback = __DSV_GiveFeedback;
pResu->RefCnt = 1;
return (IDropSource*)pResu;
}
void** GetFileUiObject(TCHAR *ptFile, REFIID riid)
{
void** pInterfaceResu = 0;
IShellFolder *pFolder;
PIDLIST_RELATIVE pFile;
PIDLIST_ABSOLUTE pITEMDLIST_File;
HRESULT iResu;
pITEMDLIST_File = ILCreateFromPath(ptFile);
if (!pITEMDLIST_File)
return 0;
iResu = SHBindToParent(pITEMDLIST_File, IID_IShellFolder, (void**)&pFolder, (PCUITEMID_CHILD*)&pFile);
if (iResu != S_OK)
return 0;
const ITEMIDLIST* pArray[1] = { pFile };
iResu = IShellFolder_GetUIObjectOf(pFolder, NULL, 1, pArray, riid, NULL, (void**)&pInterfaceResu);
if (iResu != S_OK)
return 0;
IShellFolder_Release(pFolder);
return pInterfaceResu;
}
Lastly, this should be performed in the message loop:
case WM_NOTIFY:
pdi = (NMLVDISPINFO*) lParam;
nmlv = (NMLISTVIEW*) lParam;
switch (pdi->hdr.code)
{
case LVN_BEGINDRAG:
wstring fName = L"C:\\test.txt";
IDataObject *pObj;
IDropSource *pSrc;
pObj = (IDataObject*)GetFileUiObject(LPWSTR(fName.c_str()), IID_IDataObject);
if (!pObj)
break;
pSrc = CreateDropSource();
if (!pSrc)
{
IDataObject_Release(pObj);
break;
}
DWORD dwEffect;
DoDragDrop(pObj, pSrc, DROPEFFECT_COPY | DROPEFFECT_LINK, &dwEffect);
IDropSource_Release(pSrc);
IDataObject_Release(pObj);
break;
I try to extract the icon of file and return it to GetIconLocation of shell extension.
in general I change the icons of files (te.docx.xx) with the extension of xx and returns the icon of file without the xx. (for this I cretae temp file in temp directory with the original extension e.g te.docx)
my operating system is windows 7 x64.
my code is:
STDMETHODIMP CTxtIconShlExt::GetIconLocation (
UINT uFlags, LPTSTR szIconFile, UINT cchMax,
int* piIndex, UINT* pwFlags )
{
DWORD dwFileSizeLo, dwFileSizeHi;
DWORDLONG qwSize;
HANDLE hFile;
OutputDebugStringW(L"Hello world, from GetIconLocation !");
std::string strFilePath;
std::string tempFolder="c:\\.tmp";
std::string tempFile="tmpfile";
std::string fileWithOutDN;
SHFILEINFO retShFileInfo;
for(int i = 0; m_szFilename[i] != 0; i++)
{
strFilePath += m_szFilename[i];
}
fileWithOutDN= strFilePath.substr(0,strFilePath.size()-3 );
std::string extension = fileWithOutDN.substr(fileWithOutDN.find_last_of("."));
CreateDirectory(tempFolder.c_str(),NULL);
tempFile=tempFolder+"\\"+tempFile+extension;
GetFileAttributes(tempFile.c_str()); // from winbase.h
if(INVALID_FILE_ATTRIBUTES == GetFileAttributes(tempFile.c_str()) && GetLastError()==ERROR_FILE_NOT_FOUND)
{
//File not found
HANDLE h = CreateFile(tempFile.c_str(), // name of the file
GENERIC_WRITE, // open for writing
0, // sharing mode, none in this case
0, // use default security descriptor
CREATE_ALWAYS, // overwrite if exists
FILE_ATTRIBUTE_NORMAL,
0);
if (h)
{
CloseHandle(h);
}else
{
return S_FALSE; //faild to create file
}
}
ZeroMemory(&retShFileInfo, sizeof(SHFILEINFO));
CoInitialize(NULL);
SHGetFileInfo(tempFile.c_str(),256,&retShFileInfo,sizeof(SHFILEINFO),SHGFI_ICON | SHGFI_LARGEICON);
lstrcpyn ( szIconFile, retShFileInfo.szDisplayName, cchMax );
*piIndex = retShFileInfo.iIcon;
*pwFlags = 0;
return S_OK;
my problem is that the retShFileInfo.szDisplayName return an empty array (all items are zero), it should return full path to icon location.
I try to play with the combination of the flags but nothing was helpful