我思故我在

歌德说,人的一辈子其实只能做一件事,做了,就要扎扎实实地把它做好。我也只能通过一种行业来认定我自己的人生价值。我选择了写程序,我希望我能写好。
随笔 - 55, 评论 - 457, 引用 - 159

导航

工具

每月存档

广告



访客

11月25号以后mvp.support.microsoft.com更新的服务

在十一月25日以后,再登陆mvp.support.microsoft.com站点你会发现一点新内容:

增加的“Community Solutions”,允许MVP为微软的KB库投稿,并且可以维护自己写的文章,详细内容可以到mvp.support.microsoft.com查阅

另外“My Profile ”内容变得更加全面了,我记得以前只能改名字等基本信息,现在可以上传照片,及撰写个人简介等等,不过现在还在测试阶段。

如果发现问题可以到private My Profile newsgroup反馈!

posted on 2003-11-28 09:20:00 by shannon  评论(1) 阅读(932)

VB中的连等操作

今天又是因为一次输入错误,发现一件以前没有注意的东西,就是VB中的连等操作的含义(可能别人已经知道了)。

在进行赋值操作时我意外将“-”号错写“=”号,运行测试发现和想象的结果不同,检查代码发现原来写错一个符号,也因此发现VB中的连等操作和C语言的不同.

Private Sub Command1_Click()

    Dim x As Integer, y As Integer, z As Integer

    x = y = z = 5

    Debug.Print x & ":" & y & ":" & z

End Sub

本以为结果应该输出“5:5:5”可是实际结果却是“0:0:0”

原来VB中把连等操作第一个等号以后的等号解释为逻辑操作,所以才有这个结果。这可能是基础,但是我以前确实不知道。

而这种解释方法在VB.net仍然存在,C#中是连续赋值。

虽然好像是简单的一个“x = y = z = 5”运算发表式,但是在vb.net和c#中使用的差异还是非常大的。

----------------

差点就是千里之堤,毁于蚁穴。

posted on 2003-11-26 11:01:00 by shannon  评论(1) 阅读(2874)

在VB中任意设置ListView控件的ListItem的背景颜色

以前在用C++时,因为ListView控件提供OnwerDraw方法,所以有很直观的方法任意设置每个Listitem的背景颜色。

但是在VB中,没有这个方法,但是可以设置它的背景图片,以前在网上搜索看到有关这方面的文章设置背景颜色都是设置相同间隔相同颜色(因为是用一张图片以Title的方式贴上去的),所以看来偷懒不成,自己写吧,真正动手去写才发现原来很简单。

Private Sub SetListItemColor(lv As ListView, picBg As PictureBox)

   Dim i As Integer

   Dim mItem As ListItem

   picBg.BackColor = lv.BackColor

   lv.Parent.ScaleMode = vbTwips

    picBg.ScaleMode = vbTwips

    picBg.BorderStyle = vbBSNone

    picBg.AutoRedraw = True

    picBg.Visible = False

   

    picBg.Width = lv.Width

    picBg.Height = lv.ListItems(1).Height * (lv.ListItems.Count)

    picBg.ScaleHeight = lv.ListItems.Count

    picBg.ScaleWidth = 1

    picBg.DrawWidth = 1

    '-----------------------------

    'custom.such as

    '------------------------------

    For i = 1 To 33

        Set mItem = lv.ListItems灯泡

        If mItem.Checked = False Then

            If i Mod 2 = 0 Then

                picBg.Line (0, i - 1)-(1, i), RGB(254, 209, 199), BF

            Else

               picBg.Line (0, i - 1)-(1, i), RGB(20, 54, 199), BF

            End If

        Else

             picBg.Line (0, i - 1)-(1, i), RGB(254, 200, 100), BF

        End If

    Next
 
    lv.Picture = picBg.Image

End Sub

效果:

 

-------------------

有些事想象很难,其实坐起来并不难,有些想象起来很容易,做起来并不容易,看来不实践是知道的。

posted on 2003-11-24 08:52:00 by shannon  评论(8) 阅读(9526)

信任

前天通过网络联系,以先款后货的方式在网上从深圳买了一部Pocket PC,别人都说太冒险了,很可能上当,其实我心里确实也是有些担心,因为我以前也上过当。我其实比较害怕和那边的进行交易。但是我感觉我可以相信对方。

结果还是我的感觉正确。

觉得现在的社会信任太少了,恋人之间,朋友之间,同事之间,主雇之间,尤其就是买卖之间。

