Fltk 1.3 系列教程(4)

原创,转载请注明出处。Copyright (c) Dingmaotu(Leading) (dingmaotu@126.com)

Callback、面向对象和字符串处理

以前多在163博客上写东西,现在发现对于编程的事,还是在博客园上有人关注。原来的3个教程转过来,成了转载(虽然是自己转自己的),
发到首页,被警告了……这次在博客园首发吧

上一次学了高级一点的布局,以及怎样处理resize。现在读者应该能根据手册创建任意控件组合的复杂的对话框了。
这一节我主要讲一下Fltk对控件事件的处理,顺便把上节课的例子重构成面向对象风格的。另外Fltk中有些特性(或Bug)
已经过时,很让人不爽,其中字符串处理就是一个。

在Fltk中,每一个控件只有一个我们感兴趣的事情,比如对于按扭,就是用户按下它,对于文本输入框,就是文本改变等。
所以每一个控件(Fl_Widget)都有一个方法叫callback(Fl_Callback*, void *),指定一个回调函数以及传给这个函数的参数。
回调函数的原型是这样的:

typedef void (Fl_Callback)(Fl_Widget*, void *);

第一个参数是事件发生的那个控件,第二个是在指定callback时给的任意参数。这个函数试用于所有控件,因此,我们这一节
课貌似又要结束了……不过还是那句话,写代码时还会遇到问题,下面是所有的代码(很多注释,主要内容都在这里):

  1 #include "FL/Fl.H"
