老赵点滴


  先做人,再做技术人员,最后做程序员。
  我的理想:“让外国人看中国人写的技术书籍和文章”。Try as I might
posts - 290, comments - 10850, trackbacks - 158, articles - 6
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

  几个月前,我扩展了Atlas CTP时期的的AutoCompleteBehavior,到了ASP.NET AJAX正式版之后这个扩展自然就不能用了。其实这段时间内有不少朋友问我该如何做到像Google Suggest那样带丰富样式的自动补全功能,但是由于各种原因(比如时间不够,还有现在的AutoCompleteBehavior复杂的许多,或者仅仅是“懒”……),我没有深入地研究它,更别提对它的扩展了。

  最近手头正好有些时间,也就逼迫自己耐着性子读了读AutoCompleteBehavior的代码。不得不承认Ajax Control Tookit本身也在不停的发展之中,现在无论服务器端还是客户端都提供了非常强大的基础组件,解决了大量常见问题,使得开发工作变得简单了许多。与此形成鲜明对比的是文档的缺乏,建议对于深入ASP.NET AJAX感兴趣的朋友读一下Ajax Control Toolkit的代码,这对于您理解ASP.NET的控件模型与客户端组件的开发大有碑益。

  我扩展了现有的AutoCompleteBehavior,使AutoComplete的功能能够轻松支持丰富的样式。我在这里暂时不对实现方法进行分析,不过大家可以下载代码并进行一些测试,如果有任何问题还可以反馈给我。另外,我认为目前StyledAutoCompleteBehavior的设计还不够灵活,如果有什么需要但是无法实现的功能请您告诉我,也欢迎大家给我一些功能设计方面的建议。自动补全是一个很常用的功能,可惜Ajax Control Toolkit中提供的功能实在不够用。

  在我的扩展中,StyledAutoCompleteExtender继承了AutoCompleteExtender,StyledAutoCompleteBehavior继承了AutoCompleteBehavior,在编写时我也尽可能的保持了原有的功能不变,因此大家在使用时完全可以将AutoCompleteExtender的标签直接改为StyledAutoComplteExtender而不会影响到任何功能。与AutoCompleteExtender相比,StyledAutoCompleteExtender多了一个属性ItemTemplate用于指定自动补全中每一项的模板。例如我们要实现Google Suggest那样的功能,则需要这样使用StyledAutoCompleteExtender:

<jeffz:StyledAutoCompleteExtender
    runat="server" 
    BehaviorID="AutoCompleteEx"
    ID="autoComplete1" 
    TargetControlID="myTextBox"
    ServicePath="AutoComplete.asmx" 
    ServiceMethod="GetSearchCompletionList"
    MinimumPrefixLength="2" 
    CompletionInterval="1000"
    EnableCaching="true"
    CompletionSetCount="10"
    CompletionListCssClass="completionListElement" 
    CompletionListItemCssClass="listItem" 
    CompletionListHighlightedItemCssClass="highlightedListItem">
    <ItemTemplate>
        <span style="float:left;" class="keywords">{0}</span>
        <span style="float:right;" class="result">{1}&nbsp;results</span>
        <div style="clear:both;"></div>
    </ItemTemplate>
</jeffz:StyledAutoCompleteExtender>

  请注意ItemTemplate里的内容,它将作为自动补全每一项的HTML。而诸如{0}、{1}等则作为占位符,最终在显示时将会使用服务器端返回的内容进行替换。为了配合这个功能,服务器端的Web Service方法返回值也有所变化:

[WebMethod]
public IList<object[]> GetSearchCompletionList(string prefixText, int count)
{
    if (count == 0)
    {
        count = 10;
    }

    Random random = new Random();
    IList<object[]> items = new List<object[]>(count);
    for (int i = 0; i < count; i++)
    {
        char c1 = (char)random.Next(65, 90);
        char c2 = (char)random.Next(97, 122);
        char c3 = (char)random.Next(97, 122);

        items.Add(new object[] { prefixText + c1 + c2 + c3, random.Next(10000, 300000) });
    }

    return items;
}

  方法的返回值变成了一个存放object数组的列表,每个object数组将会作为自动补全中每一项的信息发送到客户端。object数组的每个元素将用于替换模板中的占位符(事实上,我使用了String.format方法来获得每一项的HTML,因此事实上您也可以在这里使用本地化的功能等等)。

  模拟Google Suggest的效果如下(点击这里查看示例):

 

  新的控件将“补全的内容”和“显示的内容”进行了分离。目前的控件将object数组的第一个元素(下标为0)作为“补全的内容”,在显示的时候,我们可以将其忽略。例如,下面的代码将会模拟Windows Live Mail中Email提示的功能(事实上,Live Mail的这部分自动补全是纯客户端功能,我这里模拟的仅仅是“样式”)。