这能怪谁?欺骗太多吗?还是不相信自己的判断。我不知道!

恶性循环!

但是我想起码要信任自己(盲目的不算)!否则还能相信谁?

posted on 2003-11-21 08:21:00 by shannon  评论(10) 阅读(1738)

Tracking your Order

不管是eCompanyStore购物还是其他什么原因,我们都需要从国外邮寄东西过来,而有时我们想随时了解我们的包裹现在送货情况,比如现在到达哪里了,哪里耽误了时间等。

这是我们就需要用到Carrier的Tracking功能。

我17号在eCompanyStore订了两件商品,发货前会生产一个TrackingNo(标识),现在我想了解货品到了哪里,这时就可以Tracking一下。

记下要Tracking的包裹的TrackingNo(eCompanyStore的Track Order会查到)比如是:

1Z   xxxxxx  66 xxxxxxx6

UPS公司投递的包裹的TrackingNo现在都是1Z开头,后六位是客户号,再两位66是服务类型:

54代表 UPS Express PLUS;66代表 UPS Express;67代表 UPS Expedited;68代表 UPS Standard(国际部分)

再后边七位是活动数字,最后一位是校验位。

这些资料应该不算保密资料,所以我写了出来。

到UPS主页(http://www.ups.com/content/us/en/index.jsx英文或者http://www.ups.com/content/cn/zh/index.jsx中文)

在左边的“追踪(Track)”的文本框输入TrackingNo,选中下边的选项,然后点击“追踪(Track)”,过一会就列出你Tack的Order,在点击TrackingNo下边的“详细信息(Detail)”链接,你就可以看到包裹的详细运输情况。

posted on 2003-11-20 12:43:00 by shannon  评论(3) 阅读(1316)

一不小心上了SendKeys的当

SendKeyS发送一个按键给当前活动窗口!

刚才在学程序时,一不小心写错一句代码:

Private Sub Text1_KeyPress(KeyAscii As Integer)

   If KeyAscii  = 13 Then SendKeys "{Enter}"

End Sub

本来想SendKeys "{TAB}",没想到send错了,发生什么事一看便知,发生了死循环,而且PC喇叭想不停,即使关闭我的应用程序,仍然在响,根据PC喇叭的状况我本来可以断定是某个键按下没起来,后来检查不是,才发现我的代码有问题。因此也发现原来这个函数(SendKeys)是将按键信息发送到键盘缓冲区。知道这一点就有其他文章可做了,既然是发送到缓冲区,那么我就可以用我的程序发送,然后用其他程序接收,随手试验一下,竟然可行。

1:     Private Sub Command1_Click()

2:         Shell "notepad", vbNormalFocus

3:         Clipboard.SetText "sdfsdfsdfsdfsd"

4:         AppActivate "未定标题 - 记事本"

5:         SendKeys "%ep"

6:     End Sub

 

而且还可以通过它象控制台发送信息,看来错误未必总是坏事!

posted on 2003-11-19 16:58:00 by shannon  评论(5) 阅读(3738)

看“爱琴海”的照片

刚刚收到Grace的Email,我就跟随Grace的指引到了“爱琴海”。

看了作者拍摄的照片有一种不说不行的冲动!真是太美了!

看到这些图片也让我想我出生的地方,我出生的地方在黑龙江的一个小山里,应该属于小兴安岭,虽然建筑没有爱琴海的好,但是自然风光是一样的美,水是那么清澈,我还可以记得,我家旁边的小河,不管多深的水都可以看到底,水底有各种鱼,河的对岸就是高山,春天,山里的风景最美丽(因为有映山红)。

看到这样的风景有一种净化心灵的感觉!

可惜现在没有了,水变得浑浊了,山也变得光秃。但是

那么美好的风景永远都留在我的心中。

作者将“心”留在爱琴海,我是将家乡的风景留在心里!

posted on 2003-11-19 15:26:00 by shannon  评论(31) 阅读(8114)

自定义VB系统控件

还没写完,文章的主要内容是利用API来自定义VB系统自带的控件,涉及的技术主要有windows系统消息机制,Subclass等。

其中理论不多,更多可能是实际应用技巧,其中很多代码并不是我最初写出来,但是对这方面理论有了了解我想我们都可以自己写出来,我的目的就是对于VB程序员起一个抛砖引玉的作用,因为我本身用的最多也是VB所以对于系统底层的东西了解不是很多,所以可能有写的不合适的地方,所以看的时候一定带上自己的观点,自己的理解。

 

http://blog.joycode.com/shannon/posts/6843.aspx

posted on 2003-11-17 13:18:00 by shannon  评论(0) 阅读(1347)

自定义VB系统控件(初稿未完)

关键字:Visual Basic ,API ,自定义,控件,Subclass

本篇文章面对的对象是初级VB程序员,最好有些Windows系统应用程序开发知识。在结束文章以前,前边任何内容均有可能更改,如果文中代码有触犯版权等问题,请与作者联系,如果作者本人同意,将会署名发布,如果不同意将予以删除。



由于本人文笔欠佳,所以还真不知能写出什么文章,本来打算写些.net方面的文章,但是由于刚刚接触.net不久,还没有什么经验体会,对于我而言经验最多的应该还是VB吧,毕竟已经用了两年多了。但是这仅仅是我自身比较而言并不是和其他人比较,因此决定将一些平时经验和技巧写下来,也就算整理一下资料吧,虽然是班门弄斧,但是希望这篇文章能对一些VB初学者有些帮助。希望使用VB开发应用的人能通过这篇文章得到一些启发,能从另一个角度来看待VB程序开发。

先谈谈我的个人想法:

不管我们使用什么计算机语言开发,VC,VB,BCB,JAVA,NET你都脱离不开操作系统,它就是我们软件的生存土壤,JAVA的跨平台其实是因为它的虚拟机,实质上虚拟机还是要依靠操作系统,.net可以说博大精深但是它最终还是调用操作系统提供的服务,在Windows2003上运行.net程序和在windows95上运行效果肯定不同,因此只要一种语言提供给我们一种直接调用操作系统服务的接口(API)我们就不能武断的说它某些事做不了。只不过是方便与否,难以程度有差别,说这些话有些位VB申冤的嫌疑,可以说我确实有这点私心,但是如果你选择了VB你就要相信它,想办法了解到,这样才能充分发挥它的功能。

通常一种语言用久了就不知不觉中用它来思考,这有好处也有坏处,真正了解一门语言就要学会在使用中用它来思考,但是因此也会带来思维定式。很多时候局限的不是语言本身而是我们的思想。因此我们需要不停的思考,从不同角度思考,正如我的bLog的标题“我思故我在”,这句话有些唯心,但实际上在没有上学之前,在没有接收唯物主义的哲学思想之前我应该算是唯心主义者。当时我总是想我眼里的世界包括人,动物,植物等所有的一切是否仅仅是在我眼里才呈现这个样子而在别人的眼中,是否这个世界是另一个模样,这个我现在也不知道,因为接触的是用我的双手,我看到的是用的眼睛,我感知的是用我的心灵,我无法代替别人,别人无法代替我。话题好像扯远了。

Visual Basic 是一种RAD工具,之所以说它是RAD工具就是因为很多底层初级的东西已经被IDE封装好,我们只要直接用就好了,因此我们可以用VB来进行快速的应用开发。

举个例子:

如果用代码创建一个正常工作的窗体至少需要调用如下几个API:

RegisterClass或RegisterClassEx:该函数为随后在调用Createwindow函数和CreatewindowEx函数中使用的窗口注册一个窗口类

UnregisterClass:删除一个窗口类,清空该类所需的内存

DefWindowProc:该函数调用缺省的窗口过程来为应用程序没有处理的任何窗口消息提供缺省的处理。该函数确保每一个消息得到处理。调用DefWindowProc函数时使用窗口过程接收的相同参数

GetMessage:该函数从调用线程的消息队列里取得一个消息并将其放于指定的结构

TranslateMessage:该函数将虚拟键消息转换为字符消息

DispatchMessage:该函数调度一个消息给窗口程序,通常调度从GetMessage取得的消息

ShowWindow:用于设置窗口的状态,其中包括窗口的隐藏、显示、最小化、最大化、激活等

UpdateWindow: 立即更新窗口内需要更新的任何部分

CreateWindowEx:该函数创建一个具有扩展风格的重叠式窗口、弹出式窗口或子窗口,其他与CreateWindow函数相同

CallWindowProc:该函数CallWindowProc将消息信息传送给指定的窗口过程。

SetWindowLong,GetWindowLong:用于获取或设置与窗口有关的信息

PostQuitMessage:将一条消息投递到指定窗口的消息队列

DestroyWindow:清除指定的窗口以及下属所有子窗口与包容窗口.

进行几个繁琐的操作才能创建一个窗体。然后还有进行各种消息处理等等,但是有了VB这种RAD工具所有这些我们都可以不用关心,因为VB已经为我们封装好了。

我们所要做的且关心的就是怎么设计我们自己的应用。

做个比喻就像我们已经有了房子只需要按照自己的需要进行装修即可,但是非RAD工具是从楼房的地基(地址有操作系统提供)开始。

但是,凡事没有绝对的优点也没有绝对的缺点。站在不同的角度看待同一个事物却会有不同的结果。

如果我想在VB中在反过来深入底层将是很麻烦的事。

按照自己的想法盖房子和将已经建好的楼房进行改建更麻烦(我这里用的是麻烦,并不是困难),它的难点就是如何找到切入点。

但是如果能够灵活运用系统API,能够找到切入点,将会起到事半功倍的效果。

下面用实际的例子进行一些演示说明,由于本人技术及篇幅有限,不事宜做复杂的说明。那些做为专题讨论,写这篇主要目的是起到抛砖引玉的作用。

严格说来操作系统只知道窗口控件(WinControl)的存在,我这里说的窗口控件可以这么理解就是在VB中具有hWnd(窗口句柄)的控件。他们都靠系统的消息驱动,因为我在这篇文章主要侧重点是利用API来发掘VB,因此涉及的对象基本都是指窗口控件,非窗口控件的创建、更新、销毁又它的父窗口控件来负责。

使用VC++编程的人一定会熟悉很多窗体控件风格常量,然后按照自己的需要创建窗体控件样式,而我们在VB中,这些统统被IDE包装起来的,我和根本看不到,但是利用API我们可以重新定义窗体控件的样式,下面就用实际例子来演示一下:

(这里我没有列出详细的API和常量声明,因为我主要想体现的是方法和思路)

 

  任何一个窗体控件,我们都可以给它加上ControlBox(所谓ControlBox,就是窗体的图标+最小化+最大化+关闭按钮)

Public Sub ControlSysMenu(ControlName As Control, SetTrue As Boolean)

   Dim dwStyle As Long

   dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE)

   If SetTrue Then

      dwStyle = dwStyle Or WS_SYSMENU

   Else

   dwStyle = dwStyle - WS_SYSMENU

   End If

   dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle)

    SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME

