Arabic word search tool [closed] - vb6

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I want to make a search tool to find a specific word in the Arabic language that i can find just the word for example:
ذهب الولد إلى المدرسة من البيت و منهم الى البيت
If I try to find the word "من", the code not only finds the word "من", but also finds part of the word " منهم". I don't want the program to do that. I want to find the word " من" and every word like it and make whole case to the word.
To make things more clear (using an English example), if I were to search for the word 'to' in the following sentence, I would only want whole words to be found, and not words that contain the word 'to' such as 'toward' to become a part of the result.
Sentence: I want to go towards the bus.

Searches like this can be frustrating. What I usually do is to add a space at the front of the search string and also at the end and then search for SearchString.
So... "I want to go towards the bus." becomes " I want to go towards the bus. ". Then I search for " to ". The problem with this method is that punctuation can cause a problem. For example, if you wanted to search for "bus", you would use:
" I want to go towards the bus. " and search for " bus ". This would not be found because there is punctuation after the word bus.
I would encourage you to use regular expressions for this functionality. VB6 does not have regular expressions built in, but you can use the Microsoft VBScript Regular Expressions functionality to accomplish this. Please take a look at this page to help you get started: http://support.microsoft.com/kb/818802
Edit based on your comment
You have this line of code:
pos = InStr(start_at, txtBody.Text, target)
try changing that line to this:
pos = InStr(start_at, " " & txtBody.Text & " ", " " & target & " ", vbBinaryCompare)
By adding spaces in the code, you are actually checking for (space)target(space). So that you don't miss potential matches at the beginning or end of txtBody.Text, spaces are added there (for comparison purposes only). By adding vbBinaryCompare, InStr will now perform a case sensitive search.

The only completely thorough way of doing this is to use the Instr() function, and then check that the next character is a punctuation character, a newline, or the word is at the end of the string, e.g.
Option Explicit
Private Declare Function GetStringTypeW Lib "Kernel32.dll" ( _
ByVal dwInfoType As Long, _
ByVal lpSrcStr As Long, _
ByVal cchSrc As Long, _
ByRef lpCharType As Integer _
) As Long
Private Const CT_CTYPE1 As Long = &H1
Private Const C1_UPPER As Long = &H1 ' Uppercase
Private Const C1_LOWER As Long = &H2 ' Lowercase
Private Const C1_DIGIT As Long = &H4 ' Decimal digits
Private Const C1_SPACE As Long = &H8 ' Space characters
Private Const C1_PUNCT As Long = &H10 ' Punctuation
Private Const C1_CNTRL As Long = &H20 ' Control characters
Private Const C1_BLANK As Long = &H40 ' Blank characters
Private Const C1_XDIGIT As Long = &H80 ' Hexadecimal digits
Private Const C1_ALPHA As Long = &H100 ' Any linguistic character: alphabetical, syllabary, or ideographic
Private Const C1_DEFINED As Long = &H200 ' A defined character, but not one of the other C1_* types
Function FindFullWord(ByVal in_lStartPos As Long, ByRef in_sText As String, ByRef in_sSearch As String, Optional ByVal in_eCompareMethod As VbCompareMethod = vbBinaryCompare) As Long
Dim nLenText As Long
Dim nLenSearch As Long
Dim sNextChar As String
Dim iCharType As Integer
FindFullWord = InStr(in_lStartPos, in_sText, in_sSearch, in_eCompareMethod)
' Did we find the search string in the text?
If (FindFullWord > 0) Then
' Save the length of the text.
nLenText = Len(in_sText)
nLenSearch = Len(in_sSearch)
Do
' Does this position mean that the search is the end of the string?
If (FindFullWord + nLenSearch - 1) = nLenText Then
' If so, we can exit now - there are no following characters.
Exit Function
End If
' Look at the next character.
sNextChar = Mid$(in_sText, FindFullWord + nLenSearch, 1)
' Is this next char a space, punctuation character, or a blank?
If (GetStringTypeW(CT_CTYPE1, StrPtr(sNextChar), 1, iCharType)) Then
If (iCharType And C1_SPACE) = C1_SPACE Then
Exit Function
ElseIf (iCharType And C1_PUNCT) = C1_PUNCT Then
Exit Function
ElseIf (iCharType And C1_BLANK) = C1_BLANK Then
Exit Function
End If
End If
' Find the position of the search string in the text.
FindFullWord = InStr(FindFullWord + nLenSearch, in_sText, in_sSearch, in_eCompareMethod)
Loop Until FindFullWord = 0
End If
End Function
I originally started doing a test for every character which could follow a word and would not be part of that word, but the code started getting very long. And, of course, I know absolutely nothing about Arabic. So I wondered whether there was a standard way of finding out the general "type" of a character, regardless of language. And as it happens, there was.
The GetStringTypeW() method is documented in the Win32 documentation, and essentially can retrieve information about all the characters in a string. In my case, I am only looking at the character which follows the search word in a piece of text. The variable iCharType which returns the value from the string is a bitfield, and contains a number of values OR'ed together. I am using the AND operator to isolate just the values I am interested in.

