(同步个人博客 http://sxysxy.org/blogs/71 到csdn)
给窗口更多功能
窗口出来了,但是什么实用功能都不能做的话,那也就太无趣了,我们想扩展出来更多的功能,比如与用户互动之类的。
事件机制与请求机制
XYGui提供一套事件机制和请求机制。先说说事件机制:
在发生比如鼠标按下,窗口被拖拽入文件等,在窗口过程里面是可以截取到这些事件的,但是如何让XYGui库的用户自定义处理这些事件的方法呢?
常用的套路是借助oop+重载,我们约定一下比如发生鼠标按下时,调用onButtonDown,默认的XYWindow类中这个方法什么都不做。XYGui用户有需要的话,就要从XYWindow派生出来一个MyWindow类,重写onButtonDown方法。当然XYGui是支持这么做的,但是还有种更优美的方法实现事件机制,那就是:信号-槽 机制。有名的应该就是Qt的信号-槽,不过Qt c++版毕竟是c++,写起来个人觉得还是略显不自然。XYGui怎么做的呢?。用一个哈希表,一个信号量对应一个处理方法。泥可以在XYGui的example/guitest/simplewindow.rbw里面找到这样的代码:
button1 = XYPushButton.new(app, wnd, {:text => 'Clear'})
button1.connect(:ON_COMMAND) {|sender, data| editor.text=""}
button2 = XYPushButton.new(app, wnd, {:text => 'Quit'})
button2.connect(:ON_COMMAND) {|sender, data| app.exit}
wnd.show
app.mainloop
按钮关联了:ON_COMMAND信号,处理方法为后面的代码块。同时控件对象都有一个call方法,可以人为地调用某个信号的处理方法。看一下xy_widget.rb里面相关的代码:
def connect(sig, &func)
@responder[sig] = func
end
def call(sig, *arg)
@responder[sig].call *arg if @responder[sig]
end
def disconnect(sig)
@responder[sig] = nil
end
其实也就是这么简单。但正是这么简单的代码,就造就了XYGui方便,快捷,舒适的特性。
请求机制又是怎么回事呢?
需要这个东西,最主要是因为多线程。windows下的控件,应该是不允许跨线程操作的。意思就是不允许控件在线程x上,线程y试图去控制这个控件。如果这样做了是会导致程序崩溃掉的。
但是跨线程这样的需求还是很常见的,比如XYGui的example/usefultool/chatcube。这是个局域网聊天软件,有线程负责监听网络端口,在比如收到消息的时候,就要求ui发生相应的“回应”,但是网络线程和ui线程是显然是分开的,直接让网络线程去修改界面,会崩溃掉。请求机制就可以解决这个问题。请求request,会向目标控件发出一个请求,”希望它做一些事情”,request只会告诉它想要它做什么,而真正去做这些事情的,是目标控件所在的线程。这样就避免了跨线程直接控制控件导致崩溃的问题。请求机制的实现,大概是这样的:每个控件所在的线程维护一个请求队列,控件所在的线程在主循环里,会去处理线程管理的请求队列里面的事件!见xy_widget.rb里面这样的代码:
def pushRequest(&proc)
@requestMutex.lock
begin
@app.request.push(self)
@app.request.push(proc)
ensure
@requestMutex.unlock
end
end
alias :request :pushRequest
XYApp这样的封装的存在的理由,其中之一就是为了实现请求机制。XYApp实际上是一个”线程”的”概念”。XYGui里面控件第一个参数app,其实就是指明了这个控件属于哪一个线程,可以看下example/guitest/window_with_3app.rbw 加深理解(3app,也就是3个XYApp,ui在不同的3个线程上!)
XYApp的mainloop,也就是主循环,见XYGui_ext.c
MSG msg;
VALUE flag;
VALUE widget;
VALUE rq;
VALUE q;
//int mn;
q = rb_iv_get(self, "@request");
while(1)
{
if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}else
{
//rb_funcall(self, rb_intern("proRequest"), 0); //no use now....
//mn = 1; // process at most five requests once
//while(mn--)
//{
widget = rb_funcall(q, rb_intern("pop"), 0);
rq = rb_funcall(q, rb_intern("pop"), 0);
//if(widget == Qnil || rq == Qnil)break; // Ok to leave
if(widget != Qnil && rq != Qnil)
rb_funcall(rq, rb_intern("call"), 1, widget);
//}
rb_funcall(rb_mKernel, rb_intern("sleep"), 1, DBL2NUM(0.02));
}
flag = rb_iv_get(self, "@flagExit");
if(flag == Qtrue)break;
}
q就是请求队列,在没有windows的消息发生时,就去请求队列里面看看,处理(可能就是别的线程)发过来的请求。
这样就简单,方便,优美地解决了这个问题。看看chatcube(前面提到的局域网聊天软件)的例子里面是怎么用这个的:
Thread.new do #这是个新的线程
loop do
svr = @sok.accept
msg = svr.read
@msgs.request do |msgs| #请求"消息盒子"修改显示内容
msgs.text = msg
end
@textarea.request do |textarea| #请求清空输入框
textarea.text = ""
end
svr.close
end
end
以及example/usefultool/download.rbw (这是一个http下载工具)里面
wk = Thread.new do
stb.request {|s| s.text = "Analysing the url"}
tv.request {|tvr| tvr.text = "0.0%"}
#-----------------------------------------------------
ana =-> (turl) do
.....
正是有了事件机制和请求机制,XYGui才能快速开发出实用的图形界面,并且会让人用起来,写代码觉得很舒服。