在GUI程序中使用控制台的两种方法-方法.1

来源:互联网 发布:淘宝诈骗一百万 编辑:程序博客网 时间:2024/05/10 08:22

当我们第一次接触C++时,写的第一行代码,可能就是:

Code:
  1. //...  
  2.   
  3. std::cout << "Hello world" << std::endl;  
  4.   
  5. //...  

慢慢的,cin, cout, cerr 就成为我们的好朋友,我们除了把它们用于程序中和输入输出有关实际逻辑以外,还经常拿cout或cerr作调试工具。

——这会受到我们的老师表扬,因为真正的程序员,都是不会轻易使用单步调试的……(好的程序员,必须是一名不错的测试员)。

后来有一天,我们一步迈入了 Windows 的世界,我们来到了GUI(图形用户界面/接口)的世界,我们了解一切都是“画”出来的……我们兴奋地,热情地执行这一华丽的转身,旧日的朋友cin, cout, cerr 被抛弃了,它们站在我们很远很远的身后……像我们儿时在乡下的好伙伴目送我们进城,黯然伤神:“他们还会回来吗?”

但程序还需要调试。

1. 我们用::MessageBox(....)来显示一个字符串的值……吃力不讨好。

2. 我们把调试信息打印到文件里,但又不直观,又没有及时性。

3. 我们搞了一个单独的窗口,上面放个Edit,然后往里打印消息……好大的工程

4. Windows 觉得挺不好意思,于是搞些OutputDebugXXX之类的函数,来帮我们把调试消息打印到VC的消息窗口……用这方式?想想都脸红,交给实施人员测试时,附赠一套VC 6.0 ?

 其实,老朋友从来没有忘记我们,我们还可以继续使用控制台(console),还可以继续使用cin, cout, cerr。 我们将讲解两种方法,一种是典型的Window风格,一种是典型的linux风格……当然,都在Windows实现的。

 

1. 方法-1:内嵌式

顾名思义,我们主动在自己写的GUI程序中,创建一个控制台。

1.1 API

在Windows 的API,提供一大族Console的函数(最无聊的,比如可以设置字符颜色:) ),AllocConsole用来直接为一个进程创建一个控制台。注意,一个进程只能有一个控制台:

Remarks

A process can be associated with only one console, so the AllocConsole function fails if the calling process already has a console. A process can use the FreeConsole function to detach itself from its current console, then it can call AllocConsole to create a new console or AttachConsole to attach to another console.

怎么让“编程”与“英语”齐飞,C++与Windows API一色呢?请参看我的另一篇笔记《学习编程需要什么英语基础?》。

1.2 主要类设计

好,这一段就基本齐全了,连设计模式都有了:既然“A process can be associated with only one console”,那就来一个简单的“单例模式”吧。

Code:
  1. struct EmbeddedConsole  
  2. {  
  3. public:      
  4.     static void Need()  
  5.     {  
  6.         
  7.         if (!_instance)  
  8.         {  
  9.             _instance = new EmbeddedConsole;  
  10.         }  
  11.         
  12.     }  
  13.       
  14.     static void Unneed()  
  15.     {  
  16.         delete _instance;  
  17.         _instance = 0;  
  18.     }  
  19.       
  20. private:  
  21.     EmbeddedConsole()  
  22.     {  
  23.         AllocConsole();  
  24.           
  25.         SetConsoleTitle("XXX程序内嵌测试控制台");  
  26.           
  27.         freopen("conin$""r+t", stdin);  
  28.         freopen("conout$""w+t", stdout);  
  29.         freopen("conout$""w+t", stderr);   
  30.     }  
  31.       
  32.     ~EmbeddedConsole()  
  33.     {  
  34.         fclose(stderr);  
  35.         fclose(stdout);  
  36.         fclose(stdin);  
  37.           
  38.         FreeConsole();  
  39.     }  
  40.       
  41.     static EmbeddedConsole* _instance;   
  42. };  
  43.   
  44. EmbeddedConsole* EmbeddedConsole::_instance;  

EmbeddedConsole 的构造和析构函数都是private的。我们逼着自己通过“Need()”函数来new 出这个类的对象,而不是直接在全局范围内,定义一个它的对象,是因为我们需要严格的控制这个对象的“生死”时机。C++控制台程序是“耍了手段”,替我们尽量安全地往前自动生成cout, cin, cerr 这三个对象(没错,这三个老朋友都是变量),但对于GUI程序,它们要起效,必须有一个真正的控制台——我们通过AllocConsole所做的事情。
构造函数分配一个控制台,然后重定向; 析构函数关闭三个标准控制台文件,然后释放控制台,注意次序。

构造函数中,我还特意调用了一下SetConsoleTitle,用来修改控制台标题,同时也用来体现一名像我这样“专业的”程序员所必须拥有的“骚包”劲儿。

静态成员数:instance 确保我们不会愚蠢地一直尝试去重新分配控制台(AllocConsole函数是有返回值的,这里简单地认定它一定会成功 :)。

