网站导航免费论文 原创论文 论文搜索 原创论文 网学软件 学术大家 资料中心 会员中心 问题解答 原创论文 论文素材 设计下载 最新论文 下载排行 论文上传 在线投稿 联系我们
返回网学首页
最新论文 推荐专题 热门论文 素材专题
当前位置: 网学 > 网页素材 > AJAX代码 > 正文
ExtJS组件事件
来源:Http://myeducs.cn 联系QQ:点击这里给我发消息 作者: 用户投稿 来源: 网络 发布时间: 11/01/18

返回深入学习ExtJS 2.2开发系列连载教程目录
浏览的事件是依赖于DOM元素而存在,我们可以把它看作是DOM元素的事件,而Ext中对于事件的扩展主要是为使用Ext元素能使用更方便的事件机制,我们把它称为Ext元素事件,然后在Ext最大的一部分内容是组件,那么它们的事件机制呢?

Ext的组件也是有事件机制的,它是建立在Ext元素的基础之上。像Ext元素事件一样,它也有一个EventObject和对于该对象的管理机制。它们的管理机制也是如何去注册、启动、删除注册的事件。但是它的EventObject却有点不一样,它不是用来保存运行时都产生的信息,而是用来保存为该事件名注册的监听函数。在DOM2事件模型中,就可以为一个事件注册多个监听函数。这些函数要保存在一个地方,并且进行统一的相关的操作,组件的EventObject和这样差不多。

在这一节中,我们就从组件的EventObject和事件机制分别进行讲解。
4.4.1事件对象
组件的EventObject是通过Ext.util.Event类来完成,在DomReady和resize事件中,我们也见过。首先我们知道它是肯定是一个集合,因为它要保存与这个事件名对应着多个监听函数。同时它还要知道它和什么样的事件名相对应。

在Ext.util.Event的构建函数中就是这样去做的:

Ext.util.Event = function(obj, name){
        this.name = name;
        this.obj = obj;
        this.listeners = ;
    };
当构建一个事件对象,一般都会指定其事件名(name)和它的作用域(属于那一个组件或对象)。也可以仅仅是作为集合用。在DomReady和resize事件中就是直接做为集合来使用的。这个集合就是listeners属性。

对于Event对象,有了集合就得往这个集合中存放监听函数、删除监听函数等相关器处理。不然的话,这个集合是没有什么作用了。存放监听函数是通过其addListener完成的。
addListener : function(fn, scope, options){
   scope = scope || this.obj;  
   if(!this.isListening(fn, scope)){
        var l = this.createListener(fn, scope, options);
        if(!this.firing){  this.listeners.push(l);}
else{  this.listeners = this.listeners.slice(0);
                this.listeners.push(l); }

     }

},
addListener是向集合中增加监听函数,如果该函数已经存在在集合中,并其作用域也是相同的,那么就不会重复增加的。没有找到就先根据这个函数来创建一个监听函数的对象,这个对象还保存着一些其它的的信息,因为在判断时是要还要根据其作用域来判断,那么至少是有一个作用域的信息。其格式如下:this.listeners = [{fn: fn, scope: scope, options: o,fireFn:h}]
在上面的代码中,我们要注意的是如果该事件的监听函数正在执行,那么为了不改变它的集合的元素,就不直接存放在集合,而是把当前集合重新复制一个,把它赋给事件对象的listeners属性,并在这个新的集合中追加该监听函数。而原来的集合正在被fire的代码使用,当使用完成之后就会垃圾回收。下一次fire的是这个新的集合。

现在我们要考虑一下createListener是如何生成包括一些信息的监听函数对象。这个对象包括着传入的监听函数,作用域,配置选项,还有一个被用来fire的函数,这个fire函数肯定是把监听函数进行了包裹处理。而监听函数,作用域主要是作判断使用来。下面我们通过createListener的代码来分析一下:
createListener : function(fn, scope, o){
       o = o || {};
      scope = scope || this.obj;
       var l = {fn: fn, scope: scope, options: o};
       var h = fn;
       if(o.delay){ h = createDelayed(h, o, scope); }
       if(o.single){ h = createSingle(h, this, fn, scope); }
       if(o.buffer){ h = createBuffered(h, o, scope); }
       l.fireFn = h;
        return l;
},
这里对于监听函数,作用域,配置选项只要直接传入到监听函数对象中去,但是对于fireFN,它是进行了一定的处理,这个处理和4.2.2节中的addListener的包裹是相似的,并且在3.6节中也详细分析了buffer和delay的作用和代码的实现的不同。