<head id="Head1" runat="server">
    <title>Untitled Page</title>
    <style type="text/css">
        .completionListElement 
        {  
            visibility : hidden;
            margin : 0px!important;
            background-color : inherit;
            color : windowtext;
            border : buttonshadow;
            border-width : 1px;
            border-style : solid;
            cursor : pointer;
            overflow : auto;
            text-align : left; 
            list-style-type : none;
            font-family : Verdana;
            font-size: 11px;
            padding : 0;
        }
        .listItem 
        {
            background-color: white;
            padding : 1px;
        }        
        .highlightedListItem
        {
            background-color: #e9f5f7;
            padding : 1px;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
    
        <asp:TextBox runat="server" ID="myTextBox" Width="500"
            style="height:14px; border: solid 1px gray; font-family:Verdana; font-size: 11px;" />
        
        <jeffz:StyledAutoCompleteExtender
            runat="server" 
            BehaviorID="AutoCompleteEx"
            ID="autoComplete1" 
            TargetControlID="myTextBox"
            ServicePath="AutoComplete.asmx" 
            ServiceMethod="GetEmailCompletionList"
            MinimumPrefixLength="2" 
            CompletionInterval="1000"
            EnableCaching="true"
            CompletionSetCount="10"
            CompletionListCssClass="completionListElement" 
            CompletionListItemCssClass="listItem" 
            CompletionListHighlightedItemCssClass="highlightedListItem"
            DelimiterCharacters=",;">
            <ItemTemplate>{1}</ItemTemplate>
        </jeffz:StyledAutoCompleteExtender>
    </form>
</body>

  在这里,ItemTemplate的内容很简单,只是将服务器端返回的每个object数组的第二个元素显示在页面上,而第一个元素则作为补全的文本。它对应的Web Service方法如下所示:

private static string[] emails = new string[]
{
    "\"jeffrey zhao at yahoo\" &lt;jeffz@yahoo.com&gt;",
    "\"jeffrey zhao at yahoo china\" &lt;jeffz@yahoo.com.cn&gt;",
    "\"jeffrey zhao at live mail\" &lt;jeffz@live.com&gt;",
    "\"jeffrey zhao at gmail\" &lt;jeffz@gmail.com&gt;"
};


[WebMethod]
public IList<object[]> GetEmailCompletionList(string prefixText, int count)
{
    IList<object[]> items = new List<object[]>();

    prefixText = prefixText.Trim();
    foreach (string email in emails)
    {
        if (email.Contains(prefixText))
        {
            string encoded = email.Replace(prefixText, "<b>" + prefixText + "</b>");
            items.Add(new object[] { HttpUtility.HtmlDecode(email) + ";", encoded });
        }
    }
        
    return items;
}

  服务器端传回的每个object数组长度为2,第一个元素用于自动补全,第二个元素用于显示,因此需要将关键字部分进行加粗。使用效果如下(点击这里查看示例): 

 

   另外,正像Live Mail的功能一样,StyleAutoCompleteBehavior也支持输入多个Email——这是新版AutoCompleteBehavior提供的功能,请注意AutoCompleteBehavior的DelimiterCharacters属性,它是一个字符串,字符串中的每个字符即为分隔符。

目前StyledAutoCompleteBehavior只在FireFox和IE下进行了测试。

 

点击这里查看Google Suggest模拟示例

点击这里查看Windows Live Mail模拟示例

点击这里下载StyleAutoCompleteExtender/Behavior控件以及示例代码。

Feedback

#1楼    回复  引用    

2007-06-12 18:33 by 风起云涌 [未注册用户]
Ajax Control Tookit的AutoComplete的确是太简单了

#2楼    回复  引用  查看    

2007-06-12 18:38 by 菌哥      
老赵动作真快啊,呵呵

#3楼    回复  引用  查看    

2007-06-12 19:55 by Cat Chen      
不错哦!

#4楼 [楼主]   回复  引用  查看    

2007-06-12 20:26 by Jeffrey Zhao      
@菌哥
不过功能还不够……

#5楼 [楼主]   回复  引用  查看    

2007-06-12 20:26 by Jeffrey Zhao      
@风起云涌
已经支持多单词了,呵呵

#6楼    回复  引用  查看    

2007-06-12 23:05 by Dflying Chen      
{0}、{1}不如用Label代替,然后在Extender中指明id
或者更高级一点,Template中支持数据绑定……

#7楼    回复  引用    

2007-06-12 23:21 by se [未注册用户]
good...............

#8楼 [楼主]   回复  引用  查看    

2007-06-12 23:50 by Jeffrey Zhao      
@Dflying Chen
{0}、{1}这种纯粹会被替换掉的占位符会有更好的灵活性,避免使用Label可能会带来的语义上的不妥,其实用{0}、{1}就相当于用Literal。当然用{0}、{1}还有的好处是直接利用了ASP.NET AJAX的功能,完全可以使用{1:d}这种形式来控制格式。
至于用Template来绑定,这个是服务器端直接生成HTML时的常见做法吧,在现在这种AJAX环境下感觉不太适合。使用Web Service的方法还是比较妥当的,我觉得,当然一些细节上的东西需要再讨论,这就是我说“设计上”是不是应该再改进一下之类的,呵呵。

#9楼    回复  引用  查看    

2007-06-13 00:22 by 蜡人张      
 
也作过一个AutoComplete,在文本框中输入字符后,按Enter键才取数据,另外支持PgDn键分批取符合条件的数据,现有的AutoComplete或Suggest基本上都不行,不是很实用。

#10楼 [楼主]   回复  引用  查看    

2007-06-13 00:28 by Jeffrey Zhao      
@蜡人张
其实,是否实用不应该用某个单一的案例来判断,我觉得您的要求的确比较特殊,不是吗?您在实现这个效果的时候也是不考虑其他情况的,因此可以做的出来。
不过的确可以是一个发展方向,您觉得怎么样可以让一个AutoComplete变得实用呢?

#11楼    回复  引用    

2007-06-13 09:49 by 飞飞 [未注册用户]
@蜡人张
可以把你做的那个AutoComplete代码发给我吗,我现在正考虑做一个这种功能的

#12楼    回复  引用    

2007-06-13 09:50 by 飞飞 [未注册用户]
@蜡人张
czhrf@msn.com

#13楼 [楼主]   回复  引用  查看    

2007-06-13 11:35 by Jeffrey Zhao      
@飞飞
这是应该自己写的,和AutoCompleteBehavior无关。

#14楼    回复  引用    

2007-06-14 03:17 by 怪怪 [未注册用户]
关键是这个对服务器的压力不好受...

#15楼 [楼主]   回复  引用  查看    

2007-06-14 09:19 by Jeffrey Zhao      
@怪怪
嗯嗯,所以要修改一下,然后让它支持缓存……

#16楼    回复  引用  查看    

2007-06-14 10:24 by 哈密瓜牌牛奶      
好东西,Mark一下:)

