Q: Add a file to the list of recently opened files
Answer:
Windows 95 and Windows NT 4 allow users to re-open previous files by selecting the Documents menu option under the Start taskbar menu. For example, if you open a document in Microsoft Word, the filename is added to the recently opened documents list so you can access the file from the taskbar. To add a document to the list of recently opened files, use the SHAddToRecentDocs API function. Here is an example:
#include <shlobj.h>...SHAddToRecentDocs(SHARD_PATH, "c:\\cbuilder\\readme.hlp");Note: The first argument to SHAddToRecentDocs should be either SHARD_PATH or SHARD_PIDL. If you want to pass the filename as a string, use the SHARD_PATH value. SHARD_PIDL allows you to pass a pointer to an ITEMIDLIST (the ITEMIDLIST structure relates to shell programming with the shell namespace functions; SHGetDesktopFolder, SHGetMalloc and friends).
Note: You can clear all of the items in the list of recent files by passing NULL as the second example.
Note: While testing the code for this FAQ, I found out that the shell will not allow you to add a file to the recent files list if that file does not have an associated program. For example, I tried to add the file UNIT1.~H to the list. Since files with the ~H extension don't have a registered program that opens them, the shell refused to put UNIT1.~H in the list. This makes sense. When you select a file from the recent files list, Windows opens that file with its associated viewer. It can't open a file that has no associated viewer. This behavior was witnessed on Windows NT 4.
Note: There is a a problem with shlobj.h and BCB 5. If you include shlobj.h in a BCB5 GUI project, you may get a strange compiler error, such as this:
[C++ Error] shlobj.h(1762): E2238 Multiple declaration for 'FVSHOWINFO'[C++ Error] shlobj.h(1936): E2238 Multiple declaration for 'FOLDERSETTINGS'[C++ Error] shlobj.h(3717): E2238 Multiple declaration for 'DESKBANDINFO'[C++ Error] shlobj.h(4808): E2238 Multiple declaration for 'SHELLFLAGSTATE'You can eliminate these errors by adding the string NO_WIN32_LEAN_AND_MEAN to the conditional defines of your project. To see what impact this has, open the file vcl0.h in a text editor.
Q: Launch another program
Answer
Call the API CreateProcess, ShellExecute or WinExec functions. WinExec is the easiest one to use, but it is also out of date, and Microsoft doesn't recommend that you use it. This FAQ demonstrates how to use ShellExecute and CreateProcess to launch the solitaire game.
Using ShellExecute
Here is an example of how to launch notepad using ShellExecute.
void __fastcall TForm1::Button1Click(TObject *Sender){ ShellExecute(NULL, "open", "notepad.exe", "", "", SW_SHOWDEFAULT);}ShellExecute can also be used to open documents in the file system, such as Word DOC files, or Excel XLS files. The second argument to ShellExecute determines what you are trying to do. Valid values in include "open", "print","explore", "find", and a few others. When you are trying to launch an executable, pass the "open" string.
The third argument is the name of the program to execute. If you were trying to open a DOC file, you would pass the file name in the third argument. The fourth argument is the command line parameters that you want to pass to the program. The next example demonstrates how you can use this argument.
Of course, since notepad is usually associated with TXT files anyway, you could also open the readme file using this syntax:
void __fastcall TForm1::Button1Click(TObject *Sender){ ShellExecute(NULL, "open", "c:\\cbuilder5\\readme.txt", "", "", SW_SHOWDEFAULT);}Using CreatProcess
The ShellExecute function is pretty straightforward, but it does not allow you to control the child process. For finer control of what's going on, use CreateProcess instead. It is a little more complex, but it also provides more flexibility.
void __fastcall TForm1::Button1Click(TObject *Sender){ // solitaire is in the windows directory, // and has the name sol.exe. char szWindowDir [MAX_PATH]; GetWindowsDirectory(szWindowDir, MAX_PATH); AnsiString strSolitaire = AnsiString(szWindowDir) + "\\sol.exe"; STARTUPINFO StartupInfo; ZeroMemory( &StartupInfo, sizeof(STARTUPINFO)); StartupInfo.cb = sizeof(STARTUPINFO); PROCESS_INFORMATION ProcessInfo; if(CreateProcess(strSolitaire.c_str(), NULL, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartupInfo, &ProcessInfo)) { // We must close the handles returned in ProcessInfo. We can // close the handle at any time, might as well close it now CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); }}Note: If you want to pass command line arguments to the program, place the command line arguments in the second argument to CreateProcess. The code below shows how to launch Notepad and have it open a file. Notice that we don't use the first parameter for anything. The second parameter contains both the executable name, and the file to open. The two must be separated by a space.
void __fastcall TForm1::Button2Click(TObject *Sender){ char szWindowDir [MAX_PATH]; GetWindowsDirectory(szWindowDir, MAX_PATH); AnsiString strNotepad = AnsiString(szWindowDir) + "\\notepad.exe"; strNotepad = strNotepad + " " + "c:\\cbuilder3\\deploy.txt"; STARTUPINFO StartupInfo; ZeroMemory( &StartupInfo, sizeof(STARTUPINFO)); StartupInfo.cb = sizeof(STARTUPINFO); PROCESS_INFORMATION ProcessInfo; if(CreateProcess(NULL, strNotepad.c_str(), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartupInfo, &ProcessInfo)) { CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); }}Note: The last argument to CreateProcess is a pointer to a PROCESS_INFORMATION structure. If CreateProcess succeeds, the PROCESS_INFORMATION structure will contain a thread handle and a process handle for the new program. You must call CloseHandle to close the two handles that are returned in the PROCESS_INFORMATION structure. You can close the handles at any time, but you can't use the handles once you close them. The two previous code samples close the handles as soon as CreateProcess returns.
Note: You can use the process handle to determine if the new process has finished running yet. The code example below shows how you can wait for the Notepad program to terminate before proceeding with the program. The code calls the GetExitCodeProcess API function to determine if the process is still active.
//-------------------------------------------------// header file// add a bool variable to the form classprivate: // User declarations bool bWaiting; ... //-------------------------------------------------// source file//__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner){ // initialize the flag to false bWaiting = false;}//-------------------------------------------------void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose){ // Use OnCloseQuery to prevent the form from // closing while we are waiting for notepad. CanClose = !bWaiting;}//-------------------------------------------------void __fastcall TForm1::Button3Click(TObject *Sender){ // don't enter the function if we are already waiting // for another instance of notepad to close. if(bWaiting) return; char szWindowDir [MAX_PATH]; GetWindowsDirectory(szWindowDir, MAX_PATH); AnsiString strNotepad = AnsiString(szWindowDir) + "\\notepad.exe"; strNotepad = strNotepad + " " + "c:\\cbuilder3\\deploy.txt"; STARTUPINFO StartupInfo; ZeroMemory( &StartupInfo, sizeof(STARTUPINFO)); StartupInfo.cb = sizeof(STARTUPINFO); PROCESS_INFORMATION ProcessInfo; if(CreateProcess(NULL, strNotepad.c_str(), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartupInfo, &ProcessInfo)) { bWaiting = true; Label1->Caption = "Waiting for Notepad to close"; DWORD dwExitStatus = STILL_ACTIVE; // loop until Notepad closes do { if( !GetExitCodeProcess(ProcessInfo.hProcess, &dwExitStatus)) break; Application->ProcessMessages(); } while (dwExitStatus == STILL_ACTIVE); Label1->Caption = "done"; bWaiting = false; // we dont' need the handles any more, so close them CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); }}Note: When CreateProcess returns, you don't know what state the new process is in. It may be fully initialized and on the screen, but it could also be invisible and without a window handle. Sometimes, it may be beneficial to wait until the new process is fully initialized and visible on the screen. You can use the API WaitForInputIdle function to pause your program until the new process is up and running.
if(CreateProcess(NULL, strNotepad.c_str(), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartupInfo, &ProcessInfo)){ // wait for notepad to come up, time out after 2 seconds // WaitForInputIdle returns 0 if the wait succeeds WaitForInputIdle(ProcessInfo.hProcess, 2000); CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread);}Note: You can also create a process in the suspended state by adding the CREATE_SUSPENDED value to the creation flags parameter of CreateProcess. If you add the CREATE_SUSPENDED flag, the program won't start running until until you call ResumeThread. Here is an example.
if(CreateProcess(NULL, strNotepad.c_str(), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED, NULL, NULL, &StartupInfo, &ProcessInfo)){ // Make the user think something intense is happening Sleep(3000); ResumeThread(ProcessInfo.hThread); CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread);}Note: The STARTUPINFO structure allows you to control the initial location and dimension of the program's main window. You can also make the main window of the program come up hidden. Here is an example of how to control the location of the new process. Keep in mind that some programs will ignore what you pass them.
void __fastcall TForm1::Button6Click(TObject *Sender){ char szWindowDir [MAX_PATH]; GetWindowsDirectory(szWindowDir, MAX_PATH); AnsiString strNotepad = AnsiString(szWindowDir) + "\\notepad.exe"; strNotepad = strNotepad + " " + "c:\\cbuilder3\\deploy.txt"; STARTUPINFO StartupInfo; ZeroMemory( &StartupInfo, sizeof(STARTUPINFO)); StartupInfo.cb = sizeof(STARTUPINFO); StartupInfo.dwX = 0; StartupInfo.dwY = 0; StartupInfo.dwXSize = Screen->Width /3; StartupInfo.dwYSize = Screen->Height /3; StartupInfo.dwFlags = STARTF_USEPOSITION | STARTF_USESIZE; PROCESS_INFORMATION ProcessInfo; if(CreateProcess(NULL, strNotepad.c_str(), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartupInfo, &ProcessInfo)) { // we must close the handles returned in ProcessInfo.hProcess // we can close the handle at any time, might as well close it now CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); }}