创建事件对象的主要目标就是为了执行它,如何去执行,这些函数有没有执行顺序呢?我们还是通过它的fire函数来看下:
fire : function(){
var ls = this.listeners, scope, len = ls.length;
if(len > 0){
   this.firing = true;
   var args = Array.prototype.slice.call(arguments, 0);
   for(var i = 0; i < len; i++){
   var l = ls[i];   if(l.fireFn.apply(l.scope||this.obj||window,arguments)===false){
            this.firing = false;  return false; }

     }

   this.firing = false;
}

  return true;
}
在这里,我们可以看出它是按注册的先后顺序来执行注册监听函数。这个监听函数是包裹过的。这个监听函数的参数就是当前fire函数的所有参数。为了保证在执行时,不会被增加或删除监听函数的影响,这里设了一个firing属性作为标识。

当我们不需要改监听函数时,就应该删除它,一是在动作时不要执行它,二是也要释放它占有内存,这个就在removeListener中完成。
removeListener : function(fn, scope){
      var index;
     if((index = this.findListener(fn, scope)) != -1){
            if(!this.firing){ this.listeners.splice(index, 1); }
else{ this.listeners = this.listeners.slice(0);//copy
                    this.listeners.splice(index, 1); }

            return true;
     }

      return false;
},
在删除时,也不会影响对正在执行的监听函数。上面可以看出Event类是对每个组件不同的事件都会实例化。也就是第个组件每个事件都应该实例化Event类。它的内部this.listeners是用来保存应用系统开发者注册的所有的事件处理函数。

4.4.2 组件的事件机制
组件的事件机制是采用观察者模式架构的,观察者模式主是降低软件功能模块之间耦合度和提高软件的扩展性。观察者模式一般都是在架构类库时就需要设计好,它把软件的功能中共有特性抽象出来,统一实现,对于那么私有的功能,留下接口,待开发者在各自己的应用软件进行需求相关开发。也就是观察者模式把软件分成两部分,一部分是由类库来实现,一部分是由开发者来实现。

那它们之间如何去进行衔接呢,既然称为观察者,那么在类库的设计就得在合适的地方去观测开发者是否注册了接口部分,如果注册了,就调用执行它。而对于开发者来讲,只要按类库指定的格式去实现这个接口部分,就可以完成私有功能。

Ext组件的事件机制就是采用这种观察者模式来进行架构设计的,所以很多时间,它的事件机制不像浏览器事件机制那样是用来处理事件,而是用来进行组件功能的扩展而用的。在组件与组件之间,它们也是经常采用这种方式来进行相互调用。

Ext组件的事件机制是在Ext.util.Observable类中实现的,只要继承于该类就拥有事件的能力。组件的根类就是继承于它,重复提交类虽然不是组件,但是也继承于它,于是就是有事件的能力,有了扩展性和解耦性。

我们把观察者模式分成两个部分,一个是观察的主体,一个是被观察者。组件的事件机制也是分成这个两个部分来进行设计。下面我们就从这两个方面来进行分析它的实现。

观察者
观察的主体是用来进行检测部分的功能,它是在类库中实现的共有功能。对于采用观察者模式事件来讲,首先我们定义被观察者的接口,比如是要进行扩展,就是实现什么的接口或按什么格式的来编写注册的功能。Ext组件的事件机制采用是的规定格式,这个规定是格式是规定函数名和函数的参数形式。

对于这个规定,Observable类给出一个addEvent方法用来每个需要事件能力组件在初始化时就进行格式规定。addEvents : function(o){    
        if(!this.events){ this.events = {};}    
        if(typeof o == ''string''){
            for(var i = 0, a = arguments, v; v = a[i]; i++){
                if(!this.events[a[i]]){  o[a[i]] = true; } }}
else{Ext.applyIf(this.events, o); }
}
,

这个方法是为了让组件在实现自己的事件能力时规定它的事件函数的格式。它有两种应用形式,如下:

形式一:addEvents(''click'',''dblclick'',''change'')

形式二:addEvents({click:true,dblclick: dblclickEvent})

