一个简单图形界面框架XYGui的设计与实现 (四)
2016-11-29 07:52
911 查看
(同步个人博客 http://sxysxy.org/blogs/71 到csdn)
在发生比如鼠标按下,窗口被拖拽入文件等,在窗口过程里面是可以截取到这些事件的,但是如何让XYGui库的用户自定义处理这些事件的方法呢?
常用的套路是借助oop+重载,我们约定一下比如发生鼠标按下时,调用onButtonDown,默认的XYWindow类中这个方法什么都不做。XYGui用户有需要的话,就要从XYWindow派生出来一个MyWindow类,重写onButtonDown方法。当然XYGui是支持这么做的,但是还有种更优美的方法实现事件机制,那就是:信号-槽 机制。有名的应该就是Qt的信号-槽,不过Qt c++版毕竟是c++,写起来个人觉得还是略显不自然。XYGui怎么做的呢?。用一个哈希表,一个信号量对应一个处理方法。泥可以在XYGui的example/guitest/simplewindow.rbw里面找到这样的代码:
按钮关联了:ON_COMMAND信号,处理方法为后面的代码块。同时控件对象都有一个call方法,可以人为地调用某个信号的处理方法。看一下xy_widget.rb里面相关的代码:
其实也就是这么简单。但正是这么简单的代码,就造就了XYGui方便,快捷,舒适的特性。
请求机制又是怎么回事呢?
需要这个东西,最主要是因为多线程。windows下的控件,应该是不允许跨线程操作的。意思就是不允许控件在线程x上,线程y试图去控制这个控件。如果这样做了是会导致程序崩溃掉的。
但是跨线程这样的需求还是很常见的,比如XYGui的example/usefultool/chatcube。这是个局域网聊天软件,有线程负责监听网络端口,在比如收到消息的时候,就要求ui发生相应的“回应”,但是网络线程和ui线程是显然是分开的,直接让网络线程去修改界面,会崩溃掉。请求机制就可以解决这个问题。请求request,会向目标控件发出一个请求,”希望它做一些事情”,request只会告诉它想要它做什么,而真正去做这些事情的,是目标控件所在的线程。这样就避免了跨线程直接控制控件导致崩溃的问题。请求机制的实现,大概是这样的:每个控件所在的线程维护一个请求队列,控件所在的线程在主循环里,会去处理线程管理的请求队列里面的事件!见xy_widget.rb里面这样的代码:
XYApp这样的封装的存在的理由,其中之一就是为了实现请求机制。XYApp实际上是一个”线程”的”概念”。XYGui里面控件第一个参数app,其实就是指明了这个控件属于哪一个线程,可以看下example/guitest/window_with_3app.rbw 加深理解(3app,也就是3个XYApp,ui在不同的3个线程上!)
XYApp的mainloop,也就是主循环,见XYGui_ext.c
q就是请求队列,在没有windows的消息发生时,就去请求队列里面看看,处理(可能就是别的线程)发过来的请求。
这样就简单,方便,优美地解决了这个问题。看看chatcube(前面提到的局域网聊天软件)的例子里面是怎么用这个的:
以及example/usefultool/download.rbw (这是一个http下载工具)里面
正是有了事件机制和请求机制,XYGui才能快速开发出实用的图形界面,并且会让人用起来,写代码觉得很舒服。
给窗口更多功能
窗口出来了,但是什么实用功能都不能做的话,那也就太无趣了,我们想扩展出来更多的功能,比如与用户互动之类的。事件机制与请求机制
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才能快速开发出实用的图形界面,并且会让人用起来,写代码觉得很舒服。
相关文章推荐
- 一个简单图形界面框架XYGui的设计与实现 (五)
- [综合] 一个简单图形界面框架XYGui的设计与实现 (二)
- 一个简单图形界面框架XYGui的设计与实现 (一)
- 一个简单图形界面框架XYGui的设计与实现 (三)
- 在开发板S3C2440中如何用自带的QT去设计一个界面来实现控制摄像头,这个界面的框架图是怎样的
- go实现一个简单的游戏服务器框架(lotou)基本设计
- 用MATLAB设计一个超简单的变声GUI界面
- 一个简单的matlab图形界面程序GUI
- pyFormUI: 一个简单的Python GUI界面框架
- 一个简单的matlab图形界面程序GUI
- python实现一个简单的图书馆借阅系统(不涉及数据库和界面设计)
- 【远程调用框架】如何实现一个简单的RPC框架(五)优化三:软负载中心设计与实现
- Android 框架设计Demo,一个简单的MVP示例搜索功能,网络请求用Retrofit+RxJava实现
- 【matlab】一个简单的matlab图形界面程序GUI
- 【远程调用框架】如何实现一个简单的RPC框架(一)想法与设计
- 使用java的GUI设计一个简单的登陆界面(1)
- 实现一个Java GUI计算器应用程序界面
- Emit学习-实战篇-实现一个简单的AOP框架(三)
- 用C#设计一个简单的修改密码界面
- Emit学习-实战篇-实现一个简单的AOP框架(一)