`
weitd
  • 浏览: 143390 次
  • 性别: Icon_minigender_1
  • 来自: 新都
社区版块
存档分类
最新评论

如何透过扩展现有类的能力来创建用户自定义界面的控件

阅读更多
组合或扩展

当创建一个新类,往往要作出这么的一个选择:要么拥有某个工具类的实例来扮演首要的角色,要么扩展那个类。

使用ExtJs过程中,推荐从最靠近的基类开始扩展,实现所需的功能即可。这是因为Ext提供的自动生存周期引入了自动渲染的机制、自动大小调整和承担接受来自布局管理器的UI组件布局调控,还有在容器(Container)中自动销毁的功能。

组织一个新类,它就是ExtJs的类,实现起来是很方便的,这就会导致了Container→Component层次的形成,相比较,新类拥有一个ExtJs类的话,必须从外围对其渲染和组织。
The Template method Pattern模板方法模式

Ext Js的Component的继承层次采用Template Method pattern(模板方法模式)来委派给子类,交由子类来负责其特定的行为。

其意义在于,继承链中的每个子类得以在,组件生存周期的某个阶段内,“提交(contribute)”额外的逻辑功能。这样每一个类拥有属于其自身的行为;其他类加入自己的行为时也不会相互造成影响。

其中一个例子是render(渲染)函数。render函数不能够被覆盖,应该是一层一层地在各子类的实现中加入onRender方法,那么render函数就会在执行的时候把各onRender方法访问调用。每一个onRender方法必须调用其父类的onRender方法继而再“埋头处理(contribute)”自己(子类)的逻辑部分。

下面的图例演示了模板方法onRender的机能过程。

render是由容器的布局管理器(Container’s layout manager)负责调用。该方法的实现不能被“冲掉(overridden)”,它是由Ext基类提供的。this.onRender表示当前子类所写的实现(如有提供的话)。它会访问父类的版本、父类的版本又会调用父、父类的版本……最终,每个类完成了其功能,render函数的返回值对生存周期进行控制。

Image:Classflowchart.PNG

在ExtJS组件(Component)生存周期中,提供了若干有意义的模板方法以实现类特定的逻辑功能。

强调: 当编写子类时,模板方法应在实例化的过程中调用,属于生存周期内的一部分的动作,而不应是事件的一部分。事件有可能会由handler挂起,或中止。

以下是Component子类都可享有的模板方法:

    * onRender

    允许渲染期间加入特定的行为。父类的onRender被调用后,可以确定组件的Element元素。可以在此阶段中执行剩余DOM任务以完成结构上的控制(HTML结构)。

    * afterRender

    允许渲染完成后加入特定的行为。此阶段的组件元素会根据配置的要求(configuartion)设置样式,会引入已配置的CSS样式名称所指定的样式名称,并会配置可见性(visibility)和配置可激活(enable)情况。

    * onShow

    允许在显示组件的同时加入特定的行为。父类的onShow执行过后,Componenet将会显示。

    * onHide

    允许在隐藏组件的同时加入特定的行为。父类的onHide执行过后,Componenet将会隐藏。

    * onDisable

    允许在禁用组件的同时加入特定的行为。父类的onDisable执行过后,Componenet将会禁用。

    * onEnable

    允许在激活组件的同时加入特定的行为。父类的onEnable执行过后,Componenet将会激活启用。

    * onDestroy

    允许在销毁组件的同时加入特定的行为。父类的onDestroy执行过后,Componenet将会被销毁。

Ext组件类的各层次中的均有其自身的模板方法,我们可以打开来看看,这些都是根据自身不同的需求而作出的设计。

提示: 当调用父类的模板方法时,最简洁的方法就是使用Function.apply,保证所有的参数都可以接受得到,传送给那个模板方法:

Ext.ux.Subclass.superclass.onRender.apply(this, arguments);

要扩展哪个类

选择适合的类来扩展不但要考虑基类提供哪些功能,而且对性能方面也要着重考虑。无论有多少个UI控件被渲染或调控,Ext.Panel常常就是被衍生(extend)的对象。

Panel类拥有许多的能力:

    * Border(躯干)
    * Header(头部)
    * Header工具条
    * Footer(底部)
    * Footer按钮
    * Top toolbar(顶部工具条)
    * Bottom toolbar(底部工具条)
    * 承托和管理子组件

如果这些派不上用场,那使用Panel便是资源浪费。
Component(组件类)

如果要求的UI控件不需要其他的细节的控件,也就是,仅仅是封装某部分的HTML元素的话,那么可取的扩展对象就是Ext.BoxComponent或Ext.Component。如果再缩窄一步,我不需要听凭父容器提供的大小调控功能,那么使用Ext.Component就可以了。

强调: Component类并不会内省而得知哪一种元素作为holder。因此为了创建所需的元素(Element),应设定autoEl的配置项。

例如,要把一张图片封装为Component,我们于是乎这样定义:

Ext.ux.Image = Ext.extend(Ext.Component, {
    autoEl: {
        tag: 'img',
        src: Ext.BLANK_IMAGE_URL,
        cls: 'tng-managed-image'
    },

//  Add our custom processing to the onRender phase.
//  We add a ‘load’ listener to our element.
    onRender: function() {
        Ext.ux.Image.superclass.onRender.apply(this, arguments);
        this.el.on('load', this.onLoad, this);
    },

    onLoad: function() {
        this.fireEvent('load', this);
    },

    setSrc: function(src) {
        this.el.dom.src = src;
    }
});

这是一个可封装图片的Ext Component类,可参与非箱子方寸模型(non box-sizing)的布局。
BoxComponent

如果要求的UI控件不需要其他的细节的控件,也就是,仅仅是封装某部分的HTML元素的话,还要听凭布局管理器提供的大小尺寸、布局的调控,那么这个的扩展对象就是Ext.BoxComponent。

例如,假设一个Logger类打算是简单地显示log信息,就必须嵌入某种布局的风格,例如插入到一个layout:’fit’窗体,可以这样定义:

Ext.ux.Logger = Ext.extend(Ext.BoxComponent, {
    tpl: new Ext.Template("<li class='x-log-entry x-log-{0:lowercase}-entry'>",
        "<div class='x-log-level'>",
            "{0:capitalize}",
        "</div>",
        "<span class='x-log-time'>",
            "{2:date('H:i:s.u')}",
        "</span>",
        "<span class='x-log-message'>",
            "{1}",
        "</span>",
    "</li>"),

    autoEl: {
        tag: 'ul',
        cls: 'x-logger'
    },

    onRender: function() {
        Ext.ux.Logger.superclass.onRender.apply(this, arguments);
        this.contextMenu = new Ext.menu.Menu({
            items: [new Ext.menu.CheckItem({
                id: 'debug',
                text: 'Debug',
                checkHandler: Ext.ux.Logger.prototype.onMenuCheck,
                scope: this
            }), new Ext.menu.CheckItem({
                id: 'info',
                text: 'Info',
                checkHandler: Ext.ux.Logger.prototype.onMenuCheck,
                scope: this
            }), new Ext.menu.CheckItem({
                id: 'warning',
                text: 'Warning',
                checkHandler: Ext.ux.Logger.prototype.onMenuCheck,
                scope: this
            }), new Ext.menu.CheckItem({
                id: 'error',
                text: 'Error',
                checkHandler: Ext.ux.Logger.prototype.onMenuCheck,
                scope: this
            })]
        });
        this.el.on('contextmenu', this.onContextMenu, this, {stopEvent: true});
    },

    onContextMenu: function(e) {
        this.contextMenu.logger = this;
        this.contextMenu.showAt(e.getXY());
    },

    onMenuCheck: function(checkItem, state) {
        var logger = checkItem.parentMenu.logger;
        var cls = 'x-log-show-' + checkItem.id;
        if (state) {
            logger.el.addClass(cls);
        } else {
            logger.el.removeClass(cls);
        }
    },

    debug: function(msg) {
        this.tpl.insertFirst(this.el, ['debug', msg, new Date()]);
        this.el.scrollTo("top", 0, true);
    },

    info: function(msg) {
        this.tpl.insertFirst(this.el, ['info', msg, new Date()]);
        this.el.scrollTo("top", 0, true);
    },

    warning: function(msg) {
        this.tpl.insertFirst(this.el, ['warning', msg, new Date()]);
        this.el.scrollTo("top", 0, true);
    },

    error: function(msg) {
        this.tpl.insertFirst(this.el, ['error', msg, new Date()]);
        this.el.scrollTo("top", 0, true);
    }
});

接着是CSS:

.x-logger {
    overflow: auto;
}
.x-log-entry .x-log-level {
    float: left;
    width: 4em;
    text-align: center;
    margin-right: 3px;
}
.x-log-entry .x-log-time {
    margin-right: 3px;
}
.x-log-entry .x-log-message {
    margin-right: 3px;
}
.x-log-debug-entry, .x-log-info-entry, .x-log-warning-entry, .x-log-error-entry {
    display: none;
}

.x-log-show-debug .x-log-debug-entry { display: block }
.x-log-show-info .x-log-info-entry { display: block }
.x-log-show-warning .x-log-warning-entry { display: block }
.x-log-show-error .x-log-error-entry { display: block }

.x-log-debug-entry .x-log-level { background-color: #46c }
.x-log-info-entry .x-log-level  { background-color: green }
.x-log-warning-entry .x-log-level  { background-color: yellow }
.x-log-error-entry .x-log-level  { background-color: red }

我们吧log的信息的HTML列表均放置在一个布局中。我们在onRender的阶段加入处理,使得右键菜单可以根据CSS样式类的名称操控logged条目的可见性。位于该层次的对象还提供了特别的模板方法:

    * onResize

    此时此刻,BoxComponent的大小已经发生变化,此时可执行剩余的任务。

    * onPosition

    此时此刻,BoxComponent的定位已经发生变化,此时可执行剩余的任务。

Container(容器类)

如果要求的UI控件将用于承载(Contain)其他UI元素在其身上,但并不需要前文提及到的Ext.Panel那么多的功能,为避免臃肿,应采用Ext.Container容器类来继承。同样地,autoEl指定元素的配置项亦必不可少,将用于容器在某个元素之上进行渲染。同样,在视觉控制方面,滚动条是否显示方面(即overflow属性),用户都可以使用Style配置项,或容器元素的class属性的两种方式进行CSS样式制定。

注意: 对于Container层次,不要忘记哪种布局类是被用于渲染和调控子组件的。

示例中的类封装了条件命令的查询,允许用户对Store基于测试字段的数据筛选。除了功能上的封装外,还把查询任务作统一布局,封装在一个可控类中,可方便从容器身上自动添加或移除查询的条目,灵活性更高:

Ext.ux.FilterCondition = Ext.extend(Ext.Container, {
    layout: 'table',

    layoutConfig: {
        columns: 7
    },

    autoEl: {
        cls: 'x-filter-condition'
    },

    Field: Ext.data.Record.create(['name', 'type']),

    initComponent: function() {
        this.fields = this.store.reader.recordType.prototype.fields;
        this.fieldStore = new Ext.data.Store();

//      Create a Store containing the field names and types
//      in the passed Store.
        this.fields.each(function(f) {
            this.fieldStore.add(new this.Field(f))
        }, this);

//      Create a Combo which allows selection of a field
        this.fieldCombo = new Ext.form.ComboBox({
            triggerAction: 'all',
            store: this.fieldStore,
            valueField: 'name',
            displayField: 'name',
            editable: false,
            forceSelection: true,
            mode: 'local',
            listeners: {
                select: this.onFieldSelect,
                scope: this
            }
        });

//      Create a Combo which allows selection of a test
        this.testCombo = new Ext.form.ComboBox({
            triggerAction: 'all',
            store: ['<', '<=', '=', '!=', '>=', '>']
        });

//      Inputs for each type of field. Hidden and shown as necessary
        this.booleanInput = new Ext.form.Checkbox({
            hideParent: true,
            hidden: true
        });
        this.intInput = new Ext.form.NumberField({
            allowDecimals: false,
            hideParent: true,
            hidden: true
        });
        this.floatInput = new Ext.form.NumberField({
            hideParent: true,
            hidden: true
        });
        this.textInput = new Ext.form.TextField({
            hideParent: true,
            hidden: true
        });
        this.dateInput = new Ext.form.DateField({
            hideParent: true,
            hidden: true
        });

        this.items = [ this.fieldCombo, this.testCombo, this.booleanInput, this.intInput, this.floatInput, this.textInput, this.dateInput];
        Ext.ux.FilterCondition.superclass.initComponent.apply(this, arguments);
    },

    onFieldSelect: function(combo, rec, index) {
        this.booleanInput.hide();
        this.intInput.hide();
        this.floatInput.hide();
        this.textInput.hide();
        this.dateInput.hide();
        var t = rec.get('type');
        if (t == 'boolean') {
            this.booleanInput.show();
            this.valueInput = this.booleanInput;
        } else if (t == 'int') {
            this.intInput.show();
            this.valueInput = this.intInput;
        } else if (t == 'float') {
            this.floatInput.show();
            this.valueInput = this.floatInput;
        } else if (t == 'date') {
            this.dateInput.show();
            this.valueInput = this.dateInput;
        } else {
            this.textInput.show();
            this.valueInput = this.textInput;
        }
    },

    getValue: function() {
        return {
            field: this.fieldCombo.getValue(),
            test: this.testCombo.getValue(),
            value: this.valueInput.getValue()
        };
    }
});

此类管理了其包含的输入字段,可以精确的布局-大小调整,外补丁等等——都是通过CSS样式分配到元素身上这样来起作用的。

位于该层次的对象还提供了特别的模板方法:

    * onBeforeAdd

    当添加新的子组件的时候,就会调用该方法。这时会有新组件作为参数传入,或者可修改它,或者以特别的方式准备好Container。返回false表示终止添加的操作。

Panel

如果所需的UI控件要求头部、底部、或工具条之类的元素,那么Ext.Panel就是一个很不错的类给予继承了。

注意: Panel是容器的一种,不要忘记哪种布局类是被用于渲染和调控子组件的。

通常Ext.Panel所实现的类会有很高的程序结合性,一般用于与其他UI控件协调使用(通常Containers,或表单字段),并对其有特定配置的布局风格。另外,要对在其内的组件提供操作的命令,可以从tbar(顶部工具栏),bbar(底部工具栏)的两方面设置加以控制。
Field

如果所需的UI控件要求为用户交互,可以把程序的数据显示给用户,或修改进而发生给服务器的功能,那么要被扩展的类应该是Ext.form.TextField,或Ext.Form.NumberField。另外,如果要求轮换按钮(Trigger button),以备键盘按键的轮换,那就是Ext.form.TriggerField。

位于该层次的对象还提供了特别的模板方法:

    * onFocus:input输入框得到焦点后即会触发该方法的执行。
    * onBlur:input输入框失去焦点后即会触发该方法的执行。

什么时候不需要子类

有些时候,滥用子类无异于“杀鸡用牛刀”。在一些特定应用场合,某个现有的类它的方法被添加、被重写,是由这个类的构造器实例化过程中依靠参数传入的。这时候模板方法依然可用但不能够用superclass的方式,去访问父类对象;这时,我们就要使用类自身的方式得到父类对象,调用模板方法,实质上它是通过构造器的原型的路径寻址(见this.constructor.prototype)。

例如我们要一次性地在一个实例中创建带有按钮的文本框以测试输入的web地址是否正确:

new Ext.form.TextField({
    name: 'associatedUrl',
    fieldLabel: 'Associated page',

//  Wrap the input, and render the test button
    onRender: function() {
        this.constructor.prototype.onRender.apply(this, arguments);
        this.wrap = this.el.wrap({
            cls: 'x-form-field-wrap'
        });
        this.testButton = new Ext.Button({
            renderTo: this.wrap,
            buttonSelector: 'em',
            cls: 'x-trigger-button',
            style: {
                position: 'absolute',
                right: 0,
                top: 0
            },
            text: 'Test',
            handler: this.testUrl,
            scope: this
        });
        if(!this.width){
            this.wrap.setWidth(this.el.getWidth() +
            this.testButton.getEl().getWidth() + 5);
        }
    },

//  Keep the wrap size correct
    onResize : function(w, h){
        this.constructor.prototype.onResize.call(this, w, h);
        if(typeof w == 'number'){
            this.el.setWidth(this.adjustWidth('input', w -
            (this.testButton.getEl().getWidth() + this.buttonSpacing)));
        }
        this.wrap.setWidth(this.el.getWidth() +
            this.testButton.getEl().getWidth() + this.buttonSpacing);
    },

//  Perform the test by opening the fields value as a web page
    testUrl: function(button, event) {
        if (this.isValid()) {
            var e = this.testButton.getEl();
            var w = window.open(this.getValue(), this.id.replace('-',''), 'status=true');
        }
    }
})

综述

    * 扩展现有的Ext组件类以便发挥可控生存周期的能力。
    * 从基础功能的类起实现扩展。
    * 用模板方法加入和重写行为。
    * 创建实例指定的模板方法,用在一次性的设计中。

转http://www.extjs.com/learn/Tutorial:Creating_new_UI_controls_%28Chinese%29
分享到:
评论

相关推荐

    C#.net自定义控件开发用户自定义控件扩展控件

    总结来说,这个案例将教你如何在C#.NET中进行自定义控件开发,特别是用户控件和扩展控件的创建。通过实践,你可以掌握自定义控件的继承与重写,用户控件的组合与设计,以及文件上传功能的实现,这些都是构建高效、...

    C# 自定义用户控件(PictureBox)

    4. **事件处理**:可能需要添加新的事件,或者扩展现有的事件处理,来响应用户的操作或者其他控件的交互。 5. **资源管理**:如果控件需要加载或处理图像资源,可能需要编写代码来管理这些资源,如图片加载、缓存...

    ASP.NET用户控件和自定义控件

    在ASP.NET中,用户控件和自定义控件是两种重要的组件,它们扩展了.NET Framework的内置控件,帮助开发者创建具有特定功能和交互性的界面元素。 **用户控件(User Control)** 用户控件是ASP.NET中的基本自定义UI元素...

    C# 自定义用户控件

    自定义用户控件是C#编程中的一个重要概念,它允许开发者根据特定需求创建自己的UI元素,扩展标准控件的功能,或者设计出全新的界面组件。本文将深入探讨C#自定义用户控件的相关知识点。 首先,理解基础:用户控件...

    asp.net 自定义用户控件 事件

    在ASP.NET中,自定义用户控件提供了一种方式来扩展内置控件的功能,或创建完全新的交互式元素。 ### 1. 自定义用户控件概述 自定义用户控件(Custom Control)是基于已存在的服务器控件,通过继承`System.Web.UI....

    用户控件 自定义控件

    用户控件(User Control)是ASP.NET提供的一种强大功能,允许开发者根据应用需求自定义界面元素。通过使用用户控件,可以大大减少代码重复,并且提高代码的可维护性和复用性。 用户控件是一种轻量级的控件,它通常...

    vb6.0用户自定义控件

    总之,"jcbutton"控件是一个示例,展示了VB6.0如何通过用户自定义控件扩展其功能,实现更复杂、更个性化的用户界面。这种技术在许多Windows应用程序中都得到了广泛应用,对于提升软件的用户体验和独特性至关重要。

    C#自定义控件库

    "C#自定义控件库"是指使用C#语言编写的、由开发者自定义的控件集合,这些控件可以扩展.NET Framework的标准控件集,为用户提供更丰富的界面元素和功能。自定义控件是软件开发中的一个重要环节,特别是在UI设计和用户...

    c# 自定义用户控件例(含自定义方法和事件)

    在C#编程中,自定义用户控件是创建具有特定功能和界面的UI元素的关键步骤。这使得开发者能够根据项目需求扩展.NET Framework的基础控件集。在这个“c# 自定义用户控件例(含自定义方法和事件)”中,我们将深入探讨...

    vb6.0用户控件自定义控件

    在VB6.0中,用户控件(User Control)和自定义控件(Custom Control)是两种非常重要的组件开发技术,它们允许开发者扩展Visual Basic的标准控件库,以满足特定项目的需求。通过创建用户控件和自定义控件,我们可以...

    C#自定义日期控件

    在C#中,创建自定义控件通常涉及继承一个现有的UI控件类,如System.Windows.Forms.Control或System.Windows.Forms.MonthCalendar,并在其基础上进行扩展。在提供的"SourceVS2005"文件中,开发者可能已经创建了一个...

    C#用户自定义控件C#用户自定义控件

    2. 添加新项:在项目中右键点击,选择"添加" -&gt; "新建项",然后选择"Windows控件库"或"类库",为控件创建一个新的类。 3. 继承控件基类:在新建的类中,选择继承自`System.Windows.Forms.Control`(对于Windows ...

    asp.net用户自定义控件及调的工程

    在ASP.NET中,用户自定义控件(User Control)是一种非常重要的特性,它允许开发者创建自己的可重用组件,以满足特定的业务需求或界面设计。这个“asp.net用户自定义控件及调的工程”可能是一个示例项目,演示了如何...

    自定义WebForm控件集

    通过自定义这些控件,开发者不仅可以提升网站的功能性,还可以优化用户界面,增强网站的用户体验。此外,对于学习和理解ASP.NET控件的工作原理,以及如何自定义和扩展这些控件,这个资源也是非常有价值的。在实际...

    MFC扩展自定义控件

    在Microsoft Foundation Class (MFC)库中,自定义控件是一种强大的工具,允许开发者扩展标准Windows控件的功能或创建全新的用户界面元素。本资源提供的"myControl"压缩包包含了一系列MFC自定义控件的源代码,涵盖了...

    C#自定义用户控件及用户控件验证

    本教程将深入探讨如何在C#中创建自定义用户控件,并实现自定义验证功能,通过具体的案例来加深理解。 首先,让我们了解自定义用户控件的基本步骤: 1. 创建新项目:打开Visual Studio,选择“新建项目”,然后选择...

    MVVM自定义用户控件

    创建自定义用户控件意味着我们需要扩展WPF或UWP的基础控件,以满足特定的需求或提供独特的功能。以下是创建自定义用户控件的步骤: 1. **定义控件类**:在C#中,创建一个新的类,继承自`System.Windows.Controls....

    1027 C# 自定义UI界面 控件库 源码_c#控件_C#UI界面库_UI控件库_C#控件_c#控件库_

    在C#编程环境中,自定义UI界面控件库是一个重要的组成部分,它允许开发者根据特定需求创建独特的用户界面,提升应用程序的交互性和视觉效果。标题提到的"1027 C# 自定义UI界面 控件库 源码"提供了一组源代码,包含了...

    自定义用户控件网页教程

    在ASP.NET Web应用程序中,自定义用户控件(User Control)是一种非常强大的工具,它允许开发者创建可重用、可定制的网页组件。本教程将深入讲解如何在Web项目中创建和使用自定义用户控件,以提升开发效率和代码复用...

Global site tag (gtag.js) - Google Analytics