第一种每个参数都是字符形的事件名,第二种形式是采用JSON对象把所有的事件和其事件对象对应起来。它通过继承方法成为每个实例化的组件的方法,也就是说它把所有的事件都存放在当前组件的events中。但是无论采用形式一还是采用形式二。其生成的事件对象都只能说是一个伪对象。因为默认的方式这个事件对象采用true来标识。形式二的第二种用法是一般都不会用的,因为在开发时,组件实例的很多事件都不会被使用。而在生成组件实例的时候就为其每个事件都生成事件对象是不值得的。这样会占用内存和影响效率。它这种把组件对象的实现推迟到addListener中创建是有一定考虑的。

在这里我们只看到了事件名,但是我们并不没有看到如何去规定事件注册函数的格式,这个在组件初始化中只是通过注释的方式来进行说明,如在Component类initComponet方法中就有如下this.addEvents( /** @event disable    
* Fires after the component is disabled.
                    * @param {Ext.Component} this  */
         ''disable'',.. ..}
这样的注释就是用来规定函数的参数。如这个disable的事件只有一个参数就是当前组件,这个参数是组件在调用这个事件回调函数时传入的,开发者在编写事件回调函数可以使用这个参数。这个和浏览器中事件监听函数中参数event是一样的意思。其实对于这个的事件名,也可以不用通过addEvents声明,可以通过注释来声明,在addListener函数中如果没有找到对应的事件名就会自动创建该事件名的。

对于每个事件,它的监听函数的格式(即参数)是通过组件那些需要扩展的地方来实现的。在组件中,如果什么地方需要开发进行它的私有功能的扩展,那么就在这个地方执行开发者注册的事件函数。

这个调用就是根据上面指定的事件名来找到其对应的监听函数(可能有多个)。组件要判断这个事件是否注册了监听函数和如何找到它们就是通过Observable类中的fireEvent方法:fireEvent : function(){
if(this.eventsSuspended !== true){    //支持全局的挂起事件
  var ce = this.events[arguments[0].toLowerCase()];          
  if(typeof ce == "object"){
  return ce.fire.apply(ce,Array.prototype.slice.call(arguments,1));}

   }

return true;
},
fireEvent用在组件需要扩展的地方进行检测是否注册事件监听及运行它们。我们已经看出它的一个参数是事件名,先通过这个事件名在当前组件注册的事件中找其对应的事件对象起来,一般来说都可以找到,因为在使用fireEvent时是根据规定事件名来指定的。如果找到该事件名对应是对象,那么就通过事件对象内部的fire方法来执行它。在默认的时候,它的事件对象都是 boolean类型的true。如果其是object类型,那么就是在开发通过注册方法进行了事件监听函数注册。

我们看一下①处的传入参数的方式,它会把fireEvent所有的参数除去第一个事件名的参数传入到事件对象中的所有监听函数中去。也就是组件在使用fireEvent时候给定的参数就是为了给事件的监听函数规定其参数格式。这个体现在addEvents中就是采用注释的方式来说明。

我们对应着上面的''disable''的事件名,而component类中的disable方法中就有着如下的代码:this.fireEvent("disable", this);它是用来调用执行开发者注册的事件监听,也就是观察者模式的监测点。如果组件指定类,那么在其类的代码中就是对应于建立这样的监测点,用来调用执行事件监听函数。

对于组件的事件能力,也就只需要这两个步骤。我们在编写组件时,如果想让它拥有事件能力,那么首先得继承于Observable类,之后,再按这二步来进行注册事件名和建立监测点。在第二章的leftMenu类中就是这样采用的。

对于观察的主体,它还提供一个其它的方法来进行相关的操作,比如我要暂定事件或重定启动事件。它提供如下表的三个方法:

方法名

作用及使用说明

suspendEvents

它是用来暂定该组件的事件能力。通过改变代码3.19this.eventsSuspended属性值为true

resumeEvents

对于暂定事件能力的组件,重新启动它的事件能力,通过改变代码3.19this.eventsSuspended属性值为false.

relayEvents

它的格式如下:relayEventso,events),它是用来把当前组件中的事件对象逐个地转移注册到o参数指定的对象(组件或元素等)上去。它们对应的关系就是events参数集合中指定的事件名。这就要求o对象和当前组件都有着相关的事件名。在gridPanel中就有应用。

4.8其它方法的说明

被观察者
被观察者就是开发者所编写的事件监听函数。对于这些监听函数,我们得按指定的格式来编写。这个格式是对于每个事件都是不同的。那么编写完成之后,我们就得把它们注册到相对应的事件对象中去。好让组件能监测到它,并在需要的时候去执行它来完成私有功能。