第44 行在类之外定义了类的静态成员数据,这是必须的,除非那是一个静态常量成员内置类型数据,请参看《纠正“C++测试题的一些问题”的问题》。

 

1.3 测试例子

来一个使用例子。请在Code::Blocks(或你用的其它编程IDE),通过"Win32 GUI project”向导,生成一个应用,提问窗口类型时,请选择“Dialog”, 编译运行后,应该出来一个有两个按钮的对话框。点击“test”按钮,应该出一个消息框(MessageBox),这就是我们要改掉的地方,加入以上代码,全部代码如下:

Code:
  1. #define WIN32_LEAN_AND_MEAN  
  2.   
  3. #include <windows.h>  
  4. #include <iostream>  
  5. #include <string>  
  6.   
  7. #include "resource.h"  
  8.   
  9. HINSTANCE hInst;  
  10.   
  11. struct EmbeddedConsole  
  12. {  
  13. public:      
  14.     static void Need()  
  15.     {  
  16.          
  17.         if (!_instance)  
  18.         {  
  19.             _instance = new EmbeddedConsole;  
  20.         }  
  21.        
  22.     }  
  23.       
  24.     static void Unneed()  
  25.     {  
  26.         delete _instance;  
  27.         _instance = 0;  
  28.     }  
  29.       
  30. private:  
  31.     EmbeddedConsole()  
  32.     {  
  33.         AllocConsole();  
  34.         SetConsoleTitle("XXX程序内嵌测试控制台");  
  35.           
  36.         freopen("conin$""r+t", stdin);  
  37.         freopen("conout$""w+t", stdout);  
  38.         freopen("conout$""w+t", stderr);   
  39.     }  
  40.       
  41.     ~EmbeddedConsole()  
  42.     {  
  43.         fclose(stderr);  
  44.         fclose(stdout);  
  45.         fclose(stdin);  
  46.           
  47.         FreeConsole();  
  48.     }  
  49.       
  50.     static EmbeddedConsole* _instance;   
  51. };  
  52.   
  53. EmbeddedConsole* EmbeddedConsole::_instance;  
  54.   
  55. BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)  
  56. {  
  57.     std::string str;  
  58.       
  59.     switch (uMsg)  
  60.     {  
  61.         case WM_INITDIALOG:  
  62.             /* 
  63.              * TODO: Add code to initialize the dialog. 
  64.              */  
  65.             return TRUE;  
  66.   
  67.         case WM_CLOSE:  
  68.             EndDialog(hwndDlg, 0);  
  69.             return TRUE;  
  70.   
  71.         case WM_COMMAND:  
  72.             switch (LOWORD(wParam))  
  73.             {  
  74.                     /** TODO: Add more control ID's, when needed. 
  75.                      */  
  76.                 case IDC_BTN_QUIT:  
  77.                     EndDialog(hwndDlg, 0);  
  78.                       
  79.                     EmbeddedConsole::Unneed(); //不要了!  
  80.                     return TRUE;  
  81.   
  82.                 case IDC_BTN_TEST:  
  83.                     EmbeddedConsole::Need(); //我要!  
  84.                     std::cout << "please input :";  
  85.                     std::cin >> str;  
  86.                     std::cerr << str << std::endl;  
  87.                     ::MessageBox(hwndDlg, str.c_str(), "Information"  
  88.                         , MB_ICONINFORMATION);  
  89.                     return TRUE;  
  90.             }  
  91.     }  
  92.   
  93.     return FALSE;  
  94. }  
  95.   
  96.   
  97. int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)  
  98. {  
  99.     hInst = hInstance;  
  100.   
  101.     // The user interface is a modal dialog box  
  102.     return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DialogProc);  
  103. }  

第82行,是用户按下“test”按钮时,要执行的代码。

我们先在83行通过Need()函数,确保控制台生成。然后就是我们熟悉的 cin, cout , cerr 的使用了,当然这里的cerr用得没有什么逻辑。

第76行,那是用户按下"quit"按钮时,要执行的代码。我们只是调用了Unneed()函数,表示我们在程序退出前,释放掉之前创建的控制台。

对了,如果你害怕在发布发行版时,忘了屏掉这个功能,可以通过宏来检测编译版本,如果发现它是release版时,就什么也别做。由于不同的编译器用于判断编译版本,有不同的宏名称,所以我就不写了。

 

1.4 截图

效果图

 

如果仅是为了通过控制来辅助调试,这不是我最喜欢的方法,因为它违反了我们推崇的“高内聚,低耦合”的原则(这里扯了一点这方面的内容)。另外一个方法是什么呢?

欲知后事如何,且听下回分解。 :-)

 

------------欢迎关注《白话 C++》的出版-------------------------

如果您想与我交流,请点击如下链接成为我的好友:
http://student.csdn.net/invite.php?u=112600&c=f635b3cf130f350c