This is how you can get last text item from clipboard:
OpenClipboard(nullptr);
HANDLE hData = GetClipboardData(CF_TEXT);
char* pszText = static_cast<char*>(GlobalLock(hData));
std::string text;
if (pszText != nullptr)
{
text.assign(pszText);
}
GlobalUnlock(hData);
CloseClipboard();
std::cout << text;
This is how you can set an text item from clipboard:
std::string source("text");
if (OpenClipboard(nullptr))
{
HGLOBAL clipbuffer;
char* buffer;
EmptyClipboard();
clipbuffer = GlobalAlloc(GMEM_DDESHARE, source.length() + 1);
buffer = (char*)GlobalLock(clipbuffer);
strcpy(buffer, source.c_str());
GlobalUnlock(clipbuffer);
SetClipboardData(CF_TEXT, clipbuffer);
CloseClipboard();
}
But I don't know how to delete last text item from clipboard, I would like to be able to not let the user see that I am using the clipboard or change his clipboard so he cant paste last thing he copied...
How can I do this, delete an text item from windows clipboard using c++ ?
This is the strangest way to pass text from one app to another! Sending e-mail also comes to mind.
There is a Windows-native way to do that: send a WM_COPYDATA message.
See https://learn.microsoft.com/en-us/windows/win32/dataxchg/wm-copydata for details.
I have code that executes in InitInstance that checks if my application is already running, and if so brings it to the foreground. Standard code:
if (m_hMutex != nullptr)
{ // indicates running instance
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
EnumWindows(searcher, (LPARAM)&hOther);
if (hOther != nullptr)
{
::SetForegroundWindow(::GetLastActivePopup(hOther));
if (IsIconic(hOther))
{
::ShowWindow(hOther, SW_RESTORE);
}
}
return FALSE; // terminates the creation
}
}
The above I have no issues with. Recently I added support for double-clicking a file from File Explorer and it open in my software CDialog derived). So I do the following in InitInstance:
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (PathFileExists(cmdInfo.m_strFileName))
{
m_bOpenFileFromFileExplorer = true;
m_strFileToOpenFromFileExplorerPath = cmdInfo.m_strFileName;
}
Then, in my main dialog OnInitDialog I do this:
if (theApp.OpenFileFromFileExplorer())
{
CString strFileToOpen = theApp.GetFileToOpenFromFileExplorerPath();
CString strFileExtension = PathFindExtension(strFileToOpen);
strFileExtension.MakeLower();
if (strFileExtension == _T(".srr"))
PostMessage(WM_COMMAND, ID_FILE_OPENREPORT);
else if (strFileExtension == _T(".mwb"))
PostMessage(WM_COMMAND, ID_FILE_OPEN_CHRISTIAN_LIFE_AND_MINISTRY_REPORT);
}
Finally, each of my respective handlers for each editor does something like this):
void CMeetingScheduleAssistantDlg::OnFileOpenReport()
{
CCreateReportDlg dlgReport(this);
CString strFilePath, strFileName;
if (theApp.OpenFileFromFileExplorer())
{
strFilePath = theApp.GetFileToOpenFromFileExplorerPath();
strFileName = PathFindFileName(strFilePath);
if (strFilePath == _T("") || strFileName == _T(""))
{
// Error!
return;
}
}
else
{
CString strTitle, strFilter;
strTitle.LoadString(IDS_STR_SELECT_SRR_FILE);
strFilter.LoadString(IDS_STR_SRR_FILTER);
CFileDialog dlgOpen(TRUE, _T(".SRR"),
nullptr, OFN_PATHMUSTEXIST | OFN_HIDEREADONLY, strFilter, this);
dlgOpen.m_ofn.lpstrTitle = strTitle;
// get a file to open from user
if (dlgOpen.DoModal() != IDOK)
return;
strFilePath = dlgOpen.GetPathName();
strFileName = dlgOpen.GetFileName();
}
// AJT V9.1.0 - Most Recent File List support
theApp.AddToRecentFileList(strFilePath);
// tell report we want to open it
dlgReport.SetFileToOpen(strFilePath, strFileName);
// display it
dlgReport.DoModal();
// AJT V9.1.0 Bug Fix
SetDayStates(m_calStart);
SetDayStates(m_calEnd);
}
The other handler works in a similar way. There are no issues with the code implemented and users can double-click a file and it will open in the software with the right editor.
Ofcourse, if my software is already running (but only on the primary dialog) and the user double clicks a file, the duplicate instance will trigger and it will simply bring that window to the foreground.
What I would like to do is this:
Is the duplicate instance on the primary window?
Bring it to the foreground.
Trigger to open this file the user has double in this attempted instance.
Shutdown this instance.
Else
Bring it to the foreground.
Not much else we can do since a modal window is open in the other instance.
So just shut down.
End if
So how do I do that bit:
Is the duplicate instance on the primary window?
Bring it to the foreground.
Trigger to open this file the user has double in this attempted instance.
Shutdown this instance.
Else
?
Update
The problem is that:
HWND hOther = nullptr;
if (DetectRunningInstance(hOther))
{
DetectFileToOpenFromFileExplorer(); // AJT v20.1.6
CString strFile = GetFileToOpenFromFileExplorerPath();
LPCTSTR lpszString = strFile.GetBufferSetLength(_MAX_PATH);
COPYDATASTRUCT cds;
cds.dwData = 1;
cds.cbData = _MAX_PATH;
cds.lpData = (LPVOID)lpszString;
DWORD dwResult;
SendMessageTimeout(hOther, WM_COPYDATA,
NULL, (LPARAM)(LPVOID)&cds, SMTO_BLOCK, 2000, &dwResult);
strFile.ReleaseBuffer();
return FALSE; // Terminates the creation
}
...
...
BOOL CMeetingScheduleAssistantDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
if (pCopyDataStruct->dwData == nnn)
{
LPCTSTR lpszString = (LPCTSTR)(pCopyDataStruct->lpData);
AfxMessageBox(lpszString);
}
return TRUE;
}
The above passes the string, even if the other instance of MSA has a modal window up. So SendMessageTimeout never actual times out.
Got!
if (GetLastActivePopup() != this)
This is what I have so far...
Once I have determined that another instance is already running, and that a file was provided on the command line, I then run this method before I cancel the duplicate instance:
void CMeetingScheduleAssistantApp::TryToOpenFileInOtherInstance(HWND hOtherInstance)
{
CString strFile = GetFileToOpenFromFileExplorerPath();
LPCTSTR lpszString = strFile.GetBufferSetLength(_MAX_PATH);
COPYDATASTRUCT cds;
cds.dwData = xxxxx;
cds.cbData = _MAX_PATH;
cds.lpData = (LPVOID)lpszString;
DWORD_PTR dwResult;
if (SendMessageTimeout(hOtherInstance, WM_COPYDATA,
NULL, (LPARAM)(LPVOID)&cds, SMTO_BLOCK, 2000, &dwResult) != 0)
{
// The message was sent and processed
if (dwResult == FALSE)
{
// The other instance returned FALSE. This is probably because it
// has a pop-up window open so can't open the file
::OutputDebugString(_T("InitInstance::SendMessageTimeout [dwResult was FALSE].\n"));
}
}
else
{
DWORD dwError = ::GetLastError();
if (dwError == ERROR_TIMEOUT)
{
// The message timed out for some reason
::OutputDebugString(_T("InitInstance::SendMessageTimeout [ERROR_TIMEOUT].\n"));
}
else
{
// Another unknown error
}
CString strError = _T("");
strError.Format(_T("InitInstance::SendMessageTimeout [%d: %s]\n"), dwError, GetLastErrorAsStringEx(dwError));
::OutputDebugString(strError);
}
strFile.ReleaseBuffer();
}
In the WM_COPYDATA message handler:
BOOL CMeetingScheduleAssistantDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
if (pCopyDataStruct->dwData == xxx)
{
LPCTSTR lpszString = (LPCTSTR)(pCopyDataStruct->lpData);
{
if (GetLastActivePopup() != this) // Popup windows!
{
// TODO: Tell user?
return FALSE;
}
theApp.SetFileToOpenFromFileExplorer(lpszString);
OpenFileFromFileExplorer();
}
}
return TRUE;
}
I was trying to adapt the CopyData approach as described here but could not get it to work — Got it to work and code is shown here.
I'm new to C++ and MFC programming (using VS2005). I'm trying to use CFileDialog. Here is my code:
void CsampleDlg::StopRfWrite()
{
if (pFile != NULL)
fclose(pFile);
pFile = NULL;
char dir[512];
GetCurrentDirectoryA(512, dir);
CString connected=CString(dir) + _T("\\");
CString conn = connected + fn;
TCHAR szFilters[]= _T("RF data file (*.bin)|*.bin||");
CFileDialog fileDlg(FALSE, _T("bin"), fn,
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, szFilters);
// Display the file dialog. When user clicks OK, fileDlg.DoModal()
// returns IDOK.
if(fileDlg.DoModal() == IDOK)
{
CString pathName = fileDlg.GetPathName();
CString fileName = fileDlg.GetFileTitle();
SetWindowText(fileName);
MoveFile(conn,pathName);
}
else
{
DeleteFile(conn);
}
}
and receive
Debug Assertion failed error; (Line 384 in wincore.cpp) it is invoked by fileDlg.DoModal() call. It is random, sometimes program run fine.
Call Stack indicates the following row:
ASSERT(pWnd != NULL);
I want to run netstat -ano command using popen.
And I want to show the result of executing the command through MFC List Control.
Run the following code from the win32 console project and it will run correctly.
However, if run it on MFC project, popen("netstat -ano *.c","r") result is null.
The development environment is VC6.0.
Please let me know why null is coming. Thank you :)
FILE *fp = NULL;
char line[10240];
char* network[5];
int nIndexNetwork = 0;
if( (fp=popen("netstat -ano *.c","r")) == NULL)
{
printf("[%d:%s]\n", errno, strerror(errno));
CString str;
str.Format("%d",errno);
GetDlgItem(IDC_EDIT1)->SetWindowText(str);
return 1;
}
while(fgets(line, 10240, fp) != NULL)
{
char *word = strtok(line, " ");
while (word != NULL) // 자른 문자열이 나오지 않을 때까지 반복
{
network[nIndexNetwork] = word;
nIndexNetwork++;
printf("%s\n", network[nIndexNetwork-1]); // 자른 문자열 출력
word = strtok(NULL, " "); // 다음 문자열을 잘라서 포인터를 반환
}
nIndexNetwork = 0;
m_ctlList_network.AddItem(network[4],
network[0],
network[1],
network[2],
network[3]);
}
pclose(fp);
return 0;
It does not look like popen will work in a Win32 app. You will have to use, Creating a Child Process with Redirected Input and Output. I have used this example to get the output of a process and display it in MFC. It is not as simple as popen, but it works. I have not worked with this code in years but will post it. This first part just goes in some tool box, it is reusable for any process.
//console_pipe.h
// the console_pipe.cpp declarations .....................................................
//C style use
HRESULT RunProcess( HWND hWnd //Window to 'SendMessage' to
,WORD idPost //ID for message with the data pointer in wparam
,WORD idEnd //ID for message notifying the process has ended
,LPCTSTR psFileExe //Path.File of process to run
,LPCTSTR psCmdLine= NULL //command line
);
//C++
extern HANDLE hGlobalThread;
extern HANDLE hChildProcess;
class RunPipeConsoleProcess
{
HANDLE hProcess;
WORD idPost;
WORD idEnd;
CString strExeFile;
CString strArguments;
public:
void SetMessageIDs( WORD inPost, WORD inEnd ) { idPost= inPost; idEnd= inEnd; }
void SetExecutable( LPCTSTR psExe ) { strExeFile= psExe; }
void SetArguments( LPCTSTR psArg ) { strArguments= psArg; }
bool StartThread( HWND hPostTo ) { hProcess= hChildProcess; return RunProcess( hPostTo, idPost, idEnd, strExeFile, strExeFile + strArguments ) == S_OK; }
bool StopThread( )
{
if( hGlobalThread && hProcess )
TerminateProcess( hProcess, -1 );
// ::PostThreadMessage( (DWORD)hGlobalThread, ID_CONSOLE_THREAD_STOP, 0, 0 );
return true;
}
};
//console_pipe.cpp
#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#ifndef DWORD
typedef unsigned long DWORD;
#endif
#define CONPIPE_BUF_SIZE 1024
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
Redirect.c
Description:
This sample illustrates how to spawn a child console based
application with redirected standard handles.
The following import libraries are required:
user32.lib
Dave McPherson (davemm) 11-March-98
Modified 6-30-12 for use with MFC, Dan Bloomquist
RunProcess(...)
Creates a process running in its own thread. The process
is expected to use STDOUT to notify the condition of the process.
This call will return after starting the process with
the handle of the process. All you should need to do is
check for NULL, this means the process failed to start.
There will be no input to the process, it is expected
to work on its own after starting. A worker thread will
gather the STDOUT a line at a time and send it back to
the window specified.
TODO: The comments below reflect sending data to the
process input pipe. Here we are only interested in the
output of the process.
--*/
#pragma comment(lib, "User32.lib")
void DisplayError(TCHAR *pszAPI);
void ReadAndHandleOutput(HANDLE hPipeRead);
void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut,
HANDLE hChildStdIn,
HANDLE hChildStdErr,
LPCTSTR psPathFile,
LPCTSTR psCommandLine );
DWORD WINAPI GetAndSendInputThread( LPVOID lpvThreadParam );
HANDLE hChildProcess = NULL;
HANDLE hStdIn = NULL; // Handle to parents std input.
BOOL bRunThread = TRUE;
HWND hPostMessage= NULL;
HANDLE hOutputRead= NULL;
HANDLE hInputRead= NULL;
HANDLE hInputWrite= NULL;
WORD idGlbPost;
WORD idGlbEnd;
HANDLE hGlobalThread;
HRESULT RunProcess( HWND hWnd //Window to 'SendMessage' to
,WORD idPost //ID for message with the data pointer in wparam
,WORD idEnd //ID for message notifying the process has ended
,LPCTSTR psFileExe //Path.File of process to run
,LPCTSTR psCmdLine
)
{
hPostMessage= hWnd;
HANDLE hOutputReadTmp;
HANDLE hOutputWrite;
HANDLE hInputWriteTmp;
HANDLE hErrorWrite;
DWORD ThreadId;
SECURITY_ATTRIBUTES sa;
idGlbPost= idPost;
idGlbEnd= idEnd;
// Set up the security attributes struct.
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
// Create the child output pipe.
if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0))
DisplayError(_T("CreatePipe"));
// Create a duplicate of the output write handle for the std error
// write handle. This is necessary in case the child application
// closes one of its std output handles.
if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite,
GetCurrentProcess(),&hErrorWrite,0,
TRUE,DUPLICATE_SAME_ACCESS))
DisplayError(_T("DuplicateHandle"));
// Create the child input pipe.
if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0))
DisplayError(_T("CreatePipe"));
// Create new output read handle and the input write handles. Set
// the Properties to FALSE. Otherwise, the child inherits the
// properties and, as a result, non-closeable handles to the pipes
// are created.
if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
GetCurrentProcess(),
&hOutputRead, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
DisplayError(_T("DupliateHandle"));
if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
GetCurrentProcess(),
&hInputWrite, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
DisplayError(_T("DupliateHandle"));
// Close inheritable copies of the handles you do not want to be
// inherited.
if (!CloseHandle(hOutputReadTmp)) DisplayError(_T("CloseHandle hOutputReadTmp"));
if (!CloseHandle(hInputWriteTmp)) DisplayError(_T("CloseHandle hInputWriteTmp"));
// Get std input handle so you can close it and force the ReadFile to
// fail when you want the input thread to exit.
if ( (hStdIn = GetStdHandle(STD_INPUT_HANDLE)) ==
INVALID_HANDLE_VALUE )
DisplayError(_T("GetStdHandle"));
PrepAndLaunchRedirectedChild( hOutputWrite, hInputRead, hErrorWrite, psFileExe, psCmdLine );
// Close pipe handles (do not continue to modify the parent).
// You need to make sure that no handles to the write end of the
// output pipe are maintained in this process or else the pipe will
// not close when the child process exits and the ReadFile will hang.
if (!CloseHandle(hOutputWrite)) DisplayError(_T("CloseHandle hOutputWrite"));
if (!CloseHandle(hInputRead )) DisplayError(_T("CloseHandle hInputRead"));
if (!CloseHandle(hErrorWrite)) DisplayError(_T("CloseHandle hErrorWrite"));
// Launch the thread that gets the input and sends it to the child.
hGlobalThread= CreateThread( NULL, 0, GetAndSendInputThread, (LPVOID)hOutputRead, 0, &ThreadId );
if (hGlobalThread == NULL)
return -1;
return 0;
}
///////////////////////////////////////////////////////////////////////
// PrepAndLaunchRedirectedChild
// Sets up STARTUPINFO structure, and launches redirected child.
///////////////////////////////////////////////////////////////////////
void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut,
HANDLE hChildStdIn,
HANDLE hChildStdErr,
LPCTSTR psPathFile,
LPCTSTR psCommandLine )
{
//static int test= 0;
//ASSERT( ! test++ );
PROCESS_INFORMATION pi;
STARTUPINFO si;
// Set up the start up info struct.
ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hChildStdOut;
si.hStdInput = hChildStdIn;
si.hStdError = hChildStdErr;
// Use this if you want to hide the child:
// si.wShowWindow = SW_HIDE;
// Note that dwFlags must include STARTF_USESHOWWINDOW if you want to
// use the wShowWindow flags.
// Launch the process that you want to redirect (in this case,
// Child.exe). Make sure Child.exe is in the same directory as
// redirect.c launch redirect from a command line to prevent location
// confusion.
BOOL bSuccess = FALSE;
bSuccess = CreateProcess(
psPathFile, // app console
const_cast< LPTSTR >( psCommandLine ), //command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
CREATE_NO_WINDOW, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&si, // STARTUPINFO pointer
&pi // receives PROCESS_INFORMATION
);
// If an error occurs, exit the application.
if ( ! bSuccess )
{
DWORD err= GetLastError( );
DisplayError( TEXT( "CreateProcess" ) );
}
//if (!CreateProcess(NULL,psTestFile,NULL,NULL,TRUE,
// CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi))
// DisplayError(_T("CreateProcess");
// Set global child process handle to cause threads to exit.
hChildProcess= pi.hProcess;
// Close any unnecessary handles.
if( ! CloseHandle( pi.hThread ) )
DisplayError( _T("CloseHandle pi.hThread") );
}
///////////////////////////////////////////////////////////////////////
// ReadAndHandleOutput
// Monitors handle for input. Exits when child exits or pipe breaks.
///////////////////////////////////////////////////////////////////////
//#include <boost/iostreams/device/file_descriptor.hpp>
//#include <boost/iostreams/stream.hpp>
void ReadAndHandleOutput( HANDLE hPipeRead )
{
ASSERT( false );
int file_descriptor= _open_osfhandle( (intptr_t)hPipeRead, 0 );
FILE* file= _fdopen( file_descriptor, "r" );
std::ifstream stream( file );
std::string line;
//loop until stream empty
for( ; std::getline( stream, line ); )
{
SendMessage( hPostMessage, idGlbPost, (LPARAM)line.c_str( ), 0 );
TRACE( "line: %s\n", line.c_str( ) );
}
//should be so
ASSERT( GetLastError( ) == ERROR_BROKEN_PIPE );
if (!CloseHandle(hStdIn)) DisplayError(_T("CloseHandle hStdIn"));
if (!CloseHandle(hOutputRead)) DisplayError(_T("CloseHandle hOutputRead"));
if (!CloseHandle(hInputWrite)) DisplayError(_T("CloseHandle hInputWrite"));
}
///////////////////////////////////////////////////////////////////////
// GetAndSendInputThread
// Thread procedure that monitors the console for input and sends input
// to the child process through the input pipe.
// This thread ends when the child application exits.
///////////////////////////////////////////////////////////////////////
DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam)
{
HANDLE hPipeRead= (HANDLE)lpvThreadParam;
int file_descriptor= _open_osfhandle( (intptr_t)hPipeRead, 0 );
FILE* file= _fdopen( file_descriptor, "r" );
std::ifstream stream( file );
std::string line;
//loop until stream empty
for( ; std::getline( stream, line ); )
{
SendMessage( hPostMessage, idGlbPost, (LPARAM)line.c_str( ), 0 );
TRACE( "line: %s\n", line.c_str( ) );
}
//should be so
return 1;
ASSERT( GetLastError( ) == ERROR_BROKEN_PIPE );
//if (!CloseHandle(hStdIn)) DisplayError(_T("CloseHandle"));
if (!CloseHandle(hOutputRead)) DisplayError(_T("CloseHandle hOutputRead"));
if (!CloseHandle(hInputWrite)) DisplayError(_T("CloseHandle hInputWrite"));
SendMessage( hPostMessage, idGlbEnd, 0, 0 );
return 1;
}
///////////////////////////////////////////////////////////////////////
// DisplayError
// Displays the error number and corresponding message.
///////////////////////////////////////////////////////////////////////
void DisplayError(TCHAR *pszAPI)
{
TRACE( "DisplayError: %s\n", pszAPI );
return;
// return;
LPVOID lpvMessageBuffer;
TCHAR szPrintBuffer[CONPIPE_BUF_SIZE];
DWORD nCharsWritten;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpvMessageBuffer, 0, NULL);
StringCbPrintf(szPrintBuffer, CONPIPE_BUF_SIZE,
_T("ERROR: API = %s.\n error code = %d.\n message = %s.\n"),
pszAPI, GetLastError(), (TCHAR *)lpvMessageBuffer);
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),szPrintBuffer,
lstrlen(szPrintBuffer),&nCharsWritten,NULL);
LocalFree(lpvMessageBuffer);
//ExitProcess(GetLastError());
}
Now, in your dialog or whatever declaration, add the member:
RunPipeConsoleProcess pipes;
Then when you are ready, do the likes of:
pipes.SetMessageIDs( ID_CONSOLE_OUTPUT_WIN, ID_CONSOLE_OUTPUT_DONE );
pipes.SetExecutable( "netstat" );
pipes.SetArguments( "-ano );
if( ! pipes.StartThread( *this ) )
{
cEditStat.SetSel( -1, -1 );
cEditStat.ReplaceSel( _T("Failed to start JPEG Resize/Move Process\r\n") );
return;
}
The thread running the process will send messages with the IDs:
ID_CONSOLE_OUTPUT_WIN and ID_CONSOLE_OUTPUT_DONE
So add to your message map:
ON_MESSAGE( ID_CONSOLE_OUTPUT_WIN, OnPostConsoleWnd )
ON_MESSAGE( ID_CONSOLE_OUTPUT_DONE, OnPostConsoleWndDone )
OnPostConsoleWnd will get the output from the process in wparam.
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 );
}
}