Отсутствие данных в диалоговом окне свойств файла Windows при открытии в ShellExecuteEx

Я хочу показать диалоговое окно свойств файла Windows для файла из моего кода C ++ (в Windows 7, используя VS 2012). Я нашел следующий код в этом ответе (который также содержит полный MCVE). Я тоже пробовал звонить CoInitializeEx() во-первых, как упоминалось в документация 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);

Этот код работает, то есть диалоговое окно свойств и ShellExecuteEx() возвращается TRUE, Однако в подробности На вкладке свойство size указано неверно, а свойства date отсутствуют:

Окно свойств открывается через мою программу

Остальные свойства в подробности вкладка (например, атрибуты файла) верна. Как ни странно, свойства размера и даты отображаются правильно в генеральный вкладка (самая левая вкладка).

Если я открою окно свойств через проводник Windows (файл → щелчок правой кнопкой мыши → Свойства), то все свойства в подробности Вкладка отображается правильно:

Окно свойств открывается через проводник Windows

Я попробовал его с несколькими файлами и типами файлов (например, txt, rtf, pdf) на разных дисках и на трех разных компьютерах (1 немецкая 64-битная Windows 7, 1x английская 64-битная Windows 7, 1x английская 32-битная Windows 7) , Я всегда получаю один и тот же результат, даже если я запускаю программу от имени администратора. Хотя на (64-битной) Windows 8.1 код работает для меня.

Моя оригинальная программа, в которой я обнаружил проблему, является приложением MFC, но я вижу ту же проблему, если помещаю приведенный выше код в консольное приложение.

Что я должен сделать, чтобы показать правильные значения в подробности вкладка на Windows 7? Это вообще возможно?

14

Решение

Как предложил Раймонд Чен, заменить путь PIDL (SHELLEXECUTEINFO::lpIDList) правильно отображает в диалоговом окне свойств поля размера и даты в Windows 7 при вызове через ShellExecuteEx(),

Похоже, что реализация Windows 7 ShellExecuteEx() глючит, так как более новые версии ОС не имеют проблем с SHELLEXCUTEINFO::lpFile,

Существует другое возможное решение, которое включает создание экземпляра IContextMenu и зовет IContextMenu::InvokeCommand() метод. Я думаю, это то, что ShellExecuteEx() делает под капотом. Прокрутите вниз до Решение 2 например код.

#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 API.


Следующий код требует 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 );
}
}
3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]