End Sub



任何一个窗体组件,我们都可以给它加上标题栏,通过拖动标题栏,可以实现控件的运行时移动。



Public Sub ControlCaption(ControlName As Control, SetTrue As Boolean)

    Dim dwStyle As Long

    dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE)

    If SetTrue Then

       dwStyle = dwStyle Or WS_CAPTION Or WS_THICKFRAME

    Else

       dwStyle = dwStyle - WS_CAPTION - WS_THICKFRAME

    End If

    dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle)

     SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME

End Sub



任何一个窗体组件,我们都可以控制其显示风格为模式对话框的风格



Public Sub ControlModal(ControlName As Control, SetTrue As Boolean)

    Dim dwStyle As Long

    dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE)

    If SetTrue Then

       dwStyle = dwStyle Or WS_POPUP

    Else

       dwStyle = dwStyle - WS_POPUP

    End If

    dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle)

     SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME

End Sub



任何一个窗体组件,我们都可以控制其显示风格为对话框的风格。

Public Sub ControlDialog(ControlName As Control, SetTrue As Boolean)

   Dim dwStyle As Long

   dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE)

   If SetTrue Then

      dwStyle = dwStyle Or WS_DLGFRAME

   Else

      dwStyle = dwStyle - WS_DLGFRAME

   End If

   dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle)

   SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME

