[RESOLVED] VB3 - How to word-wrap a long string of Text?
Hi guys,
We have an old VB3 application that allows users to type into a Text Box (Multi Line, Scroll Bars) and save the resulting Text to a variable "Note".
Currently, we force the user to hit [Enter] at the end of each typed line in order to print the "Note" onto a specific area of the page. Page real-estate dictates that the Text "Note" cannot be free-form typed without carriage-returns, otherwise the "Printer.Print Note" would overwrite other page output or fall off the right-hand side of the page.
What we'd like is for the user to free-form type into the Text Box, and have the code parse the resulting long Text "Note" and apply a carriage-return at a specific point (say character position 60, unless that is halfway through a word, in which case it would be at the preceding space/punctuation before the word appearing at position 60). Sounds complicated, but must be a common requirement ... unless there's a smart way for the Text Box control to automatically format the carriage-returns when it word-wraps!?
It is hard to be specific since VB3 is so old most of us haven't even had it installed for over a decade. Even tougher with so many people being on 64-bit Windows where it won't run anymore.
As far as I can tell, the text "wraps" at word breaks just fine within a multi-line TextBox. Your problem is really that you want wrapping when you use Printer.Print, right?
If so, the answer is probably not to fiddle with the TextBox or place any hopes there since it does what it is supposed to. Indeed I'd filter out any newlines typed by the user, or else allow them to be considered paragraph breaks as they really are. TextBox and similar controls are not line-oriented but paragraph-oriented.
So you'd take the String value of the entered text and format that for wrapping as you print it, not before. The Printer object does not support this in VB6, and most likely not in creaky old VB3 either.
Widths are seldom in "characters" but instead in pixels, twips, inches, etc. mainly because most text is rendered in proportional fonts. If you are using a fixed-pitch font with a known character width then I suppose you could write code to parse the String into lines no longer than X chars wide each, avoiding breaking words. But in general you are going to want to use API calls to render text to the printer, most likely via the DrawText() function.
Whether VB3 can make use of a 16-bit version of DrawText() I cannot say. Documentation for Windows 3.1 and Visual Basic 3.0 is very thin online and most people probably do not have the Help or SDK files installed.
You summed requirements up pretty well 'dilettante' ... hopefully someone will have a Code Snippet to parse a long string into fixed-width lines.
We're having to run VB3 development and application inside an XP virtual machine running on 64-bit Windows 7 machines... haven't even tested running in Windows 10 yet In the absence of documentation, relying on VB3 Help (which is actually pretty good!)
' 0 1 2 3 4
' 1234567890123456789012345678901234567890
txt = "The MSDN Library is a member of the Visual Studio 6.0 family of development products, which includes: "
With Text2
.Text = txt
End With
With PB1
.AutoRedraw = True
.FontName = "Courier"
PB1.Print txt
End With
With PB3
oo = 33
Dim NoteTX(4)
For ii = 1 To 4
aa = Mid(txt, oo, 1)
If aa = " " Then
b = b
Else
xtr = Left(txt, oo) ' The MSDN Library is a member of t
bb = InStrRev(xtr, " ") ' 32 on 1st line
NoteTX(ii) = Left(xtr, oo - 1)
' print line
PB3.Print NoteTX(ii)
' scrub, prep for next pass
txt = Mid(txt, bb + 1)
End If
Next ii
End With
Loose ends:
Dealing with non-fixed length fonts
Line 3 .. didn't properly deal with "left-over" word
Branch If aa = " " Then
I preset oo = 33 .. the number of characters, comparable to your "60"
I preset NoteTX to 4 lines .. you'll need to be smarter
Nonetheless, hope this gives you some ideas
EDIT-1
BTW, I used PB's as they support the .Print Method.
I figured it would be comparable to Printer.Print.
Spoo
Last edited by Spooman; Sep 13th, 2017 at 06:31 AM.
Superb 'Spooman'... I'll have a wee play around with that this afternoon and let you know how I get on. Converting from VB6 to VB3 will be harder than it looks, but a great start... Thanks
Not sure why that would be hard to convert to VB3. I don't see any code there that should not be supported.
I would add As String to the dim statement and $ on the Mid statements even in VB6. May be required in VB3 not sure there as it has been about 20 years since I used VB3,
Option Explicit
Private Sub Command1_Click()
Const TMARGIN As Single = 0.5 'Inches.
Const LMARGIN As Single = 0.5
Const VGAP As Single = 0.25
Dim P As Integer
Dim Text As String
Dim PrintTop As Single
Dim PrintedHeight As Single
'For testing use a virtual printer driver:
For P = 0 To Printers.Count - 1
If Printers(P).DeviceName = "Microsoft XPS Document Writer" Then
Exit For
End If
Next
If P = Printers.Count Then Exit Sub
Set Printer = Printers(P)
With Printer
.FontName = "Arial"
.FontSize = 10
Text = Text1.Text
'Note that we want to delete any empty paragraphs found at the end:
Do While Right$(Text, 2) = vbNewLine
Text = Left$(Text, Len(Text) - 2)
Loop
PrintedHeight = PrintingHelp.PrintBoxed(Text, _
vbInches, _
LMARGIN, _
TMARGIN, _
3.5)
PrintTop = TMARGIN + PrintedHeight + VGAP
.FontName = "Comic Sans MS"
Text = Text2.Text
Do While Right$(Text, 2) = vbNewLine
Text = Left$(Text, Len(Text) - 2)
Loop
PrintedHeight = PrintingHelp.PrintBoxed(Text, _
vbInches, _
LMARGIN, _
PrintTop, _
5.5)
PrintTop = PrintTop + PrintedHeight + VGAP
.FontName = "Courier New"
.FontSize = 14
Text = Text1.Text
Do While Right$(Text, 2) = vbNewLine
Text = Left$(Text, Len(Text) - 2)
Loop
PrintedHeight = PrintingHelp.PrintBoxed(Text, _
vbInches, _
LMARGIN, _
PrintTop, _
6.5)
PrintTop = PrintTop + PrintedHeight + VGAP
.ForeColor = vbRed
Text = "01234 6789 01234 6789 01234 6789 01234 6789 01234 6789 01234 6789"
PrintedHeight = PrintingHelp.PrintBoxed(Text, _
vbInches, _
LMARGIN, _
PrintTop, _
4.5)
.EndDoc
End With
End Sub
PrintingHelp.bas
Code:
Option Explicit
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
'wFormat flags:
Private Const DT_BOTTOM As Long = &H8&
Private Const DT_CALCRECT As Long = &H400&
Private Const DT_CENTER As Long = &H1&
Private Const DT_EDITCONTROL As Long = &H2000&
Private Const DT_END_ELLIPSIS As Long = &H8000&
Private Const DT_EXPANDTABS As Long = &H40&
Private Const DT_EXTERNALLEADING As Long = &H200&
Private Const DT_HIDEPREFIX As Long = &H100000
Private Const DT_INTERNAL As Long = &H1000&
Private Const DT_LEFT As Long = &H0&
Private Const DT_MODIFYSTRING As Long = &H10000
Private Const DT_NOCLIP As Long = &H100&
Private Const DT_NOFULLWIDTHCHARBREAK As Long = &H80000
Private Const DT_NOPREFIX As Long = &H800&
Private Const DT_PATH_ELLIPSIS As Long = &H4000&
Private Const DT_PREFIXONLY As Long = &H200000
Private Const DT_RIGHT As Long = &H2&
Private Const DT_SINGLELINE As Long = &H20&
Private Const DT_TABSTOP As Long = &H80&
Private Const DT_TOP As Long = &H0&
Private Const DT_VCENTER As Long = &H4&
Private Const DT_WORDBREAK As Long = &H10&
Private Const DT_WORD_ELLIPSIS As Long = &H40000
Private Declare Function DrawText Lib "user32" Alias "DrawTextA" ( _
ByVal hDC As Long, _
ByVal Str As String, _
ByVal nCount As Long, _
ByRef RECT As RECT, _
ByVal wFormat As Long) As Long
Public Sub Flush()
With Printer
Printer.PSet (.CurrentX, .CurrentY), vbWhite
End With
End Sub
Public Function PrintBoxed(ByVal Text As String, _
ByVal BoxScale As ScaleModeConstants, _
ByVal BoxLeft As Single, _
ByVal BoxTop As Single, _
ByVal BoxWidth As Single, _
Optional ByVal BoxHeight As Single, _
Optional ByVal Center As Boolean, _
Optional ByVal AlignBottom As Boolean) As Single
'Returns vertical space actually used, in BoxScale units.
Dim ExtraFlags As Long
Dim BoxRect As RECT
Dim DrawCall As Long
Flush 'Updates the hDC with font, color, etc. changes if any.
If Center Then ExtraFlags = DT_CENTER
If AlignBottom Then ExtraFlags = ExtraFlags Or DT_BOTTOM
If BoxHeight = 0 Then ExtraFlags = ExtraFlags Or DT_NOCLIP
With Printer
BoxRect.Left = .ScaleX(BoxLeft, BoxScale, vbPixels)
BoxRect.Top = .ScaleY(BoxTop, BoxScale, vbPixels)
BoxRect.Right = BoxRect.Left + .ScaleX(BoxWidth, BoxScale, vbPixels)
BoxRect.Bottom = BoxRect.Top + .ScaleY(BoxHeight, BoxScale, vbPixels)
DrawCall = DrawText(.hDC, _
Text, _
Len(Text), _
BoxRect, _
ExtraFlags _
Or DT_NOPREFIX _
Or DT_WORDBREAK)
If DrawCall = 0 Then
Err.Raise &H80047700, _
"PrintingHelp", _
"System error " & CStr(Err.LastDllError) & " in PrintBoxed."
End If
PrintBoxed = .ScaleY(DrawCall, vbPixels, BoxScale)
End With
Flush 'Flushes our text to the printer.
End Function
I'll just add my two-cents regarding VB3. As mentioned ad-nauseam on other posts of this forum, I've been doing this since PDS-Basic and even before. And, through VB6 (but not including .NET), the upgrade has been relatively painless. This is particularly true for all the Windows VB versions (through VB6).
As others have said, it's been a "few" years since I've messed with older version of VB, but I'm thinking that VB3 code will pull into the VB6 IDE and just directly execute and compile. If not "directly", it certainly won't take much to get it going.
Regarding your actual problem/question, here's some code I found in my "junk drawer". I didn't write it, and I have no idea who did, but it's named well for what you're describing.
Code:
Private Sub PrintWrappedText(ByVal txt As String, ByVal indent As Single, ByVal left_margin As Single, ByVal top_margin As Single, ByVal right_margin As Single, ByVal bottom_margin As Single)
' Print a string on a Printer or PictureBox, wrapped within the margins.
Dim next_paragraph As String
Dim next_word As String
Dim pos As Integer
' Start at the top of the page.
Printer.CurrentY = top_margin
' Repeat until the text is all printed.
Do While Len(txt) > 0
' Get the next paragraph.
pos = InStr(txt, vbCrLf)
If pos = 0 Then
' Use the rest of the text.
next_paragraph = Trim$(txt)
txt = ""
Else
' Get the paragraph.
next_paragraph = Trim$(Left$(txt, pos - 1))
txt = Mid$(txt, pos + Len(vbCrLf))
End If
' Indent the paragraph.
Printer.CurrentX = left_margin + indent
' Print the paragraph.
Do While Len(next_paragraph) > 0
' Get the next word.
pos = InStr(next_paragraph, " ")
If pos = 0 Then
' Use the rest of the paragraph.
next_word = next_paragraph
next_paragraph = ""
Else
' Get the word.
next_word = Left$(next_paragraph, pos - 1)
next_paragraph = Trim$(Mid$(next_paragraph, pos + 1))
End If
' See if there is room for this word.
If Printer.CurrentX + Printer.TextWidth(next_word) _
> right_margin _
Then
' It won't fit. Start a new line.
Printer.Print
Printer.CurrentX = left_margin
' See if we have room for a new line.
If Printer.CurrentY + Printer.TextHeight(next_word) _
> bottom_margin _
Then
' Start a new page.
Printer.NewPage
Printer.CurrentX = left_margin
Printer.CurrentY = top_margin
End If
End If
' Now print the word. The ; makes the
' Printer not move to the next line.
Printer.Print next_word & " ";
Loop
' Finish the paragraph by ending the line.
Printer.Print
Loop
End Sub
Good Luck,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Thanks for all your help and opinions guys. Once I've had a chance to review and try out all of the above, I'll feed back here!
(Incidentally, I seem to recall even VB4 wouldn't natively read our VB3 code. Rest assured, once time permits we want to get as far away from VB3 as we can, however the software is still LIVE and in daily use... )
(Incidentally, I seem to recall even VB4 wouldn't natively read our VB3 code. Rest assured, once time permits we want to get as far away from VB3 as we can, however the software is still LIVE and in daily use... )
By default VB3 saved files in binary format. I can't remember if VB4 could read those or not [I barely touched Vb4] but I know VB5 and 6 can not.
There was also an option to save in text format which can be read by the other versions without issue.
The only VB3 project upgrade issues other than the binary save were related to 3rd party vbx controls.
Most VB3 projects which use only basic controls can be saved as text then updated and compiled in VB6 without issue.
Ahhhh, DataMiser, excellent catch. Yes, I remember that too.
@Morken: It's been too long to remember exactly where it is, but there's a Option/Setting somewhere in VB3 to tell it to save your files as ASCII/ANSI. You need to select that option. Selecting this won't change anything about how VB3 works, but it will save your FRM (and BAS, and other) files in a Notepad-readable form, which is the default (and only) way that VB6 does things. That may even fix the problems you were having with trying to go to VB4.
Good Luck,
Elroy
EDIT1: Morken, just as some more FYI, older versions of VB could save files into something called p-code. This is a quasi-compiled version of the code. However, it can be easily un-compiled and displayed again, so long as that version of the IDE knows how to do that (which later versions do not). They did this to save a bit of disk space, and also to load your project a bit faster. However, as computers got faster and disk space got larger (and cheaper), Microsoft realized that saving files as p-code just didn't really gain that much, so they abandoned it.
Interestingly, VB6 still uses p-code, but not for saving its files. VB6 uses this "compiled" p-code to run/execute your code while developing in the IDE. When actually compiled, it's no longer p-code. It's true machine code.
Also, and I don't remember the exact dates/versions of all the transitions, but not all the older versions of VB actually compiled to machine code. The early versions (including VB3 I believe) only compiled the executable to p-code. As such, if someone got your executable, they could relatively easily de-compile it back to a relatively good version of your source code.
Last edited by Elroy; Sep 13th, 2017 at 09:42 AM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Yes VB3 compiles only to P-Code and can be de-compiled into usable though pretty much unreadable VB code.
I think VB5 was the first version that produce native execuables with the exception of VBDOS of course. VBDOS could not only create a true EXE but could create a true standalone EXE.
Yes VB3 compiles only to P-Code and can be de-compiled into usable though pretty much unreadable VB code.
I think VB5 was the first version that produce native execuables with the exception of VBDOS of course. VBDOS could not only create a true EXE but could create a true standalone EXE.
Correct, compile to native EXE's started in VB5. Everything before is pcode, I think.
Yes VB3 compiles only to P-Code and can be de-compiled into usable though pretty much unreadable VB code.
I think VB5 was the first version that produce native execuables with the exception of VBDOS of course. VBDOS could not only create a true EXE but could create a true standalone EXE.
right, but weren't those still interpreted / p-code ? (and the standalone just static linked the runtime)
Honestly I am not sure now, I know the runtime was not required on the target system when a standalone exe was generated and I remember the compiler created obj files and used link to build the exe but last one I build was in the 90s so is all a bit vague at this point.
Not sure why that would be hard to convert to VB3. I don't see any code there that should not be supported...
The first two things that jumped out for me is the InStrRev function which VB3 didn't have, and the use of "With...End With" which VB3 didn't have either.
The use of "With...End With" would make the code look strange compared to what someone using VB3 would be familiar with.
Used Elroy's 'PrintWrappedText' function which was pure VB3 with the exception of 'vbCRLf' constants - easily replaced with Chr(13) and Chr(10). There was a wee problem with new paragraphs in the Txt string being translated with double newlines, however a wee condition to see if next_word was Asc(13) or Asc(10) did the trick.
Many many thanks for all of your guidance and help... very much appreciated.
Re: [RESOLVED] VB3 - How to word-wrap a long string of Text?
Mork
Glad you got it working.
Oh yeah, and a belated Welcome to the Forums ..
In that regard, it is good practice to mark this thread as RESOLVED.
To do that, you'll notice that just about your 1st post, there is a little drop down menu named Thread Tools. Click the down arrow, and select the RESOLVED option.