Check if picturebox contains an image - visual-studio

Is there any built-in method of PictureBox compontent to check if a certain portion of its content matches another PictureBox's content? e.g:
I know I can do it by comparing each individual pixel's color with PictureBox's Point(X,Y) Method, but that seems a bit overkill to me. Not to mention it would probably be way too slow.

You can compare each pixel faster by accessing the picture's memory.
It's very easy and fast, I wrote a 2D game engine using this way when I was a new VB6 programmer. My 2D Game Engine (yLib)
All the things you got to do is that get the first pixel's address.
The below function do the job for you:
module1:
Public Declare Function VarPtrArray Lib "msvbvm60" Alias "VarPtr" (Ptr() As Any) As Long
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)
Public Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (destination As Any, ByVal Length As Long)
Public Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type
Public Type SAFEARRAY2D
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
Bounds(0 To 1) As SAFEARRAYBOUND
End Type
Public Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type
Public Type Picture
pic As STDPicture
c() As Byte
bmp As BITMAP
SA As SAFEARRAY2D
End Type
Public Type lyPicture
pic As STDPicture
c() As Byte
bmp As BITMAP
SA As SAFEARRAY2D
End Type
main form or module:
Private Sub LoadPicArray2D(val As lyPicture)
' get bitmap info from image box
Call GetObjectAPI(val.pic, Len(val.bmp), val.bmp) 'dest
' exit if not 24-bit bitmap
' make the local matrix point to bitmap pixels
If val.bmp.bmPlanes 1 Or val.bmp.bmBitsPixel 24 Or val.bmp.bmWidthBytes / val.bmp.bmWidth 3 Then
Call Err.Raise(500, "Only 24-bit bitmaps.", "Only 24-bit bitmaps.")
End If
With val.SA
.cbElements = 1
.cDims = 2
.Bounds(0).lLbound = 0
.Bounds(0).cElements = val.bmp.bmHeight
.Bounds(1).lLbound = 0
.Bounds(1).cElements = val.bmp.bmWidthBytes
.pvData = val.bmp.bmBits
End With
Call CopyMemory(ByVal VarPtrArray(val.c()), VarPtr(val.SA), 4)
End Sub
Public Sub Main()
Dim pic As lyPicture
Set pic.pic = Form1.TargetPictureBox.Picture
Call LoadPicArray2D(pic)
'Just remember that the picture's memory stored top-down this mean the
' upper pixel lays in lower array bound.
' but the array is correct in horizontal arrangement.
' And the array is formated BGR pic.c(0,0) = B , pic.c(1,0) = G, pic.c(2,0) = R
pic.c(0,0) = 0 ' the pixel at the left bottom (0, MAX_PICTURE_HEIGHT-1) of the picture.
pic.c(0,MAX_PICTURE_HEIGHT-1) = 0 ' the pixel at (0, 0)
' This is very IMPORTANT to release the array at the end of the code.
Call ZeroMemory(ByVal VarPtrArray(val.c()), 4)
' I dont tested this code just copied from my game engine. :D
End Sub

Related

Detection Color from screen of exe file

