VB6: Slow Binary Write? - vb6

Wondering why a particular binary write operation in VB is so slow. The function reads a Byte array from memory and dumps it into a file like this:
Open Destination For Binary Access Write As #1
Dim startP, endP As Long
startP = BinaryStart
endP = UBound(ReadBuf) - 1
Dim i as Integer
For i = startP To endP
DoEvents
Put #1, (i - BinaryStart) + 1, ReadBuf(i)
Next
Close #1
For two megabytes on a slower system, this can take up to a minute. Can anyone tell me why this is so slow?
Edit: The reason for choosing VB6 is that it runs on 100% of our target platforms as a single EXE, without any separate dependencies (except VBRUN, which is on pretty much everything).

Well, are you reading and writing each byte 1 by one? In that case you are iterating through 2 million elements instead of just taking a chunk of data at a time and write it to the stream.

Take out the DoEvents call. If you're writing two megabytes of data one byte at a time, that loop has 2097152 DoEvents calls. That will really really slow down the process.

Dim startP, endP As Long -- here you declare startP as Variant and endP as Long.
DoEvents -- yields control to the OS, calling on each iteration makes virtually any loop endless.
And then, if you want to save a piece of an array to a file, that should be...
Hmm... What should it be then?
Option 1.
Declare another array to hold the piece, CopyMemory the data into it and put it into a file using a single Put:
Put #1, , arrName
That, however, may be not wise memory-wise.
Hence, Option 2.
Create an array that refers to the data in the big array. This way nothing will be allocated twice:
Dim bigArray(1 To 1000) As Byte
Dim chunk() As Byte
Dim i As Long
'Filling the array for test purposes
For i = LBound(bigArray) To UBound(bigArray)
bigArray(i) = Rnd * 254
Next
'Create an array that refers to 100 bytes from bigArray, starting from 500th
CreateSAFEARRAY ArrPtr(chunk), 1, VarPtr(bigArray(500)), 1, 100
Open "c:\1.txt" For Binary Access Write As #1
Put #1, , chunk
Close #1
'Always destroy it manually!
DestroySAFEARRAY ArrPtr(chunk)
This code requires the following helper functions (put in a separate module):
Option Explicit
Private Declare Function SafeArrayAllocDescriptor Lib "oleaut32" (ByVal cDims As Long, ppsaOut As Any) As Long
Private Declare Function SafeArrayDestroyDescriptor Lib "oleaut32" (psa As Any) As Long
Public Declare Function GetMem4 Lib "msvbvm60" (ByVal pSrc As Long, ByVal pDst As Long) As Long
Public Declare Function PutMem4 Lib "msvbvm60" (ByVal pDst As Long, ByVal NewValue As Long) As Long
Public Declare Function PutMem8 Lib "msvbvm60" (ByVal pDst As Long, ByVal NewValueLow As Long, ByVal NewValueHigh As Long) As Long
Private Const S_OK As Long = 0
Public Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (arr() As Any) As Long
Public Function CreateSAFEARRAY(ByVal ppBlankArr As Long, ByVal ElemSize As Long, ByVal pData As Long, ParamArray Bounds()) As Long
Dim i As Long
If (UBound(Bounds) - LBound(Bounds) + 1) Mod 2 Then Err.Raise 5, "SafeArray", "Bounds must contain even number of entries."
If SafeArrayAllocDescriptor((UBound(Bounds) - LBound(Bounds) + 1) / 2, ByVal ppBlankArr) <> S_OK Then Err.Raise 5
GetMem4 ppBlankArr, VarPtr(CreateSAFEARRAY)
PutMem4 CreateSAFEARRAY + 4, ElemSize
PutMem4 CreateSAFEARRAY + 12, pData
For i = LBound(Bounds) To UBound(Bounds) - 1 Step 2
If Bounds(i + 1) - Bounds(i) + 1 > 0 Then
PutMem8 CreateSAFEARRAY + 16 + (UBound(Bounds) - i - 1) * 4, Bounds(i + 1) - Bounds(i) + 1, Bounds(i)
Else
SafeArrayDestroyDescriptor ByVal CreateSAFEARRAY
CreateSAFEARRAY = 0
PutMem4 ppBlankArr, 0
Err.Raise 5, , "Each dimension must contain at least 1 element"
End If
Next
End Function
Public Function DestroySAFEARRAY(ByVal ppArray As Long) As Long
GetMem4 ppArray, VarPtr(DestroySAFEARRAY)
If SafeArrayDestroyDescriptor(ByVal DestroySAFEARRAY) <> S_OK Then Err.Raise 5
PutMem4 ppArray, 0
DestroySAFEARRAY = 0
End Function

