I'm working on a drag and drop problem now and trying to get the PIDLs of the items being dragged from windows shell to my application.
The code below can get correct PIDLs if the dragged item is 'My Computer' or 'Control Panel' itself, but it doesn't work when the dragged item is an item in the 'Control Panel'.
What's wrong with my code?
#define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
#define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
STGMEDIUM medium;
UINT fmt = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
FORMATETC fe= {fmt, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT GETDATA_RESULT = pDataObject->GetData(&fe, &medium);
if (SUCCEEDED(GETDATA_RESULT))
{
LPIDA pida = (LPIDA)GlobalLock(medium.hGlobal);
LPCITEMIDLIST pidlFolder = GetPIDLFolder(pida);
int n = pida->cidl; // the n is always correct
if( n > 0 )
{
LPCITEMIDLIST pidlItem = GetPIDLItem(pida, 0);
// the pidlItem is wrong when the dragged object is an item in 'Control Panel'
}
GlobalUnlock(medium.hGlobal);
}
ReleaseStgMedium(&medium);
Any idea? Thanks
Zach#Shine
If I D&D Mouse, Network Connections and Fonts I get the following output in my test app:
0 Mouse | sfgao=4 hr=0
1 ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\::{7007ACC7-3202-11D1-AAD2-00805FC1270E} | sfgao=20000004 hr=0
2 C:\WINDOWS\Fonts | sfgao=60000004 hr=0
The Network Connections and Fonts pidls can be converted to fully qualified shell paths while Mouse only returns a relative path/displayname.
This makes sense if you check the documentation for IShellFolder::GetDisplayNameOf:
...They do not guarantee that
IShellFolder will return the requested
form of the name. If that form is not
available, a different one might be
returned. In particular, there is no
guarantee that the name returned by
the SHGDN_FORPARSING flag will be
successfully parsed by
IShellFolder::ParseDisplayName. There
are also some combinations of flags
that might cause the
GetDisplayNameOf/ParseDisplayName
round trip to not return the original
identifier list. This occurrence is
exceptional, but you should check to
be sure.
It is clear that when dealing with controlpanel items, you need to keep the pidl around and only use GetDisplayNameOf for display strings in your UI.
(IShellLink::SetIDList on the Mouse pidl will create a working shortcut so the pidl is valid)
void OnDrop(IDataObject*pDO)
{
STGMEDIUM medium;
UINT fmt = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
FORMATETC fe= {fmt, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT hr = pDO->GetData(&fe, &medium);
if (SUCCEEDED(hr))
{
LPIDA pida = (LPIDA)GlobalLock(medium.hGlobal);
if (pida)
{
LPCITEMIDLIST pidlFolder = GetPIDLFolder(pida);
for (UINT i=0; i<pida->cidl; ++i)
{
LPCITEMIDLIST pidlItem = GetPIDLItem(pida,i);
LPITEMIDLIST pidlAbsolute = ILCombine(pidlFolder,pidlItem);
if (pidlAbsolute)
{
IShellFolder*pParentSF;
hr= SHBindToParent(pidlAbsolute,IID_IShellFolder,(void**)&pParentSF,&pidlItem);
if (SUCCEEDED(hr))
{
STRRET str;
hr= pParentSF->GetDisplayNameOf(pidlItem, SHGDN_FORPARSING, &str);
if (SUCCEEDED(hr))
{
TCHAR szName[MAX_PATH];
hr= StrRetToBuf(&str,pidlItem,szName,MAX_PATH);
if (SUCCEEDED(hr))
{
SFGAOF sfgao = SFGAO_FOLDER|SFGAO_FILESYSTEM|SFGAO_HASSUBFOLDER|SFGAO_CANLINK;
hr= pParentSF->GetAttributesOf(1,&pidlItem,&sfgao);
TRACE(_T("%u %s | sfgao=%X hr=%X\n"),i,szName,sfgao,hr);
CreateTestShortcut(pidlAbsolute);
}
}
pParentSF->Release();
}
ILFree(pidlAbsolute);
}
}
GlobalUnlock(medium.hGlobal);
}
ReleaseStgMedium(&medium);
}
}
Related
This is the event handlers i implemented to the copy, paste and Cut buttons in my MFCRibbonBar:
in the MyRibbonView.cpp:
void CMyRibbonView::OnEditCopy()
{
CWnd *wnd = GetFocus();
if (wnd == pEdit)
pEdit->Copy();
if (!OpenClipboard())
{
AfxMessageBox(_T("Cannot open the Clipboard"));
return;
}
if (!EmptyClipboard())
{
AfxMessageBox(_T("Cannot empty the Clipboard"));
return;
}
HGLOBAL hGlob = GlobalAlloc(GMEM_FIXED, 64);
strcpy_s((char*)hGlob, 64, "Current selection\r\n");
if (::SetClipboardData(CF_TEXT, hGlob) == NULL)
{
CString msg;
msg.Format(_T("Unable to set Clipboard data, error: %d"), GetLastError());
AfxMessageBox(msg);
CloseClipboard();
GlobalFree(hGlob);
return;
}
CloseClipboard();
}
void CMyRibbonView::OnEditPaste()
{
if (OpenClipboard())
{
HANDLE hClipboardData = GetClipboardData(CF_TEXT);
char *pchData = (char*)GlobalLock(hClipboardData);
CString strFromClipboard;
strFromClipboard = pchData;
pEdit->SetWindowText(strFromClipboard);
GlobalUnlock(hClipboardData);
CloseClipboard();
}
}
void CMyRibbonView::OnEditCut()
{
OnEditCopy();
pEdit->SetWindowText(L" ");
}
There is no errors, it's just not working. I tested it by adding the messages to check if it's actually the data or not but they're not popping up.
You need to GlobalLock your hGlob memory before copying your character string into it (this operation converts it into a usable pointer for your process - see here), and then call GlobalUnlock after you've done that (so that the clipboard can access hGlob):
HGLOBAL hGlob = GlobalAlloc(GMEM_FIXED, 64); // Maybe also need GMEM_MOVEABLE here instead?
char* cCopy = (char*)GlobalLock(hGlob);
strcpy_s(cGlob, 64, "Current selection\r\n");
GlobalUnlock(hGlob);
if (::SetClipboardData(CF_TEXT, hGlob) == NULL)
{
//...
And you'll need a similar arrangement for the paste operation.
I tried to show only the drives shared with Remote Desktop in a file selection dialog. Below is what I want to show:
(Image) Windows 10 - Only the shared drives are shown.
But the list of the drives and folders in the left pane are different according to the Windows OS version. The links to the screenshots are in a reply comment.
How can we show the same list of the drives and folders in the multiple Windows versions?
This is the implementation:
CFileDialog my_file_dialog(TRUE);
CComPtr<IFileDialogCustomize> file_dialog = my_file_dialog.GetIFileDialogCustomize();
CComPtr<IFileDialog2> dialog2;
if (FAILED(file_dialog->QueryInterface(&dialog2))) {
::AfxMessageBox(_T("Failed to query the interface of IFileDialog2."));
return;
}
CComPtr<IShellItemFilter> shell_item_filter =
new CVisibleShellItemFilter(REMOTE_APP_VISIBLE_PATHS);
if (FAILED(dialog2->SetFilter(shell_item_filter))) {
::AfxMessageBox(_T("Failed to set the shell item filter to the file dialog."));
return;
}
CComPtr<IShellItem> shell_item;
if (FAILED(::SHCreateItemInKnownFolder(FOLDERID_ComputerFolder, 0, nullptr, IID_PPV_ARGS(&shell_item)))) {
::AfxMessageBox(_T("Failed to create a shell item for the computer folder."));
return;
}
if (FAILED(dialog2->SetDefaultFolder(shell_item))) {
::AfxMessageBox(_T("Failed to set the default folder to the computer folder."));
return;
}
CComPtr<IShellItem> shell_item_desktop;
if (FAILED(::SHCreateItemInKnownFolder(FOLDERID_Desktop, 0, nullptr, IID_PPV_ARGS(&shell_item_desktop)))) {
::AfxMessageBox(_T("Failed to create a shell item for the desktop."));
return;
}
if (FAILED(dialog2->SetNavigationRoot(shell_item_desktop))) {
::AfxMessageBox(_T("Failed to set the navigation root to the computer folder."));
return;
}
CComPtr<IShellItem> currently_selected_folder;
if (FAILED(dialog2->GetFolder(¤tly_selected_folder))) {
::AfxMessageBox(_T("Failed to set the navigation root to the computer folder."));
return;
}
if (!IsVisible(ToPathPartsList(REMOTE_APP_VISIBLE_PATHS), currently_selected_folder)) {
if (FAILED(dialog2->SetFolder(shell_item))) {
::AfxMessageBox(_T("Failed to set the folder to the computer folder."));
return;
}
}
if (my_file_dialog.DoModal() == IDOK) {
m_message.SetWindowTextW(my_file_dialog.GetPathName());
}
else {
m_message.SetWindowTextW(_T(""));
}
What I did were:
Construct an instance of CFiledialog.
Retrieved an instance of IFileDialogCustomize by CFiledialog::GetIFileDialogCustomize().
Retrieved an instance of IFileDialog2 from the isntance of IFileDialogCustomize.
Set an instance of IShellItemFilter to the file dialog by IFileDialog::SetFilter().
Set the default folder to the PC (My Computer) folder by IFileDialog::SetDefaultFolder().
Set the navigation root to the PC (My Computer) folder by IFileDialog::SetNavigationRoot().
Set the folder to the PC (My Computer) folder by IFileDialog::SetFolder() if the current folder is not a shared drive or its descendant folders.
Show the file dialog.
I did 2. to retrieve an instance of IFileDialog2 from the instance of IFileDialogCustomize. This is because I want to support both "Open" and "Save" file dialog by the same routine.
The point is 4.. I will show the implementation of IShellItemFilter later.
I did 5. because a folder other than a shared drive or its descendant folders is shown if the default folder is not than a shared drive or its descendant folders.
I did 6. because I don't want to show the minimum contents in the file dialog, and I don't want to show the desktop folder.
I did 7. because a folder other than a shared drive or its descendant folders is shown if the current folder is not than a shared drive or its descendant folders.
The implementation of IShellItemFilter was:
// If the desktop_absolute_parsing is "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}", it shows the
// virtual folder that represents My Computer (PC).
// http://www.atmarkit.co.jp/ait/articles/1004/09/news094.html
static const std::wstring MY_COMPUTER_PATH = L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}";
// If the desktop_absolute_parsing starts with \\tsclient\, it shows a drive shared with
// Remote Desktop (or RemoteApp) or its child folder.
// https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/74673059-17b0-4a80-80ac-66b5dc419b56?forum=vcgeneralja
static const std::wstring REMOTE_APP_SHARED_FOLDER_PATH_PREFIX = L"\\\\tsclient\\";
static const std::vector<std::wstring> REMOTE_APP_VISIBLE_PATHS = {
// Add My Compter to the paths so that users can navigate from the My Compter in the folder
// view.
MY_COMPUTER_PATH,
REMOTE_APP_SHARED_FOLDER_PATH_PREFIX,
};
// Converts a give string to lower cases.
// str String
// return Lower case string.
std::wstring ToLower(std::wstring str) {
std::transform(str.begin(), str.end(), str.begin(),
[](auto ch) { return std::tolower(ch, std::locale()); });
return str;
}
// Split a givn path to the parts. Users need to split a path into the path parts, and pass to
// the function below for the processing speed.
// ex) "c:\\windows\\system32" -> {"c:", "\\", "windows", "system32"}
// file_path Path to be splitted.
// return Path parts.
std::vector<std::wstring> SplitPath(const std::wstring& file_path) {
using std::experimental::filesystem::v1::path;
path p(file_path);
return std::vector<std::wstring>(p.begin(), p.end());
}
// Checks if a path is equal to or an ancestor of another path.
// ancestor_path_parts Given path parts.
// descendant_path_parts Other path parts.
// returns true if ancestor_path_parts is equal to or an ancestor of descendant_path_parts.
// false, otherwise.
bool IsPathSelfOrAncestor(const std::vector<std::wstring>& ancestor_path_parts,
const std::vector<std::wstring>& descendant_path_parts) {
// Do not compare the path strings directly. Because "C:\Windows\System32" matches
// "C:\Windows\System", for example.
return std::search(descendant_path_parts.begin(), descendant_path_parts.end(),
ancestor_path_parts.begin(), ancestor_path_parts.end()) ==
descendant_path_parts.begin();
}
// Checks if two given paths are in a direct line. i.e. A path is equal to, ancestor of, or
// decendant of another path.
// path_parts1 Given path parts.
// path_parts2 Other path parts.
// return true if the given two paths are in a direct line. false, otherwise.
bool IsInDirectLine(const std::vector<std::wstring>& path_parts1, const std::vector<std::wstring>& path_parts2) {
return IsPathSelfOrAncestor(path_parts1, path_parts2) ||
IsPathSelfOrAncestor(path_parts2, path_parts1);
}
// Gets the display name from a given shell item.
// sigdnName SIGDN name.
// shell_item Shell item.
// name Display name.
// return S_OK if this method succeeds. false, otherwise.
HRESULT GetDisplayName(IShellItem* shell_item, SIGDN sigdnName, std::wstring& name) {
LPWSTR raw_name = nullptr;
HRESULT result;
if (FAILED(result = shell_item->GetDisplayName(sigdnName, &raw_name))) {
return result;
}
name = raw_name;
::CoTaskMemFree(raw_name);
raw_name = nullptr;
return S_OK;
}
// Checks if a given shell item is visible in a file/folder view.
// visible_path_parts_list List of the visble paths parts.
// shell_item Shell item to be checked.
// return true if the given shell item is visible. false, otherwise.
bool IsVisible(const std::vector<std::vector<std::wstring> >& visible_path_parts_list, IShellItem* shell_item) {
std::wstring desktop_absolute_parsing;
if (FAILED(GetDisplayName(shell_item, SIGDN_DESKTOPABSOLUTEPARSING,
desktop_absolute_parsing))) {
::AfxMessageBox(_T("Failed to get the diplay name of a shell item."));
return false;
}
auto path_parts = SplitPath(ToLower(desktop_absolute_parsing));
for (const auto& visible_path_parts : visible_path_parts_list) {
// Check if shell_item and visible_path are in the direct line. i.e. shell_item and
// visible_path are same, shell_item is an ancestor of visible_path, or shell_item is
// an descendant of visible_path.
// Let users to navigate in the case that shell_item is an ancestor of visible_path.
// Otherwise, users can not navigate to the visible_path through the folder view in
// the file selection dialog.
if (IsInDirectLine(path_parts, visible_path_parts)) {
return true;
}
}
return false;
}
// Converts the list of paths into the list of the path parts.
std::vector<std::vector<std::wstring> > ToPathPartsList(
const std::vector<std::wstring>& paths) {
std::vector<std::vector<std::wstring> > path_parts_list;
for (const auto& path : paths) {
path_parts_list.push_back(SplitPath(ToLower(path)));
}
return path_parts_list;
}
// CVisibleShellItemFilter show only the visible shell items which are listed in the given list
// of paths.
class CVisibleShellItemFilter : public IShellItemFilter {
public:
CVisibleShellItemFilter(const std::vector<std::wstring>& visible_paths) :
m_visible_path_parts_list(ToPathPartsList(visible_paths)) { }
HRESULT QueryInterface(REFIID riid, void** ppvObject) override {
if (ppvObject == nullptr) {
return E_POINTER;
}
if (riid == IID_IUnknown || riid == IID_IShellItemFilter) {
*ppvObject = static_cast<void*>(this);
AddRef();
return NO_ERROR;
}
else {
*ppvObject = nullptr;
return E_NOINTERFACE;
}
}
ULONG AddRef() override {
return ++m_reference_count;
}
ULONG Release() override {
ULONG reference_count = --m_reference_count;
if (reference_count == 0) {
delete this;
}
return reference_count;
}
HRESULT IncludeItem(IShellItem *psi) override {
return IsVisible(m_visible_path_parts_list, psi) ? S_OK : S_FALSE;
}
HRESULT GetEnumFlagsForItem(IShellItem *psi, SHCONTF *pgrfFlags) override {
*pgrfFlags = static_cast<SHCONTF>(-1);
return S_OK;
}
private:
ULONG m_reference_count = 0;
const std::vector<std::vector<std::wstring> > m_visible_path_parts_list;
};
CVisibleShellItemFilter::IncludeItem() returns S_OK if the desktop absolute parsing of a file is "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" or starts with "\tsclient\". "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" means PC (My Computer) folder, and "\tsclient\" is the file path prefix of the shared drives.
Thanks,
Updated (2017/09/07 11:54 JST)
"the content" -> "the list of the drives and folders"
I'm trying to create a wgl context according to the tutorial at https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context_(WGL). For whatever reason, wglCreateContext returns null, and GetLastError returns 6, or Invalid Handle. I have used the tutorial before, and it worked just fine.
I'm trying to create a dummy context to a hidden window so that I can create another context for the user window. What's going on?
The Windows API is referred through the winAPI identifier, and wgl is a global struct containing function pointers to OpenGL.
struct WglContext
{
winAPI.HDC hdc;
winAPI.HGLRC handle;
}
__gshared Win32GL wgl;
///WGL-specific global data.
struct Win32GL
{
import oswald : OsWindow, WindowConfig, WindowError;
OsWindow helperWindow;
winAPI.HINSTANCE instance;
PFN_wglCreateContext createContext;
PFN_wglDeleteContext deleteContext;
PFN_wglGetProcAddress getProcAddress;
PFN_wglMakeCurrent makeCurrent;
PFN_wglCreateContextAttribsARB createContextAttribsARB;
PFN_wglGetExtensionStringARB getExtensionStringARB;
PFN_wglGetExtensionStringEXT getExtensionStringEXT;
bool extensionsAreLoaded;
static void initialize()
{
if (wgl.instance !is null)
return; //The library has already been initialized
WindowConfig cfg;
cfg.title = "viewport_gl_helper";
cfg.hidden = true;
auto windowError = OsWindow.createNew(cfg, &wgl.helperWindow);
if (windowError != WindowError.NoError)
{
import std.conv : to;
assert(false, "Failed to create helper window: " ~ windowError.to!string);
}
wgl.instance = winAPI.LoadLibrary("opengl32.dll");
if (wgl.instance is null)
assert(false, "Viweport failed to load opengl32.dll");
wgl.bind(cast(void**)&wgl.createContext, "wglCreateContext\0");
wgl.bind(cast(void**)&wgl.deleteContext, "wglDeleteContext\0");
wgl.bind(cast(void**)&wgl.getProcAddress, "wglGetProcAddress\0");
wgl.bind(cast(void**)&wgl.makeCurrent, "wglMakeCurrent\0");
}
static void terminate()
{
if (!wgl.instance)
return;
winAPI.FreeLibrary(wgl.instance);
wgl.helperWindow.destroy();
}
void bind(void** func, in string name)
{
*func = winAPI.GetProcAddress(this.instance, name.ptr);
}
}
struct WglContext
{
winAPI.HDC hdc;
winAPI.HGLRC handle;
}
WglContext wglCreateTmpContext()
{
assert(wgl.instance);
winAPI.PIXELFORMATDESCRIPTOR pfd;
pfd.nSize = winAPI.PIXELFORMATDESCRIPTOR.sizeof;
pfd.nVersion = 1;
pfd.dwFlags = winAPI.PFD_DRAW_TO_WINDOW | winAPI.PFD_SUPPORT_OPENGL | winAPI.PFD_DOUBLEBUFFER;
pfd.iPixelType = winAPI.PFD_TYPE_RGBA;
pfd.cColorBits = 24;
auto hdc = winAPI.GetDC(wgl.helperWindow.platformData.handle);
const pixelFormat = winAPI.ChoosePixelFormat(hdc, &pfd);
if (winAPI.SetPixelFormat(hdc, pixelFormat, &pfd) == winAPI.FALSE)
assert(false, "Failed to set pixel format for temp context");
writeln(hdc);
writeln(pixelFormat);
winAPI.HGLRC context = wgl.createContext(hdc);
if (context is null)
{
import std.conv: to;
assert(false, "Failed to create temp context: Error Code " ~ winAPI.GetLastError().to!string);
}
if (wgl.makeCurrent(hdc, context) == winAPI.FALSE)
{
wgl.deleteContext(context);
assert(false, "Failed to make temp context current");
}
return WglContext(hdc, context);
}
void main()
{
wgl.initialize(); //Fetches function pointers, and loads opengl32.dll
auto context = wglCreateTmpContext();
wglDeleteContext(context); //Delegates to wgl.deleteContext
wgl.terminate(); //Unloads opengl32.dll and nulls function pointers
}
I know nothing on that winAPI you use. Anyhow, I'm sure of:
You must:
Create a window and get its HWND handle (Windows definition of
HWND, see below). Tipically the window uses WS_CLIPCHILDREN | WS_CLIPSIBLINGS
style; but try also the WS_OVERLAPPEDWINDOW as in the link you
posted.
Verify you get a valid HWND, and a valid HDC from it.
HWND and HDC are defined as *void. I would not trust on auto.
Quickie question: I'm toying with some of the new taskbar APIs in Windows 7 and have gotten Recent Items on my Apps jumplist to show up, but I would like to display them under a different title than the filename (most files my app will be opening will have very similar names). I don't see any way to do that with the IShellItem interface, though. Would I have to use custom categories and IShellLinks to accomplish this?
For reference, my current code looks like this:
void AddRecentApp(const wchar_t* path, const wchar_t* title /* Can I even use this? */ ) {
HRESULT hres;
hres = CoInitialize(NULL);
IShellItem* recentItem;
hres = SHCreateItemFromParsingName(path, NULL, IID_PPV_ARGS(&recentItem));
if(SUCCEEDED(hres)) {
SHARDAPPIDINFO recentItemInfo;
recentItemInfo.pszAppID = MY_APP_USER_MODEL_ID;
recentItemInfo.psi = recentItem;
SHAddToRecentDocs(SHARD_APPIDINFO, &recentItemInfo);
recentItem->Release();
}
}
Figured it out. IShellItems are just a representation of a file, so they only will provide that file's information (no custom title, etc.) An IShellLink is essentially a shortcut, and is much more flexible in terms of display and actions taken when launched, so are more appropriate in this situation. Here's my new code:
void AddRecentApp(const wchar_t* path, const wchar_t* title) {
HRESULT hres;
hres = CoInitialize(NULL);
// Shell links give us more control over how the item is displayed and run
IShellLink* shell_link;
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shell_link));
if(SUCCEEDED(hres)) {
// Set up the basic link properties
shell_link->SetPath(path);
shell_link->SetArguments(L"--some-command-line-here"); // command line to execute when item is opened here!
shell_link->SetDescription(title); // This is what shows in the tooltip
shell_link->SetIconLocation(L"/path/to/desired/icon", 0); // can be an icon file or binary
// Open up the links property store and change the title
IPropertyStore* prop_store;
hres = shell_link->QueryInterface(IID_PPV_ARGS(&prop_store));
if(SUCCEEDED(hres)) {
PROPVARIANT pv;
InitPropVariantFromString(title, &pv);
// Set the title property.
prop_store->SetValue(PKEY_Title, pv); // THIS is where the displayed title is actually set
PropVariantClear(&pv);
// Save the changes we made to the property store
prop_store->Commit();
prop_store->Release();
}
// The link must persist in the file system somewhere, save it here.
IPersistFile* persist_file;
hres = shell_link->QueryInterface(IID_PPV_ARGS(&persist_file));
if(SUCCEEDED(hres)) {
hres = persist_file->Save(L"/link/save/directory", TRUE);
persist_file->Release();
}
// Add the link to the recent documents list
SHARDAPPIDINFOLINK app_id_info_link;
app_id_info_link.pszAppID = MY_APP_USER_MODEL_ID;
app_id_info_link.psl = shell_link;
SHAddToRecentDocs(SHARD_APPIDINFOLINK, &app_id_info_link);
shell_link->Release();
}
}
I'm trying to write some simple code that will return the directory for the recycle bin on a local drive. Seems like it would be simple -- should be a thousand answers on Google. Haven't found one yet :(
I HAVE found that FAT and NTFS drives have different base names (RECYCLED and RECYCLER). I've found that 'the' recycle bin is a virtual folder that combines the recycle bins of all drives on the machine.
What I haven't found is a way to find C: drive's recycle bin directory -- even on a Vietnamese (or any other non-English) machine. (No posts I can find indicate whether "RECYCLER" gets internationalized or not)
Can anyone point me to a definitive answer?
Thanks
UPDATE: Aware of CSIDL_BITBUCKET and the functions that use it. From everything I've read though, it points to a virtual directory which is the union of all deleted files by that user on all drives. Looking for the physical recycle bin directory (on my Vista it appears to be C:\$Recycle.Bin as far as I can tell)
Using Raymond Chen's advice, and someone else's technique (can't remember where I found it) I present a function that will find the Recycle Bin directory on a drive. The function cycles through the directories in the root directory looking at hidden and/or system directories. When it finds one, it checks the child subdirectories looking for one that has CLSID_Recycle Bin.
Note that I've included two GetFolderCLSID functions below. Raymond Chen's is the simpler one, but it doesn't work on Windows 2000. The other implementation is longer, but appears to work everywhere.
Call like: CString recycleDir = FindRecycleBinOnDrive(L"C:\");
CString FindRecycleBinOnDrive(LPCWSTR path)
{
CString search;
search.Format(L"%c:\\*", path[0]);
WIN32_FIND_DATA fd = {0};
HANDLE fHandle = FindFirstFile(search, &fd);
while(INVALID_HANDLE_VALUE != fHandle)
{
if(FILE_ATTRIBUTE_DIRECTORY == (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) //only check directories
{
if(0 != (fd.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN))) //only check hidden and/or system directories
{
//the recycle bin directory itself won't be marked, but a SID-specific child directory will, so now look at them
CString childSearch;
childSearch.Format(L"%c:\\%s\\*", path[0], fd.cFileName);
WIN32_FIND_DATA childFD = {0};
HANDLE childHandle = FindFirstFile(childSearch, &childFD);
while(INVALID_HANDLE_VALUE != childHandle)
{
if((FILE_ATTRIBUTE_DIRECTORY == (childFD.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) && //only check directories
(childFD.cFileName[0] != L'.')) //don't check . and .. dirs
{
CString fullPath;
fullPath.Format(L"%c:\\%s\\%s", path[0], fd.cFileName, childFD.cFileName);
CLSID id = {0};
HRESULT hr = GetFolderCLSID(fullPath, id);
if(SUCCEEDED(hr))
{
if(IsEqualGUID(CLSID_RecycleBin, id))
{
FindClose(childHandle);
FindClose(fHandle);
//return the parent (recycle bin) directory
fullPath.Format(L"%c:\\%s", path[0], fd.cFileName);
return fullPath;
}
}
else
{
Log(logERROR, L"GetFolderCLSID returned %08X for %s", hr, fullPath);
}
}
if(FALSE == FindNextFile(childHandle, &childFD))
{
FindClose(childHandle);
childHandle = INVALID_HANDLE_VALUE;
}
}
}
}
if(FALSE == FindNextFile(fHandle, &fd))
{
FindClose(fHandle);
fHandle = INVALID_HANDLE_VALUE;
}
}
_ASSERT(0);
return L"";
}
//Works on Windows 2000, and even as Local System account
HRESULT GetFolderCLSID(LPCWSTR path, CLSID& pathCLSID)
{
LPMALLOC pMalloc = NULL;
HRESULT hr = 0;
if (SUCCEEDED(hr = SHGetMalloc(&pMalloc)))
{
LPSHELLFOLDER pshfDesktop = NULL;
if (SUCCEEDED(hr = SHGetDesktopFolder(&pshfDesktop)))
{
LPITEMIDLIST pidl = NULL;
DWORD dwAttributes = SFGAO_FOLDER;
if (SUCCEEDED(hr = pshfDesktop->ParseDisplayName(NULL, NULL, (LPWSTR)path, NULL, &pidl, &dwAttributes)))
{
LPPERSIST pPersist = NULL;
if (SUCCEEDED(hr = pshfDesktop->BindToObject(pidl, NULL, IID_IPersist, (LPVOID *) &pPersist)))
{
hr = pPersist->GetClassID(&pathCLSID);
pPersist->Release();
}
pMalloc->Free(pidl);
}
pshfDesktop->Release();
}
pMalloc->Release();
}
return hr;
}
//Not supported on Windows 2000 since SHParseDisplayName wasn't implemented then
//HRESULT GetFolderCLSID(LPCWSTR pszPath, CLSID& pathCLSID)
//{
// SHDESCRIPTIONID did = {0};
// HRESULT hr = 0;
// LPITEMIDLIST pidl = NULL;
// if (SUCCEEDED(hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, NULL))) //not supported by Windows 2000
// {
// IShellFolder *psf = NULL;
// LPCITEMIDLIST pidlChild = NULL;
// if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&psf, &pidlChild)))
// {
// hr = SHGetDataFromIDList(psf, pidlChild, SHGDFIL_DESCRIPTIONID, &did, sizeof(did));
// psf->Release();
// pathCLSID = did.clsid;
// }
// CoTaskMemFree(pidl);
// }
// return hr;
//}
In Win32, use SHGetSpecialFolderLocation. Pass CSIDL_BITBUCKET as the CDIL parameter.
A little bit late, but perhaps better late than never...
After debugging shell32.dll, I have found that for each version of windows the recycle path is hardcoded and, also, depends on the filesystem of that drive. I have tested this on Windows XP, Vista and Windows7:
Let X: be the drive we want to get the path to the recycle bin and let SID be the SID of the current user, then:
switchif(OsType) {
case WindowsXP:
{
if(PartitionType("X:") == NTFS)
{
printf("Path is: X:\\Recycler\\SID\\");
}
else
{
printf("Path is X:\\RECYCLED\\");
}
}
case WindowsVista:
case Windows7:
{
if(PartitionType("X:") == NTFS)
{
printf("Path is: X:\\$Recycle.bin\\SID\\");
}
else
{
printf("Path is X:\\$RECYCLE.BIN\\");
}
}
}
A wiki article presents the same facts:
http://en.wikipedia.org/wiki/Recycle_Bin_%28Windows%29