End Sub



只要有窗口,这是我们的前提,你可以在运行时随便更改它的大小。

Public Sub ControlSize(ControlName As Control, SetTrue As Boolean)

   Dim dwStyle As Long

   dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE)

   If SetTrue Then

      dwStyle = dwStyle Or WS_THICKFRAME

   Else

      dwStyle = dwStyle - WS_THICKFRAME

   End If

   dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle)

   SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME

End Sub

通过以上几个实例,我们知道了如何使用VB没有提供的窗体风格样式。下面将根据我的实际经历介绍几个具体的窗体控件的某些比较使用的扩展功能。

有两个知识点不能不说,消息机制,子类(Subclass)。

Windows消息机制:

众所周知,Windows系统是靠消息来驱动的。一个应用程序内部的运行不是靠顺序,而是靠事件的触发来控制,而各个事件与组件间的通讯就是靠消息来完成的。

Windows是一个多任务的操作系统,在同一时刻,系统中有着多个应用程序的实例在运行。在这样的一个操作系统中,不可能像过去的DOS那样,由一个应用程序来享用所有的系统资源,这些资源是由Windows统一管理的。那么,系统如何协调各个应用实例的运行,如何为各个应用实例分配CPU时间呢?如何相应用户的输入呢?事实上,Windows时刻监视着用户的一举一动,并分析用户的动作与哪一个应用程序相关,然后,将用户的动作以消息的形式发送到系统中的消息队列,这个消息队列就是应用程序正常运行的基础,也是一条纽带,将应用程序中各个部分连接成一个整体来完成特定的任务。直到应用程序终止它会不停的检测系统的消息队列,对队列中未处理的消息进行分析,根据消息所包含的内容采取适当的动作来响应用户所作的操作。

