|
Sometimes you need to display messages on the console or in a dialog to help with debugging, or when an error occurs that would not normally be detected by application code. wxWidgets provides a range of logging functions for the different ways in which you might want to use logging. For example, if allocation fails when trying to create a very large wxBitmap, an error will be reported in a dialog box using wxLogError (see Figure 15-2). Or if you simply want to print the values of a function's arguments on the debugger window, you can use wxLogDebug. Where the error actually appears (in a dialog, on the debugger window, or on the standard error stream) depends on the name of the logging function used and also on the wxLog "target" that is currently active, as described later. Figure 15-2. Log dialogAll logging functions have the same syntax as printf or vprintfthat is, they take the format string as the first argument and a variable number of arguments or a variable argument list pointer. For example: wxString name(wxT("Calculation")); int nGoes = 3; wxLogError(wxT("%s does not compute! You have %d more goes."), name.c_str(), nGoes); The logging functions are as follows. wxLogError is the function to use for error messages that must be shown to the user. The default behavior is to pop up a message box to inform the user about the error. Why not just use wxMessageBox? Well, messages that come from wxLogError can be suppressed by creating an object of wxLogNull and log errors are also queued up and shown in idle time in a single dialog. So if a series of errors happens, the user won't have to click on OK for each and every error. wxLogFatalError is like wxLogError, but it also terminates the program with exit code 3 (using the standard abort function). Unlike all the other logging functions, this function can't be overridden by changing the log target. wxLogWarning is like wxLogError, but the information is presented as a warning, not an error. wxLogMessage is for all normal, informational messages. They also appear in a message box by default. wxLogVerbose is for verbose output. Normally, the messages are suppressed, but they can be activated by calling wxLog::SetVerbose if the user wants to know more details about the program's progress. wxLogStatus is for status messages, which will be displayed in the status bar of the active or specified wxFrame, if it has one. wxLogSysError is mostly used by wxWidgets itself. It logs the specified message text as well as the last system error code (errno or the result of GetLastError depending on the platform) and the corresponding error message. The second form of this function takes the error code explicitly as the first argument. wxLogDebug is the function to use for debug output. It only displays the message in debug mode (when the preprocessor symbol __WXDEBUG__ is defined) and expands to nothing in release mode. Under Windows, you must either run the program under a debugger or use a third-party program such as DebugView from http://www.sysinternals.com to actually see the debug output. wxLogTrace only does anything in debug builds, like wxLogDebug. The reason for making it a separate function is that usually there are a lot of trace messages, so it makes sense to separate them from other debug messages. Moreover, the second version of this function takes a trace mask as the first argument, which enables you to further restrict the number of messages generated. For example, wxWidgets uses the mousecapture mask internally. If you add this string to the trace masks via wxLog::AddTraceMask, you will see trace messages when the mouse is captured. void wxWindowBase::CaptureMouse() { wxLogTrace(wxT("mousecapture"), wxT("CaptureMouse(%p) "), this); ... } void MyApp::OnInit() { // Add mousecapture to the list of trace masks wxLog::AddTraceMask(wxT("mousecapture")); ... } You may be wondering, why not use C standard input/output functions or C++ streams? The short answer is that they're good generic mechanisms, but they are not really adapted for wxWidgets, whereas the log classes are. There are three main advantages. First, wxLog is portable. It is a common practice to use printf statements or cout and cerr C++ streams for writing information. Although this works just fine under Unix, these messages go nowhere under Windows, where the stdout of graphical applications is not assigned to anything. Thus, you might view wxLogMessage as a simple substitute for printf. You can also redirect logging calls to cout by writing: wxLog *logger=new wxLogStream(&cout); wxLog::SetActiveTarget(logger); There is also the possibility to redirect the output sent to cout to a wxTextCtrl by using the wxStreamToTextRedirector class. Second, wxLog is more flexible. The output of wxLog functions can be redirected or suppressed entirely based on their importance, which is either impossible or difficult to do with traditional methods. For example, only error messages or only error messages and warnings might be logged, filtering out all informational messages. Third, wxLog is more complete. Usually, an error message should be presented to the user when some operation fails. Let's take the simple but common case of a file error: suppose that you're writing your data file on disk and there is not enough space. The actual error might have been detected inside wxWidgets code (say, in wxFile::Write), so the calling function doesn't really know the exact reason of the failure; it only knows that the data file couldn't be written to the disk. However, as wxWidgets uses wxLogError in this situation, the exact error code and the corresponding error message will be given to the user. Now we'll describe how logging works, in case you need to use more than the default behavior. wxWidgets has the notion of a log target: it is just a class derived from wxLog. It implements the virtual functions of the base class, which are called when a message is logged. Only one log target is active at any momentthe one used by logging functions. The normal usage of a log object is to install it as the active target with a call to wxLog::SetActiveTarget, and it will be used automatically by all subsequent calls to logging functions. To create a new log target class, you only need to derive it from wxLog and implement one (or both) of DoLog and DoLogString. Implementing DoLogString is enough if you're happy with the standard wxLog message formatting (prepending Error: or Warning: and time-stamping) but just want to send the messages somewhere else. You can override DoLog to do whatever you want, but you have to distinguish between the different message types yourself. See src/common/log.cpp for how wxWidgets does it. There are some predefined classes deriving from wxLog that you can use without change, and looking at these classes might be helpful to show you how to create a new log target class. wxLogStderr logs messages to the file pointer argument (FILE*), using stderr if no argument was supplied. wxLogStream has the same functionality as wxLogStderr but uses ostream and cerr instead of FILE* and stderr. wxLogGui is the standard log target for wxWidgets applications and is used by default. It provides the most reasonable handling of all types of messages for a given platform. wxLogWindow provides a "log console," which collects all messages generated by the application and also passes them to the previously active log target. The log window frame has a menu that lets the user clear the log, close it completely, or save all messages to a file. wxLogNull may be used to temporarily suppress output of the logging functions. As an example, trying to open a non-existent file will usually provoke an error message, but if for some reason this is unwanted, just create an instance of wxLogNull on the stack. While the object is in scope, no errors will be reported. You can use an extra pair of braces to create the appropriate scope. For example: wxFile file; // wxFile.Open() normally complains if file can't be opened; // we don't want it to { wxLogNull logNo; if ( !file.Open("bar") ) ... process error ourselves ... } // ~wxLogNull called, old log target restored wxLogMessage("..."); // ok Log targets can also be combined: for example, you might want to redirect the messages somewhere else (perhaps to a file) but also to process them as usual. For this, you can use wxLogChain and wxLogPassThrough. For example: // This implicitly sets the active log target wxLogChain *logChain = new wxLogChain(new wxLogStderr); // all the log messages are sent to stderr and also processed // as usual // don't delete logChain directly as this would leave a dangling // pointer as active log target; use SetActiveTarget instead delete wxLog::SetActiveTarget(new wxLogGui); wxMessageOutput Versus wxLogSometimes, wxLog is not the most appropriate class to use, with its higher-level functionality such as time-stamping and delayed log messages. The wxMessageOutput class and its derivatives offer a low-level printf replacement that you can use in console and GUI applications. Use wxMessageOutput::Printf where you would normally use printf; for example, to write to standard error: #include "wx/msgout.h" wxMessageOutputStderr err; err.Printf(wxT("Error in app %s.\n"), appName.c_str()); You can also use wxMessageOutputDebug to write to the debugger's console or standard error, depending on platform and whether the program is being run in the debugger. Unlike wxLogDebug, these calls are not stripped out in release mode. GUI applications can use wxMessageOutputMessageBox to immediately show the message in a dialog, not collating and delaying the messages as wxLog does. There is also a wxMessageOutputLog class, which passes messages to wxLogMessage. As with wxLog, wxMessageOutput has the concept of a current target, set with wxMessageOutput::Set and retrieved with wxMessageOutput::Get. This target is set with an appropriate object when wxWidgets initializes: an instance of wxMessageOutputStderr for console applications, and a wxMessageOutputMessageBox for GUI applications. wxWidgets makes use of this object internally, for example in wxCmdLineParser, as follows: wxMessageOutput* msgOut = wxMessageOutput::Get(); if ( msgOut ) { wxString usage = GetUsageString(); msgOut->Printf( wxT("%s%s"), usage.c_str(), errorMsg.c_str() ); } else { wxFAIL_MSG( _T("no wxMessageOutput object?") ); } |
|