2 #include "FL/Fl_Double_Window.H"
3 #include "FL/Fl_Button.H"
4 #include "FL/Fl_Input.H"
5 #include "FL/Fl_Multiline_Input.H"
6 #include "FL/Fl_Box.H"
7 #include "FL/Fl_Pack.H"
8
9 // 以下文件包含的是常见的显示消息的函数,例如
10 // void fl_alert(const char *fmt, ...), 显示信息,像MessageBox
11 // int fl_ask(const char *fmt, ...), 问问题(返回1或0)
12 // void fl_beep(int type), beep一下
13 // 这些函数都很方便(printf类型的),但是为什么不放到Fl.H这个大杂烩类呢?
14 // 可见Fltk的API还有诸多不合理之处
15 #include "FL/fl_ask.H"
16
17 class EscapeChar; // 见下文
18
19 // Fltk中最方便的就是继承已有的类,进行各种自定义
20 class CommentDialog: public Fl_Double_Window
21 {
22 public:
23 // 继承的构造函数格式也基本是固定的
24 CommentDialog(int x, int y, int w, int h, const char* title)
25 :Fl_Double_Window(x, y, w, h, title)
26 {
27 CreateUI();
28 size_range(w, h, 0, 0);
29 resizable(_comment);
30 // 对于Window来说,它的Callback就是当它被关闭时要调用的函数
31 // 这对于实现文档有改变,问用户想不想继续之类很有用
32 this->callback(gcallback, (void*)this);
33 }
34 // 这是Fltk标准的也是唯一的回调函数,第一个是指向触发回调的控件,
35 // 第二个是在绑定回调时设置的任意参数。
36 //
37 // 使用面向对象的风格时,因为不能把类成员函数作为回调函数,所以一般使用这种方法:
38 // 在父控件的类中声明一个静态回调函数,所有的子控件需要设置回调时
39 // 都使用这个回调,而这个回调的第二个参数可以设为
40 // 父控件类实例的指针,通过这个指针调用相应的方法
41 static void gcallback(Fl_Widget* w, void *data)
42 {
43 CommentDialog *d = (CommentDialog*)data;
44 if(w == d->_name->child(1)) d->OnNameCheck(w);
45 else if(w == d->_mail->child(1)) d->OnMailCheck(w);
46 else d->OnOk(w); // “好的”按钮或者直接关闭窗口
47 }
48
49 void OnNameCheck(Fl_Widget* w);
50 void OnMailCheck(Fl_Widget* w);
51 void OnOk(Fl_Widget* w);
52
53 const char* GetName() const {return ((Fl_Input*)_name->child(0))->value();}
54 const char* GetMail() const {return ((Fl_Input*)_mail->child(0))->value();}
55 const char* GetComment() const {return _comment->value();}
56
57 private:
58 void CreateUI();
59 Fl_Pack *CreatePack(int y, const char * item, const char* button);
60
61 Fl_Pack * _name;
62 Fl_Pack * _mail;
63 Fl_Pack * _ok;
64 Fl_Multiline_Input * _comment;
65 };
66
67 void CommentDialog::CreateUI()
68 {
69 _name = CreatePack(10, "姓名:", "检查!");
70 _mail = CreatePack(50, "邮件:", "检查!");
71 _comment = new Fl_Multiline_Input(50, 100, w()-60, h()-150, "评论:");
72 _ok = CreatePack(h()-40, NULL, "好的");
73 }
74
75 Fl_Pack * CommentDialog::CreatePack(int y, const char * item, const char* button)
76 {
77 Fl_Pack * pack = new Fl_Pack(50, y, w()-20, 30);
78 pack->type(Fl_Pack::HORIZONTAL);
79 pack->spacing(10);
80 Fl_Widget * itemw;
81 if(item)
82 itemw = new Fl_Input(0,0,w()-175, 30, item);
83 else
84 itemw = new Fl_Box(0, 0, w()-175, 30);
85 Fl_Button *b = new Fl_Button(0, 0, 100, 30, button);
86 b->callback(CommentDialog::gcallback, (void *)this);
87 pack->end();
88
89 pack->resizable(itemw);
90 return pack;
91 }
92 // 以下的检查代码只是把输入显示出来!因为本次主要讲回调函数的设置,
93 // 所以没有实现真正的检查代码。注意Fltk很方便的显示信息的函数
94 void CommentDialog::OnNameCheck(Fl_Widget* w)
95 {
96 EscapeChar name(GetName());
97 fl_alert("您好: %s!", (const char*) name);
98 }
99 void CommentDialog::OnMailCheck(Fl_Widget* w)
100 {
101 EscapeChar name(GetName());
102 EscapeChar mail(GetMail());
103 fl_alert("%s, 您好! 您的Email是:\n%s!",
104 (const char*)name,
105 (const char*)mail);
106 }
107 void CommentDialog::OnOk(Fl_Widget* w)
108 {
109 EscapeChar name(GetName());
110 EscapeChar comment(GetComment());
111 fl_alert("%s 认为:\n%s\n多谢您的评论!",
112 (const char*)name,
113 (const char*)comment);
114 // 当最后一个窗口隐藏时,Fltk自动退出
115 hide();
116 }
117
118 int main(int argc, char **argv)
119 {
120 CommentDialog *dlg = new CommentDialog(100, 200, 460, 320, "FltkCallback");
121 dlg->show();
122 return Fl::run();
123 }

大家可以看到,大部分代码都还是和第三部分所讲的一样的,如果您对任何内容不熟悉,可以再复习“Fltk 1.3 系列教程(3)”。
这是代码运行的截图:

上面的代码还展示了怎样在面向对象的情形下使用类成员方法作为回调处理函数,因为Fltk的回调机制比较通用灵活,这些的
实现都很简单。通过封装已有代码,就构建了一个可重用的对话框,在实际情况下应该比直接过程式代码好用。

细心与好奇的读者可能注意到那个奇怪的EscapeChar类。如果你不做任何处理,你会发现在fl_alert不能正确显示
email的“@”字符。为什么呢?因为Fltk一个过时的特性,label和symbol系统。在任何Fltk控件的label设置字符串
中(即构造函数中指定的字符串或者label(const char*)函数中设置的字符串),如果有且仅有以下符号(摘自Fltk 1.3 Doc):

