精华内容
下载资源
问答
  • ' inModelCode = Left(Right(Trim(StrData), 14), 9) '要删除 jim Dim TempStr9() As String Dim q As Integer TempStr9() = Split(Trim(strData), "\") For q = 0 To UBound(TempStr9()) If InStr(TempStr9(q)...
  • 本文主要讲的是VB中的指针,我看过的讲得最好的一篇文章。很底层的东西,有兴趣的可以看看,对学习Windows API编程很有帮助。若翻译有欠妥的地方,欢迎指正。原文网址:http://www.thevbzone.com/secrets.htm作者...

    1d3ad1763620205845599eddf6438d46.png

    本文是从我个人博客上搬过来的。翻译于去年(2018)。原博客文章将会删除。

    本文主要讲的是VB中的指针,是我看过的讲得最好的一篇文章。很底层的东西,有兴趣的可以看看,对学习Windows API编程很有帮助。若翻译有欠妥的地方,欢迎指正。


    • 原文网址:http://www.thevbzone.com/secrets.htm
    • 作者:Kevin Wilson
    • 目录:
    1. 介绍
    2. 在Visual Basic 中使用指针
    3. VarPtr,StrPtr和ObjPtr
    4. ByRef / ByVal
    5. AddressOf 和回调
    6. 访问“隐藏的”API

    1 介绍:

    Visual Basic被称为“快速应用程序开发(RAD)开发工具”,因为它旨在为你处理Windows“基础工作”,从而使你可以专注于重要的东西,如程序的功能和文档。

    例如,你打开VB,新建一个项目,然后在项目中添加一个标准的“窗体”,按“F5”来执行程序,该窗体就被显示出来,我们操作起来非常简单,但是,你可能不知道,VB在背后帮我们做了很多事情。 VB需要调用“CreateWindow”来实际创建“窗体”,并为构成它界面的属性赋值。 然后,需要通过调用各种Win32 API来修改窗体上的字体,前景色,背景色,设备上下文(device context)等。 最后,VB需要用子类化的方式,将新创建的窗体挂钩(hook)到Windows消息流中,让窗体捕获到发送给它的Windows消息,最后使用“WindowProc”回调函数,处理每个Windows消息。 窗体的接口越复杂,窗体对象的创建和功能处理代码就越复杂。 而C和C ++程序员就没有这么轻松了,他们需要亲自编写代码,来创建所有的对象,处理消息流以及销毁对象(或者通过模板来生成代码)。

    对于会正确使用VB开发工具的程序员来讲,Visual Basic能帮助你做一些类似上述“基础”的事情,是一个非常强大的功能。但是,与此同时,那些不太了解如何编程的人,也拥有了很大的能力。也正是这个原因,Visual Basic被C和C++程序员嘲笑。他们说,“任何人都可以用VB做开发,但只有真正的程序员才能使用C / C++做开发。我认为,聪明的程序员选择Visual Basic,因为VB在对象创建,消息处理,对象销毁过程中,可以帮你消除潜在的Bug。VB能提供更简单更快捷Windows事件处理,VB能为你提供更强大的界面功能,VB能让你更轻松的访问COM对象和第三方控件,VB更容易阅读,因为它非常接近阅读英语,而C/C++看上去却非常神秘。VB允许你轻松访问Win32 API (这使得程序员有驾驭Windows的强大功能的能力)。以及最最重要的是,Visual Basic可以通过组件,库,和其他用C/C++编写的代码来挂钩住(hook)C/C++的强大功能和速度。嘿。。。。。。C/C++程序员,现在怎么不继续吹牛逼了?^_^

    事情就是这样。。。。。。即使是工作多年的VB程序员也没有意识到VB的真正力量,因为他们没有掌握(或意识到)VB提供的一些关键概念和功能。这些概念很少被大家知道,或者受重视的程度还远远不够,因为我将它们称之为“VB的秘密”。

    2 在VB中使用指针:

    我曾经在求职面试中被问到一个问题,现在我意识到这是一个有坑的问题。“Visual Basic 是否有或者使用‘指针’?”任何用过VB的人都会显而易见的回答“No”,在VB中,你看不到任何像C/C++中那样的指针的声明,这就是当时我认为面试官要问的点,而她对我的答案也表示认同。然而,正确的答案应该是“Yes”。

    Visual Basic (就像几乎所有其他编程语言一样)确实使用指针,广泛的使用。不同的是,Visual Basic会尽可能的将它们隐藏起来,或者将它们称为不同的东西,以免给你带来负担。

    接下来,我们谈谈,如何使用指针直接访问变量中的信息(VarPtr / StrPtr / ObjPtr),通过指针将信息传递给函数(ByRef / ByVal),取回和传递指向函数的指针(AddressOf)。

    3 VarPtr, StrPtr 和 ObjPtr:

    VB函数“VarPtr”(变量指针),“StrPtr”(字符串指针)和“ObjPtr”(对象指针)是无官方文档(undocumented)无技术支持的(unsupported)的函数,微软在VB5.0和VB6.0中引入。这些函数(跟随很多其他的函数一起)在http://VB.Net中不再使用。这些函数允许你获取VB变量在内存中的地址(指针),以及变量所指向的实际数据在内存中的地址。这些函数为什么作用这么大?因为,如果你知道数据的内存地址,你可以任意操控它,直接复制或者传递出去,而不需要借助VB的帮助。这样做,速度上更快,并且(在某些情况下)你可以做到VB本身做不到的事情。

    下面是微软MSDN关于“VarPtr”的说明:

    该函数可以用来获取一个变量或数组元素的地址。它使用变量名或数组元素作为参数,返回它的地址。然而,你需要注意的是,未锁定的动态数组可能会被Visual Basic重新分配地址,所以你当你使用VarPtr获取数组元素地址时,必须非常小心。
    下面的示例获取变量的地址:
    Dim lngVariableAddress As Long
    Dim dblMyVariable As Double
    lngVariableAddress = VarPtr(dblMyVariable)
    下面的示例获取某个数组的第4个元素的地址:
    Dim lngElementAddress As Long
    Dim lngArrayOfLongs(9) As Long
    '下面的代码将获取数组中第4个元素的地址
    lngElementAddress = VarPtr(lngArrayOfLongs(3))
    限制:VarPtr函数不能用来获取数组的地址。。。。。。

    下面是微软MSDN关于“StrPtr”的说明:

    在Visual Basic 中,字符串是以BSTR来存储的。如果你对一个字符串变量使用VarPtr,你将得到BSTR的地址,它是该字符串指针的指针。要获取字符串缓冲本身的地址,你需要使用StrPtr函数。该函数返回字符串第一个字符的地址。需要考虑到在Visual Basic中,字符串是以UNICODE来存储的。
    要获取一个字符串的第一个字符的地址,请将该字符串变量传递给StrPtr函数。
    示例:
    Dim lngCharAddress As Long
    Dim strMyVariable As String
    strMyVariable = "Some String"
    LngCharAddress = StrPtr(strMyVariable)
    当你需要传递一个指向UNICODE字符串指针给API时,你可以使用StrPtr函数。

    下面是微软MSDN关于“ObjPtr”的说明:

    ObjPtr函数使用一个对象变量名作为参数,获取该对象变量所引用的接口的地址。
    一种试用该函数的情况,是当你需要处理集合对象的时候。相比使用Is操作符遍历集合对象而言,通过使用对象地址作为索引关键字,你可以获取更快的访问速度。在很多情况下,对象的地址是唯一可以信赖的键值。
    示例:
    objCollection.Add MyObj1, CStr(ObjPtr(MyObj1))
    '...
    objCollection.Remove CStr(ObjPtr(MyObj1))

    注意在“VarPtr”说明的底部的“限制”,它说你不能使用VarPtr获取数组的地址。在某种程度上,说得没错。你不能将变量“MyArray”传递给它(因为VB将数组保存在一个叫做“SafeArray”的OLE对象中),但是,如果你获取了数据的第一个元素“MyArray(0)”的地址,你就有了整个数组的地址,因为数组元素在内存中是连续存储的(按照数字顺序从第一个元素到最后一个元素)。所以,如果某个Win32 API 或者C / C++ 函数需要一个指向某个字节数组的指针,像这样:

    Option Explicit
    Private Type POINTAPI
       X As Long
       Y As Long
    End Type
     
    'BOOL Polyline(
    '  HDC          hDC,    // handle of device context
    '  CONST POINT *lpPT,   // address of array containing endpoints
    '  int          cPoints // number of points in the array
    ');
    Private Declare Function Polyline Lib "GDI32.DLL" (ByVal hDC As Long, _
            ByRef lpPT As Any, ByVal cPoints As Long) As Long

    你可以像这样调用它:

    Private Sub Form_Load()
      Dim ThePoints() As POINTAPI
      Me.AutoRedraw = True
      Me.Visible = True
      Me.Move 0, 0, Me.Width, Me.Height
      ReDim ThePoints(1 To 5) As POINTAPI
      ThePoints(1).X = 0:   ThePoints(1).Y = 0
      ThePoints(2).X = 100: ThePoints(2).Y = 0
      ThePoints(3).X = 100: ThePoints(3).Y = 100
      ThePoints(4).X = 0:   ThePoints(4).Y = 100
      ThePoints(5).X = 0:   ThePoints(5).Y = 0
      If Polyline(Me.hDC, ByVal VarPtr(ThePoints(1)), 5) = 0 Then Debug.Print "FAILED!"
      Me.Refresh
      Erase ThePoints
    End Sub
    

    注意:将坐标点(pointers)保存到动态数组中要小心,因为当动态数组重新分配内存地址后,或者动态数组改变了大小后,或者被重新定义(ReDim)后,实际数据将非常有可能被保存到一个新的内存地址。

    关于VarPtr,StrPtr,ObjPtr的更多信息,可以参见以下链接:

    http://support.microsoft.com/default.aspx?scid=kb;en-us;Q199824

    http://msdn.microsoft.com/library/en-us/dnw32dev/html/ora_apiprog6_topic1.asp

    http://msdn.microsoft.com/library/en-us/dnovba00/html/LightningStrings.asp

    http://msdn.microsoft.com/library/en-us/dnovba01/html/Lightweight.asp

    4 ByRef / ByVal

    到目前为止,VB程序员在调用Win32 API(或任何C/C++导出函数)时遇到的最大的问题,就是参数的正确传递。在应该使用“ByVal”的地方使用了“ByRef”(或者反过来也一样),或者当函数期望传入一个指针时,你传入的却是一个值或者一个变量,这些都可能会导致系统崩溃。要想搞懂如何正确的传递参数,就要了解Windows程序如何调用堆栈的(calling stacks),以及主调程序与被调函数之间的内存分配。

    首先,我们来讨论一下什么是“调用堆栈”,以及当传递参数给函数时,它是怎样受内存分配影响的。“调用堆栈”说白了,就是内存中的一段空间,那些传递给函数的变量和值,以及函数返回值都存储在这段内存空间中。它被叫做“堆栈”(stack)是因为参数值是一个挨着一个被存储在这段内存空间中,访问这些参数值时,也是按照这种假定来访问。正因为如此,从纵向角度从下到上来看,这些参数一个堆在另一个上面,组成了要给到函数的全部参数信息。当参数被添加到函数的调用堆栈上时,它被称为“压入”(push)到调用堆栈上面。当参数从函数的调用堆栈移除的时候,它被称之为从堆栈“弹出”(pop)。“堆栈”,“压入”,“弹出”这些都是汇编术语(是的,我们现在讨论得非常底层),如果你将程序或者DLL反编译到汇编语言,你会看到一些代码行上有“push”,“pop”等单词。

    当Visual Basic调用Win32 API(或者任何导出的C/C++函数)时,它期望被调函数使用“标准调用惯例”(Standard Calling Convention)(_stdcall),这与C/C++默认的调用惯例(_cdecl)刚好不同。这就意味着,当函数被调用的时候,参数被从右到左传入到内存中(或者压入到堆栈上面),被调函数负责清理参数的内存(或者从堆栈弹出)。如果调用一个声明为任何非“标准调用惯例”_stdcall的导出函数,Visual Basic不知道如何处理堆栈和传出传入的参数,VB就会跳出信息说“错误的DLL调用惯例”(Bad DLL Calling Convention)。

    调用参数时内存如何分配和收回?调用堆栈是什么?以及它们在Windows中是怎么工作的?想知道更多更深入的解释,我强烈的推荐一本Dan Appleman写的书:“Dan Appleman’s Win32 API Puzzle Book and Tutorial for Visual Basic Programmers”。

    现在我们跳出底层的内存运作机制,回到VB上面来。调用函数时,给它传递参数的方式有2种,要么传递给它明确的值,要么传递给它一个指针,该指针指向内存中的某个存储了值的地址。当你传递像数字,大小,标志等简单信息时,你希望用值的方式(ByVal: By Value)传递信息,因为你想让函数获取你所传递的值,而不是这个值当前被存储在内存中的地址。现在当你想要传递更加复杂的数据的时候,比如一个数据类型,一组数组,或者一个对象引用,你需要传递一个引用(或指针)到被调函数,告诉它数据在内存中的地址是多少。这是通过指定ByRef(By Reference)关键字完成的。这样,被调函数到内存中找到这个地址,读取相关的数据。这里有一个例外,当你传递字符串(String)参数给Win32 API函数时(或者任何C/C++导出函数),请使用ByVal方式(除非你传递字符串数组,这种情况下使用ByRef,或者用ByVal传递字符串数组的第一个元素)。

    所以到现在为止,你可以说:“我已经知道ByRef/ByVal这两种方式传递参数了”。是的,但是你意识到通过ByRef传递参数时,是在传递指针吗?如果你把这个概念再往前推进一步,你可以让函数接口变得更加通用:把通过引用传递(ByRef)改为通过值传递(ByVal),然后传递一个明文的指针。所以,你可以像这样声明函数:

    Option Explicit
     
    Private Type RECT
      Left   As Long
      Top   As Long
      Right   As Long
      Bottom   As Long
    End Type
     
    'int FillRect(
    '  HDC         hDC,  // handle to device context
    '  CONST RECT *lpRC, // pointer to structure with rectangle
    '  HBRUSH      hBR   // handle to brush
    ');
    Private Declare Function FillRect Lib "USER32.DLL" (ByVal hDC As Long, _
            ByVal lpRC As Long, ByVal hBR As Long) As Long
    Private Declare Function CreateSolidBrush Lib "GDI32.DLL" (ByVal crColor As Long) As Long
    Private Declare Function DeleteObject Lib "GDI32.DLL" (ByVal hObject As Long) As Long
     
    Private Sub Form_Load()
      Dim hBrush As Long
      Dim MyRect As RECT
      
      Me.AutoRedraw = True
      Me.Visible = True
      Me.Move 0, 0, Me.Width, Me.Height
      With MyRect
        .Top = 0: .Left = 0: .Right = 100: .Bottom = 100
      End With
      
      hBrush = CreateSolidBrush(vbRed)
      If hBrush = 0 Then Exit Sub
      If FillRect(Me.hDC, VarPtr(MyRect), hBrush) = 0 Then Debug.Print "FAILED!"
      Me.Refresh
      DeleteObject hBrush
    End Sub

    如果你仔细想想的话,在声明函数和参数的时候,它可以给你各种选择。你不在局限于特定的变量类型。你可以将所有参数都变成“Long”型变量,然后将指针传递给任何函数参数(只要你小心翼翼的话)。比如说在传递自定义类型的时候遇到麻烦了,忘掉它,传递指针就好了。再比如在传递对象的时候遇到麻烦了,忘掉它,传递指针就好了。VB5.0中不允许将变量数组作为函数返回类型,忘掉它,返回一个长整型,指向数组的内存地址,然后用API函数CopyMemory将它拷贝到本地数组中。看出来我要干什么了吗?

    联合ByRef和ByVal一起使用VarPtr,StrPtr和ObjPtr,可以让你传递任何格式的数据,如果你知道自己在做什么的话。

    5 AddressOf 和回调

    操作符“AddressOf”跟回调(call back)息息相关。“但什么是回调?”你问。回调是VB事件在Windows中的等价物。事实上,从底层来讲,当回调函数捕获了以Windows系统消息形式存在的原始事件之后,回调函数会触发VB事件。回调函数常见于Win32 API内部(和其他C/C++代码中),特别是当你的应用程序中需要监测用户和/或Windows的活动的时候。在VB中你不怎么看到回调函数,是因为VB通过“事件”(Events)处理消息和通知,这种方式比起使用回调函数更加简单和安全。

    假如说,在你的项目中,Windows发送给窗体的每一个消息你都想收到通知,(很多消息也许你根本不会用到),当然还有一些由于其他API调用,可能发送到你的窗体的个性化消息。要想达到这个目的,你所要做的就是设置一个被Windows认可的回调函数(“WindowProc”),然后告诉Windows(通过“SetWindowLong” API函数)将它的所有与你的窗体有关的消息发送给你的回调函数,这样你就能够检查它们,对消息做出反应,将消息传递下去(通过“CallWindowProc” API函数)。所有这一切,就叫做“子类化”(Sub-Classing),功能非常强大(同时也是非常危险的)的技术,你可以用这个技术来个性化的重画你的窗体,菜单和内容(或你想对你的窗体干的任何事情)。

    使用“AddressOf”有2个缺点:

    1)你只能获取VB标准模块中的函数或子程序(公有或私有)的地址。没有办法能绕过这个限制。

    2)它只能被作为函数或子程序参数列表的一部分被调用。绕过这个限制的办法如下:

    Option Explicit
     
    Public Sub Main()
      Dim lngProcAddress As Long
      lngProcAddress = GetProcAddress(AddressOf WindowProc)
    End Sub
     
    Public Function GetProcAddress(ByVal lngAddressOf As Long) As Long
      GetProcAddress = lngAddressOf
    End Function
     
    Public Function WindowProc(ByVal hWnd As Long, ByVal uMsg As Long, _
                               ByVal wParam As Long, ByVal lParam As Long) As Long
      ' < YOUR CODE GOES HERE >
    End Function

    你会注意到我们传递“AddressOf”和我们想得到地址(内存指针)的函数名到“GetProcAddress”函数,该函数只是简单的返回地址值,简单又非常有效。函数和子程序的地址不会变,所以你可以将想要调用的函数和子程序的地址保存下来,而不必在每次调用的时候都使用AddressOf。

    “那我们来看看实际应用吧!”你说。ok!

    如上所述,这里有一个“子类化”的示例:

    '标准模块中的代码
    Option Explicit
     
    Private Const GWL_WNDPROC = (-4)
    Private lngPrevProc As Long
    Private lngHWND As Long
     
    Private Declare Function SetWindowLong Lib "USER32.DLL" Alias "SetWindowLongA" _
            (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
    Private Declare Function CallWindowProc Lib "USER32.DLL" Alias "CallWindowProcA" _
            (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal uMsg As Long, _
            ByVal wParam As Long, ByVal lParam As Long) As Long
     
    '这就是回调函数,接收发送给hWnd的消息
    Private Function WindowProc(ByVal hWnd As Long, _
                                ByVal uMsg As Long, _
                                ByVal wParam As Long, _
                                ByVal lParam As Long) As Long
      '在立即窗口中显示消息和参数
      '注意:你可以通过比较“uMsg”的值与Windows消息(WM_*) 常量(在WINUSER.H文件中定义)
      '来找到什么消息被发送了。
      Debug.Print _
      "hWnd=" & hWnd & ", uMsg=" & uMsg & ", wParam=" & wParam & ", lParam=" & lParam
      
      '传送消息到它本应该去的地方。这里,这是必须的,否则窗体将因为停止接收消息而变得无响应
      WindowProc = CallWindowProc(lngPrevProc, hWnd, uMsg, wParam, lParam)
    End Function
     
    '该函数启动一个新的子类化
    Public Function Subclass_Start(ByVal hWnd As Long) As Boolean
      '停掉任何之前的子类化
      If Subclass_Stop = False Then Exit Function
      '尝试开始一个新的子类化
      lngPrevProc = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WindowProc)
      If lngPrevProc <> 0 Then
        lngHWND = hWnd
        Subclass_Start = True
      End If
    End Function
     
    '该函数停止任何已经存在的子类化
    Public Function Subclass_Stop() As Boolean
      '如果之前的子类化未启动,就退出
      If lngPrevProc = 0 Or lngHWND = 0 Then
        Subclass_Stop = True
      Else
        '将消息处理程序设置回原始状态
        If SetWindowLong(lngHWND, GWL_WNDPROC, lngPrevProc) <> 0 Then
          Subclass_Stop = True
        End If
      End If
      '清除使用过的变量
      lngPrevProc = 0
      lngHWND = 0
    End Function
    
    '窗体代码
    Option Explicit
     
    Private Sub Form_Load()
      Subclass_Start Me.hWnd
    End Sub
     
    Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
      Subclass_Stop
    End Sub

    下面是一个“列举”的示例,它是反馈Windows列表信息非常流行的方法(例如所有窗体列表,窗体上所有对象的列表,已安装的所有字体列表等):

    Option Explicit
     
    Private lngWinHandle() As Long
    Private lngWinHandleCount As Long
     
    Private Declare Function EnumWindows Lib "USER32.DLL" (ByVal lpEnumFunc As Long, _
            ByVal lParam As Long) As Long
     
    '这就是回调函数,它会遍历当前桌面下所有的窗口
    Private Function EnumWindowsProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
      '递增窗口句柄数组的大小
      lngWinHandleCount = lngWinHandleCount + 1
      ReDim Preserve lngWinHandle(1 To lngWinHandleCount) As Long
      '将信息添加到数组
      lngWinHandle(lngWinHandleCount) = hWnd
      '让函数继续执行
      EnumWindowsProc = 1
    End Function
     
    Public Function GetAllWindows(ByRef Return_Handles() As Long, _
           Optional ByRef Return_WinCount As Long) As Boolean
      '清除掉之前的信息
      Erase lngWinHandle
      lngWinHandleCount = 0
      '开始遍历窗体
      If EnumWindows(AddressOf EnumWindowsProc, 0) <> 0 Then
        Return_Handles = lngWinHandle
        Return_WinCount = lngWinHandleCount
        GetAllWindows = True
      End If
      Erase lngWinHandle
      lngWinHandleCount = 0
    End Function
    Option Explicit
    Private Sub Form_Load()
      Dim lngWindows() As Long
      Dim lngWindowsCount As Long
      Dim lngCounter As Long
      Dim strWindows As String
      If GetAllWindows(lngWindows, lngWindowsCount) = True Then
        If lngWindowsCount > 0 Then
          For lngCounter = 1 To lngWindowsCount
            strWindows = strWindows & " " & lngWindows(lngCounter) & Chr(13)
          Next
        End If
        Me.AutoRedraw = True
        Me.Print strWindows
      End If
      Erase lngWindows
    End Sub

    6 访问“隐藏的”API

    这部分绝对是这篇文章所述所有秘密中最“秘密”的部分。在Windows中确实有很多隐藏的Win32 API调用……诀窍是找到它们并找出如何调用它们,因为微软肯定不会告诉你。

    但是微软为什么要隐藏它们呢?”你可能会问。因为这些隐藏的函数为API添加了额外的功能,只有微软知道如何使用它们。这使得他们自己的产品(在Windows下运行)可以获得优势地位。因为只有他们知道如何访问这些更强大的,更快的隐藏着的API函数,而其他人只能使用官方在MSDN中公开披露的正规的API函数。“不公平的竞争优势”?你的想法的一点没错!但是谁也没有说过微软在公平的展开业务,或者有道德的做生意。这些商业行为正不断地将微软置于法庭和报纸的头条新闻中。

    “这些隐藏的API都是什么样的?如何找出它们?如何使用它们?”非常好的问题。网络上有很多网页,它们致力于找到这些隐藏的API,纰漏它们的功能给“平等的竞争环境”,并为像你我这样的开发者提供更酷的功能。这里有一些很好的网页:

    • http://www.geocities.com/SiliconValley/4942/index.html(无法访问,译者注)
    • http://www.users.qwest.net/~eballen1/nt.sekrits.html(无法访问,译者注)
    • http://www.mvps.org/vbnet/code/shell/undocshelldlgs.htm
    • http://www.mvps.org/vbnet/code/shell/undocformatdlg.htm
    • http://www.mvps.org/vbnet/code/shell/undocchangeicondlg.htm
    • http://www.mvps.org/vbnet/code/shell/undocshpaths.htm
    • http://www.ercb.com/ddj/1992/ddj.9211.html(无法访问,译者注)

    你可以在“VB标准模块”下的modCOMDLG32.bas模块中找到一些这些“隐藏的API”。它们看起来像这样:

    Public Declare Function DLG_FindFile Lib "shell32.dll" Alias "#90" _
           (ByVal pidlRoot As Long, ByVal pidlSavedSearch As Long) As Long
    Public Declare Function DLG_FindComputer Lib "shell32.dll" Alias "#91" _
           (ByVal pidlRoot As Long, ByVal pidlSavedSearch As Long) As Long

    你会注意到它们的别名是数字“#90”,“#91”。这些被称为“序数”,它们是一种通过DLL公开API而不暴露它们的名称的方法。由此可见,如果你编写了一个函数,只想给自己使用,而不想让其他人知道该函数的更多信息,那么你可以通过数字公开它。除了一个数字,其他人对此是一无所知的,但只有你知道如何调用它。

    偷偷摸摸的,是吧?:)

    好了!这就是我要说的所有内容了。如果我想到了VB中的其他的秘密,或者“隐藏的”和“模糊的”功能(或想起来我还打算放在这里的任何内容),我会把它们加进来。编码愉快!

    (全文结束)

    展开全文
  • # 想要实现的效果如图所示!...vb什么控件可以类似Excel直接往表格输入数据,可以增加空白行呢? 如果有这样的控件请大神指点一二,如果需要写代码,还望大神动动手赐教下!!!感激不尽!!
  • ❝现在Vue几乎公司里都用,所以掌握Vue至关重要,这里我总结了几点,希望对大家有用❞1、Vue项目什么要在列表组件写key,作用是什么?我们在业务组件,会经常使用循环列表,当时用v-for命令时,会在后面写上...

    现在Vue几乎公司里都用,所以掌握Vue至关重要,这里我总结了几点,希望对大家有用

    1、Vue项目中为什么要在列表组件中写key,作用是什么?

    我们在业务组件中,会经常使用循环列表,当时用v-for命令时,会在后面写上:key,那么为什么建议写呢?

    key的作用是更新组件时判断两个节点是否相同。相同则复用,不相同就删除旧的创建新的。正是因为带唯一key时每次更新都不能找到可复用的节点,不但要销毁和创建节点,在DOM中还要添加移除节点,对性能的影响更大。所以才说,当不带key时,性能可能会更好。因为不带key时,节点会复用(复用是因为Vue使用了Diff算法),省去了销毁或创建节点的开销,同时只需要修改DOM文本内容而不是移除或添加节点。既然如此,为什么我们还要建议带key呢?因为这种不带key的模式只适合渲染简单的无状态的组件。对于大多数场景来说,列表都得必须有自己的状态。避免组件复用引起的错误。带上key虽然会增加开销,但是对于用户来说基本感受不到差距,「为了保证组件状态正确,避免组件复用」,这就是为什么建议使用key。

    2、Vue的双向绑定,Model如何改变View,View又是如何改变Model的?

    我们先看一幅图,下面一幅图就是Vue双向绑定的原理图。bdcd1919ac9399675ed62245a727963a.png

    第一步,使数据对象变得“可观测”

    我们要知道数据在什么时候被读或写了。

       let person = {
            'name''maomin',
            'age'23
        }
        let val = 'maomin';
        Object.defineProperty(person, 'name', {
            get() {
                console.log('name属性被读取了')
                return val
            },
            set(newVal) {
                console.log('name属性被修改了')
                val = newVal
            }
        })
        // person.name
        // name属性被读取了
        // "maomin"
        // person.name='xqm'
        // name属性被修改了
        // "xqm"

    通过Object.defineProperty()方法给person定义了一个name属性,并把这个属性的读和写分别使用get()set()进行拦截,每当该属性进行读或写操作的时候就会触发get()set()。这样数据对象已经是“可观测”的了。

    核心是利用es5Object.defineProperty,这也是Vue.js为什么不能兼容IE8及以下浏览器的原因。

    Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

        Object.defineProperty(
            obj, // 定义属性的对象
            prop, // 要定义或修改的属性的名称
            descriptor // 将要定义或修改属性的描述符【核心】
        )

    「写一个简单的双向绑定:」

    html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Documenttitle>
    head>
    <body>
        <input type="text" id="input"/>
        <div id="text">div>
    body>
    <script>let input = document.getElementById('input');let text = document.getElementById('text');let data = {value:''};Object.defineProperty(data,'value',{set:function(val){
                text.innerHTML = val;
                input.value = val;
            },get:function(){return input.value;
            }
        });
        input.onkeyup = function(e){
            data.value = e.target.value;
        }
    script>
    html>
    第二步,使数据对象的所有属性变得“可观测”

    上面,我们只能观测person.name的变化,那么接下来我们要让所有的属性都变得可检测。

        let person = observable({
            'name''maomin',
            'age'23
        })
        /**
         * 把一个对象的每一项都转化成可观测对象
         * @param { Object } obj 对象
         */

        function observable(obj{
            if (!obj || typeof obj !== 'object') {
                return;
            }
            let keys = Object.keys(obj); //返回一个表示给定对象的所有可枚举属性的字符串数组
            keys.forEach((key) => {
                defineReactive(obj, key, obj[key])
            })
            return obj;
        }
        /**
         * 使一个对象转化成可观测对象
         * @param { Object } obj 对象
         * @param { String } key 对象的key
         * @param { Any } val 对象的某个key的值
         */

        function defineReactive(obj, key, val{
            Object.defineProperty(obj, key, {
                get() {
                    console.log(`${key}属性被读取了`);
                    return val;
                },
                set(newVal) {
                    console.log(`${key}属性被修改了`);
                    val = newVal;
                }
            })
        }
        // person.age
        // age属性被读取了
        // 23
        // person.age=24
        // age属性被修改了
        // 24

    我们通过Object.keys()将一个对象返回一个表示给定对象的所有可枚举属性的字符串数组,然后遍历它,使得所有对象可以被观测到。

    第三步,依赖收集,制作一个订阅器

    我们就可以在数据被读或写的时候通知那些依赖该数据的视图更新了,为了方便,我们需要先将所有依赖收集起来,一旦数据发生变化,就统一通知更新。创建一个依赖收集容器,也就是消息订阅器Dep,用来容纳所有的“订阅者”。订阅器Dep主要负责收集订阅者,然后当数据变化的时候后执行对应订阅者的更新函数。

    「设计了一个订阅器Dep类:」

       class Dep {
            constructor(){
                this.subs = []
            },
            //增加订阅者
            addSub(sub){
                this.subs.push(sub);
            },
            //判断是否增加订阅者
            depend () {
                if (Dep.target) {
                    this.addSub(Dep.target)
                }
            },
            //通知订阅者更新
            notify(){
                this.subs.forEach((sub) =>{
                    sub.update()
                })
            }
        }
    Dep.target = null;

    创建完订阅器,然后还要修改一下defineReactive

    function defineReactive (obj,key,val{
            let dep = new Dep();
            Object.defineProperty(obj, key, {
                get(){
                    dep.depend(); //判断是否增加订阅者
                    console.log(`${key}属性被读取了`);
                    return val;
                },
                set(newVal){
                    val = newVal;
                    console.log(`${key}属性被修改了`);
                    dep.notify() //数据变化通知所有订阅者
                }
            })
        }

    我们将订阅器Dep添加订阅者的操作设计在get()里面,这是为了让订阅者初始化时进行触发,因此需要判断是否要添加订阅者。

    第四步,订阅者Watcher

    「设计一个订阅者Watcher类:」

      class Watcher {
        // 初始化
            constructor(vm,exp,cb){
                this.vm = vm; // 一个Vue的实例对象
                this.exp = exp; // 是node节点的v-model或v-on:click等指令的属性值。如v-model="name",exp就是name;
                this.cb = cb; // 是Watcher绑定的更新函数;
                this.value = this.get();  // 将自己添加到订阅器的操作
            },
      // 更新
            update(){
                let value = this.vm.data[this.exp];
                let oldVal = this.value;
                if (value !== oldVal) {
                    this.value = value;
                    this.cb.call(this.vm, value, oldVal);
                },
            get(){
                Dep.target = this;  // 缓存自己
                let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
                Dep.target = null;  // 释放自己
                return value;
            }
        }

    订阅者Watcher在初始化的时候需要将自己添加进订阅器Dep中,如何添加呢?我们已经知道监听器Observer是在get()执行了添加订阅者Wather的操作的,所以我们只要在订阅者Watcher初始化的时候触发对应的get()去执行添加订阅者操作即可。那要如何触发监听器get(),再简单不过了,只要获取对应的属性值就可以触发了。

    订阅者Watcher运行时,首先进入初始化,就会执行它的 this.get() 方法, 执行Dep.target = this;,实际上就是把Dep.target 赋值为当前的渲染 Watcher ,接着又执行了let value = this.vm.data[this.exp];。在这个过程中会对数据对象上的数据访问,其实就是为了触发数据对象的get()

    每个对象值的get()都持有一个dep,在触发 get()的时候会调用 dep.depend()方法,也就会执行this.addSub(Dep.target),即把当前的 watcher订阅到这个数据持有的dep.subs中,这个目的是为后续数据变化时候能通知到哪些 subs 做准备。完成依赖收集后,还需要把 Dep.target恢复成上一个状态Dep.target = null; 因为当前vm的数据依赖收集已经完成,那么对应的渲染Dep.target 也需要改变。

    update()是用来当数据发生变化时调用Watcher自身的更新函数进行更新的操作。先通过let value = this.vm.data[this.exp];获取到最新的数据,然后将其与之前get()获得的旧数据进行比较,如果不一样,则调用更新函数cb进行更新。


    总结:

    实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。

    bdcd1919ac9399675ed62245a727963a.png
    在这里插入图片描述
    实现一个Vue数据绑定:

    「index.html」

    html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Documenttitle>
    head>
    <body>
        <h1 id="name">h1>
        <input type="text">
        <input type="button" value="改变data内容" onclick="changeInput()">
    <script src="observer.js">script>
    <script src="watcher.js">script>
    <script>function myVue (data, el, exp{this.data = data;
            observable(data);                      //将数据变的可观测
            el.innerHTML = this.data[exp];         // 初始化模板数据的值new Watcher(this, exp, function (value{
                el.innerHTML = value;
            });return this;
        }var ele = document.querySelector('#name');var input = document.querySelector('input');var myVue = new myVue({name'hello world'
        }, ele, 'name');//改变输入框内容
        input.oninput = function (e{
            myVue.data.name = e.target.value
        }//改变data内容function changeInput(){
            myVue.data.name = "改变后的data"
        }
    script>
    body>
    html>

    「observer.js」(为了方便,这里将订阅器与监听器写在一块)

         // 监听器
         // 把一个对象的每一项都转化成可观测对象
         // @param { Object } obj 对象
         
        function observable (obj{
            if (!obj || typeof obj !== 'object') {
                return;
            }
            let keys = Object.keys(obj);
            keys.forEach((key) =>{
                defineReactive(obj,key,obj[key])
            })
            return obj;
        }
         // 使一个对象转化成可观测对象
         // @param { Object } obj 对象
         // @param { String } key 对象的key
         // @param { Any } val 对象的某个key的值
         
        function defineReactive (obj,key,val{
            let dep = new Dep();
            Object.defineProperty(obj, key, {
                get(){
                    dep.depend();
                    console.log(`${key}属性被读取了`);
                    return val;
                },
                set(newVal){
                    val = newVal;
                    console.log(`${key}属性被修改了`);
                    dep.notify()                    //数据变化通知所有订阅者
                }
            })
        }

     // 订阅器Dep 
        class Dep {
            
            constructor(){
                this.subs = []
            }
            //增加订阅者
            addSub(sub){
                this.subs.push(sub);
            }
            //判断是否增加订阅者
            depend () {
                if (Dep.target) {
                    this.addSub(Dep.target)
                }
            }

            //通知订阅者更新
            notify(){
                this.subs.forEach((sub) =>{
                    sub.update()
                })
            }
            
        }
        Dep.target = null;

    「watcher.js」

     class Watcher {
            constructor(vm,exp,cb){
                this.vm = vm;
                this.exp = exp;
                this.cb = cb;
                this.value = this.get();  // 将自己添加到订阅器的操作
            }
            get(){
                Dep.target = this;  // 缓存自己
                let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
                Dep.target = null;  // 释放自己
                return value;
            }
            update(){
                let value = this.vm.data[this.exp];
                let oldVal = this.value;
                if (value !== oldVal) {
                    this.value = value;
                    this.cb.call(this.vm, value, oldVal);
                }
        }
    }

    3、Vue的computed与watch的区别在哪里?

    我们先看一个例子:

    html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Documenttitle>
    head>
    <body>
        <div id="app">
           <p>{{a}}p>
           <p>{{b}}p>
           <p>{{c}}p>
           <button @click='change'>changebutton>
        div>
    body>
    <script src="vue.js">script>
    <script>var vm =new Vue({el:'#app',data:{a:1,b:2
            },methods:{
                change(){this.a = 5;
                }
            },watch:{
                a(){console.log('watch');
                }
            },computed:{
                c(){console.log('computed');return this.a + this.b;
                }
            }
        })
    script>
    html>

    一开始的时候,8da9e090dc2a2678fa8978b14c5aa380.png点击按钮时,e6d7ceb860c15d878a549913e3558e31.png我们可以看到一开始的时候,打印出了computed,当点击按钮时,data内的属性值a发生变化,打印出watch,接着我们不停点击按钮,并没有打印。(?查看总结4)

    我们来总结一下,

    1. 最本质的区别,computed为计算属性,watch为监听属性。
    2. watch就是单纯的监听某个数据的变化,支持深度监听。computed是计算属性,是依赖于某个或者某些属性值,当依赖值发生变化时,也会发生变化。
    3. 计算属性不在data中,计算属性依赖值在data中。watch监听的数据在data中。(不一定在只是data,也可能是props)
    4. watch用于观察和监听页面上的vue实例,当你需要在数据变化响应时,执行异步操作,或高性能消耗的操作,那么watch为最佳选择。computed可以关联多个实时计算的对象,当这些对象中的其中一个改变时都会触发这个属性,具有缓存能力,所以只有当数据再次改变时才会重新渲染,否则就会直接拿取缓存中的数据。
    5. computed是在Dep.update()执行之后,数据更新之前,对数据重新改造。watch是在set刚开始发生的时候添加的回调,可以监听数据的变化。

    4、为什么在Vue3.0采用了Proxy,抛弃了Object.defineProperty?

    Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。为了解决这个问题,经过Vue内部处理后可以使用以下几种方法来监听数组。

    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()

    由于只针对以上八种方法进行了hack处理,所以其他数组的属性方法也是检测不到的,还是具有一定的局限性。

    这里我们举个例子,可以看得更加明白:

    html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Documenttitle>
    head>
    <body>
        <div id="app">
            <ul>
                <li v-for='item in watchArr'>{{item.name}}li>
            ul>
            <button @click='change'>changebutton>
        div>
    body>
    <script src="vue.js">script>
    <script>var vm = new Vue({el'#app',data: {watchArr: [{name'1',
                },{name'2',
                }],
            },methods: {
                change() {this.watchArr =[{name'3',
                    }];this.watchArr.splice(01);this.watchArr[0].name = 'xiaoyue'// 无法监听this.watchArr.length = 5// 无法监听
                }
            },watch: {
                watchArr(newVal) {console.log('监听了');
                },
            }
        })
    script>
    html>

    想必看到上面的例子我们会更加明白Object.defineProperty的局限性。接下来,我们接着说Object.defineProperty只能劫持对象的属性,因此,我们需要对每个对象的每个属性进行遍历。Vue2.0里,是通过「递归+遍历data对象」来实现对数据的监控的,如果属性值也是对象的话,那么需要深度遍历。显然如果能够劫持一个完整的对象才是更好的选择。

    那么Proxy有以下两个优点:

    1. 可以劫持整个对象,并返回一个新对象
    2. 有13种劫持操作

    摒弃 Object.defineProperty,基于Proxy的观察者机制探索

    5、为什么Vuex的mutation不能做异步操作?

    因为更改state的函数必须是纯函数,纯函数既是统一输入就会统一输出,没有任何副作用;如果是异步则会引起额外的副作用,导致更改后的state不可预测。

    6、Vue中的computed是如何实现的?

    实质是一个惰性的wather,在取值操作时根据自身标记dirty属性返回上一次计算结果或重新计算值在创建时就进行一次取值操作,收集依赖变动的对象或属性(将自身压入dep中),在依赖的对象或属性变动时,仅将自身标记dirty致为true

    7、Vue的父组件和子组件的生命周期钩子函数执行顺序是什么?

    1. 加载渲染过程 (父)beforeCreate 「→」  (父)created 「→」  (父)beforeMount 「→」 (子)beforeCreate 「→」 (子)created 「→」 (子)beforeMount 「→」 (子)mounted 「→」 (父)mounted
    2. 子组件更新过程 (父)beforeUpdate 「→」 (子)beforeUpdate 「→」 (子)Updated 「→」  (父)Updated
    3. 父组件更新过程 (父)beforeUpdate 「→」   (父)Updated
    4. 销魂过程 (父)beforeDestroy 「→」  (子)beforeDestory 「→」 (子)destroyed  「→」 (父)destroyed

    作者:「Vam的金豆之路」

    主要领域:「前端开发」

    我的微信:「maomin9761」

    微信公众号:「前端历劫之路」

    0b38ecbb1a118621db4820e64f044899.png

    展开全文
  • VB中如何对所连接的数据库进行添加、更新和删除数据操作呢??? 插入新数据: INSERT INTO 语句,所需做的声明想向哪个表插入数据、向哪一列中插入数据,以及插入什么数据,基本语法如下: INSERT INTO ...

    在VB中如何对所连接的数据库进行添加、更新和删除数据操作呢???

    插入新数据:

    INSERT INTO 语句,所需做的是声明想向哪个表插入数据、向哪一列中插入数据,以及插入什么数据,基本语法如下:

    INSERT INTO table_name (column_name) VALUES (data_values)

    插入数据只需简单地在表名后面的圆括号中列举出每一列的名称(以逗号隔开),在VALUES后面的圆括号中,简单地列举匹配列的每个数据项,以逗号隔开。字符和日期数据必须位于一个单引号之内。

    为了检查添加的数据是否正确,使用RDBMS的管理工具查看表中的数据,或者使用下面的SQL语句查看:这个语句会显示表中所有的数据。

    SELECT * FROM table_name

    更新数据:

    UPDATE语句,插入新数据和更新已经存在的数据之间的主要差别在于需要指明要更改那些记录。一般用WHERE字句来指明需要更新哪些记录,该字句允许声明只有那些满足一定条件的记录才被更新。基本语法:

    UPDATE table_name
    SET column_name = value
    WHERE condition

    WHERE字句:

    AND和OR逻辑运算符允许在一个WHERE字句中测试多个条件。

    AND字句单独位于一行中,可以提高代码的可读性

    删除数据:

    DELETE语句,只需简单地指定从哪一个表中删除记录,并且如果需要,则添加一个WHERE子句以指定要删除哪些记录。

    • 删除表中所有记录的语法:(执行之后,则需要重新输入所有的记录)
    DELETE FROM table_name
    • 删除表中部分记录的语法:
    DELETE FROM table_name  WHERE column_name = value
    展开全文
  • 在使用VB 6.0中文企业版时,删除某些控件时会出错,VB直接闪退。删除label、textbox等控件时会闪退,而删除按钮等其它控件时不会出错,这什么?怎么解决? 求帮忙,急!!! ...

    在使用VB 6.0中文企业版时,删除某些控件时会出错,VB直接闪退。删除label、textbox等控件时会闪退,而删除按钮等其它控件时不会出错,这是为什么?怎么解决?
    求帮忙,急!!!

    展开全文
  • VB.NET监视文件夹的变化摘要 : 有时候,处于业务的需要,我们要不断的监视文件的变化,比如文件的创建、删除、重命名等;而且某些人要确保重要文件的只读,并且要及时的报告文件版本的变更情况。或者,你想监视你...
  • Hello,大家好,很多人觉得快捷键很low,没什么技术含量,学不会都一样,其实并不,将一些快捷键组合在一起使用,也能解决我们工作的许多问题,今天就分大家分享下如何使用快捷键达到合并同类项的目的,效果如下...
  • 2、SQL语句备份一项,实际进行的追加到现有媒体,所以很无奈的在每次操作之前都进行了一次删除,请问重写媒体的语句该怎么写; 3、另外在指定路径的文件夹不存在时,如何自动生成; 4、当然如果可以,再帮我检测...
  • 什么Adodc1.Recordset.RecordCount=0?** ![图片说明](https://img-ask.csdn.net/upload/201904/13/1555153948_790377.jpg) # 详细代码 ``` '删除现有用户的确定事件 Private Sub CmdSure2_Click() ...
  • 有高手么,我还遇到个千年费解的问题,学了那么久楞看不懂,下面的一段代码 可以说无懈可击,但是运行时候datagrid控件什么内容都不显示,数据库记录确实被删除啦,但是奇怪的datagrid控件的内容变成空白,...
  • B:不完全,因为纤程要先ConvertThreadToFiber,才能CreateFiber,VB中就一个线程,你把它Convert成纤程,那纤程删除时整个窗体也就结束了,所以我要再建一个线程,在该线程内部调用ConvertThreadToFiber…… ...
  • VB程序设计综合复习题 1、叙述Visual Basic的特点。 2、当窗体上有三个文本框和一个命令按钮,若程序运行时,焦点定位在第三个文本框(Text3)处,应对何控件的什么属性进行何种设置? 3、若要对窗体上的三个文本框...
  •  B:不完全,因为纤程要先ConvertThreadToFiber,才能CreateFiber,VB中就一个线程,你把它Convert成纤程,那纤程删除时整个窗体也就结束了,所以我要再建一个线程,在该线程内部调用ConvertThreadToFiber…… ...
  • 每次他的电脑的“收藏夹”都乱七八糟的,而且在网吧“收藏夹”的用处并不是很打,时间稍长就什么信息都有了,所以我帮他写了个小东西--屏蔽了IE的“收藏夹”,隐藏了开始菜单的“收藏夹”,并且删除了windows...
  • 关于为什么要写这个文本过滤器:例如,总共有8000条警告要修复,我负责的警告类型有6000条,而其中错误输出的有5500条,我真正要修复的只有500条,修复500条已经够头晕的了,还要在8000条警告要人工分辨那些属于...
  • 关于为什么要写这个文本过滤器:例如,总共有8000条警告要修复,我负责的警告类型有6000条,而其中错误输出的有5500条,我真正要修复的只有500条,修复500条已经够头晕的了,还要在8000条警告要人工分辨那些属于...
  • 如果使用本软件过程,想恢复到默认配置,请到\bin目录下删除config.ini文件,重启本软件即可. 预期下个新版本功能: 增加歌手列表功能.有时间的话会尽早做.或者您有什么好的建议告诉我. QQ:87867861
  • ├─Example022-在VB中制作和使用菜单 ├─Example023-设计弹出菜单 ├─Example024-向菜单中添加图标 ├─Example025-动态装入菜单项 ├─Example026-动态创建控件 ├─Example027-具有动感的图片按钮 ├─Example...
  • MhUI第三版,前两版因为思路不清析,结构复杂,所以虽然实现了功能,但也什么用的 这版第三版,尝鲜版,只是部分开源 'lionking1990版权所有 '未经许可不得用于商业活动 '如转载,请勿删除声明 尝鲜版仅部分代码...
  • VB.NET利用正则表达式巧妙限制字符输入

    千次阅读 热门讨论 2014-07-17 20:46:10
    在通常的程序设计,对字符串的操作不可缺少的部分。例如,我们做的机房收费系统,里面有很多的文本框,按照不同的需要,我们要输入不同的内容,卡号要输入数字,姓名框也要限制输入字符的长度。有时候甚至还需要...
  • '----------------------------欢迎参考 本品作者:7秒的记忆-------------------------------- '作者的话: '因为半成品,所以没有做好看的皮肤和外观等,也没做关闭什么的,都后期美化的事情了(photoshop刚...
  • VB.NET 入门经典 (第三版)

    热门讨论 2009-07-29 16:56:26
    在介绍完创建应用程序的基础知识后(如创建窗口和对话框,如何用代码处理突发事件,什么是面向对象的程序设计,以及如何在应用程序使用它等)。 目录: 第1章 初识Visual Basic .NET 1 1.1 Windows和DOS编程 1 1.2...
  • 在介绍完创建应用程序的基础知识后(如创建窗口和对话框,如何用代码处理突发事件,什么是面向对象的程序设计,以及如何在应用程序使用它等)。 目录: 第1章 初识Visual Basic .NET 1 1.1 Windows和DOS编程 1 1.2...
  • Hello,大家好,在生活忘掉密码一件很常见的事情,但是密码的找回或者重置就不那么容易了,那么当excel工作表的密码被遗忘有没有什么办法将密码删除呢,当然有了,今天跟大家分享一种快速删除excel工作表密码...
  • 编程人员必备的东东,比如有许多行代码或字符串,要在每个字符串前、后加什么字符,要删除字符,替换字符,删除前面,后面几个字符,删除多行代码的空白行。或交换某特定字符的前后内容。编程过程,用这个可以...

空空如也

空空如也

1 2 3 4 5 6
收藏数 105
精华内容 42
关键字:

vb中什么是删除