过了很久才抽时间写了这篇东西,望和谐处之
代码的具体下载位置在:http://yuan505.vicp.net/cy_filesxxx/vbsrc/autocomplete.rar
在IE地址栏中输入文字,与该文字相关的候选地址列表就会出现在其下方——这就是我们早已见怪不怪的Windows自动完成。那么,我们能在自己的应用程序中实现这样的功能么?答案当然是肯定的。使用Shell API函数SHAutoComplete,我们可以轻易办到这一点。
SHAutoComplete函数允许用户将系统的自动完成功能绑定到任意文本框上(包括组合框上内嵌的文本框),并让用户可以选择历史(History),文件系统(File System)和最近使用列表(MRU List)3种候选来源。现在,你应用程序中的文本框可以表现得与IE或Windows资源管理器上的地址栏一样了。
……
那这样大家满足了么?远远没有。
很遗憾,人这种生物可不是那么容易满足的。
“我不要系统提供的侯选来源!我要在侯选列表显示自己定义的内容!”,程序员甲激动地喊道。
“轰隆隆~~~”,阿甲话音刚落,远处就随雷声传来一个声音回答道,“OK~~,如你所愿!”
……
要在自动完成中使用自定义的候选来源难不难?不难。为啥捏?因为MSDN上写的就那么简单。
IEnumString是自定义候选来源中必须要实现的接口。它有如下四个接口方法:
1、 Next
从枚举序列中获取celt个元素。如果要获取的元素个数比序列中所剩余的元素个数要多的话,则返回所有剩余的元素。pceltFetched参数将返回最后实际获取的元素个数。
2、 Skip
跳过下面celt个元素
3、 Reset
重置枚举序列到起始状态。即下次获取操作将从第1个元素开始。
4、 Clone
顾名思义,克隆,也就是创建一个与当前枚举序列元素相同、状态也相同的枚举器对象。
在你自己的候选来源类中实现了这个接口就等于完成了80%的工作。接下去,只要按照伟大的MSDN的指示,一步步创建AutoComplete对象、设定候选来源就OK了。
1、创建自动完成对象
CoCreateInstance CLSID_IAutoComplete, 0, 1, IID_IAutoComplete, oAutoComplete(i)
2、绑定候选来源
Call oAutoComplete(i).Init(hTxtWnd, MySource, 0, 0)
3、等待奇迹出现
到这里,一个相当简单的自定义候选来源已经可以付诸使用,一切貌似万事大吉,可以收工回家了。然而,又是一个然而,老天并没有让事情如想象的那么一帆风顺。聪明的程序员们很快会发现应用程序有时会崩溃。这又是为虾米?程序员的命运为虾米总是那么那么的坎坷捏?不怕,同志们,有句话说得好,问题和答案总是同时产生。
VB的硬伤——多线程就是程序崩溃的根源。每一次在绑定了自动完成对象的文本框里按下按键的时候,系统就会新创建一个线程,我们的候选来源类就是在这个线程里被实例化。离开了vb主线程的怀抱,脆弱的vb类显得是那么的无助。调用任何一个涉及到线程本地存储信息的函数或语句(比如ReDim)就会让宿主程序死无全尸。
在vb中使用多线程之所以会崩溃,全因vb的单元线程模型所累。在别的线程里调用主线程中的对象,必须要符合COM的规则,要使用列集和散集(Marshal和Unmarshal)来处理对象,创建COM调用代理。为所有的对象都搞列集和散集操作显然不是一件好玩的事情,于是,程序员乙就问道,“难道我们就没有活路了么?”答案又是积极向上而且非常和谐的。 Windows的消息机制就是一根不错的救命稻草。在其他线程中的对象使用阻塞式的Windows 消息API  SendMessage发送自定义消息到主程序,告知主程序该对象的某某方法正被调用,请主程序做些相应的反应。这种方法可以使多线程的执行方式貌似单线程一样,从而大大降低了程序代码在其他线程中发生不幸的机会。
到这里,问题似乎都已经解决了,可是,我们又要转折一下语气,可是可是,程序还是会挂。为啥?因为我们写了”Declare Function SendMessage……”。VB在每个API调用结束后都会调用一次API GetLastError,这还不要紧,要命的是它调用完这个API后还把其结果写到Err对象中。访问Err对象的这一步用到了线程本地存储信息,于是,应用程序不幸归西。对此,解决方案有二:一、使用类型库来声明API。二、不让VB做傻事。使用类型库比较稳定,但每次修改API都要重新进行编译。麻烦了点,的确是麻烦了点。所以,我决定采取第2种方案,制止VB做傻事。思路挺简单,就是不让VB调用__vbaSetSystemError函数。具体实施起来就是,在程序运行时找到函数导入表中的__vbaSetSystemError函数位置,用自己的空函数替换掉。很幸运,前段时间我写过一个拦截API的东西,里面就包含有一个用以定位PE可执行文件导入导出函数的CVBPEFnLocator类。使用它可以很轻松的完成上面这个任务。
Public Sub PatchSetSystemError()
    Dim oPEFnLocator As CVBPEFnLocator
    Set oPEFnLocator = New CVBPEFnLocator
    
    With oPEFnLocator
        .SetTargetTo -1
        .AutoRestore = False
        
        .LocateImportFunction GetModuleHandle(vbNullString), "msvbvm60.dll", "__vbaSetSystemError"
        .ImpReplace AddressOf MySetSystemError
    End With
    
    Set oPEFnLocator = Nothing
End Sub 最后,这回真的是最后了,呵呵,大事告成,至少目前看起来是这样。

解决方案 »

  1.   

    hoho~~~,高精尖产品先收录进口袋。凡是蹂躏VB的盔甲、橇COM墙角的东东,我都喜欢。
      

  2.   

    绿豆就是偶偶像,(BLOG好象没怎么更新了哦)
      

  3.   

    凡是蹂躏VB的盔甲、橇COM墙角的东东,我都喜欢。我也踢绿豆一脚 太有才了
      

  4.   

    好东西,收藏了!!但是"【实用代码】Windows Shell接口之VB实现(一)"在哪里啊?
      

  5.   

    还是VC写简单啊
    直接派生IEnumString就可以了
      

  6.   

    是啊,我也在找"【实用代码】Windows Shell接口之VB实现(一)"在哪里啊?
      

  7.   

    【实用代码】Windows Shell接口之VB实现(一)
    http://topic.csdn.net/t/20040619/07/3105437.html
      

  8.   

    支持,喜欢你的YD凡是蹂躏VB的盔甲、橇COM墙角的东东,我都喜欢。我也踢绿豆一脚 太有才了
      

  9.   

    偶尔搜索到你写的:【实用代码】Windows Shell接口之VB实现(二):自动完成接口
    在测试你的代码时候发现一个有趣的现象:
    中文列表,微软拼音支持.
    五笔,全拼这二个都不支持,其他的输入法我没有试.微软拼音输入法输入的时候,这个字,先是有条下划线的,按回车确定,这个字才进去,然后出现下拉提示.
    五笔和全拼这二个输入法,是输入完就进去了,不需要回车确认.但在输入完二个字的时候,你删除第二个字,提示又出现了.有时间的话,麻烦看看.这个究竟是什么原因造成的.