#17楼    回复  引用    

2007-06-14 15:47 by 工控展览网 [未注册用户]
经典

#18楼    回复  引用  查看    

2007-06-24 22:52 by 南方小鬼      
希望尽早把对缓存的支持完善,那就功不可没了,呵呵

#19楼 [楼主]   回复  引用  查看    

2007-06-24 23:28 by Jeffrey Zhao      
@南方小鬼
AutoComplete有缓存啊,只是访问Web Service没有缓存。

#20楼    回复  引用  查看    

2007-06-25 18:53 by 南方小鬼      
两个缓存是什么区别?不好意思,我是新手。
是AutoComplete的items在缓存还是服务器上数据库中的被访问记录在缓存

#21楼 [楼主]   回复  引用  查看    

2007-06-25 19:01 by Jeffrey Zhao      
@南方小鬼
AutoComplete会保存每次从服务器端得到的结果,只要页面没有刷新,这个结果就被保留在内存中。
Web Service缓存则是利用HTTP协议的缓存,让服务器端返回302这个Status Code,客户端则会使用之前得到的信息作为这次请求的内容。这样的缓存,即使页面刷新了,还是有缓存效果的。

#22楼    回复  引用  查看    

2007-06-25 19:04 by 南方小鬼      
老赵要是能再讲讲ASP.NET 2.0 控件使用技巧等就太好了

