15.4 错误报告

有时候你需要在控制台或者一个对话框中显示一条消息以帮助调试或者用来提示那些不能被你的代码正常处理的行为.wxWidgets提供了很多用于记录错误的函数,这些函数工作方式各不相同,你可以使用它们来进行运行情况的报告和记录.比如,当你正在分配一个很大的图片时,可能由于这个图片太大了,系统无法分配足够的资源,系统将使用wxLogError函数显示一个对话框来报告这个错误(如下图所示).又或者说,你想将某个参数的值打印在调试窗口中以便于调试,你可以使用wxLogDebug函数.究竟这些错误信息或者调试信息是显示在终端上,对话框中还是别的什么地方,取决于你所使用的函数,以及当前激活的wxLog目标对象,我们将在稍后的部分描述相关内容.

所有的这种记录函数都拥有类似于printf或vprintf的语法,也就是:第一个参数是格式化文本参数,后面是不定类型的变量或者一组指向变量的指针,如下所示:

wxString name(wxT("Calculation"));
int nGoes = 3;
wxLogError(wxT("%s does not compute! You have %d more goes."),
           name.c_str(), nGoes);

下面我们逐个描述一下这些函数:

wxLogError函数用来显示那些必须显示给用户的错误消息.其默认行为是弹出一个对话框来通知用户相应的错误.那为什么不直接使用 wxMessageBox呢?原因是,首先wxLogError提示的错误消息是可以通过创建一个wxLogNull的Log目标来屏蔽让其不显示出来的,而且这些消息也是排在系统队列中,在系统空闲的时候显示的.因此如果有一系列的错误同时出现,它们将显示在同一个对话框中,而如果使用 wxMessageBox,你的用户可能不得不不停的点击OK按钮.

wxLogFatalError和wxLogError类似,不过除了显示错误消息,它还使用标准的系统调用abort,以错误码3结束整个程序的运行.和其它类似的函数不同,这个函数显示的消息不能通过设置空的打印目标的方法来屏蔽.

wxLogWarning也和wxLogError类似,不过显示的信息将作为警告而不是错误.

wxLogMessage则用来显示所有正常的,信息类型的消息,默认也是显示在对话框中.

wxLogVerbose则用来显示那些冗长的详细信息.通常情况下,这种信息是不显示的,但是如果用户想显示它以便了解程序运行的更详细的情况,可以通过使用wxLog::SetVerbose函数改变这种默认的行为.

wxLogStatus则用来显示状态条消息,如果当前的frame窗口拥有一个状态条,那么这个消息将显示在那里.

wxLogSysError通常主要被wxWidgets自己使用,它用来报告那些系统错误,同时会显示由errno或者GetLastError(依平台的不同)指示的错误码和错误消息.它的另外一种形式允许你的第一个参数的位置显式的指示系统错误码.

wxLogDebug用来显式调试信息.这些信息只在调试版本中(定义了WXDEBUG宏)才会出现,在正式版本中将被移除.在 windows平台上,只有当程序在一个调试器中运行或者使用第三方工具比如来自http://www.sysinternals.com的 DebugView工具运行的时候才会显示出来.

wxLogTrace和wxLogDebug的功能几乎完全一样,也是只在调试模式才会显示信息.之所以有这个函数,是为了提供一个和普通的调试模式不同的级别以便区分普通的调试信息和用于跟踪的调试信息.它的另外一种形式允许你指定一个掩码,通过wxLog:: AddTraceMask函数设置了掩码以后,只有掩码符合的跟踪消息才会被显示出来以实现跟踪消息的过滤.比如在wxWidgets内部使用了 mousecapture掩码.如果你设置了这个掩码,在鼠标移动的时候你将看到跟踪信息.

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"));
    ...
}

你可能会疑惑,为什么不直接使用C的标准输入输出函数或者C++的流呢?简短的回答是,它们都是很不错的机制,但是并不一定适用于wxWidgets.wxLog机制主要有以下三个优点:

首先,wxLog是可移植的.常用的printf语句或者C++的cout流和cerr流在unix系统下工作是没有问题的,但是在 windows系统中,对于图形化界面的应用程序,这些函数或流可能不能正常显示需要的内容.因此,你可以使用wxLogMessage作为printf 的一个简单的替代品.

你也可以通过下面的方法将所有的log信息转向标准的cout流:

wxLog *logger=new wxLogStream(&cout);
wxLog::SetActiveTarget(logger);

另外,将发送往cout的输出重定向到一个wxTextCtrl控件也是可行的,这需要使用到wxStreamToTextRedirector类.

其次:wxLog更灵活.使用wxLog机制的输出可以被分情况重定向或者隐藏,比如只显示错误消息和告警消息,忽略所有正常的信息.而如果使用标准的函数或流,这是不可能的或者说是很难作到的.

