How to hide Desktop icons with Windows API in C++? - c++

The answers I've found link to fHideIcon, but I get a 404 error on Microsoft's page for those links.
I've also tried:
SHELLSTATE ss;
SecureZeroMemory(&ss, sizeof(ss));
SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE);
But that didn't work.
Does anyone know how to do this?

The following approach which uses the official shell API is a little bit involved, but works even under Windows 10.
Steps:
Get the IFolderView2 interface of the desktop (supported since Windows Vista).
Call IFolderView2::SetCurrentFolderFlags() with FWF_NOICONS for both the dwMask and dwFlags parameters.
The effect of the flag is visible immediately. There is no need to restart the computer nor "explorer.exe". The flag also persists after logoff or reboot.
The tricky thing is step 1). Raymond Chen shows C++ code for that in his article "Manipulating the positions of desktop icons", specifically in his FindDesktopFolderView() function.
Here is a full example in form of a console application. It is based on Raymond Chen's code. The program toggles the visibility of the desktop icons each time it is run.
The code has been tested under Windows 10 Version 1803.
"Library" code:
#include <ShlObj.h> // Shell API
#include <atlcomcli.h> // CComPtr & Co.
#include <string>
#include <iostream>
#include <system_error>
// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
// RAII wrapper to initialize/uninitialize COM
struct CComInit
{
CComInit() { ThrowIfFailed( ::CoInitialize( nullptr ), "CoInitialize failed" ); }
~CComInit() { ::CoUninitialize(); }
CComInit( CComInit const& ) = delete;
CComInit& operator=( CComInit const& ) = delete;
};
// Query an interface from the desktop shell view.
void FindDesktopFolderView( REFIID riid, void **ppv, std::string const& interfaceName )
{
CComPtr<IShellWindows> spShellWindows;
ThrowIfFailed(
spShellWindows.CoCreateInstance( CLSID_ShellWindows ),
"Failed to create IShellWindows instance" );
CComVariant vtLoc( CSIDL_DESKTOP );
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
ThrowIfFailed(
spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp ),
"Failed to find desktop window" );
CComQIPtr<IServiceProvider> spProv( spdisp );
if( ! spProv )
ThrowIfFailed( E_NOINTERFACE, "Failed to get IServiceProvider interface for desktop" );
CComPtr<IShellBrowser> spBrowser;
ThrowIfFailed(
spProv->QueryService( SID_STopLevelBrowser, IID_PPV_ARGS( &spBrowser ) ),
"Failed to get IShellBrowser for desktop" );
CComPtr<IShellView> spView;
ThrowIfFailed(
spBrowser->QueryActiveShellView( &spView ),
"Failed to query IShellView for desktop" );
ThrowIfFailed(
spView->QueryInterface( riid, ppv ),
"Could not query desktop IShellView for interface " + interfaceName );
}
Example to toggle desktop icons using the above code:
void ToggleDesktopIcons()
{
CComPtr<IFolderView2> spView;
FindDesktopFolderView( IID_PPV_ARGS(&spView), "IFolderView2" );
DWORD flags = 0;
ThrowIfFailed(
spView->GetCurrentFolderFlags( &flags ),
"GetCurrentFolderFlags failed" );
ThrowIfFailed(
spView->SetCurrentFolderFlags( FWF_NOICONS, flags ^ FWF_NOICONS ),
"SetCurrentFolderFlags failed" );
}
int wmain(int argc, wchar_t **argv)
{
try
{
CComInit init;
ToggleDesktopIcons();
std::cout << "Desktop icons have been toggled.\n";
}
catch( std::system_error const& e )
{
std::cout << "ERROR: " << e.what() << ", error code: " << e.code() << "\n";
return 1;
}
return 0;
}

The third parameter isn't about changing the setting, it's to select the SHGetSetSettings() behavior.
FALSE will get the value of the current setting and store it in ss, TRUE will set the value of the setting to what is in ss.
So basically you have to do ss.fHideIcons = TRUE and then call SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE) to set it.
I know, it's weird, but on the other hand it allows you to change multiple settings simultaneously because SSF_* is a bitmask.