#23楼 [楼主]   回复  引用  查看    

2007-06-25 19:07 by Jeffrey Zhao      
@南方小鬼
慢慢来吧,其实我比较重视的是模型的理解。我一直认为理解了模型就可以举一反三,一通百通。

#24楼    回复  引用  查看    

2007-06-25 19:44 by 南方小鬼      
谢谢老赵的指点
感觉自己要学的东西真的相当多啊

#25楼 [楼主]   回复  引用  查看    

2007-06-25 19:50 by Jeffrey Zhao      
@南方小鬼
学无止境,一起学。:)

#26楼    回复  引用    

2007-07-05 10:22 by tinsuki [未注册用户]
下了下来一学习,说不具有“CompletionListCssClass”“CompletionListItemCssClass ”
“CompletionListHighlightedItemCssClass”这三个公共属性

#27楼 [楼主]   回复  引用  查看    

2007-07-05 13:04 by Jeffrey Zhao      
@tinsuki
您对比一下我给的示例和您自己写的有什么区别吧。:)

#28楼    回复  引用    

2007-07-12 13:56 by LixingTie [未注册用户]
最近在看赵老师主讲的Webcast,收获不小,真的挺感谢你,也从心底里佩服你,老赵!

#29楼    回复  引用    

2007-07-26 20:25 by selina [未注册用户]
谢谢赵老师,我有一个问题:用什么方法可以取到result的值?

#30楼    回复  引用    

2007-07-26 20:26 by selina [未注册用户]
我想取到自动完成的那项的result,在其它的地方用.

#31楼    回复  引用  查看    

2007-08-13 22:08 by Anthan      
今天用到这个功能,突然想起来你写的东西,下下来仔细看了下,虽然有部分看不太懂,不过还是给了很大的启发,非常感谢你。

#32楼    回复  引用  查看    

2007-08-14 11:08 by Anthan      
可以在文本框中我只显示邮件人,即"jeffrey zhao at live mail“,然后在我选中以后在后台抓到它所对应的value即"jeffz@live.com",这样可以吗?

#33楼 [楼主]   回复  引用  查看    

2007-08-14 12:33 by Jeffrey Zhao      
@Anthan
可以的,我的第二个例子就是说明这种灵活性的。

#34楼    回复  引用  查看    

2007-08-14 22:55 by Anthan      
@Jeffrey Zhao
就是有点类似于下拉框的功能,选中一个Text,后台得到一个Value。
我所能想到的是把Value隐藏起来,在后台Split出来,只是这样太丑陋了,呵呵
文本框只能得到一个string类型的Text属性啊。

#35楼 [楼主]   回复  引用  查看    

2007-08-14 23:04 by Jeffrey Zhao      
@Anthan
这样啊,这个就不太好做了,只有用丑陋的方法吧,赫赫。

#36楼    回复  引用    

2007-08-21 17:05 by kenson [未注册用户]
趙老師,我運行你samples里面的三個文件,說找不到StyledAutoCompleteExtender。。是不是還要引用dll啊。能不能給一個完整的實例,download下來就能運行的呢。謝謝趙老師

#37楼 [楼主]   回复  引用  查看    

2007-08-21 22:01 by Jeffrey Zhao      
@kenson
AJAX Control Toolkit的dll总是需要的,这东西比较大,还是自己下载吧。:)

#38楼    回复  引用    

2007-08-22 11:10 by kenson [未注册用户]
哈哈, 謝謝趙老師。我原來沒有引用AJAX Control Toolkit這個參考,所以一直都生成dll失敗。現在終于明白了。。