Fltk会把它显示成这些图形符号,看起来非常方便,但是实际上问题很多,例如一但有其他字符串出现,就完全
不显示了。大家可以参考http://www.fltk.org/str.php?L2689,这种特性问题之多难以处理。所以建议在
新代码中,尽量少使用这些功能。Fl_Browser控件中也使用“@”符号来表示字符串的格式,在开头加上“@.”就
可以关闭格式解析,问题不大。但是在我们的代码中email肯定含有“@”符号,而这个是不能在消息框的label中
正确显示的。怎么解决这个问题呢?

第一种是高手使用的方法,也是比较恶心的方法,就是自己实现绘制符号的函数,然后替换默认的符号处理系统,
这样一来,一切都清静了。见Erco的例子大全:http://seriss.com/people/erco/fltk/#DisableSymbols

第二种就是把字符串中的“@”字符替换成“@@”,这样就可以转义掉“@”字符,相对于修改整个系统的行为,这个
方法还是比较容易接受的。但是问题又出来了:如果Fltk提供了字符串处理函数或者它就是用的ASCII,实现这个功能是
很简单的,但是它使用的是UTF8,于是我们又不得不看看Fltk中的UTF8函数了。

大家知道在1.3之前Fltk是不支持Unicode的,这对于CJK国家非常不便。1.3通过utf8支持了unicode,但是这些
支持是非常基础的,在"FL/fl_utf8.h"中,只有最底层的utf8处理函数,而不是高层的字符串操作函数。虽然UTF8
兼容ASCII,但是即使输入都是ASCII,你按照默认的处理也是不行的(会有乱码,只能保证它是'\0'结束的)。

所以鄙人认为实现一个fl_string类是十分必要的。但是这个教程不能面面俱到,可能以后会专门写一篇文章讲这个
类的实现。下面的类是EscapeChar,能转义UTF8字符串中的任何ASCII字符。

 1 // 不得已而为之
2 #include <cstring> // for strlen strcpy
3 #include "FL/fl_utf8.h" // for fl_utf8len(char c)
4 class EscapeChar
5 {
6 public:
7 EscapeChar(const char* text, char c='@')
8 :_c(c)
9 {
10 _text = new char[2*strlen(text)];
11 int k = 0;
12 while(*text){
13 int j = fl_utf8len(*text);
14 for(int i = 0; i < j; ++i)
15 _text[k+i] = *text++;
16 k += j;
17 if( j == 1 && _text[k-j] == _c){
18 _text[k] = _c;
19 k += j;
20 }
21 }
22 _text[k] = '\0';
23 }
24 ~EscapeChar()
25 {
26 if(_text) delete _text;
27 }
28 EscapeChar(const EscapeChar& e)
29 :_c(e._c)
30 {
31 _text = new char[strlen(e._text)];
32 strcpy(_text, e._text);
33 }
34
35 EscapeChar& operator=(const EscapeChar& e)
36 {
37 if(&e == this)
38 return *this;
39 _c = e._c;
40 size_t len = strlen(e._text);
41 if(strlen(_text) < len){
42 delete _text;
43 _text = new char[len];
44 }
45 strcpy(_text, e._text);
46
47 return *this;
48 }
49 operator char*() const {return _text;}
50
51 private:
52 char _c;
53 char *_text;
54 };

这次的教程就这么多内容,有任何问题,欢迎指出。以前的教程如下,供感兴趣的人参考:
http://www.cnblogs.com/leading/archive/2011/10/15/fltk_1_3_tutorial_3.html
http://blog.163.com/dingmaotu@126/blog/static/2148430201183072750266/ (教程2,因为原来网易图片不让外链,很难看,干脆链原来的网址吧)
http://www.cnblogs.com/leading/archive/2011/10/15/fltk_1_3_tutorial_1.html

posted @ 2011-10-17 10:55  Leading  阅读(5034)  评论(0编辑  收藏  举报