这个注册动作就是由Observable类的addListener完成:
addListener : function(eventName, fn, scope, o){
if(typeof eventName == "object"){
  o = eventName;
  for(var e in o){
    if(this.filterOptRe.test(e)){ continue; }   ①
    if(typeof o[e]=="function"){this.addListener(e,o[e],o.scope,o);}
else{this.addListener(e, o[e].fn, o[e].scope, o[e]);s}            ③
   }

return;
}

o = (!o || typeof o == "boolean") ? {} : o;                            ④      
eventName = eventName.toLowerCase();
var ce = this.events[eventName] || true;
if(typeof ce == "boolean"){ ce = new Ext.util.Event(this, eventName);
                                  this.events[eventName] = ce; }
          ⑤
ce.addListener(fn, scope, o);                                             ⑥
},
开发就是通过它来实现向该组件指定的事件中注册监听函数。和元素的事件注册的使用方法是一模一样的,它也有三个方式的注册形式,详细见4.2.2节,不同的它的o中只能解析四个配置项,这个通过①处的filterOptRe正则表达式/^(?:scope|delay|buffer|single)$/就可以看出来它只支持scope|delay|buffer|single四个配置项,它的含义和元素事件是也相同。

和元素事件一样,它也一个缩写的方法名on。它的代码的层次也是和元素的事件注册方法相同,不同的地方在②③处是采用递归调用当前函数来实现注册多个监听函数。其注册单个监听函数代码部分在④到⑥处。在⑤处,我们可以看出在addEvents中没有生成事件对象推迟到这里实现。同时没有指定事件名,也会在这里生成与事件相对应的事件对象。

被观察者最主要的的任务就是编写并注册该监听函数。这个注册就得是开发者在具体使用该组件中而进行注册用的。在第二章中main.js文件中就通过注册监听函数建立leftMenu和主区域之间的关系,见代码清单2.8。