The following seems to work (adapted from https://stackoverflow.com/a/6403014/5743288):
#include <windows.h>
int main ()
{
HWND hProgman = FindWindowW (L"Progman", L"Program Manager");
HWND hChild = GetWindow (hProgman, GW_CHILD);
ShowWindow (hChild, SW_HIDE);
Sleep (2000);
ShowWindow (hChild, SW_SHOW);
}
Please note: this approach is not supported by Microsoft and disables right-clicking on ths desktop.

Related

Get path of Windows Explorer window in C++ [duplicate]

I've been banging my head on the wall of how I can go about doing this. Basically my application needs to figure out the directory path of the active file explorer (ie the file explorer in the foreground) in windows in c++ using the winapi.
Instead of this:
TCHAR* getWindowDir(){
TCHAR* windowTitle = new TCHAR[MAX_PATH];
HWND windowHandle = GetForegroundWindow();
GetWindowText(windowHandle,windowTitle,MAX_PATH);
return windowTitle;
}
Which obviously returns the window title I want it to return the active directory.
Create an instance of IShellWindows and use that to enumerate all currently open Explorer windows. Using various related interfaces, you can get the window handle and the current folder in form of a PIDL from each item enumerated by IShellWindows. If the window handle is equal to the result of GetForegroundWindow(), convert the PIDL into a path.
In the following I present some code for getting information about all Explorer windows. It is partially based on code of Raymond Chen but uses smart pointers for less fragile and cleaner code. I've also added error handling via exceptions.
First the required includes and some utility code:
#include <Windows.h>
#include <shlobj.h>
#include <atlcomcli.h> // for COM smart pointers
#include <atlbase.h> // for COM smart pointers
#include <vector>
#include <system_error>
#include <memory>
#include <iostream>
// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
// Deleter for a PIDL allocated by the shell.
struct CoTaskMemDeleter
{
void operator()( ITEMIDLIST* pidl ) const { ::CoTaskMemFree( pidl ); }
};
// A smart pointer for PIDLs.
using UniquePidlPtr = std::unique_ptr< ITEMIDLIST, CoTaskMemDeleter >;
Now we define a function GetCurrentExplorerFolders() to return information about all currently open Explorer windows, including window handle and PIDL of the current folder.
// Return value of GetCurrentExplorerFolders()
struct ExplorerFolderInfo
{
HWND hwnd = nullptr; // window handle of explorer
UniquePidlPtr pidl; // PIDL that points to current folder
};
// Get information about all currently open explorer windows.
// Throws std::system_error exception to report errors.
std::vector< ExplorerFolderInfo > GetCurrentExplorerFolders()
{
CComPtr< IShellWindows > pshWindows;
ThrowIfFailed(
pshWindows.CoCreateInstance( CLSID_ShellWindows ),
"Could not create instance of IShellWindows" );
long count = 0;
ThrowIfFailed(
pshWindows->get_Count( &count ),
"Could not get number of shell windows" );
std::vector< ExplorerFolderInfo > result;
result.reserve( count );
for( long i = 0; i < count; ++i )
{
ExplorerFolderInfo info;
CComVariant vi{ i };
CComPtr< IDispatch > pDisp;
ThrowIfFailed(
pshWindows->Item( vi, &pDisp ),
"Could not get item from IShellWindows" );
if( ! pDisp )
// Skip - this shell window was registered with a NULL IDispatch
continue;
CComQIPtr< IWebBrowserApp > pApp{ pDisp };
if( ! pApp )
// This window doesn't implement IWebBrowserApp
continue;
// Get the window handle.
pApp->get_HWND( reinterpret_cast<SHANDLE_PTR*>( &info.hwnd ) );
CComQIPtr< IServiceProvider > psp{ pApp };
if( ! psp )
// This window doesn't implement IServiceProvider
continue;
CComPtr< IShellBrowser > pBrowser;
if( FAILED( psp->QueryService( SID_STopLevelBrowser, &pBrowser ) ) )
// This window doesn't provide IShellBrowser
continue;
CComPtr< IShellView > pShellView;
if( FAILED( pBrowser->QueryActiveShellView( &pShellView ) ) )
// For some reason there is no active shell view
continue;
CComQIPtr< IFolderView > pFolderView{ pShellView };
if( ! pFolderView )
// The shell view doesn't implement IFolderView
continue;
// Get the interface from which we can finally query the PIDL of
// the current folder.
CComPtr< IPersistFolder2 > pFolder;
if( FAILED( pFolderView->GetFolder( IID_IPersistFolder2, (void**) &pFolder ) ) )
continue;
LPITEMIDLIST pidl = nullptr;
if( SUCCEEDED( pFolder->GetCurFolder( &pidl ) ) )
{
// Take ownership of the PIDL via std::unique_ptr.
info.pidl = UniquePidlPtr{ pidl };
result.push_back( std::move( info ) );
}
}
return result;
}
Example showing how to call GetCurrentExplorerFolders(), convert PIDL into path and catch exceptions.
int main()
{
::CoInitialize( nullptr );
try
{
std::wcout << L"Currently open explorer windows:\n";
for( const auto& info : GetCurrentExplorerFolders() )
{
CComHeapPtr<wchar_t> pPath;
if( SUCCEEDED( ::SHGetNameFromIDList( info.pidl.get(), SIGDN_FILESYSPATH, &pPath ) ) )
{
std::wcout << L"hwnd: 0x" << std::hex << info.hwnd
<< L", path: " << static_cast<LPWSTR>( pPath ) << L"\n";
}
}
}
catch( std::system_error& e )
{
std::cout << "ERROR: " << e.what() << "\nError code: " << e.code() << "\n";
}
::CoUninitialize();
}
Possible output:
Currently open explorer windows:
hwnd: 0x0030058E, path: C:\Windows
hwnd: 0x000C06D4, path: C:\Program Files

DirectX: Get InfoQueue before Device

Is there a way to get the InfoQueue or set the break parameters before the Device is created?
Right now i am creating the Device and then getting the InfoQueue, but any messages that are emitted before that point are going to be ignored and buried in the output window.
ID3D11Device* pDevice;
//...Create Device...
ID3D11InfoQueue* pInfoQueue;
pDevice->QueryInterface(__uuidof(ID3D11InfoQueue), &pInfoQueue);
pInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE);
And I want something like:
ID3D11InfoQueue* pInfoQueue;
//...Get InfoQueue...
pInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE);
ID3D11Device* pDevice;
//...Create Device...
Given the documentation for ID3D11InfoQueue saying that you get the interface pointer via a QueryInterface call on the device I would say the answer is 'no'.
What you want to do is enable DXGI debugging which will provide debug information for the device & swapchain creation.
#include <dxgidebug.h>
#if defined(_DEBUG)
Microsoft::WRL::ComPtr<IDXGIInfoQueue> dxgiInfoQueue;
typedef HRESULT (WINAPI * LPDXGIGETDEBUGINTERFACE)(REFIID, void ** );
HMODULE dxgidebug = LoadLibraryEx( L"dxgidebug.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32 );
if ( dxgidebug )
{
auto dxgiGetDebugInterface = reinterpret_cast<LPDXGIGETDEBUGINTERFACE>(
reinterpret_cast<void*>( GetProcAddress( dxgidebug, "DXGIGetDebugInterface" ) ) );
if ( SUCCEEDED( dxgiGetDebugInterface( IID_PPV_ARGS( dxgiInfoQueue.GetAddressOf() ) ) ) )
{
dxgiInfoQueue->SetBreakOnSeverity( DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true );
dxgiInfoQueue->SetBreakOnSeverity( DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true );
}
}
#endif
See this blog post and this one

Hide desktop icons (c++)(SHGetSetSetting) [duplicate]

The answers I've found link to fHideIcon, but I get a 404 error on Microsoft's page for those links.
I've also tried:
SHELLSTATE ss;
SecureZeroMemory(&ss, sizeof(ss));
SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE);
But that didn't work.
Does anyone know how to do this?
The following approach which uses the official shell API is a little bit involved, but works even under Windows 10.
Steps:
Get the IFolderView2 interface of the desktop (supported since Windows Vista).
Call IFolderView2::SetCurrentFolderFlags() with FWF_NOICONS for both the dwMask and dwFlags parameters.
The effect of the flag is visible immediately. There is no need to restart the computer nor "explorer.exe". The flag also persists after logoff or reboot.
The tricky thing is step 1). Raymond Chen shows C++ code for that in his article "Manipulating the positions of desktop icons", specifically in his FindDesktopFolderView() function.
Here is a full example in form of a console application. It is based on Raymond Chen's code. The program toggles the visibility of the desktop icons each time it is run.
The code has been tested under Windows 10 Version 1803.
"Library" code:
#include <ShlObj.h> // Shell API
#include <atlcomcli.h> // CComPtr & Co.
#include <string>
#include <iostream>
#include <system_error>
// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
// RAII wrapper to initialize/uninitialize COM
struct CComInit
{
CComInit() { ThrowIfFailed( ::CoInitialize( nullptr ), "CoInitialize failed" ); }
~CComInit() { ::CoUninitialize(); }
CComInit( CComInit const& ) = delete;
CComInit& operator=( CComInit const& ) = delete;
};
// Query an interface from the desktop shell view.
void FindDesktopFolderView( REFIID riid, void **ppv, std::string const& interfaceName )
{
CComPtr<IShellWindows> spShellWindows;
ThrowIfFailed(
spShellWindows.CoCreateInstance( CLSID_ShellWindows ),
"Failed to create IShellWindows instance" );
CComVariant vtLoc( CSIDL_DESKTOP );
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
ThrowIfFailed(
spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp ),
"Failed to find desktop window" );
CComQIPtr<IServiceProvider> spProv( spdisp );
if( ! spProv )
ThrowIfFailed( E_NOINTERFACE, "Failed to get IServiceProvider interface for desktop" );
CComPtr<IShellBrowser> spBrowser;
ThrowIfFailed(
spProv->QueryService( SID_STopLevelBrowser, IID_PPV_ARGS( &spBrowser ) ),
"Failed to get IShellBrowser for desktop" );
CComPtr<IShellView> spView;
ThrowIfFailed(
spBrowser->QueryActiveShellView( &spView ),
"Failed to query IShellView for desktop" );
ThrowIfFailed(
spView->QueryInterface( riid, ppv ),
"Could not query desktop IShellView for interface " + interfaceName );
}
Example to toggle desktop icons using the above code:
void ToggleDesktopIcons()
{
CComPtr<IFolderView2> spView;
FindDesktopFolderView( IID_PPV_ARGS(&spView), "IFolderView2" );
DWORD flags = 0;
ThrowIfFailed(
spView->GetCurrentFolderFlags( &flags ),
"GetCurrentFolderFlags failed" );
ThrowIfFailed(
spView->SetCurrentFolderFlags( FWF_NOICONS, flags ^ FWF_NOICONS ),
"SetCurrentFolderFlags failed" );
}
int wmain(int argc, wchar_t **argv)
{
try
{
CComInit init;
ToggleDesktopIcons();
std::cout << "Desktop icons have been toggled.\n";
}
catch( std::system_error const& e )
{
std::cout << "ERROR: " << e.what() << ", error code: " << e.code() << "\n";
return 1;
}
return 0;
}
The third parameter isn't about changing the setting, it's to select the SHGetSetSettings() behavior.
FALSE will get the value of the current setting and store it in ss, TRUE will set the value of the setting to what is in ss.
So basically you have to do ss.fHideIcons = TRUE and then call SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE) to set it.
I know, it's weird, but on the other hand it allows you to change multiple settings simultaneously because SSF_* is a bitmask.
The following seems to work (adapted from https://stackoverflow.com/a/6403014/5743288):
#include <windows.h>
int main ()
{
HWND hProgman = FindWindowW (L"Progman", L"Program Manager");
HWND hChild = GetWindow (hProgman, GW_CHILD);
ShowWindow (hChild, SW_HIDE);
Sleep (2000);
ShowWindow (hChild, SW_SHOW);
}
Please note: this approach is not supported by Microsoft and disables right-clicking on ths desktop.

How to get the path of an active file explorer window in c++ winapi

I've been banging my head on the wall of how I can go about doing this. Basically my application needs to figure out the directory path of the active file explorer (ie the file explorer in the foreground) in windows in c++ using the winapi.
Instead of this:
TCHAR* getWindowDir(){
TCHAR* windowTitle = new TCHAR[MAX_PATH];
HWND windowHandle = GetForegroundWindow();
GetWindowText(windowHandle,windowTitle,MAX_PATH);
return windowTitle;
}
Which obviously returns the window title I want it to return the active directory.
Create an instance of IShellWindows and use that to enumerate all currently open Explorer windows. Using various related interfaces, you can get the window handle and the current folder in form of a PIDL from each item enumerated by IShellWindows. If the window handle is equal to the result of GetForegroundWindow(), convert the PIDL into a path.
In the following I present some code for getting information about all Explorer windows. It is partially based on code of Raymond Chen but uses smart pointers for less fragile and cleaner code. I've also added error handling via exceptions.
First the required includes and some utility code:
#include <Windows.h>
#include <shlobj.h>
#include <atlcomcli.h> // for COM smart pointers
#include <atlbase.h> // for COM smart pointers
#include <vector>
#include <system_error>
#include <memory>
#include <iostream>
// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
// Deleter for a PIDL allocated by the shell.
struct CoTaskMemDeleter
{
void operator()( ITEMIDLIST* pidl ) const { ::CoTaskMemFree( pidl ); }
};
// A smart pointer for PIDLs.
using UniquePidlPtr = std::unique_ptr< ITEMIDLIST, CoTaskMemDeleter >;
Now we define a function GetCurrentExplorerFolders() to return information about all currently open Explorer windows, including window handle and PIDL of the current folder.
// Return value of GetCurrentExplorerFolders()
struct ExplorerFolderInfo
{
HWND hwnd = nullptr; // window handle of explorer
UniquePidlPtr pidl; // PIDL that points to current folder
};
// Get information about all currently open explorer windows.
// Throws std::system_error exception to report errors.
std::vector< ExplorerFolderInfo > GetCurrentExplorerFolders()
{
CComPtr< IShellWindows > pshWindows;
ThrowIfFailed(
pshWindows.CoCreateInstance( CLSID_ShellWindows ),
"Could not create instance of IShellWindows" );
long count = 0;
ThrowIfFailed(
pshWindows->get_Count( &count ),
"Could not get number of shell windows" );
std::vector< ExplorerFolderInfo > result;
result.reserve( count );
for( long i = 0; i < count; ++i )
{
ExplorerFolderInfo info;
CComVariant vi{ i };
CComPtr< IDispatch > pDisp;
ThrowIfFailed(
pshWindows->Item( vi, &pDisp ),
"Could not get item from IShellWindows" );
if( ! pDisp )
// Skip - this shell window was registered with a NULL IDispatch
continue;
CComQIPtr< IWebBrowserApp > pApp{ pDisp };
if( ! pApp )
// This window doesn't implement IWebBrowserApp
continue;
// Get the window handle.
pApp->get_HWND( reinterpret_cast<SHANDLE_PTR*>( &info.hwnd ) );
CComQIPtr< IServiceProvider > psp{ pApp };
if( ! psp )
// This window doesn't implement IServiceProvider
continue;
CComPtr< IShellBrowser > pBrowser;
if( FAILED( psp->QueryService( SID_STopLevelBrowser, &pBrowser ) ) )
// This window doesn't provide IShellBrowser
continue;
CComPtr< IShellView > pShellView;
if( FAILED( pBrowser->QueryActiveShellView( &pShellView ) ) )
// For some reason there is no active shell view
continue;
CComQIPtr< IFolderView > pFolderView{ pShellView };
if( ! pFolderView )
// The shell view doesn't implement IFolderView
continue;
// Get the interface from which we can finally query the PIDL of
// the current folder.
CComPtr< IPersistFolder2 > pFolder;
if( FAILED( pFolderView->GetFolder( IID_IPersistFolder2, (void**) &pFolder ) ) )
continue;
LPITEMIDLIST pidl = nullptr;
if( SUCCEEDED( pFolder->GetCurFolder( &pidl ) ) )
{
// Take ownership of the PIDL via std::unique_ptr.
info.pidl = UniquePidlPtr{ pidl };
result.push_back( std::move( info ) );
}
}
return result;
}
Example showing how to call GetCurrentExplorerFolders(), convert PIDL into path and catch exceptions.
int main()
{
::CoInitialize( nullptr );
try
{
std::wcout << L"Currently open explorer windows:\n";
for( const auto& info : GetCurrentExplorerFolders() )
{
CComHeapPtr<wchar_t> pPath;
if( SUCCEEDED( ::SHGetNameFromIDList( info.pidl.get(), SIGDN_FILESYSPATH, &pPath ) ) )
{
std::wcout << L"hwnd: 0x" << std::hex << info.hwnd
<< L", path: " << static_cast<LPWSTR>( pPath ) << L"\n";
}
}
}
catch( std::system_error& e )
{
std::cout << "ERROR: " << e.what() << "\nError code: " << e.code() << "\n";
}
::CoUninitialize();
}
Possible output:
Currently open explorer windows:
hwnd: 0x0030058E, path: C:\Windows
hwnd: 0x000C06D4, path: C:\Program Files

Missing data in Windows file properties dialog when opened by ShellExecuteEx

I want to show the Windows file properties dialog for a file from my C++ code (on Windows 7, using VS 2012). I found the following code in this answer (which also contains a full MCVE). I also tried calling CoInitializeEx() first, as mentioned in the documentation of ShellExecuteEx():
// Whether I initialize COM or not doesn't seem to make a difference.
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
SHELLEXECUTEINFO info = {0};
info.cbSize = sizeof info;
info.lpFile = L"D:\\Test.txt";
info.nShow = SW_SHOW;
info.fMask = SEE_MASK_INVOKEIDLIST;
info.lpVerb = L"properties";
ShellExecuteEx(&info);
This code works, i.e. the properties dialog is shown and ShellExecuteEx() returns TRUE. However, in the Details tab, the size property is wrong and the date properties are missing:
The rest of the properties in the Details tab (e.g. the file attributes) are correct. Strangely, the size and date properties are shown correctly in the General tab (left-most tab).
If I open the properties window via the Windows Explorer (file → right-click → Properties), then all properties in the Details tab are shown correctly:
I tried it with several files and file types (e.g. txt, rtf, pdf) on different drives and on three different PCs (1x German 64-bit Windows 7, 1x English 64-bit Windows 7, 1x English 32-bit Windows 7). I always get the same result, even if I run my program as administrator. On (64-bit) Windows 8.1 the code is working for me, though.
My original program in which I discovered the problem is an MFC application, but I see the same problem if I put the above code into a console application.
What do I have to do to show the correct values in the Details tab on Windows 7? Is it even possible?
As Raymond Chen suggested, replacing the path with a PIDL (SHELLEXECUTEINFO::lpIDList) makes the properties dialog correctly show the size and date fields under Windows 7 when invoked through ShellExecuteEx().
It seems that the Windows 7 implementation of ShellExecuteEx() is buggy since newer versions of the OS do not have an issue with SHELLEXCUTEINFO::lpFile.
There is another solution possible that involves creating an instance of IContextMenu and calling the IContextMenu::InvokeCommand() method. I guess this is what ShellExecuteEx() does under the hood. Scroll down to Solution 2 for example code.
Solution 1 - using a PIDL with ShellExecuteEx
#include <atlcom.h> // CComHeapPtr
#include <shlobj.h> // SHParseDisplayName()
#include <shellapi.h> // ShellExecuteEx()
// CComHeapPtr is a smart pointer that automatically calls CoTaskMemFree() when
// the current scope ends.
CComHeapPtr<ITEMIDLIST> pidl;
SFGAOF sfgao = 0;
// Convert the path into a PIDL.
HRESULT hr = ::SHParseDisplayName( L"D:\\Test.txt", nullptr, &pidl, 0, &sfgao );
if( SUCCEEDED( hr ) )
{
// Show the properties dialog of the file.
SHELLEXECUTEINFO info{ sizeof(info) };
info.hwnd = GetSafeHwnd();
info.nShow = SW_SHOWNORMAL;
info.fMask = SEE_MASK_INVOKEIDLIST;
info.lpIDList = pidl;
info.lpVerb = L"properties";
if( ! ::ShellExecuteEx( &info ) )
{
// Make sure you don't put ANY code before the call to ::GetLastError()
// otherwise the last error value might be invalidated!
DWORD err = ::GetLastError();
// TODO: Do your error handling here.
}
}
else
{
// TODO: Do your error handling here
}
This code works for me under both Win 7 and Win 10 (other versions not tested) when called from a button click handler of a simple dialog-based MFC application.
It also works for console applications if you set info.hwnd to NULL (simply remove the line info.hwnd = GetSafeHwnd(); from the example code as it is already initialized with 0). In the SHELLEXECUTEINFO reference it is stated that the hwnd member is optional.
Don't forget the mandatory call to CoInitialize() or CoInitializeEx() at the startup of your application and CoUninitialize() at shutdown to properly initialize and deinitialize COM.
Notes:
CComHeapPtr is a smart pointer included in ATL that automatically calls CoTaskMemFree() when the scope ends. It's an ownership-transferring pointer with semantics similar to the deprecated std::auto_ptr. That is, when you assign a CComHeapPtr object to another one, or use the constructor that has a CComHeapPtr parameter, the original object will become a NULL pointer.
CComHeapPtr<ITEMIDLIST> pidl2( pidl1 ); // pidl1 allocated somewhere before
// Now pidl1 can't be used anymore to access the ITEMIDLIST object.
// It has transferred ownership to pidl2!
I'm still using it because it is ready to use out-of-the-box and plays well together with the COM APIs.
Solution 2 - using IContextMenu
The following code requires Windows Vista or newer as I'm using the "modern" IShellItem API.
I wrapped the code into a function ShowPropertiesDialog() that takes a window handle and a filesystem path. If any error occurs, the function throws a std::system_error exception.
#include <atlcom.h>
#include <string>
#include <system_error>
/// Show the shell properties dialog for the given filesystem object.
/// \exception Throws std::system_error in case of any error.
void ShowPropertiesDialog( HWND hwnd, const std::wstring& path )
{
using std::system_error;
using std::system_category;
if( path.empty() )
throw system_error( std::make_error_code( std::errc::invalid_argument ),
"Invalid empty path" );
// SHCreateItemFromParsingName() returns only a generic error (E_FAIL) if
// the path is incorrect. We can do better:
if( ::GetFileAttributesW( path.c_str() ) == INVALID_FILE_ATTRIBUTES )
{
// Make sure you don't put ANY code before the call to ::GetLastError()
// otherwise the last error value might be invalidated!
DWORD err = ::GetLastError();
throw system_error( static_cast<int>( err ), system_category(), "Invalid path" );
}
// Create an IShellItem from the path.
// IShellItem basically is a wrapper for an IShellFolder and a child PIDL, simplifying many tasks.
CComPtr<IShellItem> pItem;
HRESULT hr = ::SHCreateItemFromParsingName( path.c_str(), nullptr, IID_PPV_ARGS( &pItem ) );
if( FAILED( hr ) )
throw system_error( hr, system_category(), "Could not get IShellItem object" );
// Bind to the IContextMenu of the item.
CComPtr<IContextMenu> pContextMenu;
hr = pItem->BindToHandler( nullptr, BHID_SFUIObject, IID_PPV_ARGS( &pContextMenu ) );
if( FAILED( hr ) )
throw system_error( hr, system_category(), "Could not get IContextMenu object" );
// Finally invoke the "properties" verb of the context menu.
CMINVOKECOMMANDINFO cmd{ sizeof(cmd) };
cmd.lpVerb = "properties";
cmd.hwnd = hwnd;
cmd.nShow = SW_SHOWNORMAL;
hr = pContextMenu->InvokeCommand( &cmd );
if( FAILED( hr ) )
throw system_error( hr, system_category(),
"Could not invoke the \"properties\" verb from the context menu" );
}
In the following I show an example of how to use ShowPropertiesDialog() from a button handler of a CDialog-derived class. Actually ShowPropertiesDialog() is independent from MFC, as it just needs a window handle, but OP mentioned that he wants to use the code in an MFC app.
#include <sstream>
#include <codecvt>
// Convert a multi-byte (ANSI) string returned from std::system_error::what()
// to Unicode (UTF-16).
std::wstring MultiByteToWString( const std::string& s )
{
std::wstring_convert< std::codecvt< wchar_t, char, std::mbstate_t >> conv;
try { return conv.from_bytes( s ); }
catch( std::range_error& ) { return {}; }
}
// A button click handler.
void CMyDialog::OnPropertiesButtonClicked()
{
std::wstring path( L"c:\\temp\\test.txt" );
// The code also works for the following paths:
//std::wstring path( L"c:\\temp" );
//std::wstring path( L"C:\\" );
//std::wstring path( L"\\\\127.0.0.1\\share" );
//std::wstring path( L"\\\\127.0.0.1\\share\\test.txt" );
try
{
ShowPropertiesDialog( GetSafeHwnd(), path );
}
catch( std::system_error& e )
{
std::wostringstream msg;
msg << L"Could not open the properties dialog for:\n" << path << L"\n\n"
<< MultiByteToWString( e.what() ) << L"\n"
<< L"Error code: " << e.code();
AfxMessageBox( msg.str().c_str(), MB_ICONERROR );
}
}