Delphi Printer.Canvas.TextWidth property - delphi

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.

Related

Delphi printing with Canvas, align text to the right

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;

TVirtualStringTree ScaleBy Stretch

I'm Trying to get a VST to resize automatically when its Height and Width is changed.
I don't have this problem with other placed VCL components, some of them have a Property "Stretch" like TImage, which lets them adjust automatically. The VST remains stubborn and keeps nodeHeights and Column widths.
I have seen Header->hoAutoResize, hoColumnResize. In this matter, AutoSizeIndex <> -1 is not so useful, since I need every column to scale down. I think that's why these don't do anything. Changing the AutoSizeIndex to my last Column (3, because i have 4 Columns) and having hoAutoResize = True; still doesn't affect my column widths. hoColumnResize is the setting that lets the User resize columns, so no luck with that either
I have seen TreeOptions->AutoOptions->toAutoChangeScale and toAutoSpanColumns. I found out that toAutoSpanColumns is counter-productive in my case, so that's off. My Font-Size is adjusting.
I have found Tree.Scaleby, but I can't make it work in my favor and it's undocumented in the official .pdf docs I have.
All 4 columns have minWidth of 10, so no issue there
All 4 have coEnabled, coVisible, coResizable := True and coFixed, coSmartResize := False fwiw
I guess I'm just hitting the wrong combinations of settings or something.
Any hints would be great, thanks.
From your post I understand that you want to automatically adjust widths of all columns proportionally when the TVirtualStringTree width changes. If you want that to also happen with the row heights, you can just apply the following correspondingly.
There is no setting for the a propertional column width, but it is simple to achieve in the OnResize event of the TVirtualStringTree:
procedure TForm1.VSTResize(Sender: TObject);
begin
VST.Header.Columns[0].Width := MulDiv(VST.Width, 50, 100);
VST.Header.Columns[1].Width := MulDiv(VST.Width, 30, 100);
VST.Header.Columns[2].Width := MulDiv(VST.Width, 20, 100);
end;
In the above, the columns are kept as 50%, 30% and 20% of the components width.
You need to set Header->AutoSizeIndex to the index of yours last column, besides the Header->hoAutoResize to true.
Something like this if you wish to do it programmatically:
vt.Header.Options := vt.Header.Options + [hoAutoResize];
vt.Header.AutoSizeIndex := vt.Header.Columns.GetLastVisibleColumn;

Need guidance on drawing PNGs into a Delphi XE6 TDrawGrid

I'm using a drawgrid to display a "dungeon" map. I have an array filled with data describing what types of physical features are located in each cell. The basic layout of rooms and corridors, etc. There's a PNG for each permutation.
I'm trying to "draw" the map by drawing the appropriate PNG into each cell of the drawgrid. I can do it more or less directly using DrawGrid1.Canvas.Draw(x,y,pngImage), but figuring out the exact x,y in pixels is frustrating because of the gridlines (at least, I'm finding it frustrating), and I'm not sure what other issues I'll run into down the road.
I also tried pngImage.Draw(DrawGrid1.Canvas,Rect), but once again, I have to calculate the Rect, which really seems unnecessary as the cells and PNGs are all 40x40 pixels.
Reading related articles and examples, OnDrawCell seems to be a better way, because given ARow and ACol, the Rect for the given cell is apparently precalculated (unless I'm misinterpreting what I've read in several places). But none of the examples I've found actually show how OnDrawCell is triggered. The easy answer "when something gets drawn into a cell" doesn't really explain it much better. That's what I'm trying to do.
(I've often found this to be the case with Delphi's documentation: How is explained (not always exactly for your given use case), but when or why is left shrouded in mystery..)
Of course, there are a few other things to figure out as well, like controlling the drawgrid so it doesn't erase the PNG when a cell is clicked..
Any relevant suggestions will be greatly appreciated. TFRM
Why is figuring out exact X and Y in pixels so hard? It is actually pretty simple.
In order to get left border of your rectangle you simply multiply width of you column plus width of your gridline by number of the column. And if you have gridline on the very left of the first column then add the width of your gridline.
And in order to get right border of your rectangle simply add width of the column to the previous result.
And for calculating Y you just use height instead of width
So the code would look something like this (written from my mind and untested)
const
CellWidth = 40;
CellHeight = 40;
GridLineThickness = 1;
procedure DrawCell(Row, Column: Integer; Image: Bitmap);
var Rect: TRect;
begin
Rect.Left := ((CellWidth + GridLineThickness) * Column) + GridLineThickness;
Rect.Right := Rect.Left + CellWidth;
Rect.Top := ((CellHeight + GridLineThickness) * Row) + GridLineThickness;
Rect.Bottom := Rect.Top + CellHeight;
PaintBox1.Canvas.StretchDraw(Rect, Image);
end;
And if you want an ability to zoom in or zoom out just multiply the CellWidth and CellHeight by a zoom factor. And when being zoomed out a lot just omit rendering grid lines.

How do I use DrawText DT_CALCRECT properly?

I am generating a report that has a caption in the footer. I use DrawText to find out the caption's dimensions. The problem is the text is always clipped, but when I have a carriage return at the end of the text, all the text appears perfectly.
lClientRect := Rect(0, 0, 4770, 59);
lFlags := DT_CALCRECT or DT_EXPANDTABS or Alignments[Alignment]
or WordWraps[WordWrap] or DT_NOPREFIX or DT_TOP or DT_EXTERNALLEADING;
DrawText(lCanvas.Handle, PChar(lsCaption), Length(lsCaption), lClientRect, lFlags);
I examined the rect after the call to DrawText, and it is (0, 0, 4366, 59), but when I have a carriage return, it is (0, 0, 4366, 118).
I don't have any clue on what is happening. Any help will be appreciated.
The carriage return adds a second line of text to the string, thus doubling the height of the calculated rectangle. (Windows is flexible about whether a line-feed or carriage-return character starts a new line.)
As for why the text is clipped (on the bottom edge, I assume), it might be that you're calculating the size using a different font than you have when you draw the text.

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

Resources