Related

VB6 - converting string to double using specific culture?

I am a long time C# developer and have had to look at some VB6 code lately. After some digging, we found out that, somewhere, somehow, we are storing a number as a string. Now we are reading it back and our code is not handling culture differences very well. Here is an example:
Dim text as String
Dim myVal as Double
Set text = "1,123456"
'This sets the value of myVal to 1123456 on our system - incorrect
myVal = text
Set text = "1.123456"
'This sets the value of myVal to 1.123456 on our system - correct
myVal = text
Keeping in mind that this is VB6 and NOT VB.NET, are there any built-in methods, functions, whatever, that can convert a string to a double using a particular culture?
Or at the very least, hinting to the conversion that we may be dealing with a different format?
We are still digging how the value gets written and see if we can reverse engineer the process to give us a consistent result. However, our customer(s) already has(have) data in one format or the other (or both, we are checking...), so a proper conversion algorithm or implementation would be a better answer at this point.
I've used this quick and dirty function in the past to get a double from a text. Here in Argentina, people sometimes use the point and sometimes the comma to separate decimal values, so this function is ready to accept both formats. If only one punctuation is present, it's assumed that it's the decimal separator.
function ConvertDBL( texto )
dim retval, SepDecimal, SepMiles
If texto = "" then texto = "0"
retval = replace(trim(texto), " ", "")
If cdbl("3,24") = 324 then
SepDecimal = "."
SepMiles = ","
else
SepDecimal = ","
SepMiles = "."
end if
If InStr( Retval, SepDecimal ) > 0 then
If InStr( Retval, SepMiles ) > 0 then
If InStr( Retval, SepDecimal ) > InStr( Retval, SepMiles ) then
Retval = replace( Retval, SepMiles, "" )
else
Retval = replace( Retval, SepDecimal, "" )
Retval = replace( Retval, SepMiles, SepDecimal )
end if
end if
else
Retval = replace( Retval, SepMiles, SepDecimal )
end if
ConvertDbl = cdbl( retval )
end function
You cannot choose an arbitrary culture by passing a culture object like you can in .Net. You can choose:
Functions which always use the current regional settings. CStr, Format, CDbl etc. Implicit conversions (like the ones in your question) also use the current regional settings.
Functions which always use USA regional settings (similar to the .Net invariant culture). Str, Val
Usually the current regional settings are taken from Control Panel. There are rumours that you can call SetThreadLocale to change the regional settings from code at runtime - never tried it myself.
In Visual Basic tested works very fast and efficient.
Step 1: Conversion from a string to a value representation.
Step 2: Conversion from a value representation to a double integer value.
Dim Str as String
Dim mValue as double
str = "100,10"
mValue = CDbl(Val(str))

String to Function Name in Visual Studio

