一个简单图形界面框架XYGui的设计与实现 (四)

(同步个人博客 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才能快速开发出实用的图形界面,并且会让人用起来,写代码觉得很舒服。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值