I have a printer control program( EXE file)
When the printer is working properly, the program shows green color. And displays red color when stopped. Now: I want to write a color recognition code in Visual Basic In such a way that the written program be can detect the color change of that Exe program. That is, to understand when the printer is a stop and when it starts
The code I wrote has a problem : when I put the form on the paint. The color codes care different And I can not do this check with multiple codes
my code:
Declare Function GetPixel Lib "gdi32" (ByVal hDC As Long, ByVal X As Long, ByVal Y As Long) As Long
Declare Function GetDesktopWindow Lib "user32" () As Long
Declare Function GetWindowDC Lib "user32" (ByVal hWnd As Long) As Long
Private Sub Timer1_Timer()
Dim Pixel As Long
Dim Left As Long
Dim Top As Long
Dim hDC As Long
' Get Desktop window
hDC = GetWindowDC(GetDesktopWindow)
' Use size of screen
Left = Me.Left / Screen.TwipsPerPixelX
Top = Me.Top / Screen.TwipsPerPixelY
Pixel = GetPixel(hDC, Left, Top)
Me.Caption = Hex(Pixel)
Me.BackColor = Pixel
End Sub
image1
image2
It looks like the Pixel data is taken from the title bar of your window. The red in your image is closer to 236 (236,26,37 is the RGB value) and what your form is showing converts to 137,126,180 (RGB for 897EB4) and 146,135,187 (RGB for 9287BB). You can see that the color is wrong because the background of your form doesn't match the red from the desktop.
I would try reading the Pixel further away from the form:
Left = (Me.Left / Screen.TwipsPerPixelX) - 20
Top = (Me.Top / Screen.TwipsPerPixelY) - 20
Nonetheless, even if the color is affected by the dropshadow, you can implement some tolerance for the value you are receiving. For example, you should accept values within +/- 10 of your expected value using a function like this:
Function CheckColor(p_iRed, p_iGreen, p_iBlue, p_iTargetRed, p_iTargetGreen, p_iTargetBlue, p_iTolerance) As Boolean
CheckColor = CheckColorPart(p_iRed, p_iTargetRed, p_iTolerance) And CheckColorPart(p_iGreen, p_iTargetGreen, p_iTolerance) And CheckColorPart(p_iBlue, p_iTargetBlue, p_iTolerance)
End Function
Function CheckColorPart(p_iValue, p_iTarget, p_iTolerance) As Boolean
CheckColorPart = (p_iValue >= p_iTarget - p_iTolerance And p_iValue <= p_iTarget + p_iTolerance)
End Function
This should handle the slight variations in color you are seeing and detect what you are looking for.

How to represent 64-bit integer in VB6?

I am having to augment a legacy app to handle 64-bit integers. However, VB6 doesn't have a data type for that. The recommendation online that I've found has been to use the Currency data type.
However, I've found that I am running into some overflow issues.
Example - Results in Overflow during CCur call:
dim c as currency
' set maximum value of int64
c = CCur("9223372036854775807")
However, if I apply a smaller number (but still much larger than int32), it does work:
dim c as currency
' Remove the last 4 digits
c = CCur("922337203685477")
So what am I missing here? How can I handle a 64-bit value?
The only thing that I need to do with the 64-bit values is to read them from a SQL Server stored procedure (it comes as sql type bigint) and then display it to the form.
ADO Field.Value is type Variant. When you retrieve an adBigInt in VB6 the Variant will be of subtype Decimal.
You can use Variant datatype with CDec() conversion.
dim c as variant
' set maximum value of int64
c = CDec("9223372036854775807")
Now you can even use standard vb6 math operations, or string conversion functions on c.
Dim c As Variant, d As Variant
c = CDec("9223372036854775807")
Dim i As Integer
i = 1000
d = 10
Debug.Print c + i
Debug.Print c / d
Debug.Print CStr(c)
Results
9223372036854776807
922337203685477580,7
9223372036854775807
Just be aware that Decimal type Variant is wider than 64 bits, so you don't get the 'Overflow' on the server side :)
The answer is, it depends on what you're going to do with the 64 bit value. If you simply want to hold a value without doing any arithmetic on it, then it may be better to create a byte array or long array. For example:
Dim SixtFourBit(7) As Byte
or
Dim SixtyFourBit(1) As Long
Using the currency type is a simpler solution since you can apply arithmetic to it. But the Currency type is a fixed format representation, always having four decimal places. That means the lower bytes of the 64 bit representation go to make up the fractional part of the Currency value (sort of).
To coerce between Currency and arrays use the devilish CopyMemory windows API function:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Integer)
Sub SomeFunction()
Dim AnArray(7) As Byte
Dim ACurrency as Currency
ACurrency = 123.4567
CopyMemory AnArray(0), VarPtr(ACurrency), 8&
' Inspecting AnArray in the watch window will show you the byte representation of ACurrency
End Sub
With the caveat, that this sort of trickery is to be generally avoided. Incorrect use of CopyMemory can kill your program.
VB6 can be used with variant type of I8 to provide a 64-bit signed integer. (UI8 does not work with VB6). There are some limitations, for example TypeName does not work, whilst VarType does.
Example of function cInt64 to create a 64-bit integer variant which first creates a decimal variant then converts this to an I8 variant:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Enum VARENUM
VT_I8 = &H14
End Enum ' } VARENUM;
Private Type Dec_Hdr
DecType As Integer
DecScale As Byte
DecSign As Byte
End Type
Function cInt64(v As Variant) As Variant
Dim DecHdr As Dec_Hdr
cInt64 = CDec(v) ' first create a Decimal Variant
CopyMemory DecHdr, ByVal VarPtr(cInt64), Len(DecHdr)
'Correct possible CDec conversion errors with Hex strings
If VarType(v) = vbString Then
If InStr(1, v, "&h", vbTextCompare) = 1 Then DecHdr.DecSign = 0
End If
'Remove any decimal places
If DecHdr.DecScale Then cInt64 = Fix(cInt64)
'Convert Decimal to Int64, setting sign and scale to zero
CopyMemory ByVal VarPtr(cInt64), CLng(VT_I8), 4
'Adjust for Decimal Sign
If (DecHdr.DecSign <> 0) And (cInt64 > 0) Then cInt64 = -cInt64
'Finally check variant is of type I8
If (VarType(cInt64) <> VT_I8) Then Err.Raise 6
End Function

