Cross-Platform GUI Programming with wxWidgets
|
Sometimes you may want to run other applications from your own application, whether it's an external browser or another of your own applications. wxExecute is a versatile function that can be used to run a program with or without command-line arguments, synchronously or asychronously, optionally collecting output from the launched process, or even redirecting input to and output from the process to enable the current application to interact with it. Running an Application
Here are some simple examples of using wxExecute: // Executes asychronously by default (returns immediately) wxExecute(wxT("c:\\windows\\notepad.exe")); // Does not return until the user has quit Notepad wxExecute(wxT("c:\\windows\\notepad.exe c:\\temp\\temp.txt"), wxEXEC_SYNC);
Note that you can optionally enclose parameters and the executable in quotation marks, which is useful if there are spaces in the names. Launching Documents
If you want to run an application to open an associated document, you can use the wxMimeTypesManager class on Windows and Linux. You can find out the file type associated with the extension and then use it to get the required command to pass to wxExecute. For example, to view an HTML file: wxString url = wxT("c:\\home\\index.html"); bool ok = false; wxFileType *ft = wxTheMimeTypesManager-> GetFileTypeFromExtension(wxT("html")); if ( ft ) { wxString cmd; ok = ft->GetOpenCommand(&cmd, wxFileType::MessageParameters(url, wxEmptyString)); delete ft; if (ok) { ok = (wxExecute(cmd, wxEXEC_ASYNC) != 0); } }
Unfortunately, this doesn't work under Mac OS X because OS X uses a completely different way of associating document types with applications. For arbitrary documents, it's better to ask the Finder to open a document, and for HTML files, it's better to use the special Mac OS X function, ICLaunchURL. wxExecute is not always the best solution under Windows either, where ShellExecute may be a more effective function to use for HTML files. Even on Unix, there may be specific fallback scripts you want to use if an associated application is not found, such as htmlview. To work around these problems, we include the files launch.h and launch.cpp in examples/chap20/launch. This implements the functions wxLaunchFile, wxViewHTMLFile, wxViewPDFFile, and wxPlaySoundFile that work on Windows, Linux, and Mac OS X. wxLaunchFile is a general-purpose document launcher. Pass a document file name or an executable file name with or without arguments and an optional error message string that can be presented to the user if the operation fails. If an HTML file is passed, wxLaunchFile will call wxViewHTMLFile. On Mac OS X, it will use the finder to launch a document, and on other platforms, wxMimeTypesManager will be used. Note that on Mac OS X, applications will sometimes be launched such that their window is behind the current application's window. A workaround is to use the osascript command-line tool to bring the window to the front. If the application you have just launched is AcmeApp, you can call wxExecute(wxT("osascript -e \"tell application \\\"AcmeApp\\\"\" -e \"activate\" -e \"end tell\"")); For Linux, wxViewHTMLFile, wxViewPDFFile, and wxPlaySoundFile include fallbacks for when an associated application is not found. You may want to adjust the fallbacks to your own requirements. wxPlaySoundFile is intended for playing large audio files in a separate application; for small sound effects in your application, use wxSound instead. Redirecting Process Input and Output
There may be times when you want to "capture" another process and allow your application (and/or its user) to control it. This can be preferable to rewriting an entire complex program just to have the functionality integrated in your application. wxExecute can help you integrate another console-based process by letting you redirect its input and output. To do this, you pass an instance of a class derived from wxProcess to wxExecute. The instance's OnTerminate function will be called when the process terminates, and the process object can be used to extract output from or send input to the process. You can see various examples of wxExecute usage in samples/exec in your wxWidgets distribution. We also provide an example of embedding the GDB debugger in examples/chap20/pipedprocess. We don't provide the toolbar bitmaps or compilable application, but otherwise it's complete and will work on Windows, Linux, and Mac OS X if GDB is available. debugger.h and debugger.cpp implement a piped process and a window containing a toolbar and a text control, used for displaying debugger output and getting input from the user to send to the debugger. textctrlex.h and textctrlex.cpp implement a control derived from wxStyledTextCtrl but with some wxTextCtrl compatibility functions and standard event handlers for copy, cut, paste, undo, and redo. processapp.h and processapp.cpp implement an application class that can handle input from several processes in idle time. The debugger is started with DebuggerProcess *process = new DebuggerProcess (this); m_pid = wxExecute(cmd, wxEXEC_ASYNC, process); It can be killed with wxKill(m_pid, wxSIGKILL, NULL, wxKILL_CHILDREN);
To send a command to the debugger, an internal variable is set to let the input to the process to be picked up in idle time: // Send a command to the debugger bool DebuggerWindow::SendDebugCommand(const wxString& cmd, bool needEcho) { if (m_process && m_process->GetOutputStream()) { wxString c = cmd; c += wxT("\n"); if (needEcho) AddLine(cmd); // This simple sets m_input to be processed // by HasInput in OnIdle time m_process->SendInput(c); return true; } return false; }
HasInput is called periodically by the application object from its idle handler and is responsible for sending input to the process and reading output from the standard error and standard output streams: bool DebuggerProcess::HasInput() { bool hasInput = false; static wxChar buffer[4096]; if ( !m_input.IsEmpty() ) { wxTextOutputStream os(*GetOutputStream()); os.WriteString(m_input); m_input.Empty(); hasInput = true; } if ( IsErrorAvailable() ) { buffer[GetErrorStream()->Read(buffer, WXSIZEOF(buffer) - 1).LastRead()] = _T('\0'); wxString msg(buffer); m_debugWindow->ReadDebuggerOutput(msg, true); hasInput = true; } if ( IsInputAvailable() ) { buffer[GetInputStream()->Read(buffer, WXSIZEOF(buffer) - 1).LastRead()] = _T('\0'); wxString msg(buffer); m_debugWindow->ReadDebuggerOutput(buffer, false); hasInput = true; } return hasInput; }
Note a crucial difference from the code in the wxWidgets exec sample, which assumes that it can read a line at a time. This will cause a hang if a carriage return is not output by the process. The previous code uses a buffer to keep reading as much input as possible, which is safer. The ProcessApp class can be used as a base class for your own application class, or you can copy the functions into your class. It maintains a list of processes, registered with the application instance with RegisterProcess and UnregisterProcess, and handles process input in idle time, as follows: // Handle any pending input, in idle time bool ProcessApp::HandleProcessInput() { if (!HasProcesses()) return false; bool hasInput = false; wxNode* node = m_processes.GetFirst(); while (node) { PipedProcess* process = wxDynamicCast(node->GetData(), PipedProcess); if (process && process->HasInput()) hasInput = true; node = node->GetNext(); } return hasInput; } void ProcessApp::OnIdle(wxIdleEvent& event) { if (HandleProcessInput()) event.RequestMore(); event.Skip(); }
|
|