举个简单的例子:

假设我们的应用程序的某个窗体有个File菜单,那么,在运行该应用程序的时候,如果用户单击了File菜单,这个动作将被Windows (而不是应用程序本身!)所捕获,Windows经过分析得知这个动作应该由上面所说的那个应用程序去处理,因此,Windows就发送了个叫做WM_COMMAND的消息给应用程序,该消息所包含的信息告诉应用程序:“用户单击了File菜单”,应用程序得知这一消息之后,采取相应的动作来响应它,在VB中默认会去执行File Click事件。这个过程被称为消息处理。Windows为每一个应用程序(确切地说是每一个线程)维护了相应的消息队列,应用程序的任务就是不停的从它的消息队列中获取消息,分析消息和处理消息,直到一条接到叫做WM_QUIT消息为止,这个过程通常是由一种叫做消息循环的程序结构来实现的。

   Do While GetMessage(wMsg, 0&, 0&, 0&)

     Call TranslateMessage(wMsg)’翻译消息

      Call DispatchMessage(wMsg)’ 撤去消息

   Loop

其中wMsg就是一个消息结构,它可以这样定义:

Public Type Msg

    hwnd As Long

    message As Long

    wParam As Long

    lParam As Long

    time As Long

    pt As POINTAPI

End Type

参数1:hwnd是消息要发送到的那个窗口的句柄,这个窗口就是咱们用CreateWindows函数创建的那一个。如果是在一个有多个窗口的应用程序中,用这个参数就可决定让哪个窗口接收消息。

参数2:message是一个数字,它唯一标识了一种消息类型。每种消息类型都在Windows文件中定义了,这些常量都以WM_开始后面带一些描述了消息特性的名称。比如说当应用程序退出时,Windows就向应用程序发送一条WM_QUIT消息。

参数3:一个32位的消息参数,这个值的确切意义取决于消息本身。

参数4:同上。

参数5:消息放入消息队列中的时间,在这个域中写入的并不是日期,而是从Windows启动后所测量的时间值。Windows用这个域来使用消息保持正确的顺序。

参数6:消息放入消息队列时的鼠标坐标.



消息循环以GetMessage调用开始,它从消息队列中取出一个消息:

GetMessage(&msg,NULL,0,0),第一个参数是要接收消息的MSG结构的地址,第二个参数表示窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息;第三,四参数指定消息范围。后面三个参数被设置为默认值,这就是说你打算接收发送到属于这个应用程序的任何一个窗口的所有消息。在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。如果GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其他消息,则返回TRUE。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。 均为NULL时就表示获取所有消息。

消息用GetMessage读入后(注意这个消息可不是WM_QUIT消息),它首先要经过函数TranslateMessage()进行翻译,这个函数会转换成一些键盘消息,它检索匹配的WM_KEYDOWN和WM_KEYUP消息,并为窗口产生相应的ASCII字符消息(WM_CHAR),它包含指定键的ANSI字符.但对大多数消息来说它并不起什么作用,所以现在没有必要考虑它。

下一个函数调用DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。我们在讲到登记窗口类时曾提到过,登记窗口类时,我们曾指定Windows把函数WindosProc作为咱们这个窗口的窗口过程(就是指处理这个消息的东东)。就是说,Windows会调用函数WindowsProc()来处理这个消息。在WindowProc()处理完消息后,代码又循环到开始去接收另一个消息,这样就完成了一个消息循环。

