Delphi printing with Canvas, align text to the right - delphi

I'm printing using TPrinter.Canvas and am trying to align the text to the right due to money displaying numbers.
The text is printed with this code:
Printer.Canvas.Textout(800, 100, 250.00); //charge1
Printer.Canvas.Textout(800, 200, 10.00); //charge2
Printer.Canvas.Textout(800, 300, 260.00); //Total Amount
How can I align the lines to display correctly?

Instead of using VCL's TextOut, you should use the Winapi DrawText and choose to align to the right using that within a given rectangle, specifically using DT_RIGHT. For example...
DrawText(Canvas.Handle, PChar(T), Length(T), R, DT_SINGLELINE or DT_RIGHT);

You could use the lower level device context Api's to more accurately control the text output, such as DrawText() as suggested by Jerry. However, doing this will require that you pre-calculate/set the bounding rectangle for your text output so that Windows knows the area which any alignment is relative to.
There are two potentially simpler alternatives:
Option 1 - For Fixed Width Fonts ONLY
This may be appropriate for you since you appear to be using a fixed-width font.
All we do here is introduce padding to your strings prior to output. This may be simpler than the calculations required for the output rectangles etc.
To do this, decide on the longest string you will accept for output and then add whatever spaces you need to the left of each string to ensure they are all the same length. With a fixed width font, a space takes up the same horizontal space as any other character so by making all strings the same length you will achieve "right alignment":
procedure PrintRightJustified(const aX, aY, aMaxLen: Integer; aValue: String);
var
s: String;
begin
s := aValue;
// Pad the string with spaces to the maximum length
if Length(s) < aMaxLen then
s := StringOfChar(' ', aMaxLen - Length(s)) + s;
Printer.Canvas.Textout(aX, aY, s);
end;
Then call this method to output your strings right-justified to a consistent maximum length, e.g. if 6 is your maximum length (999.99):
PrintRightJustified(800, 100, 6, '250.00'); //charge1
PrintRightJustified(800, 200, 6, '10.00'); //charge2
PrintRightJustified(800, 300, 6, '260.00'); //Total Amount
How to handle scenarios where the specified string is longer than the maximum width is left as an exercise.
Option 2 - For Any Font
Another way, which would work for fixed width or variable width fonts (e.g. Arial) would be to choose the position of the rightmost edge of the strings and calculate the X position relative to this based on the device width calculated for each string you are outputting:
procedure PrintRightJustified(const aRightX, aY; aValue: String);
var
leftX: Integer;
begin
leftX := aRightX - Printer.Canvas.TextWidth(aValue);
Printer.Canvas.Textout(leftX, aY, aValue);
end;
You don't need to specify a maximum width for this version, it works regardless of font being used:
PrintRightJustified(900, 100, '250.00'); //charge1
PrintRightJustified(900, 200, '10.00'); //charge2
PrintRightJustified(900, 300, '260.00'); //Total Amount
This does mean that text may potentially "bleed" too far to the left without some mechanism to detect and handle this. Again, If that is a concern then either move the right-edge further across or you will have to come up with a way of handling it (left as an exercise for you since we don't know what your precise requirements are).

To the precedent answer!
With the above given code (Answer 1) I couldn'get my number aligned with one space.
After some thinking, I check on Word with my font, Times New Roman, how many space I need to get the number aligned, (the decimal dot have to be vertically aligned).
I noticed that two spaces were necessary not only one.
I juste wrote the command two times:
s:= StringOfChar(' ', aMaxLen - Length(s)) + s;
s:= StringOfChar(' ', aMaxLen - Length(s)) + s;
Man could do
s:= StringOfChar(' ', aMaxLen - Length(s))+ StringOfChar(' ', aMaxLen -
Length(s)) + s;
Excuse my english but it is not my mother language.
I hope it is a bit more understandable.

To Option 1 - For Fixed Width Fonts ONLY
First on word you check how many space you need to align your number. For Times I did need 2 spaces so you just double the procedure line:
s := StringOfChar(' ', aMaxLen - Length(s)) + s;
s := StringOfChar(' ', aMaxLen - Length(s)) + s;

Related

TStringGrid cannot display very long (6K) strings

I want to load some text in a TStringGrid. The strings are short except for a column where the string is over 100K. It seems that TStringGrid cannot handle this. The text does not appear in the cell until I double click the cell to edit it. But even then the behavior is erratic.
To reproduce: put a grid on the form, set goEdit= true. Run the application and double click a cell. Paste some text (should not contain enters) and press Enter to end the editing. The text disappear.
In the text I made, the limit is about 6208 ASCII chars.
Any quick fix/workaround for this?
The text is painted with ExtTextOut. That is known to fail for very long strings. For instance: ExtTextOut fails with very long strings unless lower font quality specified. From what I can tell, it is tricky to work out exactly what length of string causes failure.
I suggest that if you need to support such long strings then you draw them yourself by implementing an OnDrawCell event handler. Don't draw the entire string, because after all the user won't be able to see anything outside the cell's rectangle. That way you will be able to avoid the problem of sending ExtTextOut a string that is too long for it to handle.
you need to use Word break. of course without Word break nothing will be displayed. and of couse your text must contain spaces.
const
N = 16000;
var
R: TRect;
s: String;
i: Integer;
begin
R := ClientRect;
SetLength(s, N);
for i := 1 to N do
if Random(10) = 0 then
s[i] := ' '
else
s[i] := Char(65 + Random(26));
Canvas.Font.Color := clBlack;
Canvas.TextRect(R, s, [tfCenter, tfVerticalCenter, tfWordBreak]);
end;

TMemo max width

Is there any way to make TMemo display text longer than 1024 into 1 line?
Take a look this simple code:
procedure TForm1.Button2Click(Sender: TObject);
var
s: string;
i: integer;
begin
s := '';
for i := 0 to 10000 do
s := s + 'a';
Memo1.Clear;
Memo1.Lines.Add(s);
end;
The long text "s" will be displayed in multiple lines. The Memo1 will automatically wrap the text after 1024 characters.
A TMemo is a wrapper for a native multi line edit control and is subject to the limitations it has. From INFO: Size Limits for a Multiline Edit Control:
A multiline edit control is also subject to the following limitations:
The maximum number of characters in a single line is 1024.
The maximum width of a line is 30,000 pixels.
The maximum number of lines is approximately 16,350.

Delphi Printer.Canvas.TextWidth property

I'm trying to set the column width for printing with my Delphi application. Whatever I type for the string doesn't make the width fewer. Actually I don't understand why the property returns a string, it should return width in pixels.
My code is
Printer.Canvas.TextWidth('M');
Edit: i understood it doesn't return a string but what does 'M' mean? what i m trying to do is making a column narrower. my code is located at sudrap.org/paste/text/19688
Edit: i m afraid i couldn t explain the problem clearly, i m sorry. i want it to print like this:
not like this:
Try to check TextRect function. Using this function you can specify the target rectangle where the text should be printed, so you can narrow your column.
uses Graphics;
var
Text: string;
TargetRect: TRect;
begin
Printer.BeginDoc;
Text := 'This is a very long text';
// now I'll specify the rectangle where the text will be printed
// it respects the rectangle, so the text cannot exceed these coordinates
// with the following values you will get the column width set to 50 px
TargetRect := Rect(Margin, Y, Margin + 50, Y + LineHeight);
Printer.Canvas.Font.Size := 11;
Printer.Canvas.Font.Name := 'Arial';
Printer.Canvas.Font.Style := [fsBold];
Printer.Canvas.TextRect(TargetRect, Text);
Printer.EndDoc;
end;
Except this you can get with the TextRect function set of the formatting flags which can help you to specify e.g. text alignment, word wrap etc. For instance if you would like to center the text horizontally in the specified rectangle [100;100], [250;117] you can use the following.
Text := 'Centered text';
TargetRect := Rect(100, 100, 250, 117);
Printer.Canvas.TextRect(TargetRect, Text, [tfCenter]);
Or in your case might be more useful word wrap. Here's an example with rectangle [100;100], [200;134] where the text is automatically wrapped by the TextRect function.
Text := 'This is a very long text';
TargetRect := Rect(100, 100, 200, 134);
Printer.Canvas.TextRect(TargetRect, Text, [tfWordBreak]);
If you use a fixed width font on the canvas, you should get the same result for all single-character strings. If you use a variable width font, each character will return a different width.
Printer.Canvas.Font.Name = 'Courier New';
Printer.Canvas.Font.Size = 13;
ColumnWidth := Printer.Canvas.TextWidth('M');
For different fonts or different font sizes, you will get different results.
I don't see how you're saying it returns text. If it were returning text your code wouldn't even compile, you would be getting errors when you tried to multiply a number by text. You even convert it to a string for display purposes.
Are you being mislead by the fact that with a variable-width font that you'll get different answers for different strings? You can even get different answers for the same letters in a different order. For some fonts "WAM" will produce a different answer than "WMA" because of how the W and A fit together.
Also, you're simply assuming that your labels are narrower than 15 M's. While this is generally the case it's not good programming practice. Instead, you should be asking for the width of each label and using something a bit above the biggest answer.
Finally, your handling of LineHeight is atrocious. Simply add 300 to y if that's what you really want although it should be some multiple of your line height, not a fixed value. You'll get VERY different results from your code off printers with different DPI settings.
Have you even tried stepping through this code with the debugger to see what's going on internally? Your output of the position to the printout suggests you aren't using the debugger.

How do I determine the height of a line of text in a TMemo programmatically?

I've got a TMemo, and I want to always make it exactly high enough to display the number of lines it contains. Unfortunately, I don't quite know how to calculate that. I can't base it off the .Font.Size property, because that will vary based on DPI. And I can't use TCanvas.TextHeight because TMemo doesn't seem to have a canvas.
Anyone know how to do this right?
I see a problem, i think all lines on a TMemo are equal on height, but some can be empty...
So getting Height of empty ones will give zero while they are not zero height on the TMemo.
So the solution maybe doing something like Memo.Lines.Count*LineHeight
Beware that the Lineheight may not be getted by Canvas.TextHeight since Canvas.TextHeight will give more or less exact height of minimal height for a text... i mean it will not give same height for text 'ABC' than for 'ABCp', etc...
I would recomend (if not want to call Windows API) to use Font.Height, but if it is negative the internal leading of each line is not measured...
So i would recomend to do the next steps (tested):
Asign a positive value for Memo.Font.Height on the OnCreate event or anywhere you want, with this the lineheight ot the TMemo will be such value you asigned
Total height now can be obtained directly by Memo.Lines.Count*LineHeight, since you have asigned a positive value to Memo.Font.Height (remember that would make Memo.Font.Size to be negative)
Personally i do this on the TForm OnCreate event (to ensure it is done only once), just to ensure visual font size is not changed and MyMemo.Font.Height includes internal leading of each line:
MyMemo.Font.Height:=Abs(MyMemo.Font.Size*MyMemo.Font.PixelsPerInch div Screen.PixelsPerInch);
Ensure the previous to be done only once, otherwise the text size will be visaully bigger and bigger, as much as times you run it... it is caused because not allways MyMemo.Font.PixelsPerInch is equal to Screen.PixelsPerInch... in my case they are 80 and 96 respectively.
Then, when i need to know line height i just use:
Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72)
That gives exact height of one TMemo line, since all lines have the same height, the total height would be:
MyMemo.Lines.Count*Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72)
So, to make TMemo height as big as its contained text i do this (read the comment of each line, they are very important):
MyMemo.Font.Height:=Abs(MyMemo.Font.Size*MyMemo.Font.PixelsPerInch div Screen.PixelsPerInch); // I do this on the Tform OnCreate event, to ensure only done once
MyMemo.Height:=1+MyMemo.Lines.Count*Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72); // I do this anywhere after adding the text and/or after editing it
I you do not want to play with Screen.PixelsPerInch you can just do this (read the comment of each line, they are very important):
MyMemo.Font.Height:=Abs(MyMemo.Font.Height); // This may make text size to visually change, that was why i use the corrector by using MyMemo.Font.PixelsPerInch and Screen.PixelsPerInch
MyMemo.Height:=1+MyMemo.Lines.Count*Abs(MyMemo.Font.Height*MyMemo.Font.PixelsPerInch div 72);
Hope this can help anyone.
You can write your own implementation of TCanvas.TextHeight for TMemo:
function CountMemoLineHeights(Memo: TMemo): Integer;
var
DC: HDC;
SaveFont: HFont;
Size: TSize;
I: Integer;
begin
DC:= GetDC(Memo.Handle);
SaveFont:= SelectObject(DC, Memo.Font.Handle);
Size.cX := 0;
Size.cY := 0;
// I have not noticed difference in actual line heights for TMemo,
// so the next line should work OK
Windows.GetTextExtentPoint32(DC, 'W', 1, Size);
// BTW next (commented) line returns Size.cY = 0 for empty line (Memo.Lines[I] = '')
// Windows.GetTextExtentPoint32(DC, Memo.Lines[I], Length(Memo.Lines[I]), Size);
Result:= Memo.Lines.Count * Size.cY;
SelectObject(DC, SaveFont);
ReleaseDC(Memo.Handle, DC);
end;
You need to use a TCanvas for this. You can either create a TBitMap in the background and use its TCanvas (after assigning the Memo's Font property to the Bitmap.Canvas' one), or use a TCanvas from another component. Somthing like this:
BMP:=TBitMap.Create;
TRY
BMP.Canvas.Font.Assign(Memo.Font);
TotalHeight:=0;
FOR LineNo:=1 TO Memo.Lines.Count DO INC(TotalHeight,BMP.Canvas.TextHeight(Memo.Lines[PRED(I)]))
FINALLY
FreeAndNIL(BMP)
END;
Edit:
Or perhaps this one:
BMP:=TBitMap.Create;
TRY
BMP.Canvas.Font.Assign(Memo.Font);
LineHeight:=BMP.Canvas.TextHeight('Wq');
TotalHeight:=Memo.Lines.Count*LineHeight
FINALLY
FreeAndNIL(BMP)
END;
I originally suggested looing at the "Lines" TStrings list member in TMemo.
Instead, please look at the "Font" member in the parent class, TCustomEdit.
'Hope that helps .. PS