Related

mouse_event not defining and DllImport not working

I'm using Visual Basic 6.0 (I know, it's outdated, but what can I say? I'm old-timey like that). I've been trying to get a program to work that makes the mouse click on a predetermined point on the screen after clicking a button (that's not what the whole program will be, but I'm still building it and this is my roadblock)
[System.Runtime.InteropServices.DllImport("user32.dll")]
Private Declare Function mouse_event Lib "user32.dll" Alias "mouse_event()" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Private Const MOUSEEVENTF_LEFTDOWN = &H2
Private Const MOUSEEVENTF_LEFTUP = &H4
Public Function Mouse_LeftClick()
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
End Function
Private Sub Command1_Click()
Dim X As Long
Dim Y As Long
Dim mouse_x As Long
Dim mouse_y As Long
X = CLng(1285)
Y = CLng(134)
mouse_x = CLng(X * 65535 / Screen.Width)
mouse_y = CLng(Y * 65535 / Screen.Height)
i = mouse_event(MOUSEEVENTF_ABSOLUTE + MOUSEEVENTF_MOVE, mouse_x, mouse_y, 0, 0)
n = Mouse_LeftClick()
End Sub
Private Sub Command2_Click()
End
End Sub
(This is literally the entire program so far)
To be clear, I'm relatively new at this, so this code was almost entirely taken from another website. But I have since forgotten what that website was, unfortunately.
Now, before I had that first line there (the DllImport), VB6 was telling me that "mouse_event()" didn't exist in user32.dll -- which, to my understanding, it still doesn't.
Once I researched that problem, though, I found the DllImport line which I placed exactly where the internet told me to place it, but now that very same line is producing this error message:
Compile error:
Invalid outside procedure
...Which only confused me, since I got that from people who actually knew what they were doing (I assumed so, anyways).
Anyways, it's been so long since this program's worked, I can't actually remember the last time I was able to run it without getting some critical error that ends the program, so I turn to StackOverflow to tell me what I've done terribly wrong with my code.
Thank you in advance for anyone who offers their help.
Remove the DLLImport stuff. It's VB.Net, it's not valid VB6 at all.
[System.Runtime.InteropServices.DllImport("user32.dll")]
And then, like Hans said, remove the parentheses from the alias in the Declare.

How to return a byte array as a variant in vb6

I've been trying to figure out why the function below throws an error saying "Type Mismatch" when it returns. From what I know about VB6, this should work without any issue, yet it obviously does not. Can anyone see what I am doing wrong here and let me know how I can fix it? Thanks in advance.
Private Function GetByteArray(source As Variant, index As Integer, length As Integer) As Variant
Dim buff() As Byte
ReDim buff(0 To length - 1)
Dim i As Integer
For i = 0 To length - 1
buff(i) = CByte(source(index + i))
Next i
GetByteArray = buff
End Function
It turns out that the problem did not have anything to do with the Function I posted, but rather with what I was doing with the result. I was using the method to get the bytes of a double, and then using CDbl to get the double value. This is where the error was really happening.
The way I should have been doing this is to use the following code:
CopyMemory rfcTest.rfcFloat, GetByteArray(buff, 0, 8), Len(rfcTest.rfcFloat)
Note that in order to use this, you must also declare the CopyMemoryMethod like this:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)

Using CryptHashData On Very Large Input