因此,从某种角度上来看,Windows应用程序是由一系列的消息处理代码来实现的。这和传统的过程式编程方法很不一样,编程者只能够预测用户所利用应用程序用户界面对象所进行的操作以及为这些操作编写处理代码,却不可以这些操作在什么时候发生或者是以什么顺序来发生,也就是说,我们不可能知道什么消息会在什么时候以什么顺序来临。那么windows是如何解决这个问题的呢?windows采用一种叫做回调函数(callback function)的特殊函数,这个函数由Windows直接调用。实际上每个窗口类都必须有一个回调函数。在Windows中消息循环和窗口类的回调函数已经都被封装起来,我们一般情况不会接触,如果我们想重新注册窗口过程函数WindowProc(就是这个回调函数),我们必须使用子类(Subclass)的技术。

(这部分说的可能比较多,而且都是开发Windows应用程序的基础部分(基础的东西虽然难度不大,但是非常重要)。但是因为对于部分VB程序员来说可能接触不多,因此说的多了点。)

子类(Subclass):

按照上文提到的因为对于正常的VB通常不能直接处理Windows系统的消息,但是我们可以通过子类的方法截获Windows消息并且自定义其处理方法(而如果想截获其他应用程序的消息就需要使用钩子(Hook)技术)。

应用程序可以用过SetWindowLong API 函数为具有窗口句柄(hWnd)的窗体、控件或其他对象安装新的消息处理(Message handler)过程函数WindowProc。这个新的WindowProc过程必须被定义在模块(.BAS)文件中。

Private Sub Form_Load()

    OldWindowProc = SetWindowLong( _

        hwnd, GWL_WNDPROC, _

        AddressOf NewWindowProc)

End Sub

现在,如果窗体收到Windows消息,系统将调用新的WindowProc 过程(NewWindowProc),这个新的窗口过程函数将检查当前的消息行为是否被指定,如果没有指定具体的行为,将被传递给源窗口过程函数WindowProc,有源WindowProc进行默认的处理。这个过程是非常重要的,否则因为当前窗口可能会因为消息遗失,造成不能进行重绘、更新等其他窗口默认的标准行为。而且新的窗口过程必须返回源过程函数返回的结果。

下面用一个实际代码例子演示处理WM_SYSCOMMAND消息的过程:

WM_SYSCOMMAND: 当用户选择“窗口菜单”的一条命令是触发。



Public Function NewWindowProc( _

    ByVal hwnd As Long, ByVal msg As Long, _

    ByVal wParam As Long, lParam As WINDOWPOS) As Long

Const WM_SYSCOMMAND = &H112

Const SC_SIZE = &HF000&



    ' 检查是否是WM_SYSCOMMAND消息

    If msg = WM_SYSCOMMAND Then

        ' 如果收到的消息是WM_SYSCOMMAND ,进一步检查命令参数是否是SC_SIZE, 如果是就忽略它,不进行任何处理。

        If (wParam And &HFFF0) = SC_SIZE Then Exit Function

    End If



    '*其余的消息传递给源窗口过程函数*非常重要

    NewWindowProc = CallWindowProc( _

        OldWindowProc, hwnd, msg, wParam, _

        lParam)

End Function



上面的过程函数首先检查收到的消息是否是WM_SYSCOMMAND消息,如果是,那么再进一步检查参数(wParam)是否是SC_SIZE命令。如果是表示窗体想要调整大小。但是我们自定义的窗口过程函数已经对它进行了处理,因此这个消息将不会被传递到源窗口过程函数。而我们自定义的这个窗口过程没有处理的消息将全部进一步传递给源窗口过程函数(它的地址保存在OldWindowProc中)。

需要注意的是,当我们卸载我们子类的对象前,我们必须恢复它的窗口过程函数。

Private Sub Form_Unload(Cancel As Integer)

    SetWindowLong hwnd, GWL_WNDPROC, OldWindowProc

End Sub



因为我们卸载一个窗口对象,系统会发送WM_NCDESTROY消息给对象,因此我们可以通过检测这个消息来自动恢复对象的源窗口过程。

Public Function NewWindowProc( ByVal hwnd As Long, ByVal msg As Long, _

    ByVal wParam As Long, lParam As WINDOWPOS) As Long

Const WM_NCDESTROY = &H82

Const WM_SYSCOMMAND = &H112

Const SC_SIZE = &HF000&



    ' 如果组件被销毁,恢复源窗口过程处理函数

    If msg = WM_NCDESTROY Then

        SetWindowLong   hwnd, GWL_WNDPROC,OldWindowProc

    End If



    If msg = WM_SYSCOMMAND Then

        If (wParam And &HFFF0) = SC_SIZE Then Exit Function

    End If

    NewWindowProc = CallWindowProc( _

        OldWindowProc, hwnd, msg, wParam, _

        lParam)

