仿google的下拉提示框(C#)[转cnblogs]

时间:2015/12/2 14:56:00来源:互联网 作者:flyso 点击: 990 次

最近根据需求做一个仿google的下拉提示控件,7月7号把写好的代码贴上了,不过会有输入中文(或者亚洲文字)的错误,现在做了相关修改,解决了不少BUG,还存在问题,希望朋友们提出,谢谢。
      给的需求是写一个控件,也就是ascx用户自定义控件。控件有两个属性,一个是数据源datatable,一个是要筛选的字段名。也就是说当你文本框中输入值后,就在数据源中查找给出的要筛选的字段,得到的信息给出下拉提示。
      考虑到一个页面可能要写多个这样的控件,所以感觉不好用document.getElementById来找到文本框对象,也没有用层隐藏的办法,取代是层的添加和删除(在这里,我的思路是这样的,如果有朋友有更好的方法,希望提出共享,学习学习,谢谢)。
      后来还因为提示数据可能过多,所以加了滚动条,同时响应解决了上下键控制滚动条的问题(真是搞人啦,那个offset和scroll看了阿一的一篇后晓得怎么算的,http://www.cnblogs.com/JustinYoung/archive/2006/10/11/offsetwidth-clientwidth.html)。

      还有自动完成的问题算个小插曲吧,其实不是大问题,丁学,玉开等都给出了答案,其实在他们给答案的时候我也找到答案了,还是谢谢他们了,大家都为园子做了这么多。不多罗嗦啦,把代码贴出来。

下面是代码:
先是建了一个用户控件Suggest.ascx

前台代码:

下拉提示控件 前台代码


程序代码 程序代码
  <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Suggest.ascx.cs" Inherits="Suggest" %>
  
  <style type="text/css">
  
  .pnlSuggest
  {
     border: #000000 1px solid;
     background-color: #FFFFFF;
     z-index: 9527;
     position: absolute;
     overflow-y: auto;
     overflow-x: hidden;
     text-overflow: clip;
  }
  
.pnlSuggest table
{
     width: 100%;
}

.pnlSuggest tr
{
     width: 100%;
}

.trmouseover
{
     width: 100%;
     background-color: #397CC3;
}

.trmouseover td
{
     text-align: left;
     overflow: hidden;
     text-overflow: clip;
     background-color: #397CC3;
}

.trmouseout
{
     width: 100%;
     background-color: #FFFFFF;
}

.trmouseout td
{
     text-align: left;
     overflow: hidden;
     text-overflow: clip;
     background-color: #FFFFFF;
}

.txtValues
{
     display: none;
}

.dataSource
{
     display: none;
}

.hiddentd
{
     display: none;
}

.hiddenValues
{
     display: none;
}

</style>

<script language="javascript" type="text/javascript">

//为string对象添加一个清除前后空格的属性
String.prototype.trim = function()
{
     return this.replace(new RegExp("(^[\\s]*)|([\\s]*$)", "g"), "");
};

//显示下拉信息
function ShowSuggest(objInputText)
{
     objInputText.onkeyup = ControlSuggest;
    
     objInputText.onblur = RemoveSuggest;
    
     var oldValue = objInputText.parentElement.getElementsByTagName("h6")[0];
    
     //对提示框的控制
     function ControlSuggest()
     {
         var ie = (document.all)?true:false;
         if(ie)
         {
             var keycode = event.keyCode;
            var txtvalues = objInputText.value.trim().split(";");
            
            if( !CheckSuggest() && txtvalues[txtvalues.length-1] != oldValue.innerText.trim())
            {
                CreateSuggest();
                return ;
            }
            
            if(keycode == 32 && txtvalues[txtvalues.length-1] != oldValue.innerText.trim())
            {
                CreateSuggest();
                return ;
            }
            
            //按向下创建提示
            if(!CheckSuggest() && txtvalues[txtvalues.length-1].length !=0 && keycode == 40)
            {//文本框有内容,提示不存在,向下
                CreateSuggest();
                return;
            }
            
           //当删除的时候,参量要初始化
            if(keycode == 8 && txtvalues[txtvalues.length-1].length == 0)
            {
                DeleteSuggest();
                oldValue.innerText = "";
                return ;
            }
            
            if(CheckSuggest())
            {
                var inputIndex = document.getElementById("inputIndex");
                var selectIndex = Number(inputIndex.value);
                
                //排除上下控制的值操作外,其他任何值改变都要去创建提示框
                if( selectIndex < 0 && txtvalues[txtvalues.length-1] != oldValue.innerText)
                {
                    CreateSuggest();
                    return ;
                }
                
                if(keycode == 40)
                {//向下
                    ChangeSelection(false);
                    return ;
                }
                
               if(keycode == 38)
                {//向上
                    ChangeSelection(true);
                    return ;
                }
                
                if(keycode == 46 || keycode == 27)
                {//del
                    DeleteSuggest();
                    oldValue.innerText = "";
                    return ;
                }
                
                var panelSuggest = document.getElementById("divSuggestPanel");
                var tb = panelSuggest.getElementsByTagName("table")[0];

                if(keycode == 13)
                {//回车
                    if(selectIndex > -1 && txtvalues[txtvalues.length-1] != oldValue.innerText)
                    {
                        CreateSuggest();
                        return ;
                    }
                    RemoveSuggest();
                    return ;
                }
                
            }
            
            if(txtvalues[txtvalues.length-1] != oldValue.innerText)
            {//当上面的条件都筛选后,只要值发生改变就创建下拉提示
                CreateSuggest();
                return ;
            }
        }
    }
    
    //删除提示前对文本做相关操作
    function RemoveSuggest()
    {
        if(CheckSuggest())
        {
            var panelSuggest = document.getElementById("divSuggestPanel");
            var inputIndex = document.getElementById("inputIndex");
            
            var txtvalues = objInputText.value.trim().split(";");
            
            if( CheckActiveElement(panelSuggest) || event.keyCode == 13)
            {
                //做个判断,判断当前活动对象 是不是TD,是的话就执行下面的操作,不是的话就不做操作,或者把文本框作为当前活动
                if(CheckActiveElement(panelSuggest) && document.activeElement.tagName != "TD")
                {
                    objInputText.focus();
                    return ;
                }
                
                //得到选定的值
                var selectIndex = Number(inputIndex.value);
                if(selectIndex >= 0)
                {
                    var tb = panelSuggest.getElementsByTagName("table")[0];
                    txtvalues[txtvalues.length-1] = tb.rows[selectIndex].cells[0].innerText;
                    objInputText.value = GetValues(txtvalues);
                }
            }
            
            document.body.removeChild(inputIndex);
            document.body.removeChild(panelSuggest);
            oldValue.innerText = "";
        }
        else
        {
            return ;
        }
    }
    
    //删除提示的方法,不对文本做任何操作
    function DeleteSuggest()
    {
        if(CheckSuggest())
        {
            var panelSuggest = document.getElementById("divSuggestPanel");
            var inputIndex = document.getElementById("inputIndex");
            document.body.removeChild(inputIndex);
            document.body.removeChild(panelSuggest);
        }
    }
    
    //加载提示框
    function CreateSuggest()
    {
        var txtvalues = objInputText.value.trim().split(";");
        
        //提示框存在,而且文本框值与上次的输入不同时,才进行下面的加载工作
        if(CheckSuggest())
        {
            if( oldValue.innerText.trim() == txtvalues[txtvalues.length-1].trim())
            {
                return ;
            }
            else
            {
                DeleteSuggest();
            }
        }
        
        if(CheckSuggest() && txtvalues[txtvalues.length-1].trim().length ==0)
        {//提示框存在,但是文本框没有内容,这时删除提示框
            DeleteSuggest();
            oldValue.innerText = "";
            return ;
        }
        
        //如果输入为空格,就退出
        if(txtvalues[txtvalues.length-1].trim().length == 0)
        {
            return ;
        }
        
        //从数据源中取数据
        var suggestList = GetSuggestList();
        
        if(suggestList == null||suggestList.length < 1)
        {//对传入的数组进行判断,为空或者列表为0就退出
            DeleteSuggest();                                  //开始的输入有提示,后面的输入可能没有提示,所以数据源为空时要尝试删除提示
            oldValue.innerText = "";
            return ;
        }
        
        oldValue.innerText = txtvalues[txtvalues.length-1];              //以上条件都符合,根据数据源来创建数据
        
        var inputIndex = document.createElement("input");     //用隐藏控件来做索引的保存
        inputIndex.type = "hidden";
        inputIndex.id = "inputIndex";
        inputIndex.value = -1;
        
        var suggest = "";                                     //根据数据源来写div提示信息
        suggest += "<table>";
        for(var nIndex = 0; nIndex < suggestList.length; nIndex++)
        {
            suggest += "<tr onmouseover=\" for(var n=0;n<this.parentElement.rows.length;n++){this.parentElement.rows[n].className='trmouseout';};this.className='trmouseover';var inputIndex = document.getElementById('inputIndex');inputIndex.value = this.rowIndex; \" onmouseout=\"this.className='trmouseout';\"  >";
            suggest += suggestList[nIndex];
            suggest += "</tr>";
        }
        suggest += "</table>";
        
        var panelSuggest = document.createElement("div");                //创建装提示框的容器div
        
        panelSuggest.id = "divSuggestPanel";
        panelSuggest.className = "pnlSuggest";                           //设置对象的类
        panelSuggest.style.width = objInputText.clientWidth + "px";      //设置对象的宽度,与文本框宽度相同
        panelSuggest.style.top = (GetPosition()[0] + objInputText.offsetHeight + 1) + "px";
        panelSuggest.style.left = GetPosition()[1] + "px";
        panelSuggest.innerHTML = suggest;
        
        document.body.appendChild(panelSuggest);                         //把提示框和索引控件添加进来        
        document.body.appendChild(inputIndex);
        
        //判断显示条数的多少,多于10条就用滚动条操作
        if(suggestList.length > 10)
        {
            var h = panelSuggest.getElementsByTagName("tr")[1].offsetHeight;
            panelSuggest.style.height = (h * 10) + "px";
            panelSuggest.style.width = (objInputText.clientWidth + 20) + "px";
        }
        
    }
    
    //更换选项
    function ChangeSelection(isup)
    {

        if(CheckSuggest())
        {
            var txtvalues = objInputText.value.trim().split(";");
            
            var inputIndex = document.getElementById("inputIndex");                 //得到索引的值
            var selectIndex = Number(inputIndex.value);
            
            var panelSuggest = document.getElementById("divSuggestPanel");          //得到提示框
            var tb = panelSuggest.getElementsByTagName("table")[0];
            var maxIndex = tb.rows.length - 1;                                      //提示信息的最大索引
            
            if(isup)
            {//向上
                if(selectIndex >= 0)                                                //索引不能为负
                {
                    tb.rows[selectIndex].className = "trmouseout";
                    selectIndex--;
                    if(selectIndex >= 0)
                    {
                        tb.rows[selectIndex].className = "trmouseover";
                    }
                }
            }
            else
            {
                if(selectIndex < maxIndex)                                          //大于等于最大索引就不做任何操作
                {
                    if(selectIndex >= 0)
                    {
                        tb.rows[selectIndex].className = "trmouseout";
                    }
                    selectIndex++;
                    tb.rows[selectIndex].className = "trmouseover";
                }
            }
            
            inputIndex.value = selectIndex;
            //控制滚动条的上下
            if(selectIndex >= 0)
            {
                if(tb.rows[selectIndex].offsetTop < panelSuggest.scrollTop)
                {
                    panelSuggest.scrollTop = tb.rows[selectIndex].offsetTop;
                }
                if(tb.rows[selectIndex].offsetTop + tb.rows[selectIndex].offsetHeight > panelSuggest.scrollTop + panelSuggest.offsetHeight)
                {
                    panelSuggest.scrollTop = tb.rows[selectIndex].offsetTop + tb.rows[selectIndex].offsetHeight - panelSuggest.offsetHeight;
                }
            }
            
        }
        
    }
    
    //判断活动对象是否为obj对象的从属对象
    function CheckActiveElement(obj)
    {
        var isAe = false;
        var objtemp = document.activeElement;
        while(objtemp != null)
        {
            if(objtemp == obj)
            {
                isAe = true;
                break;
            }
            objtemp = objtemp.parentElement;
        }
        return isAe;
    }
    
    //检查提示框是否存在
    function CheckSuggest()
    {
        var panelSuggest = document.getElementById("divSuggestPanel");
        if(panelSuggest == null)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    
    //获取文本框的位置
    function GetPosition()
    {
        var top = 0,left = 0;
        var obj = objInputText;
        do
        {
            top += obj.offsetTop;         //距离顶部
            left += obj.offsetLeft;       //距离左边
        }
        while (obj = obj.offsetParent);
        
        var arr = new Array();
        arr[0] = top;
        arr[1] = left;
        return arr;
    }
    
    //得到提示数据
    function GetSuggestList()
    {
        var txtvalues = objInputText.value.trim().split(";");
        var txtfield = txtvalues[txtvalues.length-1];
        var hiddenvaluefield = objInputText.parentElement.getElementsByTagName("h2")[0].innerText;
        var showtextfield = objInputText.parentElement.getElementsByTagName("h3")[0].innerText;
        var procedurename = objInputText.parentElement.getElementsByTagName("h4")[0].innerText;
        var condition = objInputText.parentElement.getElementsByTagName("h5")[0].innerText;
        var suggestlist = Suggest.GetSuggestData(txtfield,showtextfield,procedurename,condition).value;
        return suggestlist;
    }
    
    //得到文本框的显示值
    function GetValues(values)
    {
        var txtvalue="";
        for(var n=0;n<values.length;n++)
        {
            if(values[n].trim().length==0)
            {
                continue;
            }
            txtvalue+=values[n]+";";
        }
        return txtvalue;
    }
    
}

</script>

<div>
    <input type="text" runat="server" id="txtInput" name="txtInput" onkeydown="ShowSuggest(this);" style="width: 320px" autocomplete="off" />
    <h2 id="hneedfield" runat="server" class="hiddenValues"></h2>
    <h3 id="hshowfield" runat="server" class="hiddenValues"></h3>
    <h4 id="hprocedurename" runat="server" class="hiddenValues"></h4>
    <h5 id="hhashvalue" runat="server" class="hiddenValues"></h5>
    <h6 class="hiddenValues" style="display: none;"></h6>
</div>




后台代码:
下拉提示控件 后台代码


程序代码 程序代码
  
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.Data.SqlClient;
using IIP.Data.SQLHelper;

/**//// <summary>
/// 转贴地址 http://www.cnblogs.com/huowujiyx/archive/2008/07/07/1237355.html
/// 使用说明:调用该控件,要设置控件的相关属性:
///           显示提示的筛选字段名 TextField
///           查询数据的存储过程名 Procedurename
///           存储过程的各项参数   ProcedureParams(Hashtable类型,key值放参数名,value值为参数值)
///           还可以通过下面的方法,设置域的值:
///           List<string> showFields    要下拉提示的相关信息  AddShowFields(string field)方法添加
///           设置好上面的信息后就可以使用了,要得到文本框信息,通过属性Text得到
///           要取得与文本信息相匹配的隐藏信息,也就是与hiddenFields对应的信息,通过属性Values得到
/// </summary>
public partial class Suggest : System.Web.UI.UserControl
{
    private string textField;

    /**//// <summary>
    /// 设置要显示提示的筛选字段名
    /// </summary>
    public string TextField
     {
         set { textField = value; }
     }

     private string valueField;

     /**//// <summary>
     /// 设置要得到的唯一性标识的字段名
     /// </summary>
     public string ValueField
     {
         set { valueField = value; }
     }

     private string procedurename;

     /**//// <summary>
     /// 设置查询数据的存储过程名
     /// </summary>
     public string Procedurename
     {
         set { procedurename = value; }
     }

     private Hashtable procedureParams = new Hashtable();

     /**//// <summary>
     /// 设置存储过程的各项参数
     /// </summary>
     public Hashtable ProcedureParams
     {
         set { procedureParams = value; }
     }

     private List<string> showFields = new List<string>();

     /**//// <summary>
     /// 添加显示内容的字段
     /// </summary>
     /// <param name="field">字段名</param>
     public void AddShowFields(string field)
     {
         showFields.Add(field);
     }

     /**//// <summary>
     /// 得到文本框的值
     /// </summary>
     public string Text
     {
         get
         {
             string[] texts = txtInput.Value.Split(new string[] { ";" }, StringSplitOptions.None);
             DataTable dt = GetDataSourse(this.hprocedurename.InnerText, this.hhashvalue.InnerText);
             string txtvalue = "";
             foreach (string text in texts)
             {
                 DataRow[] drs = dt.Select(this.hshowfield.InnerText.Split(new string[] { "$h,w$" }, StringSplitOptions.RemoveEmptyEntries)[0] + " = '" + text + "'");
                 if (drs.Length > 0)
                 {
                     txtvalue += text + ",";
                }
            }
            return txtvalue;
        }
    }

    /**//// <summary>
    /// 得到与文本框值匹配的信息
    /// </summary>
    public string Values
    {
        get
        {
            string[] texts = txtInput.Value.Split(new string[] { ";" }, StringSplitOptions.None);
            DataTable dt = GetDataSourse(this.hprocedurename.InnerText, this.hhashvalue.InnerText);
            string value = "";
            foreach (string text in texts)
            {
                DataRow[] drs = dt.Select(this.hshowfield.InnerText.Split(new string[] { "$h,w$" }, StringSplitOptions.RemoveEmptyEntries)[0] + " = '" + text + "'");
                if (drs.Length > 0)
                {
                    value += drs[0][this.hneedfield.InnerText.Trim()].ToString() + ",";
                }
            }
            return value;
        }
    }

    /**//// <summary>
    /// 得到数据源
    /// </summary>
    /// <param name="procedurename">存储过程名</param>
    /// <param name="condition">存储过程参数</param>
    /// <returns>查询数据源</returns>
    private DataTable GetDataSourse(string procedurename, string condition)
    {
        //从数据源得到数据
        string[] keyandvalue = condition.Split(new string[] { "$h,w$" }, StringSplitOptions.RemoveEmptyEntries);

        SqlParameter[] commandparameters;
        if (keyandvalue.Length == 0)
        {
            commandparameters = null;
        }
        else
        {
            commandparameters = new SqlParameter[keyandvalue.Length];
            for (int i = 0; i < commandparameters.Length; i++)
            {
                string[] tempvalue = keyandvalue[i].Split(new string[] { "$h,57,w$" }, StringSplitOptions.None);
                commandparameters[i] = new SqlParameter(tempvalue[0], SqlDbType.VarChar, 500);
                commandparameters[i].Value = tempvalue[1];
            }
        }

        DataSet ds = SqlHelper.ExecuteDataset(Comm.connectionString, CommandType.StoredProcedure, procedurename, commandparameters);
        if (ds == null || ds.Tables[0] == null)
        {
            return null;
        }
        else
        {
            return ds.Tables[0];
        }
    }

    /**//// <summary>
    /// 根据输入内容,得到数据
    /// </summary>
    /// <param name="txtfield">显示文本</param>
    /// <param name="showtextfield">下拉提示的相关字段</param>
    /// <param name="procedurename">存储过程名</param>
    /// <param name="condition">存储过程参数</param>
    [Ajax.AjaxMethod]
    public string[] GetSuggestData(string txtfield, string showtextfield, string procedurename, string condition)
    {
        
        //数据的要显示字段和要相关得到数值的字段
        string[] shows = showtextfield.Split(new string[] { "$h,w$" }, StringSplitOptions.RemoveEmptyEntries);

        //数据的筛选
        DataTable dt = GetDataSourse(procedurename, condition);
        if (dt == null)
        {
            return null;
        }
        DataRow[] drs = dt.Select(shows[0] + " like '%" + txtfield + "%'");
        if (drs.Length == 0)
        {
           return null;
        }

        //将数据做为显示流
        List<string> fields = new List<string>();

        for (int j = 0; j < shows.Length; j++)
        {
            fields.Add(shows[j]);
        }

        string[] tbs = new string[drs.Length];
        int count = 0;
        foreach (DataRow dr in drs)
        {
            foreach (string item in fields)
            {
                tbs[count] += "<td>" + dr[item].ToString() + "</td>";
            }
            count++;
        }
        return tbs;
    }

    /**//// <summary>
    /// 页面的相关信息的保存
    /// </summary>
    private void BindValue()
    {
        //页面中加上与文本输入相关的有下拉提示的字段
        this.hshowfield.InnerText = this.textField;
        int i = 0;
        foreach (string item in showFields)
        {
            this.hshowfield.InnerText += "$h,w$";
            this.hshowfield.InnerText += item;
            i++;
        }

        //页面中加上查询数据源的存储过程
        this.hprocedurename.InnerText = this.procedurename;

        //页面中加上与存储过程匹配的参数
        this.hhashvalue.InnerText = "";
        if (procedureParams != null && procedureParams.Count > 0)
        {
            i = 0;
            foreach (DictionaryEntry temp in procedureParams)
            {
                if (i != 0)
                {
                    this.hhashvalue.InnerText += "$h,w$";
                }
                this.hhashvalue.InnerText += temp.Key.ToString() + "$h,57,w$" + temp.Value.ToString();
                i++;
            }
        }

        //唯一标识的字段名
        this.hneedfield.InnerText = this.valueField;
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        Ajax.Utility.RegisterTypeForAjax(typeof(SearchText));
        if (!IsPostBack)
        {
            BindValue();
        }
    }
}



然后在aspx页面就可以拖这个控件来用,不过在其page_load方法中,要对控件的两个控件进行设置,不然是不会有提示框弹出的,因为数据源都没有。呵呵         


Copyright © 2005 - 2016 flyso.cn. 飞搜 版权所有 鄂ICP备11002783号-3