本篇文章面对的对象是初级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中放入其他对象,而又不希望文本被覆盖掉,你就需要用到这个方法。 |
|