Vb6 Drag and Drop HTML data

In Visual basic 6, clipboard has various formats. To retrieve HTML data from the clipboard, this works great: https://support.microsoft.com/en-us/kb/274326
Now my question is, how can I get HTML information from dragged data and not the clipboard?
for example,
I'd like to have a multi-line textbox, that when I drag content from a webpage - into the textbox - the textbox will show the HTML retrieved from the OLE Dragging information, and not as vbCFtext.
I'v tried using the same technique as in the link above but instead of GetClipboardData - use Data.GetData(RegisterClipboardFormat("HTML Format")) [which is coming from Picture1_OLEDragDrop(...]
but I get an over flow error.
I have searched all over the web for a solution. anybody out there?
Edited: The above was answered, Thanks!
Adding:
Thank you very much! What would be the right way now to reverse that? meaning, to drag from a text box (that contains HTML) - and set it for dragging in HTML mode?
when I simply use this:
Private Sub Text1_OLEStartDrag(Data As DataObject, AllowedEffects As Long)
'Data.SetData StrConv(Text1.Text, vbFromUnicode), (CF_HTML)
Data.SetData Text1.Text, (CF_HTML)
End Sub
I get an error:
"Non-intrinsic OLE drag and drop formats used with SetData require Byte array data. GetData may return more bytes than were given to SetData (Error 675)"
What would be the accurate way to send back the data?
and note that in could have Unicode characters.
will I have to use memory copy and others to get this to work? I'l appreciate your help very much!
Your problem is that RegisterClipboardFormat returns a Long (actually a uint), but clipboard formats are ushort (unsigned integer) values. Since we have no such type in VB6, our DataObject types expect Integer values. Thus we have to play a few more games:
Option Explicit
Private Declare Function RegisterClipboardFormat Lib "user32" _
Alias "RegisterClipboardFormatW" ( _
ByVal lpString As Long) As Long
Private CF_HTML As Integer
Private Sub Form_Initialize()
Dim Temp As Long
Temp = RegisterClipboardFormat(StrPtr("HTML Format"))
CF_HTML = CInt(Temp And &H7FFF&) Or IIf(Temp And &H8000&, &H8000, 0)
End Sub
Private Sub Text1_OLEDragDrop( _
Data As DataObject, _
Effect As Long, _
Button As Integer, _
Shift As Integer, _
X As Single, _
Y As Single)
If Effect And vbDropEffectCopy Then
Text1.Text = StrConv(Data.GetData(CF_HTML), vbUnicode)
End If
End Sub
Private Sub Text1_OLEDragOver( _
Data As DataObject, _
Effect As Long, _
Button As Integer, _
Shift As Integer, _
X As Single, _
Y As Single, _
State As Integer)
If Data.GetFormat(CF_HTML) Then
Effect = vbDropEffectCopy
Else
Effect = vbDropEffectNone
End If
End Sub