Delphi: Autoscale TEdit based on text length does not work when removing chars

I have an input edit field where the user can enter data. I want the box width to be at least 191px (min) and maximum 450px (max).
procedure THauptform.edtEingabeChange(Sender: TObject);
begin
// Scale
if Length(edtEingabe.Text) > 8 then
begin
if Hauptform.Width <= 450 then
begin
verschiebung := verschiebung + 9;
// The initial values like 'oldedtEingabeWidth' are global vars.
edtEingabe.Width := oldedtEingabeWidth + verschiebung;
buDo.Left := oldbuDoLeft + verschiebung;
Hauptform.Width := oldHauptformWidth + verschiebung;
end;
end;
end;
This works for ENTERING text. But when I delete one char, it does not scale back accordingly.
In your code, nothing will happen when your text is less than 8 characters long.
Also, I don't see any condition under which your width becomes smaller. It only becomes larger (by 9) with each iteration.
By the way, you appear to be multiplying by 9 as an average character width. You can use Canvas.TextWidth to determine the actual width required by the text without estimating.
If you want to use "9" anyway, you should name it as a constant to make clear what it is.
Quick and dirty using TextWidth:
const
MAX_EINGABE_WIDTH = 450;
MIN_EINGABE_WIDTH = 191;
procedure THauptform.edtEingabeChange(Sender: TObject);
var Width: Integer;
begin
// Scale
Width := edtEingabe.Canvas.TextWidth(edtEingabe.Text);
if Width > MAX_EINGABE_WIDTH then
Width := MAX_EINGABE_WIDTH
else if Width < MIN_EINGABE_WIDTH then
Width := MIN_EINGABE_WIDTH
edtEingabe.Width := Width;
end;
You're just adding 9 everytime the text changes and the length is grater than 8 - regardless of the change. You need to make it a function based on the length instead.
Something like this would do the trick:
procedure THauptform.edtEingabeChange(Sender: TObject);
var
len: integer;
additionalWidth: integer;
begin
len := Length(edtEingabe.Text);
if len <=8 then
additionalWidth:=0
else
additionalWidth:=(len-8)*9; //Assuming we need an extra 9 pixels per character after the 8th one
if additionalWidth > 259 then additionalWidth := 259; // maximum - minimum
edtEingabe.Width := 191 + additionalWidth;
Width := OriginalFormWidth + additionalWidth; // You'll need to know what the minimum width of your form is
end;
This isn't really a very pretty solution, though - changing all of those properties in the same way is ugly. Instead, since it appears you're also resizing the form, you can change the Anchors property of your edit box to make it maintain its margin to the right side as well, and only resize your form.
However, you probably want to consider if this is really a good idea. Why not let the input field just have a single size? In general, it looks better if windows don't resize on their own.
Do something like this:
procedure THauptform.edtEingabeChange(Sender: TObject);
var
Edit:TEdit;
begin
Edit := TEdit(Sender);
Edit.Width := Canvas.TextWidth(Edit.Text+' |')+
Edit.Padding.Left+
Edit.Padding.Right;
end;
Note 1: Don't manually try to limit the size. Instead, set Constraints.MinWidth and Constraints.MaxWidth via the property editor. That leaves your code clean and useless GUI stuff like this in the .dfm.
Note 2: TEdit doesn't have any public canvas property that you can use to get the text width.
Note 3: I don't like this kind of interface with growing and shrinking inputs, but it's probably just a matter of personal taste.

Resources