I am trying to MD5 hash user-supplied data (a file) using The Crypto functions in AdvApi32. All is well and good unless the file is very large (hundreds of MB. or larger) in which case I eventually get an OutOfMemory exception.
I figured that the solution would be to make repeated calls to CryptHashData using the same HashObject and processing only (for example) 4096 bytes at a time.
This appears to work, but the returned hash is incorrect.
Function HashFile(File As FolderItem) As String
Declare Function CryptAcquireContextW Lib "AdvApi32" (ByRef provider as Integer, container as Integer, providerName as WString, _
providerType as Integer, flags as Integer) as Boolean
Declare Sub CryptDestroyHash Lib "AdvApi32" (hashHandle as Integer )
Declare Function CryptCreateHash Lib "AdvApi32" (provider as Integer, algorithm as Integer, key as Integer, flags as Integer, _
ByRef hashHandle as Integer) as Boolean
Declare Function CryptHashData Lib "AdvApi32" (hashHandle as Integer, data as Ptr, length as Integer, flags as Integer) as Boolean
Declare Function CryptGetHashParam Lib "AdvApi32" (hashHandle as Integer, type as Integer, value as Ptr, ByRef length as Integer, _
flags as Integer) as Boolean
Const HP_HASHVAL = &h0002
Const HP_HASHSIZE = &h0004
Const MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0"
Const PROV_RSA_FULL = 1
Const CRYPT_NEWKEYSET = &h00000008
Const CALG_MD5 = &h00008003
Dim provider As Integer
Dim hashHandle As Integer
If Not CryptAcquireContextW(provider, 0, MS_DEF_PROV, PROV_RSA_FULL, 0) Then
If Not CryptAcquireContextW(provider, 0, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET) Then
Raise New RuntimeException
End If
End If
If Not CryptCreateHash(provider, CALG_MD5, 0, 0, hashHandle) Then
Raise New RuntimeException
End If
Dim dataPtr As New MemoryBlock(4096)
Dim bs As BinaryStream
bs = bs.Open(File)
dataPtr.StringValue(0, 4096) = bs.Read(4096)
Do
If CryptHashData(hashHandle, dataPtr, dataPtr.Size, 0) Then
dataPtr = New MemoryBlock(4096)
dataPtr.StringValue(0, 4095) = bs.Read(4096)
End If
Loop Until bs.EOF
Dim size as Integer = 4
Dim toss As New MemoryBlock(4)
If Not CryptGetHashParam(hashHandle, HP_HASHSIZE, toss, size, 0) Then
Raise New RuntimeException
End If
size = toss.UInt32Value(0)
Dim hashValue As New MemoryBlock(size)
If Not CryptGetHashParam(hashHandle, HP_HASHVAL, hashValue, size, 0) Then
Raise New RuntimeException
End If
CryptDestroyHash(hashHandle)
//Convert binary to hex
Dim hexvalue As Integer
Dim hexedInt As String
Dim src As String = hashValue.StringValue(0, hashValue.Size)
For i As Integer = 1 To LenB(src)
hexvalue = AscB(MidB(src, i, 1))
hexedInt = hexedInt + RightB("00" + Hex(hexvalue), 2)
next
Return LeftB(hexedInt, LenB(hexedInt))
End Function
What am I doing wrong here? The output I get is consistent, but wrong.
Did you check that msdn example on C++ ?
Very similar answer to your question.
I think the problem is that since you read the data in blocks of 4096 bytes - when the data is not a multiple of 4096 you endup including unwanted trailing 0's or possibly garbage values. Try bs.Read(1) instead of bs.Read(4096) in the loop: Loop Until bs.EOF in-order to test if correct hash is being calculated now. If successful adjust your loop to tackle the remainder (%4096) bytes separately.

Why does my ReadFile call fail if it is not called within the same function as CreatePipe?

I'm having trouble using pipes in a larger application and so I created a minimal test application to investigate the problem.
I'm creating a pipe:
Dim sa As SECURITY_ATTRIBUTES
Dim R As Long
sa.nLength = Len(sa)
sa.bInheritHandle = 1
R = CreatePipe(hRead, hWrite, sa, 0) //hRead declared globally
Debug.Print "CreatePipe: " & R
and then I'm reading from it:
Const BufSize As Long = 1024
Dim Buffer(BufSize) As Byte
Dim lBytesRead As Long
Dim R As Long
R = ReadFile(hRead, Buffer(0), BufSize, lBytesRead, 0)
Debug.Print "ReadFile: " & R
Debug.Print Err.LastDllError
Now as far as I understand the ReadFile call should block, because nobody has written any data into the pipe.
The problem: That only happens iff I put the code right after the CreatePipe code. As soon as I put it into a separate function, it fails with the last error being ERROR_INVALID_HANDLE. (I confirmed that the value of hRead does not change)
I have absolutely no idea what's causing this kind of behaviour.
I found the solution myself. It was a rather stupid beginner's mistake, but I found some users having the same problem and since the error symptoms where not at all pointing in the right direction, I'll post my solution.
First I did further refactorings to dumb down the code even more. After making each and every variable global, I finally got a "invalid calling convention" error on the ReadFile call.
To make a long story short, the import declaration and the actual call of ReadFile were wrong for the last parameter (the OVERLAPPED parameter)
This is what I did:
Declare Function ReadFile ..., lpOverlapped As Any) As Long
Call ReadFile(..., 0)
Correct would be any one of these:
Declare Function ReadFile ..., lpOverlapped As Any) As Long
Call ReadFile(..., ByVal 0)
Declare Function ReadFile ..., ByVal lpOverlapped As Long) As Long
Call ReadFile(..., 0)
Declare Function ReadFile ..., lpOverlapped As OVERLAPPED) As Long
Call ReadFile(..., myVariableOfTypeOverlapped)

