ASP.NET可編輯下拉封裝

using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Collections;
using System.Web.UI.Design;
using System.Drawing;
using System.Text;

namespace AtomNet.Web.UI.WebControls
{
    [ToolboxData("<{0}:EditableDropDown runat=\"server\"></{0}:EditableDropDown>")]
    [Designer(typeof(AtomNet.Web.UI.WebControls.EditableDropDownDesigner))]
    public class EditableDropDown : System.Web.UI.WebControls.ListControl
    {
        /// <summary>
        /// 下拉箭頭的寬度
        /// </summary>
        const int DROPDWON_ARROW_WIDTH = 22;

        /// <summary>
        /// 默認寬度
        /// </summary>
        const int DEFAULT_WIDTH = 170;

        #region 私有屬性
        /// <summary>
        /// 輸入框
        /// </summary>
        TextBox txt = new TextBox();

        /// <summary>
        /// The path to an external .js file that contains all of the javascript functions
        /// needed by this control. If this is not set (the default behavior), then the
        /// javascript is emitted inline.
        /// </summary>
        string externalJsPath = null;

        /// <summary>
        /// 控件樣式
        /// </summary>
        string externalCssPath = null;

        #endregion

        #region EditableDropDown構造函數
        /// <summary>
        /// Initializes a new instance of the ComboBox control
        /// </summary>
        public EditableDropDown()
        {
            this.Width = new Unit(DEFAULT_WIDTH);
            //計算文本框的寬度
            txt.Width = new Unit(this.Width.Value - DROPDWON_ARROW_WIDTH, this.Width.Type);

            //此處不要定義該事件,否則與頁面js處理keydown,keyup事件衝突,無法正常顯示自定義的篩選
            //txt.TextChanged += new EventHandler(txt_TextChanged);
        }

        /*
        void txt_TextChanged(object sender, EventArgs e)
        {
            base.OnTextChanged(e);
        }
        */
        #endregion

        #region 重寫 OnInit
        /// <summary>
        /// Initializes the ComboBox control
        /// </summary>
        /// <param name="e">The <see cref="EventArgs"/> passed to this method</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            this.ID = string.Format("{0}_dropdownlist", this.ID);

            //子控件Render的時候會自動加上父控件的ID前綴_
            this.txt.ID = string.Format("{0}_input", this.ClientID);

            this.Controls.Add(txt);
        }

        #endregion