I saw in a screencast a while ago (since forgotten which, probably a Kata) where a person was writing out a unit test but wrote something like this:
public "return zero for an all gutter game"
Then they magically turned it into
public returnZeroForAnAllGutterGame
Is there a plugin for this or just a simple way to do a template that gets fired off on a key stroke?
I googled around and just couldn't think of a good way to type in a search to get what I wanted.
I couldn't find the plugin or macro you refer to, but I did create a macro that will work nicely!
First, to install do the following:
Press Alt+F11
Expand MyMacros
Open the EnvironmentEvents module
Past the code into the module (code is found at the end of this post)
Close the macro editor
To use the macro:
Press ` (grave key).
Next press "
Type the words you desire
End by typing "`
Watch the magic happen!
NOTE: You could just start typing a sting value then latter add the grave symbols before and after and it will still work.
The macro will remove spaces and then PascalCase the entire set of words. It also strips out single and double quotes. Lastly, it will convert commas to underscores if you want to use the naming convention suggested by Roy Osherove (The Art of Unit Testing, p. 211):
MethodUnderTest_Scenario_Behavior()
Examples:
public void `"return zero for an all gutter game"`
public void `"LoadMainParts, when materials files are valid, will return a list of parts sorted by sequential item number ascending"`
...will turn into this (after the second ` press):
public void ReturnZeroForAnAllGutterGame
public void LoadMainParts_WhenMaterialsFilesAreValid_WillReturnAListOfPartsSortedBySequentialItemNumberAscending
The Macro:
...
Imports System.Text.RegularExpressions
...
Private isPascalCaseAndSpaceRemovalEnabled As Boolean
Private Function ConvertToPascalCase(ByVal value As String) As String
'apply ToUpper on letters preceeded by a space, double quotes, or a comma'
Dim pattern As String = "[ ,"",\,][a-z]"
value = Regex.Replace(value, _
pattern, _
Function(m) m.Value.ToUpper, _
RegexOptions.Singleline)
'replace commas with underscores'
value = value.Replace(",", "_")
'remove spaces, graves, double quotes, and single qoutes'
Dim removalCharacters As String() = {" ", "`", """", "'"}
For Each character In removalCharacters
value = value.Replace(character, "")
Next
Return value
End Function
Private Sub TextDocumentKeyPressEvents_AfterKeyPress(ByVal Keypress As String, _
ByVal Selection As EnvDTE.TextSelection, _
ByVal InStatementCompletion As Boolean) _
Handles TextDocumentKeyPressEvents.AfterKeyPress
If isPascalCaseAndSpaceRemovalEnabled AndAlso Keypress = "`" Then
Selection.SelectLine()
Dim pattern As String = "`""(.*)""`"
Dim substringToReplace As String = Regex.Match(Selection.Text, _
pattern, _
RegexOptions.Singleline).Value
Selection.ReplacePattern(pattern, _
ConvertToPascalCase(substringToReplace), _
vsFindOptions.vsFindOptionsRegularExpression)
Selection.MoveToPoint(Selection.BottomPoint)
isPascalCaseAndSpaceRemovalEnabled = False
CancelKeyPress = True
ElseIf Keypress = "`" Then
isPascalCaseAndSpaceRemovalEnabled = True
End If
End Sub
Feel free to tailor the code to your needs.

how do you parse a string in vb6?

Some of us unfortunately are still supporting legacy app like VB6. I have forgotten how to parse a string.
Given a string:
Dim mystring As String = "1234567890"
How do you loop in VB6 through each character and do something like
for each character in mystring
debug.print character
next
In C# i would do something like
char[] myChars = mystring.ToCharArray();
foreach (char c in theChars)
{
//do something with c
}
Any ideas?
Thanks a lot
You can use the 'Mid' function to get at the individual characters:
Dim i As Integer
For i = 1 To Len(mystring)
Print Mid$(mystring, i, 1)
Next
Note this is untested.
There is no possibility to use foreach on strings.
Use
Dim i As Integer
For i = 1 To Len(YourString)
Result = Mid$(YourString, i, 1)
Next
note that the type of Result is a length-1 string, no char or byte type.
If performance is important, you'll have to convert the string to a bytearray fist (using StrConv) and then loop through it like this.
Dim i As Long
For i = 0 To UBound(Data)
Result = Data(i) ' Type is Byte '
Next
This is much more efficient.
The easiest way is to convert the string into an array of bytes and iterate over the byte array (converting each byte to a character).
Dim str As String
Dim bytArray() As Byte
Dim count As Integer
str = "This is a string."
bytArray = str
For count = 0 To UBound(bytArray)
Debug.Print Chr(bytArray(count))
Next
Don't loop; rather, set a reference to Microsoft VBScript Regular Expressions library and use regular expressions to achieve your 'do something' goal.

VB6 Can IsNumeric be wrong?

Is it possible to test a string with IsNumeric() and for it to return true, but when you cast that same string to an integer using CInt() and assign it to a variable of type integer that it will give a type mismatch error?
I ask because I was getting a type mismatch error, so I used IsNumeric() to check the string was numeric before trying to cast it, but I still get the error.
I am tearing my hair out with this.
Here is the code in question.
iGlobalMaxAlternatives = CInt(strMaxAlternatives) is where the error is occuring.
Dim strMaxAlternatives As String
Dim iGlobalMaxAlternatives As Integer
iGlobalMaxAlternatives = 0
bSurchargeIncInFare = True
strMaxAlternatives = ReadStringKeyFromRegistry("Software\TL\Connection Strings\" & sConn & "\HH", "MaxAlt")
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
iGlobalMaxAlternatives = CInt(strMaxAlternatives)
End If
You may have an overflow due the maximum integer size; the currency type actually does very well for large numbers (but beware of any regional issues). See edits below for Int64 discussion.
According to MSDN documentation on IsNumeric:
IsNumeric returns True if the data
type of Expression is Boolean, Byte,
Decimal, Double, Integer, Long,
SByte, Short, Single, UInteger,
ULong, or UShort, or an Object that
contains one of those numeric types.
It also returns True if Expression is
a Char or String that can be
successfully converted to a number.
IsNumeric returns False if Expression
is of data type Date or of data type
Object and it does not contain a
numeric type. IsNumeric returns False
if Expression is a Char or String
that cannot be converted to a number.
Since you are getting a Type Mismatch, perhaps a Double is interfering with the conversion. The IsNumeric does not guarantee it is an Integer, just that it matches one of the numeric possibilities. If the number is a double, perhaps regional settings (comma versus period and so on) are causing the exception.
You might try converting it to a double and then to an integer.
' Using a couple of steps
Dim iValue As Integer
Dim dValue As Double
dValue = CDbl(SourceValue)
iValue = CInt(iValue)
' Or in one step (might make debugging harder)
iValue = CInt(CDbl(SourceValue))
EDIT: After your clarification, it appears you are getting an overflow conversion. First try using a Long and CLng() instead of CInt(). There is still a chance the entry is Int64 though, which is more difficult using VB6.
I have used the following code for the LARGE_INTEGER and Integer8 types (both Int64), but it may not work for your situation:
testValue = CCur((inputValue.HighPart * 2 ^ 32) + _
inputValue.LowPart) / CCur(-864000000000)
This example was from an LDAP password expiration example, but like I said it may or may not work in your scenario. If you don't have the LARGE_INTEGER type, it looks like:
Private Type LARGE_INTEGER
LowPart As Long
HighPart As Long
End Type
Search for LARGE_INTEGER and VB6 for more information.
EDIT: For debugging, it may be useful to temporarily avoid error handling and then turn it back on after passing the troubling lines:
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
On Error Resume Next
iGlobalMaxAlternatives = CInt(strMaxAlternatives)
If Err.Number <> 0 Then
Debug.Print "Conversion Error: " & strMaxAlternatives & _
" - " & Err.Description
EndIf
On Error Goto YourPreviousErrorHandler
End If
Yes, "3.41" would be numeric but not an integer.
VB6 doesn't provide good methods to guarantee CInt won't fail. I've found the simplest way is to just use error-handling.
function TryParse(byval text as string, byref value as integer) as boolean
on error resume next
value = CInt(text)
TryParse = (err.number = 0)
endfunction
Of course your error-handling preferences may vary.
Yes. Try this:
If IsNumeric("65537") Then
Dim i As Integer
i = CInt("65537") 'throws an error on this line!
End If
This one's an overflow, but I think it illustrates the unreliability of IsNumeric() in general (especially for ints - for doubles it's much more reliable).
According to the VB6 documentation, "IsNumeric returns True if the data type of Expression is Boolean, Byte, Decimal, Double, Integer, Long, SByte, Short, Single, UInteger, ULong, or UShort, or an Object that contains one of those numeric types. It also returns True if Expression is a Char or String that can be successfully converted to a number."
Many of those cannot be converted to an Integer. For example "1.5" is numeric but it's not an integer. So, you can convert it to a number, but not necessarily an integer.
The following code works without a Type Mismatch error in Visual BASIC 6
Dim I As Integer
I = CInt("3.41")
The same for this variant
Dim I As Integer
Dim TempS As String
TempS = "3.41"
I = CInt(TempS)
Posting the code in question would help answer your question. Basically there are several function in VB6 that are used to convert strings into number.
CInt and Int convert into number but handle rounding different. Direct assignment works and equivalent to using CInt. Howver I recommend continuing to use CInt to make the operation clear to you and your fellow developers in the future.
CInt works on number with commas like "3,041.41" However VB6 has problem handling region settings so if you are using notation other than standard American English you will get strange results and errors.
Your best bet is to start logging the errors with the actual values it's working with so you can figure out whats going on.
IsNumeric will return true if it can convert the string to a number. Even if there are non-numeric characters in the string. I always loop though the string one character at a time and test each character. If one character fails then I can return an error.
Just found this nugget. If you run the following, script #1 returns TRUE but script #2 & #3 will fail:
SELECT ISNUMERIC('98,0') AS isNum -- Fails
SELECT CONVERT(INT, '98,0') -- Fails
SELECT CONVERT(NUMERIC(11,4), '98,0') -- Fails
Two options...
Change
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
iGlobalMaxAlternatives = CInt(strMaxAlternatives)
End If
To
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
iGlobalMaxAlternatives = CDbl(strMaxAlternatives) ' Cast to double instead'
End If
Or
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
If CDbl(strMaxAlternatives) Mod 1 = 0 Then ' Make sure there\'s no decimal points'
iGlobalMaxAlternatives = CInt(strMaxAlternatives)
End If
End If

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