当我们不需要事件的监听时,我们就要把它从事件去剔除出去。这个我们可以通过removeListener和purgeListeners方法来实现,它们不同的地方在于removeListener清除指定事件名及函数单个剔除(当然只指定事件名,就会清除该事件名所有监听函数),而purgeListeners是清除该组件所有事件的监听函数。
listeners: {
              selectionchange: {
                 fn: function(dv,nodes){
                     var l = nodes.length;
                     var s = l != 1 ? ''s'' : '''';
                     panel.setTitle(''Simple DataView (''+l+'' item''+s+'' selected)'');
                 }

              }

            }    
4.4.3 基于拦截的事件实现
在Observable类中,它还有一组方式用来实现这种事件,它是采用拦截的思想来实现的,在讲解它们之前,我们先来看一下Combox组件中Onslect的方法。
onSelect : function(record, index){
  if(this.fireEvent(''beforeselect'', this, record, index) !== false){ ①
     this.setValue(record.data[this.valueField || this.displayField]);
     this.collapse();
     this.fireEvent(''select'', this, record, index); }
  ②
},
这是我们通过上一节中常规事件机制来建立事件的监测点。我们发现该方法的事件的监测点(fireEvent处)在函数的最前和最后部分,而且在Ext组件中事件监测点基本上都会出现这两点的位置上,很显然这种实现代码耦合度高,那么能不能采用一种新的方式呢,把fireEvent代码移出Onslect方法,让它在该方法之前或之后执行呢,在3.5节中,我们讲到了函数的拦截实现,如果能采用这种方式把fireEvent部分移出函数体,那么代码就清爽多了。对于①的fireEvent,只要在执行其之前返回false,就不执行onSelect方法,对②处fireEventonSelect方法执行完成之后再执行就可以了。

这就当前流行AOP的思想,采用函数的拦截功能,在执行onSelect之前调用用户注册的''beforeselect''的监听函数,如果返回成功就执行onSelect。对于''select''刚可以在函数之后执行。Observable提供另外一组事件处理的功能:
beforeMethod : function(method, fn, scope){
           var e = this.getMethodEvent(method);
           e.before.push({fn: fn, scope: scope}); },
  afterMethod : function(method, fn, scope){
           var e = this.getMethodEvent(method);
           e.after.push({fn: fn, scope: scope}); },

beforeMethod、afterMethod就是完成拦截的功能,对于onSelect事件实现,我们可以采用这两个方式来实现。用户只要调用beforeMethod(‘onSelect’, myFn,scope), afterMethod (‘onSelect’, myFn ,scope),就可以实现上面事件的同样功能。onSelect方法就去除了fireEvent的代码。使用也是很方便。

其原理在什么地方? beforeMethod、afterMethod中最重要的就是this.getMethodEvent(method)这个函数。它实现了拦截的功能,之后的只是往before,after的数组中加fn了。
点击展开示例
当开发者调用beforeMethod或afterMethod方法,它就通过了getMethodEvent方法生成一个与它指定的拦截的函数名对应的方法事件对象。这个对象中的有before和after两个集合属性。调用beforeMethod方法就会向这个before集合加入事件的监听方法。beforeMethod方法也类似,这个监听方法的参数就是当前拦截函数的参数,它的作用域是可以在beforeMethod或afterMethod中第三个参数指定。

开发者可以直接把把监听函数注册在当前组件某个方法的前面或后面,当运行这个方法时就会自动运行这些监听函数,达到了对组件的某些方法进行扩展。这样就不需要什么事件名等,在方法事件集合中,它的事件名就是拦截函数名。它的监听函数就是通过beforeMethod或afterMethod注册在拦截函数的前面或后面。这样的话,开发者的权利更大了,它可以在当前组件的前面或后面进行拦截扩展该函数的功能。

它并不取代现有的常规的事件机制,因为还有很多代码监测点是在函数的中间,而不是开始和结尾处。不过笔者觉得还是和常规的事件机制结合起来比较好,结合起来也不是很难的,只要在beforeMethod或afterMethod方法中参数中加上事件名,在getMethodEvent中对这个事件名进行判断,如果指定了就去传入参数执行该事件注册的监听函数。这个参数也可以根据需求在beforeMethod或afterMethod方法中参数中进行指定(通过增加config参数)。之后在组件的初始化过程中,调用beforeMethod或afterMethod方法来统一建立那些要分散在那些在函数内面前或后fireEvent监测点。这个统一的代码也可以移植到比如methodFire名的方法中。这样那些如代码清单4.21 的方法就很清爽,也不会影响到现在的事件机制。

说了半天的扩展,还没有去分析getMethodEvent方法是如何去运行的呢。在①处,它和常轨事件一样,建立methodEvents集合来保存事件对象,这个对象中事件名就是拦截的方法名,在②③之间代码就是构建这个事件对象,它的格式如下:

{originalFn:onselect,methodName:’ onselect’,before:,after:}。

其中originalFn是指拦截的方法,methodName就是这个事件对应的事件名,和拦截方法名是一样的。接着的before和after就是用来保存该拦截方法的之前或之后执行的函数。

③处的makeCall是用来执行before和after集合的监听函数,⑦和⑨处的代码就是通过循环来分别运行它们各自己集合的监听函数。⑧处是运行拦截方法,⑦和⑨处正好是一个在函数前执行,一个在函数后执行。它们执行的就是makeCall函数,看出来吧,makeCall是包裹函数,它的包裹的主要作用就是处理返回值。

对于返回值,getMethodEvent建立了两个变量returnValue、cancel用来保存⑦和⑨代码每次运行时返回的状态,每次运行makeCall都会设定(改变或保持)其值。returnValue是保存其返回值,而cancel的意思是在运行完这个函数之后是否立马中断,不执行该函数链中其它函数。⑦和⑨处接下一行代码就是根据这个cancel的状态来进行是否中断函数链。在⑤处下面一行代码就是初始化这两个变量。

对于我们要实现的监听函数,其返回值可以分成三种,一种是Json对象的形式集合,如{ returnValue:xx,cancel:fase,otherattr:yy}。如果这个返回的对象中有returnValue属性,那么这个值就会设定为getMethodEvent函数的returnValue变量值。如果有cancel的话,那这个值就会设为cancel变量的值。第二种是该监听函数直接返回显式的false值,那么cancel就设为true,其链中之后的函数就不会执行。第三种是除这两种情况,都是把返回值设为returnValue变量值。这个returnValue是⑤处函数返回的值。⑤处的就是那个拦截的函数,现在如果访问那个拦截函数(当然是通过函数名),它的函数名已经指向了⑤处的函数。

⑤处的函数说起来也很简单,它就是把注册在其前面监听函数包括进来,再把之后的监听函数包括进来。与4.21不同是,现在的监测点是通过函数的拦截来自动实现。达到代码的解耦。

现在我们要执行this.beforeMethod(‘onSelect’, myFn,scope)函数分析其运行,首先通过执行beforeMethod方法第一行代码时候,那么其当前组件的methodEvents状态:
methodEvents={''onselect'':{originalFn:onselect,methodName:''onselect'',
before:,ater:}}。
同时this.onselect函数指向的不再是原来函数,而是getMethodEvent中⑤处的函数。接着运beforeMethod第二行代码,其methodEvents状态改成:
methodEvents={''onselect'':{originalFn:onselect,methodName:''onselect'',
before:[ myFn],ater:}}。
当调用this.onSelect函数时,它接着会执行③处的makeCall函数,在makeCall函数中执行myFn函数。并返回其返回值,假如它没有设定返回值,接下来就执行⑧的的原来的onselect函数。要注意的是原来的onselect还在,只不过是其引用名变成originalFn。

Ext.util.observable这一组基于拦截的实现的。看起来很诱人,但是不成熟。如果不是很清楚其原理和机制,最好还是不要用到开发中去。

4.4.4组件及元素事件结合
上面我们分析了组件的事件机制,组件的事件有很多事件名如元素的事件是一样,如click等。组件的事件可以分成两类,一类是进行组件之间解耦及增加组件的扩展。另外一类就是和元素的事件一样是用来响应用户的键盘和鼠标操作,这一类事件是建立在元素事件基础之上。它的监听函数中一般都会有event的这样的元素事件对象保存事件发生的相关信息。

组件最终是落实在页面中显示出来,那么它就对应着一个元素,如想让该组件有click事件,就只要把这个组件的click事件移交到这个元素上的click事件就可以了。下面就通过Ext.form.Field进行说明。

1、它首先定义该组件的对应的DOM元素:在其属性中定义了defaultAutoCreate:defaultAutoCreate : {tag: "input", type: "text", size: "20", autocomplete: "off"},
就是通过这个defaultAutoCreate来创建录前组件对应的DOM元素,这里它是一个text文本框。它是在其onRender方法中创建的:
if(!this.el){ var cfg = this.getAutoCreate();
.. ..
              this.el = ct.createChild(cfg, position); }
这里它创建一个文本框,当前组件的el属性指向了这个文本框元素,在组件中,基本组件对应的元素都是采用当前组件的el属性指向它。接着在其initEvents中注册元素的事件:
initEvents : function(){
      this.el.on(Ext.isIE ? "keydown" : "keypress", this.fireKey,  this);    
      .. ..
},
在这里,它注册了三个元素事件,但是我们只列出其keypress事件,对于该元素的keypress时,那么就运行当前组件的fireKey函数。那么我们看一下其fireKey函数
fireKey : function(e){
     if(e.isSpecialKey()){this.fireEvent("specialkey", this, e); }},
fireKey是事件keydown 或 keypress的监听处理函数。而它又执行当前组件specialkey事件的监听函数。specialkey是是组件事件。用户只要通过如下代码就可以向它注册监听函数:
var f=new Ext.form.Field();
  f.on(specialkey:fucntion(t,e){//相关处理 });
在这里我们也看到监听函数的第二个参数是元素的event对象。其规定是在initCompoent方法中this.addEvents{''specialkey''//同时还有/对事件监听函数的参数的说明注释部分}。这样就把组件的事件建立在元素的事件基础之上。对元素事件进行一些相关的处理改一个事件名就成其组件的事件(也可以不改名)。

通过这里,我们可以看Ext的元素事件是建立在DOM元素事件的基础之上,而Ext的组件事件(不包括做扩展用的事件)是建立在Ext元素的事件基础之上。
  • 下一篇资讯: 详解jQuery
  • 网学推荐

    免费论文

    原创论文

    浏览:
    设为首页 | 加入收藏 | 论文首页 | 论文专题 | 设计下载 | 网学软件 | 论文模板 | 论文资源 | 程序设计 | 关于网学 | 站内搜索 | 网学留言 | 友情链接 | 资料中心
    版权所有 电话:013574892963 QQ:3710167 邮箱:Educs@163.com 网学网 [Myeducs.cn] 您电脑的分辨率是 像素
    Copyright 2008-2015 Www.myeducs.Cn All Rights Reserved
    湘ICP备09003080号