#39楼 [楼主]   回复  引用  查看    

2007-08-22 21:37 by Jeffrey Zhao      
@kenson
这个应该自己能够发现问题的,呵呵。

#40楼    回复  引用  查看    

2007-08-28 14:12 by 阿不      
我将使用模板定制样式的方式改为在Service中返回一个name和value,然后在name在拼接好样式返回给客户端,这样的更灵活使用。

#41楼 [楼主]   回复  引用  查看    

2007-08-28 22:31 by Jeffrey Zhao      
@阿不
其实这就是在客户端多了一个Resolve的过程,和服务器端其实差不多,呵呵。
而且其实大多数情况客户端能够使用Template+Style来解决问题,需要在客户端拼接样式的情况其实不多……

#42楼    回复  引用    

2007-09-27 09:36 by jimliu [未注册用户]
请问webservice并不支持返回IList接口类型的参数,虽然你的webservice可以被控件执行,但webservice本身会抛出异常,如何解决此问题?

#43楼    回复  引用    

2007-10-06 14:45 by zml [未注册用户]
赵老师你好。你试一下,点击上面的演示例子的链接并没有你所描述的效果啊,什么反应都没有,我换了好几个机器也不行,我下载的例子程序也有问题。
CompletionListCssClass="completionListElement"
CompletionListItemCssClass="listItem"
CompletionListHighlightedItemCssClass="highlightedListItem"

提示这三个属性有问题,怎么回事啊?

#44楼    回复  引用    

2007-10-06 15:49 by zml [未注册用户]
找到原因了:AjaxControlToolkit版本不对

#45楼 [楼主]   回复  引用  查看    

2007-10-07 12:13 by Jeffrey Zhao      
@jimliu
本来SOAP的WebService和ASP.NET AJAX的WebService不是一件事情,呵呵。

#46楼    回复  引用  查看    

2007-10-07 19:31 by 化外白狐      
老大能不能帮我解决一个困惑呢?
用asp.net ajax1.0调用web服务函数,例如,web服务函数为[WebMethod]
public string HelloWorld(string rrr)
{
return "Hello World";
}
如果客户端rrr超过400k大小,asp.net ajax根本不能将客户端信息递交到服务端,在客户端ie上信息提示:此页面上的脚本造成ie浏览器运行速度缓慢,您的计算机可能停止响应,是否停止?有没有可解决的办法?

#47楼 [楼主]   回复  引用  查看    

2007-10-08 11:57 by Jeffrey Zhao      
@化外白狐
可能是IE的问题。IE在传输数据是可能还是hang住的,导致了你的问题的出现。

#48楼    回复  引用  查看    

2007-10-08 12:27 by 化外白狐      
那您有什么好的解决办法么?我这个情况以前在利用web services behavior用ajax模式传输大数据量的二进制数据的时候没有发生过问题,当时实现的场景是批量上传了一批文件(10个) ,每个大约10兆左右。似乎只是采用asp.net ajax才出现过的问题。

#49楼    回复  引用  查看    

2007-10-08 12:37 by 化外白狐      
我觉得可能还是和asp.net ajax机制有关系的,否则为什么web services behavior就可以顺利的实现批量上传呢?

#50楼 [楼主]   回复  引用  查看    

2007-10-08 14:56 by Jeffrey Zhao      
@化外白狐
Web Service Behavior?批量上传文件?
AJAX怎么能提交二进制数据,比如文件呢?

#51楼    回复  引用  查看    