最后:wxLog机制也是更完善的机制.通常,当有错误发生的时候,应该给用户显示一些信息.让我们来举一个简单的例子,假如你正在进行写文件操作,这时候发生了磁盘空间不足的情况,这种错误是被wxWidgets内部(wxFile::Write)处理的,因此,调用这个函数只能知道写动作发生了异常,至于是什么类型的异常则很难得到,如果在这种情况下使用wxLogError函数,正确的错误码和相应的错误信息都将显示给用户.

现在我们来描述以下wxWidgets的Log机制是怎样工作的,以便你处理那些默认没有提供的行为.

wxWidgets有一个log目标的概念:它其实就是一个wxLog的派生类.它需要实现wxLog定义的那些虚函数,这些函数将在相应的Log函数被调用的时候使用.任何时候都只有一个log目标是活动的.log目标通常的使用方法就是调用wxLog:: SetActiveTarget来安装这个目标,安装以后的目标将在相应的log函数被调用的时候自动使用.

要创建一个自定义的log目标,你只需要创建一个wxLog的派生类,并实现其虚函数DoLogString和(或)DoLog.如果你对wxWidgets默认的增加时间戳和信息类型的格式化方法感到满意,只是想更改信息的目的地,那么实现DoLogString函数就足够了,而重载 DoLog函数则使得你可以任意的定制输出信息的格式,不过同时你也需要自己区分信息的各种类型.你可以参考src/common/log.cpp文件看看wxWidgets是怎么作到这一点的.

wxWidgets自己实现了几个wxLog的派生类,你也可以读一读它们的代码,这对你创建自定义的log目标也是有好处的.这些预定义的log目标包括:

wxLogStderr将所有的信息输出到FILE作为参数的文件中,如果FILE为空,则输出到标准错误输出.

wxLogStream和wxLogStderr功能相同,不过它使用标准C++的ostream类和cerr流来代替FILE*和stderr.

wxLogGui则是wxWidgets所有wxWidgets程序默认使用的log目标,依平台的不同它实现了不同的输出处理.

wxLogWindow则提供了一个类似"跟踪终端"之类的窗口,这个窗口将显示所有的输出信息,同时这些信息也将显示在之前的log目标上.这个跟踪终端窗口提供了清除信息,关闭窗口以及将所有信息保存到文件中的功能.

wxLogNull则被用来临时阻止某些错误信息的输出,比如你打开不存在的文件的时候将显示一个错误信息,有时候你不希望显示这个信息,可以在栈上创建一个wxLogNull变量,在这个变量的作用域范围内,没有任何错误信息将被显示,而离开了其作用域,则所有的信息又可以正常显示了.

wxFile file;
// wxFile.Open()在打开一个不存在的文件时通常会显示错误信息,但是在这里我们不想看到这个信息.
{
    wxLogNull logNo;
    if ( !file.Open("bar") )
      ... process error ourselves ...
} // wxLogNull的析构函数被调用,旧的log目标被恢复.
wxLogMessage("..."); // 可以被显示

有时候你也许希望将信息输出到多个地方,比如,你可以希望所有的信息在正常显示的同时被保存在某个文件中,这时候你可以使用wxLogChain和wxLogPassThrough,如下所示:

// 这将隐式的设置当前log目标
wxLogChain *logChain = new wxLogChain(new wxLogStderr);
// 所有的输出将被同时显示在stderr和通常的地方
// 不要直接删除logChain指针,这会导致当前活动log目标为一个不确定的指针.
// 应该使用SetActiveTarget.
delete wxLog::SetActiveTarget(new wxLogGui);

wxMessageOutput VS wxLog

有时候,使用wxLog不太合适,这主要是因为wxLog对输出的信息作了过多的处理,并且会等待空闲的时候才会显示这些信息.而 wxMessageOutput和它的派生类则可以作为你的底层printf的替代品,来在GUI和命令行程序中使用.你可以象使用printf函数那样使用wxMessageOutput::Printf函数,比如,如果你想把信息打印在标准错误输入:

#include "wx/msgout.h"
wxMessageOutputStderr err;
err.Printf(wxT("Error in app %s.\n"), appName.c_str());

wxMessageOutputDebug将信息显示在调试器终端或者是标准错误输出中,这主要看程序是以什么方式运行的,和 wxLogDebug不同,wxMessageOutputDebug输出的信息在正式版本中将不会被移除.GUI应用程序还可以使用 wxMessageOutputMessageBox来即时显示消息,而不比象wxLog那样需要搜集(其它Log信息)和等待(系统空闲),同样的还存在一个wxMessageOutputLog类,它将消息输出到wxLogMessage.

和wxLog类似,wxMessageOutput也有一个当前的目标,这个目标可以通过wxMessageOutput::Set设置,通过wxMessageOutput::Get获取.默认的目标是系统初始化的时候由wxWidgets设置的,在命令行程序中使用的是 wxMessageOutputStderr,在GUI程序中使用的是wxMessageOutputMessageBox.wxWidgets内部经常使用这个对象,比如在wxCmdLineParser类中,使用了下面的代码:

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?") );
}