MS PowerPoint: how to convert a shape's position and size into screen coordinates?

I wrote me a little VBA Macro for PowerPoint (2010) that opens a popup with explanations when hovering over some Shape. This works fine. Alas, there is no event that is triggered when leaving the area again and so I now want to extend the code such that it monitors the area of the popup and when the pointer leaves that area it removes the popup again.
But now I ran into some stupid problem: the coordinates of the Shape (.Left, .Top, .Width, and .Height) are given in some "document units" (don't know exactly what unit this is in). The pointer coordinates, however, are obviously in screen pixels. To be able to reasonably compare the two to calculate whether the pointer is inside or outside I need to first convert the Shape's dimensions into screen pixels.
I googled around a lot, but while I found several at first promising code snippets, none of these worked (as most were for Excel and PowerPoint obviously has a different document model).
Could some kind soul give me a hint or some reference how to convert a Shape's dimension into screen pixels (i.e. taking scaling, window position, zoom-factor etc. into account).
M.
In case anyone's interested - here is my solution after LOTS of further googling:
Type POINTAPI
x As Long
y As Long
End Type
Type Rectangle
topLeft As POINTAPI
bottomRight As POINTAPI
End Type
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDC As Long) As Long
Private Declare Function ClientToScreen Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long
Private Function TransformShape(osh As Shape) As Rectangle
Dim zoomFactor As Double
zoomFactor = ActivePresentation.SlideShowWindow.View.zoom / 100
Dim hndDC&
hndDC = GetDC(0)
Dim deviceCapsX As Double
deviceCapsX = GetDeviceCaps(hndDC, 88) / 72 ' pixels per pt horizontal (1 pt = 1/72')
Dim deviceCapsY As Double
deviceCapsY = GetDeviceCaps(hndDC, 90) / 72 ' pixels per pt vertical (1 pt = 1/72')
With TransformShape
' calculate:
.topLeft.x = osh.Left * deviceCapsX * zoomFactor
.topLeft.y = osh.Top * deviceCapsY * zoomFactor
.bottomRight.x = (osh.Left + osh.width) * deviceCapsX * zoomFactor
.bottomRight.y = (osh.Top + osh.height) * deviceCapsY * zoomFactor
' translate:
Dim lngStatus As Long
lngStatus = ClientToScreen(hndDC, .topLeft)
lngStatus = ClientToScreen(hndDC, .bottomRight)
End With
ReleaseDC 0, hndDC
End Function
...
Dim shapeAsRect As Rectangle
shapeAsRect = TransformShape(someSape)
Dim pointerPos As POINTAPI
Dim lngStatus As Long
lngStatus = GetCursorPos(pointerPos)
If ((pointerPos.x <= shapeAsRect.topLeft.x) Or (pointerPos.x >= shapeAsRect.bottomRight.x) Or _
(pointerPos.y <= shapeAsRect.topLeft.y) Or (pointerPos.y >= shapeAsRect.bottomRight.y)) Then
' outside:
...
Else ' inside
...
End If
...
the coordinates of the Shape (.Left, .Top, .Width, and .Height) are given in some "document units" (don't know exactly what unit this is in).
Points. 72 points to the inch.
Sub TryThis()
Dim osh As Shape
Set osh = ActiveWindow.Selection.ShapeRange(1)
With ActiveWindow
Debug.Print .PointsToScreenPixelsX(.Left)
Debug.Print .PointsToScreenPixelsY(.Top)
End With
End Sub

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