End Function



需要注意的一点是,这种方式很容易造成VB IDE的崩溃。不要在调试模式中途暂停或终于应用程序,因为这样可能不能恢复源窗口过程函数,造成无法处理正常的消息,变得异常或IDE崩溃,因此切记调试前一定存盘。

除了使用子类的方法,我没还可以使用几个API函数向对象主动发消息。我们可以用SendMessage和PostMessage:

PostMessage将消息直接加入到应用程序的消息队列中,不等程序返回就退出;而SendMessage()则刚好相反,应用程序处理完此消息后,它才返回,可以参考下图:



下面就对具体实际应用举几个例子:

TextBox控件:

a.   控制Textbox输入格式,我想大多人都遇到这个问题,在TextBox作为输入接口时,有时我们希望用户只能输入数字、大写、字母等,一般的做法是对用户输入的字符这个检查。但是如果我们使用API,将很容易实现这些功能,比如:

?   只允许输入数字:

Public Function NumbersOnly(tBox As TextBox)

    Dim DefaultStyle As Long

    DefaultStyle = GetWindowLong(tBox.hwnd, GWL_STYLE)

    NumbersOnly = SetWindowLong(tBox.hwnd, GWL_STYLE, DefaultStyle Or ES_NUMBER)

End Function

?   只允许大写:

Public Function UpperCaseOnly(tBox As TextBox)

    Dim DefaultStyle As Long

    DefaultStyle = GetWindowLong(tBox.hwnd, GWL_STYLE)

    UpperCaseOnly = SetWindowLong(tBox.hwnd, GWL_STYLE, DefaultStyle Or ES_UPPERCASE)

End Function

?   只允许小写:

Public Function LowerCaseOnly(tBox As TextBox)

    Dim DefaultStyle As Long

    DefaultStyle = GetWindowLong(tBox.hwnd, GWL_STYLE)

    LowerCaseOnly = SetWindowLong(tBox.hwnd, GWL_STYLE, DefaultStyle Or ES_LOWERCASE)

End Function

当然上边三个函数可以合成一个函数,因为他们方法是一样的,只是风格参数不同而已。

b.外观风格:



VB本身提供两种风格:Flat和3D,但是也许你想改变一下外观,比如让TextBox的边界介于Flat和3D之间那种效果,如图:



怎么做呢?在VC中我们在创建一个窗口对象时可以制定它的风格,但是在VB中,IDE已经按照它自己的想法给我创建好了,如果我们想要改变它只能把已经存在的进行修改,这时我们就需要借助的GetWindowLong和SetWindowLong兄弟的帮助来完成这个任务了。



Public Sub FlatBorder(ByVal hwnd As Long)

Dim TFlat As Long

‘首先将原始的窗口属性读出来

    TFlat = GetWindowLong(hwnd, GWL_EXSTYLE)

‘进行适当修改

    TFlat = TFlat And Not WS_EX_CLIENTEDGE Or WS_EX_STATICEDGE

‘写回去

    SetWindowLong hwnd, GWL_EXSTYLE, TFlat

‘这个函数能为窗口指定一个新位置和状态。它也可改变窗口在内部窗口列表中的位置。该函数与DeferWindowPos函数相似,只是它的作用是立即表现出来的(在vb里使用:针对vb窗体,如它们在win32下屏蔽或最小化,则需重设最顶部状态。如有必要,请用一个子类处理模块来重设最顶部状态)

    SetWindowPos hwnd, 0, 0, 0, 0, 0, SWP_NOACTIVATE Or SWP_NOZORDER Or SWP_FRAMECHANGED Or SWP_NOSIZE Or SWP_NOMOVE

End Sub

*当然上边的函数可以用在所有窗口对象上,只不够有些窗口对象不需要这么做。





如果窗体中有很多TextBox需要这样设置,而且不都是控件数组,那么可以在包装一下上面的函数:

Public Sub AddBorderToAllTextBoxes(frmX As Form)

   Dim X As Control

   On Error Resume Next

   For Each X In frmX.Controls

        If TypeOf X Is TextBox Then

                FlatBorder X.hWnd

        End If

   Next



End Sub



b.   改变文字布局:

VB 中可以设置TextBox中文本水平方向居左、居右、居中,但是不能设置垂直方向,也不能微调文本距离左边界的距离,但是我们还是可以借助API的帮助来完成这个需求:

?   文本垂直居中:

Public Sub VerMiddleText(mText As TextBox)

   Dim rc As RECT

   Dim tmpTop As Long

   Dim tmpBot As Long

   '实现这个效果首先TextBox的MultiLine属性必须为True(多行文本,其实这个属性关系创建TextBox内部使用哪个类,因此一旦创建就不能修改这个属性,所以不能在代码中修改这个属性)

   If mText.MultiLine = False Then Exit Sub

   '获得没个窗口区域的边界我们可以通过发送EM_GETRECT消息来获得

   Call SendMessage(mText.hwnd, EM_GETRECT, 0, rc)

   '进行位置数据计算

   With Me.Font

      .Name = mText.Font.Name

      .Size = mText.Font.Size

      .Bold = mText.Font.Bold

   End With

   tmpTop = ((rc.Bottom - rc.Top) - (mText.Parent.TextHeight("H") \ Screen.TwipsPerPixelY)) \ 2

   tmpBot = ((rc.Bottom - rc.Top) + (mText.Parent.TextHeight("H") \ Screen.TwipsPerPixelY)) \ 2

   rc.Top = tmpTop

   rc.Bottom = tmpBot

   mText.Alignment = vbCenter

   '数据计算完毕,再发送EM_SETRECTNP消息(为一个编辑控件设置格式化矩形,与EM_SETRECT类似,只是控件此时不会重画)

   Call SendMessage(mText.hwnd, EM_SETRECTNP, 0&, rc)

   mText.Refresh



End Sub

这样我们就达到了文本垂直居中的目的,其实只要用的熟了,找到切入点,还是很容易实现的。

?   调整边距:

如果你查看TextBox中常用的消息,你会发现有这样一对消息:EM_GETMARGINS 和EM_SETMARGINS,MSDN的解释是:获取和设置编辑控件的左、右边距(不得用于NT3.51)。具体是左还是右由该消息的参数决定。

看到这些也许你就知道我们可以用这两个消息完成我们的需求,好下面实际着手进行验证:

Private Sub SetMargin(nLeft As Integer, nRight As Integer, lhWnd As Long)

Dim lLongValue As Long



    '高四位表示右边距,低四位为左边距

    lLongValue = nRight * &H10000 + nLeft



    SendMessage lhWnd, EM_SETMARGINS, _

        EC_LEFTMARGIN Or EC_RIGHTMARGIN, lLongValue

End Sub

好经过测试目的达到,但是这样做有什么意义呢?有的时候如果你想在texebox中放入其他对象,而又不希望文本被覆盖掉,你就需要用到这个方法。


posted on 2003-11-17 12:35:00 by shannon  评论(25) 阅读(18235)

谢谢大家支持,我有信心多了

看到大家的回复,使我有信心多了,我虽然从这里删除,但是我没有放弃写,因为这只是初稿,我还会改N遍,还有很多东西没有定论,所以想全部写完在拿上来,因为我觉得很多VB程序员在开发VB应用程序时只知道使用VB提供的功能,VB有的就用,VB没有的也做不到,所以我希望写一篇文章,希望VB程序员能改变一下思路,把眼光放的更广一些,不要被VB说局限。

因此这篇文章不能不设计windows系统消息处理,VB的之类化(Subclass)等,但是说实话,这些我用的也不是非常熟,所以我还要酝酿一下,如果只是把文章开头放在这里,不能继续写下去,那还不如不写。

不过能得到大家认可,我就更加有动力了,中国不缺高手,但是刚刚起步的程序员更多,需要帮助的应该是他们。我在参与讨论过程,总是遇到重复的问题,所以我想干脆都写出来,让他看好了,否则老是重复回答,我都烦了。

posted on 2003-11-10 08:10:00 by shannon  评论(1) 阅读(879)

关于我写的这篇文章

对于MVP来说确实没什么技术含量,所以笨猫猫才会问我是不是在灌水。

不过虽然我没灌水的意思,但是就结果而言我可能达到了同样的效果。

其实我只是尝试着写点东西,因为我说的都是我曾经走过的路,我的本意是希望能对初学者有些帮助。不过想想可能这个地方没什么初学者光顾,因此我决定将它删除。

表示抱歉!但是我会把它写完!

posted on 2003-11-07 17:12:00 by shannon  评论(9) 阅读(2053)

Powered by: Joycode MVC Blogger System