        #region 重寫OnPreRender
        /// <summary>
        /// Raises the PreRender event.
        /// </summary>
        /// <remarks>
        /// The common javascript functions must be dealt with here because 'RegisterClientScriptBlock'
        /// cannot be called from the Render method (it is too late in the lifesycle).
        /// </remarks>
        /// <param name="e">An <see cref="EventArgs"/> object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            if (this.Visible)
            {

                //添加樣式
                if (this.externalCssPath != null)
                {
                    this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "editabledropdown_external_css", String.Format("<link rel='stylesheet' type='text/css' href='{0}' />", this.ResolveUrl(this.externalCssPath)));
                }
                else
                {
                    if (!this.Page.ClientScript.IsClientScriptBlockRegistered("editabledropdown_external_css"))
                    {
                        this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "editabledropdown_external_css", CONTROL_CSS);
                    }
                }

                // if the javascript is in an external file, render a link to it.
                // otherwise, emit the javascript inline.
                if (this.externalJsPath != null)
                {
                    this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "editabledropdown_external_js", String.Format("<script language='javascript' type='text/javascript' src='{0}'></script>", this.ResolveUrl(this.externalJsPath)));
                }
                else
                {
                    // however, if another control has already linked in the external file, we
                    // cant emit the inline javascript because it will conflict. if that is the case,
                    // we dont have to do anything because it is already all handled.
                    if (!this.Page.ClientScript.IsClientScriptBlockRegistered("editabledropdown_external_js"))
                    {
                        // register the common javascript needed by all instances of the ComboBox control
                        this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "editabledropdown_external_js", CONTROL_JS);

                    }
                }
            }
            else
            {
                base.OnPreRender(e);
            }
        }
        #endregion

        #region 重寫Render
        /// <summary>
        /// Renders the control as HTML.
        /// </summary>
        /// <remarks>
        /// If the current browser does not support the functionality required to render the
        /// ComboBox, a normal <see cref="ListBox"/> will be rendered instead.
        /// </remarks>
        /// <param name="writer">The <see cref="HtmlTextWriter"/> to which to emit the resulting HTML.</param>
        protected override void Render(HtmlTextWriter writer)
        {
            if (this.Visible)
            {
                txt.CssClass = "edd_input";
                this.CssClass = "edd_dropdownlist";

                txt.Attributes.Add("autocomplete","off");

                txt.Attributes.Add("onkeydown", "return edd_input_keydown(this,event);");
                txt.Attributes.Add("onkeyup", "return edd_input_keyup(this,event);");
                txt.Attributes.Add("onfocus", "edd_input_focus(this);");
                txt.Attributes.Add("onblur", "edd_input_blur(this);");

                this.Attributes.Add("onchange", "edd_dropdownlist_change(this,event);");

                // render the control
                writer.Write("<div style=\"position:relative;\">");
                txt.RenderControl(writer);
                //當前總寬度減去padding-left
                writer.Write(string.Format("<ul id=\"{0}_droplist\" class=\"edd_droplist\" style=\"width:{1}px\" onmouseenter=\"edd_droplist_mouseenter(this)\" onmouseleave=\"edd_droplist_mouseleave(this)\"></ul>", 
                    this.ClientID, 
                    this.Width.Value-10));
                base.Render(writer);
                writer.Write("</div>");
            }
            else
            {
                // if the ComboBox is not supported by the current browser, just render
                // a plain old ListBox. we also do this if the control is in design-mode
                base.Render(writer);
            }
        }
        #endregion

        #region 放在工具箱上的屬性

        /// <summary>
        /// Gets or sets the maximum number of characters allowed to be manually entered.
        /// </summary>
        /// <value>
        /// The maxumum number of characters allowed to be manually entered.
        /// The default is 0, which indicates that the property is not set.
        /// </value>
        /// <remarks>
        /// If the current browser does not support rendering of the ComboBox, this
        /// property has no effect.
        /// </remarks>
        [Description("Gets or sets the maximum number of characters allowed to be manually entered."),
        Browsable(true),
        Category("Behavior")]
        public int MaxLength
        {
            get
            {
                return txt.MaxLength;
            }
            set
            {
                txt.MaxLength = value;
            }
        }

        /// <summary>
        /// Sets the path to an external .js file that contains all of the javascript
        /// required by this control. If not set, the javascript will be emitted inline.
        /// </summary>
        /// <value>
        /// The valid path to the .js file, or <c>null</c>.
        /// </value>
        /// <remarks>
        /// If the current browser does not support rendering of the ComboBox, this
        /// property has no effect.
        /// </remarks>
        [Description("Sets the path to an external .js file that contains all of the javascript required by this control. If not set, the javascript will be emitted inline."),
        Browsable(true),
        Category("Behavior")]
        public string ExternalJsPath
        {
            get
            {
                return this.externalJsPath;
            }
            set
            {
                this.externalJsPath = value;
            }
        }

        /// <summary>
        /// 樣式文件路徑
        /// </summary>
        /// <value>
        /// The valid path to the .css file, or <c>null</c>.
        /// </value>
        [Description("Sets the path to an external .js file that contains all of the javascript required by this control. If not set, the javascript will be emitted inline."),
        Browsable(true),
        Category("Apperance")]
        public string ExternalCssPath
        {
            get
            {
                return this.externalCssPath;
            }
            set
            {
                this.externalCssPath = value;
            }
        }

        /// <summary>
        /// 文本
        /// </summary>
        [Description("文本"),
        Browsable(true),
        Category("Data")]
        public override string Text
        {
            get
            {
                return txt.Text;
            }
            set
            {
                txt.Text = value;
            }
        }

        /// <summary>
        /// 寬度
        /// </summary>
        [Description("控件寬度"),
        Browsable(true),
        Category("Apperance"),
        DefaultValue("170px")]
        public override Unit Width
        {
            get
            {
                return base.Width;
            }
            set
            {
                base.Width = value;
                txt.Width = new Unit(base.Width.Value - DROPDWON_ARROW_WIDTH, base.Width.Type);
            }
        }

        /// <summary>
        /// 獲取或設置一個值,該值指示當用戶更改列表中的選定內容時是否自動產生向服務器的回發
        /// </summary>
        [Description("獲取或設置一個值,該值指示當用戶更改列表中的選定內容時是否自動產生向服務器的回發"),
        Browsable(true),
        Category("Behavior"),
        DefaultValue(false)]
        public override bool AutoPostBack
        {
            get
            {
                return base.AutoPostBack;
            }
            set
            {
                base.AutoPostBack = value;
                txt.AutoPostBack = value;
            }
        }

        #endregion

        #region  腳本

        /// <summary>
        /// 默認樣式
        /// </summary>
        private const string CONTROL_CSS = @"