Application Title Cut Off In VB6

Platform: Windows XP
Development Platform: VB6
When trying to set an application title via the Project Properties dialog on the Make tab, it seems to silently cut off the title at a set number of characters. Also tried this via the App.Title property and it seems to suffer from the same problem. I wouldn't care about this but the QA Dept. insists that we need to get the entire title displayed.
Does anyone have a workaround or fix for this?
Edit: To those who responded about a 40 character limit, that's what I sort of suspected--hence my question about a possible workaround :-) .
Actually I posted this question to try to help a fellow developer so when I see her on Monday, I'll point her to all of your excellent suggestions and see if any of them help her get this straightened out. I do know that for some reason some of the dialogs displayed by the app seem to pick up the string from the App.Title setting which is why she had asked me about the limitation on the length of the string.
I just wish I could find something definitive from Microsoft (like some sort of KB note) so she could show it to our QA department so they'd realize this is simply a limitation of VB.
The MsgBox-Function takes a parameter for the title. If you dont want to change every single call to the MsgBox-Function, you could "override" the default behavior:
Function MsgBox(Prompt, Optional Buttons As VbMsgBoxStyle = vbOKOnly, Optional Title, Optional HelpFile, Optional Context) As VbMsgBoxResult
If IsMissing(Title) Then Title = String(40, "x") & "abc"
MsgBox = Interaction.MsgBox(Prompt, Buttons, Title, HelpFile, Context)
End Function
Edit: As Mike Spross notes: This only hides the normal MsgBox-Function. If you wanted to access your custom MsgBox from another project, you would have to qualify it.
I just created a Standard EXE project in the IDE and typed text into the application title field under the Project Properties Make tab until I filled the field. From this quick test, it appears that App.Title is limited to 40 characters. Next I tried it in code by putting the following code in the default form (Form1) created for the project:
Private Sub Form_Load()
App.Title = String(41, "X")
MsgBox Len(App.Title)
End Sub
This quick test confirms the 40-characater limit, because the MsgBox displays 40, even though the code attempts to set App.Title to a 41-character string.
If it's really important to get the full string to display in the titlebar of a Form, then only way I can think of to ensure that the entire title is displayed would be to get the width of the titlebar text and use that to increase the width of your Form so that it can accommodate the complete title string. I may come back and post code for this if I can find the right API incantations, but it might look something like this in the Form_Load event:
Dim nTitleBarTextWidth As Long
Dim nNewWidth As Long
Me.Caption = "My really really really really really long app title here"
' Get titlebar text width (somehow) '
nTitleBarTextWidth = GetTitleBarTextWidth()
' Compute the new width for the Form such that the title will fit within it '
' (May have to add a constant to this to make sure the title fits correctly) '
nNewWidth = Me.ScaleX(nTitleBarTextWidth, vbPixels, Me.ScaleMode)
' If the new width is bigger than the forms current size, use the new width '
If nNewWidth > Me.Width Then
Form.Width = nNewWidth
End If
One solution using the Windows API
Disclaimer: IMHO this seems like overkill just to meet the requirement stated in the question, but in the spirit of giving a (hopefully) complete answer to the problem, here goes nothing...
Here is a working version I came up with after looking around in MSDN for awhile, until I finally came upon an article on vbAccelerator that got my wheels turning.
See the vbAccelerator page for the original article (not directly related to the question, but there was enough there for me to formulate an answer)
The basic premise is to first calculate the width of the form's caption text and then to use GetSystemMetrics to get the width of various bits of the window, such as the border and window frame width, the width of the Minimize, Maximize, and Close buttons, and so on (I split these into their own functions for readibility/clarity). We need to account for these parts of the window in order to calculate an accurate new width for the form.
In order to accurately calculate the width ("extent") of the form's caption, we need to get the system caption font, hence the SystemParametersInfo and CreateFontIndirect calls and related goodness.
The end result all this effort is the GetRecommendedWidth function, which calculates all of these values and adds them together, plus a bit of extra padding so that there is some space between the last character of the caption and the control buttons. If this new width is greater than the form's current width, GetRecommendedWidth will return this (larger) width, otherwise, it will return the Form's current width.
I only tested it briefly, but it appears to work fine. Since it uses Windows API functions, however, you may want to exercise caution, especially since it's copying memory around. I didn't add robust error-handling, either.
By the way, if someone has a cleaner, less-involved way of doing this, or if I missed something in my own code, please let me know.
To try it out, paste the following code into a new module
Option Explicit
Private Type SIZE
cx As Long
cy As Long
End Type
Private Const LF_FACESIZE = 32
'NMLOGFONT: This declaration came from vbAccelerator (here is what he says about it):'
' '
' For some bizarre reason, maybe to do with byte '
' alignment, the LOGFONT structure we must apply '
' to NONCLIENTMETRICS seems to require an LF_FACESIZE '
' 4 bytes smaller than normal: '
Private Type NMLOGFONT
lfHeight As Long
lfWidth As Long
lfEscapement As Long
lfOrientation As Long
lfWeight As Long
lfItalic As Byte
lfUnderline As Byte
lfStrikeOut As Byte
lfCharSet As Byte
lfOutPrecision As Byte
lfClipPrecision As Byte
lfQuality As Byte
lfPitchAndFamily As Byte
lfFaceName(LF_FACESIZE - 4) As Byte
End Type
Private Type LOGFONT
lfHeight As Long
lfWidth As Long
lfEscapement As Long
lfOrientation As Long
lfWeight As Long
lfItalic As Byte
lfUnderline As Byte
lfStrikeOut As Byte
lfCharSet As Byte
lfOutPrecision As Byte
lfClipPrecision As Byte
lfQuality As Byte
lfPitchAndFamily As Byte
lfFaceName(LF_FACESIZE) As Byte
End Type
Private Type NONCLIENTMETRICS
cbSize As Long
iBorderWidth As Long
iScrollWidth As Long
iScrollHeight As Long
iCaptionWidth As Long
iCaptionHeight As Long
lfCaptionFont As NMLOGFONT
iSMCaptionWidth As Long
iSMCaptionHeight As Long
lfSMCaptionFont As NMLOGFONT
iMenuWidth As Long
iMenuHeight As Long
lfMenuFont As NMLOGFONT
lfStatusFont As NMLOGFONT
lfMessageFont As NMLOGFONT
End Type
Private Enum SystemMetrics
SM_CXBORDER = 5
SM_CXDLGFRAME = 7
SM_CXFRAME = 32
SM_CXSCREEN = 0
SM_CXICON = 11
SM_CXICONSPACING = 38
SM_CXSIZE = 30
SM_CXEDGE = 45
SM_CXSMICON = 49
SM_CXSMSIZE = 52
End Enum
Private Const SPI_GETNONCLIENTMETRICS = 41
Private Const SPI_SETNONCLIENTMETRICS = 42
Private Declare Function GetTextExtentPoint32 Lib "gdi32" Alias "GetTextExtentPoint32A" _
(ByVal hdc As Long, _
ByVal lpszString As String, _
ByVal cbString As Long, _
lpSize As SIZE) As Long
Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As SystemMetrics) As Long
Private Declare Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" ( _
ByVal uAction As Long, _
ByVal uParam As Long, _
lpvParam As Any, _
ByVal fuWinIni As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Private Declare Function CreateFontIndirect Lib "gdi32" Alias "CreateFontIndirectA" (lpLogFont As LOGFONT) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Function GetCaptionTextWidth(ByVal frm As Form) As Long
'-----------------------------------------------'
' This function does the following: '
' '
' 1. Get the font used for the forms caption '
' 2. Call GetTextExtent32 to get the width in '
' pixels of the forms caption '
' 3. Convert the width from pixels into '
' the scaling mode being used by the form '
' '
'-----------------------------------------------'
Dim sz As SIZE
Dim hOldFont As Long
Dim hCaptionFont As Long
Dim CaptionFont As LOGFONT
Dim ncm As NONCLIENTMETRICS
ncm.cbSize = LenB(ncm)
If SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, ncm, 0) = 0 Then
' What should we do if we the call fails? Change as needed for your app,'
' but this call is unlikely to fail anyway'
Exit Function
End If
CopyMemory CaptionFont, ncm.lfCaptionFont, LenB(CaptionFont)
hCaptionFont = CreateFontIndirect(CaptionFont)
hOldFont = SelectObject(frm.hdc, hCaptionFont)
GetTextExtentPoint32 frm.hdc, frm.Caption, Len(frm.Caption), sz
GetCaptionTextWidth = frm.ScaleX(sz.cx, vbPixels, frm.ScaleMode)
'clean up, otherwise bad things will happen...'
DeleteObject (SelectObject(frm.hdc, hOldFont))
End Function
Private Function GetControlBoxWidth(ByVal frm As Form) As Long
Dim nButtonWidth As Long
Dim nButtonCount As Long
Dim nFinalWidth As Long
If frm.ControlBox Then
nButtonCount = 1 'close button is always present'
nButtonWidth = GetSystemMetrics(SM_CXSIZE) 'get width of a single button in the titlebar'
' account for min and max buttons if they are visible'
If frm.MinButton Then nButtonCount = nButtonCount + 1
If frm.MaxButton Then nButtonCount = nButtonCount + 1
nFinalWidth = nButtonWidth * nButtonCount
End If
'convert to whatever scale the form is using'
GetControlBoxWidth = frm.ScaleX(nFinalWidth, vbPixels, frm.ScaleMode)
End Function
Private Function GetIconWidth(ByVal frm As Form) As Long
Dim nFinalWidth As Long
If frm.ControlBox Then
Select Case frm.BorderStyle
Case vbFixedSingle, vbFixedDialog, vbSizable:
'we have an icon, gets its width'
nFinalWidth = GetSystemMetrics(SM_CXSMICON)
Case Else:
'no icon present, so report zero width'
nFinalWidth = 0
End Select
End If
'convert to whatever scale the form is using'
GetIconWidth = frm.ScaleX(nFinalWidth, vbPixels, frm.ScaleMode)
End Function
Private Function GetFrameWidth(ByVal frm As Form) As Long
Dim nFinalWidth As Long
If frm.ControlBox Then
Select Case frm.BorderStyle
Case vbFixedSingle, vbFixedDialog:
nFinalWidth = GetSystemMetrics(SM_CXDLGFRAME)
Case vbSizable:
nFinalWidth = GetSystemMetrics(SM_CXFRAME)
End Select
End If
'convert to whatever scale the form is using'
GetFrameWidth = frm.ScaleX(nFinalWidth, vbPixels, frm.ScaleMode)
End Function
Private Function GetBorderWidth(ByVal frm As Form) As Long
Dim nFinalWidth As Long
If frm.ControlBox Then
Select Case frm.Appearance
Case 0 'flat'
nFinalWidth = GetSystemMetrics(SM_CXBORDER)
Case 1 '3D'
nFinalWidth = GetSystemMetrics(SM_CXEDGE)
End Select
End If
'convert to whatever scale the form is using'
GetBorderWidth = frm.ScaleX(nFinalWidth, vbPixels, frm.ScaleMode)
End Function
Public Function GetRecommendedWidth(ByVal frm As Form) As Long
Dim nNewWidth As Long
' An abitrary amount of extra padding so that the caption text '
' is not scrunched up against the min/max/close buttons '
Const PADDING_TWIPS = 120
nNewWidth = _
GetCaptionTextWidth(frm) _
+ GetControlBoxWidth(frm) _
+ GetIconWidth(frm) _
+ GetFrameWidth(frm) * 2 _
+ GetBorderWidth(frm) * 2 _
+ PADDING_TWIPS
If nNewWidth > frm.Width Then
GetRecommendedWidth = nNewWidth
Else
GetRecommendedWidth = frm.Width
End If
End Function
Then place the following in your Form_Load event
Private Sub Form_Load()
Me.Caption = String(100, "x") 'replace this with your caption'
Me.Width = GetRecommendedWidth(Me)
End Sub
It appears that VB6 limits the App.Title property to 40 characters. Unfortunately, I can't locate any documentation on MSDN detailing this behavior. (And unfortunately, I don't have documentation loaded onto the machine where my copy of VB6 still resides.)
I ran an experiment with long titles, and that was the observed behavior. If your title is longer than 40 characters, it simply will get truncated.

Resources