Ответ 1
Как предложил Раймонд Чен, заменив путь на PIDL (SHELLEXECUTEINFO::lpIDList
), диалоговое окно свойств корректно отображает поля размера и даты под Windows 7 при вызове через ShellExecuteEx()
.
Кажется, что реализация Windows 7 из ShellExecuteEx()
ошибочна, поскольку более новые версии ОС не имеют проблемы с SHELLEXCUTEINFO::lpFile
.
Существует еще одно решение, которое включает создание экземпляра IContextMenu
и вызов метода IContextMenu::InvokeCommand()
. Наверное, это то, что ShellExecuteEx()
делает под капотом. Прокрутите вниз до Решение 2, например, кода.
Решение 1 - использование PIDL с 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
}
Этот код работает для меня как для Win 7, так и для Win 10 (другие версии, не протестированные) при вызове с помощью обработчика кнопок простого диалогового приложения MFC.
Он также работает для консольных приложений, если вы установите info.hwnd
на NULL
(просто удалите строку info.hwnd = GetSafeHwnd();
из кода примера, поскольку она уже инициализирована 0). В SHELLEXECUTEINFO указано, что член hwnd
является необязательным.
Не забудьте об обязательном вызове CoInitialize()
или CoInitializeEx()
при запуске приложения и CoUninitialize()
при завершении работы, чтобы правильно инициализировать и деинициализировать COM.
Примечания:
CComHeapPtr
- это умный указатель, включенный в ATL, который автоматически вызывает CoTaskMemFree()
, когда область действия заканчивается. Это указатель на перенос собственности с семантикой, подобный устаревшему std::auto_ptr
. То есть, когда вы назначаете объект CComHeapPtr
другому или используете конструктор с параметром CComHeapPtr
, исходный объект станет указателем NULL.
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!
Я все еще использую его, потому что он готов использовать готовые файлы и хорошо работает вместе с COM-интерфейсами.
Решение 2 - с использованием IContextMenu
Следующий код требует Windows Vista или новее, поскольку я использую "современный" IShellItem
API.
Я завернул код в функцию ShowPropertiesDialog()
, которая принимает дескриптор окна и путь к файловой системе. Если возникает какая-либо ошибка, функция выдает исключение std::system_error
.
#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" );
}
В следующем примере показан пример использования ShowPropertiesDialog()
из обработчика кнопки класса, полученного из CDialog. Фактически ShowPropertiesDialog()
не зависит от MFC, поскольку ему просто нужен дескриптор окна, но OP упомянул, что хочет использовать код в приложении MFC.
#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 );
}
}