<style type='text/css'>
.edd_input{
    border: none;
    left: 2px;
    line-height: 17px;
    position: absolute;
    top: 1px;
    z-index: 1;
}
.edd_droplist
{
    border:solid 1px #ccc;
    padding-left:10px;
    background-color:white;
    line-height: 17px;
    position: absolute;
    z-index: 1;
    top:5px;
    list-style:none;
    display:none;
}

.edd_droplist li
{
   cursor:default;
}

.edd_droplist li.active
{
    font-weight:bold;
    text-decoration:underline
}

.edd_droplist li.selected
{
    font-weight:bold;
}

.edd_droplist li:hover
{
    text-decoration:underline;
}
</style>
";

        /// <summary>
        /// The basic javascript that needs to be emitted once per page that contains any ComboBox
        /// controls.
        /// (This would be nice if it were not in the compiled code so that it could be edited/
        /// updated with out a recompile. However, in the interest of making this control self-
        /// contained and simple for the demo, it is just included here. It could easily be moved
        /// to a seperate .js file that is referenced here.)
        /// </summary>
        private const string CONTROL_JS = @"
   <script type='text/javascript'>
   <!--
        function edd_droplist_item_click(o)
        {
            var list = o.parentNode,cid = list.id.replace('_droplist','');
            if(list.hasAttribute('activeIndex'))
            {
                var activeIndex = parseInt(list.getAttribute('activeIndex'));
                if(activeIndex > -1)
                {
                    list.childNodes[activeIndex].removeAttribute('class');
                }
            }
            var tag = parseInt(o.getAttribute('tag')),idx = parseInt(o.getAttribute('index'));
            document.getElementById(cid).options[tag].selected = true;
            o.className = 'selected';
            document.getElementById(cid+'_input').value = o.innerHTML;
            list.setAttribute('activeIndex',idx);
            list.style.display = 'none';
            
        }
        function edd_input_keydown(o,e)
        {
            e = e || window.event;
            var k = e.keyCode || e.which;
            var cid = o.id.replace('_input',''),
                list = document.getElementById(cid+'_droplist');
            var activeIndex = list.hasAttribute('activeIndex') ? parseInt(list.getAttribute('activeIndex')):-1;
            var total = list.hasAttribute('childCount') ? parseInt(list.getAttribute('childCount')):0;
            if(k == 38)
            {
                if(total > 0)
                {
                    if(activeIndex == -1)
                    {
                        activeIndex = total - 1;
                    }
                    else if(activeIndex == 0)
                    {
                        list.childNodes[activeIndex].removeAttribute('class');
                        activeIndex = total - 1;
                    }
                    else
                    {
                        list.childNodes[activeIndex].removeAttribute('class');
                        activeIndex--;
                    }
                    list.childNodes[activeIndex].className = 'active';
                    list.setAttribute('activeIndex',activeIndex);

                    if(list.style.display == 'none')
                    {
                        edd_show_droplist(list);
                    }
                    
                    edd_autoscroll(list,list.childNodes[activeIndex]);
                    
                }
                o.setAttribute('cancelFilter',1);
                return false;
            }
            else if(k == 40)
            {
                if(total > 0)
                {
                    if(activeIndex == -1)
                    {
                        activeIndex = 0;
                    }
                    else if(activeIndex == total - 1)
                    {
                        list.childNodes[activeIndex].removeAttribute('class');
                        activeIndex = 0;
                    }
                    else
                    {
                        list.childNodes[activeIndex].removeAttribute('class');
                        activeIndex++;
                    }
                    list.childNodes[activeIndex].className = 'active';
                    list.setAttribute('activeIndex',activeIndex);

                    if(list.style.display == 'none')
                    {
                        edd_show_droplist(list);
                    }
                    
                    edd_autoscroll(list,list.childNodes[activeIndex]);
                    
                    
                }
                o.setAttribute('cancelFilter',1);
                return false;
            }
            else if(k == 13)
            {
                if(list.childNodes[activeIndex])
                {
                    o.value = list.childNodes[activeIndex].innerHTML;
                    document.getElementById(cid).options[parseInt(list.childNodes[activeIndex].getAttribute('tag'))].selected = true;
                }
                list.style.display = 'none';
                o.setAttribute('cancelFilter',1);
                return false;
            }
            return true;
        }

        function edd_autoscroll(l,i)
        {
            if(i.offsetTop < l.scrollTop 
            || l.scrollTop + l.offsetHeight < i.offsetTop)
            {
                l.scrollTop = i.offsetTop;
            }
        }
        function edd_input_keyup(o,e)
        {
            if(o.hasAttribute('cancelFilter'))
            {
                o.removeAttribute('cancelFilter');
                return false;
            }
            var cid = o.id.replace('_input',''),
                list = document.getElementById(cid+'_droplist');
            if(list)
            {
                list.style.display = 'none';
                list.innerHTML = '';
                list.removeAttribute('activeIndex');
                list.removeAttribute('childCount');
                var str = o.value;
                if(str && str.length > 0)
                {
                    var ops = [];
                    var tstr;
                    var dropdown = document.getElementById(cid);
                    if(dropdown && dropdown.options && dropdown.options.length > 0)
                    {
                        for(var i =0;i<dropdown.options.length;i++)
                        {
                            if(dropdown.options[i].text.indexOf(str) > -1)
                            {
                               tstr = dropdown.options[i].text;
                               ops.push('<li tag=\''+i+'\' index=\''+ops.length+'\' onclick=\'edd_droplist_item_click(this);\'>'+tstr+'</li>');
                            }
                        }
                        
                        if(ops.length > 0)
                        {
                            list.innerHTML = ops.join('');
                            edd_show_droplist(list);
                            
                            list.setAttribute('activeIndex',-1);
                            list.setAttribute('childCount',ops.length);
                        }
                    }
                }
            }
        }

        function edd_dropdownlist_change(o,e)
        {
            var cid = o.id,txt = document.getElementById(cid+'_input');
            txt.value = o.options[o.selectedIndex].text;
            var list = document.getElementById(cid+'_droplist');
            list.innerHTML = '';
            list.removeAttribute('activeIndex');
            list.removeAttribute('childCount');
        }

        function edd_droplist_mouseenter(o)
        {
            o.setAttribute('isActive','1');
        }

        function edd_droplist_mouseleave(o)
        {
            o.setAttribute('isActive','0');
            window.setTimeout(edd_close_droplist(o.id.replace('_droplist','')),500);
        }

        function edd_input_focus(o)
        {
            if(o.hasAttribute('timerID'))
            {
                window.clearTimeout(parseInt(o.getAttribute('timerID')));
                o.removeAttribute('timerID');
            }
        }

        function edd_input_blur(o)
        {
            if(o.hasAttribute('timerID'))
            {
                window.clearTimeout(parseInt(o.getAttribute('timerID')));
                o.removeAttribute('timerID');
            }
            var cid = o.id.replace('_input',''),list = document.getElementById(cid+'_droplist');
            if(list.style.display != 'none' 
             && (!list.hasAttribute('isActive') 
             || list.getAttribute('isActive') == '0'))
            {
                o.setAttribute('timerID',window.setTimeout(edd_close_droplist(cid),500));
            }
        }

        function edd_show_droplist(o)
        {
           o.style.overflowY = 'auto';
           o.style.height = 'auto';
           o.style.display = 'inline';
           if(o.offsetHeight > 200)
           {
               o.style.overflowY = 'scroll';
               o.style.height = '200px';
           }
        }

        function edd_close_droplist(cid)
        {
            return function(){
                var droplist = document.getElementById(cid+'_droplist'),
                    txt = document.getElementById(cid+'_input');
                txt.removeAttribute('timerID');

                if(document.activeElement != txt 
                && document.activeElement != droplist)
                {
                    droplist.style.display = 'none';
                }
                
            };
        }
   //-->
   </script>
   ";
        #endregion
    }

    public class EditableDropDownDesigner : ControlDesigner, IDataSourceProvider
    {
        public EditableDropDownDesigner()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        #region ControlDesigner
        public override string GetDesignTimeHtml()
        {
            EditableDropDown component = (EditableDropDown)base.Component;
            StringBuilder html = new StringBuilder("<select style='width:" + component.Width + "'></select>");
            return html.ToString();
        }
        #endregion

        #region Proxies of the properties that are involved in DataBinding
        /// <summary>
        /// This is a proxy for the DataMember field that is required to attach the DataMemberConverter to.
        /// </summary>
        public string DataMember
        {
            get
            {
                return ((EditableDropDown)base.Component).DataMember;
            }
            set
            {
                ((EditableDropDown)base.Component).DataMember = value;
            }
        }

        /// <summary>
        /// This is a proxy for the DataTextField field that is required to attach the DataFieldConverter to.
        /// </summary>
        public string DataTextField
        {
            get
            {
                return ((EditableDropDown)base.Component).DataTextField;
            }
            set
            {
                ((EditableDropDown)base.Component).DataTextField = value;
            }
        }

        /// <summary>
        /// This is a proxy for the DataValueField field that is required to attach the DataFieldConverter to.
        /// </summary>
        public string DataValueField
        {
            get
            {

                return ((EditableDropDown)base.Component).DataValueField;
            }
            set
            {
                ((EditableDropDown)base.Component).DataValueField = value;
            }
        }

        /// <summary>
        /// This is a proxy for the DataSource field that is required to attach the DataSourceConverter to.
        /// This is especially required as it allows us to represent the DataSource property as a string
        /// rather then as an object.
        /// </summary>
        public string DataSource
        {
            get
            {
                DataBinding binding = DataBindings["DataSource"];
                if (binding != null)
                    return binding.Expression;
                return string.Empty;
            }
            set
            {
                if ((value == null) || (value.Length == 0))
                    base.DataBindings.Remove("DataSource");
                else
                {
                    DataBinding binding = DataBindings["DataSource"];
                    if (binding == null)
                        binding = new DataBinding("DataSource",
                            typeof(IEnumerable), value);
                    else
                        binding.Expression = value;
                    DataBindings.Add(binding);
                }

                OnBindingsCollectionChanged("DataSource");
            }
        }
        #endregion

        #region Overrides
        /// <summary>
        /// Set to false so that the control can't be resized on the form.
        /// </summary>
        public override bool AllowResize
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Used to modify the Attributes of the 'Data' related fields such that
        /// the correct TypeConverters are added to the Attributes. For some reason
        /// adding the attributes directly doesn't work.
        /// </summary>
        /// <param name="properties">The dictionary</param>
        protected override void PreFilterProperties(IDictionary properties)
        {
            base.PreFilterProperties(properties);
            PropertyDescriptor prop = (PropertyDescriptor)properties["DataSource"];
            if (prop != null)
            {
                System.ComponentModel.AttributeCollection runtimeAttributes = prop.Attributes;
                // make a copy of the original attributes but make room for one extra attribute ie the TypeConverter attribute
                Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
                runtimeAttributes.CopyTo(attrs, 0);
                attrs[runtimeAttributes.Count] = new TypeConverterAttribute(typeof(DataSourceConverter));
                prop = TypeDescriptor.CreateProperty(this.GetType(), "DataSource", typeof(string), attrs);
                properties["DataSource"] = prop;
            }

            prop = (PropertyDescriptor)properties["DataMember"];
            if (prop != null)
            {
                System.ComponentModel.AttributeCollection runtimeAttributes = prop.Attributes;
                Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
                // make a copy of the original attributes but make room for one extra attribute ie the TypeConverter attribute
                runtimeAttributes.CopyTo(attrs, 0);
                attrs[runtimeAttributes.Count] = new TypeConverterAttribute(typeof(DataMemberConverter));
                prop = TypeDescriptor.CreateProperty(this.GetType(), "DataMember", typeof(string), attrs);
                properties["DataMember"] = prop;
            }

            prop = (PropertyDescriptor)properties["DataValueField"];
            if (prop != null)
            {
                System.ComponentModel.AttributeCollection runtimeAttributes = prop.Attributes;
                Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
                // make a copy of the original attributes but make room for one extra attribute ie the TypeConverter attribute
                runtimeAttributes.CopyTo(attrs, 0);
                attrs[runtimeAttributes.Count] = new TypeConverterAttribute(typeof(DataFieldConverter));
                prop = TypeDescriptor.CreateProperty(this.GetType(), "DataValueField", typeof(string), attrs);
                properties["DataValueField"] = prop;
            }

            prop = (PropertyDescriptor)properties["DataTextField"];
            if (prop != null)
            {
                System.ComponentModel.AttributeCollection runtimeAttributes = prop.Attributes;
                Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
                // make a copy of the original attributes but make room for one extra attribute ie the TypeConverter attribute
                runtimeAttributes.CopyTo(attrs, 0);
                attrs[runtimeAttributes.Count] = new TypeConverterAttribute(typeof(DataFieldConverter));
                prop = TypeDescriptor.CreateProperty(this.GetType(), "DataTextField", typeof(string), attrs);
                properties["DataTextField"] = prop;
            }
        }
        #endregion

        #region IDataSourceProvider methods
        /// <summary>
        /// Used by the DataFieldConverter to resolve the DataSource and DataMember combination
        /// so that it can populate a dropdown with a list of available fields.
        /// </summary>
        IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource()
        {
            DataBinding binding;
            binding = this.DataBindings["DataSource"];
            if (binding != null)
                return DesignTimeData.GetSelectedDataSource(this.Component, binding.Expression, this.DataMember);
            return null;
        }

        /// <summary>
        /// Used by the DataMemberConverter to resolve the DataSource which it can then use
        /// to populate a drop down box containing a list of available tables.
        /// </summary>
        /// <returns>The object that is our DataSource</returns>
        object IDataSourceProvider.GetSelectedDataSource()
        {
            DataBinding binding;

            binding = this.DataBindings["DataSource"];
            if (binding != null)
                return DesignTimeData.GetSelectedDataSource(this.Component, binding.Expression);
            return null;
        }
        #endregion
    }
}

參考AtomNet ComboBox,所以命名空間沒有改;

使用方法:
<%@ Register Assembly="CommonControls" Namespace="AtomNet.Web.UI.WebControls" TagPrefix="AtomNet" %>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章