2007-10-08 16:44 by 化外白狐      
//这里创建文件操作控件对象,这个对象是我自己写的一个ocx控件
var thefilecontrol=$(FileLoadControlName);
var thefileobj=new Object();
var tempfilename=""+thefilecontrol.GetFileName(true);
if(tempfilename!=null&&tempfilename!="")
{
var reg=/\\([^\\]{1,})$/;
reg.exec(tempfilename);
}
else
{
return;
}
thefileobj["FileName"]=RegExp.$1;
//初始化文件xml对象
theProjectFileXml= new ActiveXObject("MSXML2.DOMDocument");
theProjectFileXml.loadXML('<?xml version="1.0" ?> <root/>');
// 指定数据类型
theProjectFileXml.documentElement.setAttribute("xmlns:dt", "urn:schemas-microsoft-com:datatypes");
// 创建一个新节点,设置其为二进制数据节点
var l_node1 = theProjectFileXml.createElement(thefileobj["FileName"]);
l_node1.dataType = "bin.base64";
// 将文件内容存入XML节点
l_node1.nodeTypedValue = thefilecontrol.GetFileData3(tempfilename);//通过控件函数把文件数据读取出来,然后赋给一个xml对象的一个base64节点
theProjectFileXml.documentElement.appendChild(l_node1);
thefileobj["FileData"]=theProjectFileXml.xml;
return thefileobj;

#52楼    回复  引用  查看    

2007-10-08 16:46 by 化外白狐      
基本上就是从前端先载入数据,不管多少个,我都可以追加到xml里面,然后一起提交,提交后在web服务方法中做解析,存为文件

#53楼    回复  引用  查看    

2007-10-08 16:49 by 化外白狐      
把文件压到xml中以后,完全是一个str,可以从后端直接以str格式传道后端,以前没有asp.net ajax以后,我使用微软的web services behavior来处理,就是一个webservices.htc文件,这个文件主要是用于soap解析。

#54楼    回复  引用  查看    

2007-10-08 16:51 by 化外白狐      
前端读数据的控件以前使用adostream,但是ie版本高以后,adostream在本地用localhost访问没有问题,但是通过ip访问安全出现了问题,所以改为自己写的控件

#55楼 [楼主]   回复  引用  查看    

2007-10-08 18:56 by Jeffrey Zhao      
@化外白狐
用ActiveX了可能的确不一样了,还是解决您的问题吧。
您的应用能够在FireFox里运行吗?

#56楼    回复  引用  查看    

2007-10-09 05:56 by 化外白狐      

@Jeffrey Zhao
FireFox倒没有测试过。但是我的activeX主要是用于在前端获取二进制文件数据,如果直接采用js的那个读文件的方案获取文本数据大了也有这个问题。所以,现在存在问题肯定是传输链路的问题,但是以前用webservices.htc调用web服务函数没有发生那个提示信息框(一般来说出现那个提示信息就是循环 比较多的情况产生,比如您如果写一个js循环,什么也不做,就是把循环次数调大,一个循环ie就要提示了),那就说明可能是前端处理数据的地方做的处理不一样,但是没有仔细研究过微软的js,所以不知道是什么样的原因。另外,微软的asp.net ajax的web服务方法可以在web服务没有正常运行的情况下执行(也就是说web服务函数可以有IDictionary接口的数据结构声明),这个问题也很让人费解,难道微软的方案是通过传参到aspx页面然后通过反射创建对应的asmx类对象然后执行对应的函数的?如果是这样,我如果使用asp.net ajax写一个web服务函数带Hashtable参数,那么,我根本没法让其他类型的客户端(比如win32或者winform)顺利调用了。

#57楼    回复  引用  查看    

2007-10-09 05:58 by 化外白狐      
顺便,我加您的msn了,不知道您什么时候在线,我们可否在线沟通一下呢?我的msn:smallpeter_wu@hotmail.com

#58楼 [楼主]   回复  引用  查看    

2007-10-09 09:20 by Jeffrey Zhao      
@化外白狐
虽然都是asmx,但是不要把asp.net ajax的和soap访问的一视同仁,这两个完全是没有关系的,可以这么认为。执行方式不同。

#59楼 [楼主]   回复  引用  查看    

2007-10-09 09:20 by Jeffrey Zhao      
@化外白狐
我的空余时间实在不定,呵呵。

#60楼    回复  引用  查看    

2008-06-26 15:27 by laolaowhn      
.completionListElement
{
overflow : auto;
}
这个样式存在问题,当出现自动补全列表时,用上下键选这第一条,然后回车,再回车(相当于激发搜索按钮),这个时候你在往文本框里面输入内容就只能显示一条记录了,其他的记录只有通过滚动条才能显示,调试的我好辛苦啊

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      


相关链接: