Does anyone know how to get the width and height of text in a TRichEdit control, in the same way that you would use TextWidth and TextHeight on a TCanvas?
The reason I need to know this doing this is I have a RichEdit on a non-visible form that I copy the contents of to a canvas using Richedit.Perform(EM_FORMATRANGE, ...). The problem is that the EM_FORMATRANGE requires a parameter of type TFormatRange in which the target rect is specified, but I don't know what the rect should be because I don't know in advance the size of the contents in the RichEdit. Hope that makes sense.
Again use EM_FORMATRANGE for measuring, see EM_FORMATRANGE Message on MSDN:
wParam Specifies whether to render the
text. If this parameter is a nonzero
value, the text is rendered.
Otherwise, the text is just measured.
Generally you would already have a destination area, which has a width and height, where you'd do the drawing, like printing on a paper or producing a preview on a predefined surface. A most simple example for a predefined width to get the required height could be;
var
Range: TFormatRange;
Rect: TRect;
LogX, LogY, SaveMapMode: Integer;
begin
Range.hdc := ACanvas.Handle;
Range.hdcTarget := ACanvas.Handle;
LogX := GetDeviceCaps(Range.hdc, LOGPIXELSX);
LogY := GetDeviceCaps(Range.hdc, LOGPIXELSY);
Range.rc.Left := 0;
Range.rc.Right := RichEdit1.ClientWidth * 1440 div LogX; // Any predefined width
Range.rc.Top := 0;
Range.rc.Bottom := Screen.Height * 1440 div LogY; // Some big number
Range.rcPage := Range.rc;
Range.chrg.cpMin := 0;
Range.chrg.cpMax := -1;
RichEdit1.Perform(EM_FORMATRANGE, 0, Longint(#Range));
ShowMessage(IntToStr(Range.rc.Bottom * LogY div 1440)); // Get the height
RichEdit1.Perform(EM_FORMATRANGE, 0, 0); // free cache
For a more complete example see this article, or in general any RichEdit previewing/printing code...
Related
How can I set TImage size as double value? Example Image1.width := 50.1; or what component accept it, because TImage only accept integer values.
I'm working with download files, and one image should be the number of elements to download, so Image1.width max value is 340, i need to divide this value by the amount of files who will be downloaded, and increase this value on image1.width when every download be finished, but TImage only accept Integer value.
I already did it using "Round" but it is not what I need.
As answered, you cannot set the image's size to any floating point value.
However, using coordinate spaces and transformations functions, you can set an arbitrary transformation between a logical coordinate system and the viewing device. This can be used to increase the logical extent of the image's canvas size with each download and yet keep the image on the screen with an entirely different size.
The below example demonstrates the concept by drawing 4 rows and 4 columns of a 256x256 image on a 105x105 bitmap canvas of a TPicture of a TImage. Basically it achieves to draw a single 256x256 image on a 26.25x26.25 px. surface.
uses
pngimage;
procedure TForm1.Button1Click(Sender: TObject);
const
Col = 4;
Row = 4;
var
Png: TPngImage;
ImgCanvas: TCanvas;
ExtX, ExtY: Integer;
MapMode: Integer;
Size: TSize;
i, j: Integer;
begin
Png := TPngImage.Create;
try
Png.LoadFromFile('...\Attention.png');
Png.Draw(Canvas, Rect(0, 0, Png.Width, Png.Height)); // original picture
Image1.Picture.Bitmap.Canvas.Brush.Color := Color;
Image1.Picture.Bitmap.SetSize(Image1.Width, Image1.Height);
ImgCanvas := Image1.Picture.Bitmap.Canvas;
SetStretchBltMode(ImgCanvas.Handle, HALFTONE);
MapMode := SetMapMode(ImgCanvas.Handle, MM_ISOTROPIC);
if MapMode <> 0 then
try
ExtX := Png.Width * Col;
ExtY := Png.Height * Row;
if not GetWindowExtEx(ImgCanvas.Handle, Size) then
RaiseLastOSError;
if not SetWindowExtEx(ImgCanvas.Handle, Size.cx * ExtX div Image1.Width,
Size.cy * ExtY div Image1.Height, nil) then
RaiseLastOSError;
if not SetViewportExtEx(ImgCanvas.Handle, Size.cx, Size.cy, nil) then
RaiseLastOSError;
i := 0;
j := 0;
while j < ExtY do begin
while i < ExtX do begin
Png.Draw(ImgCanvas, Rect(i, j, i + Png.Width, j + Png.Height));
Inc(i, Png.Width);
end;
i := 0;
Inc(j, Png.Height);
end;
finally
SetMapMode(ImgCanvas.Handle, MapMode);
end
else
RaiseLastOSError;
finally
Png.Free;
end;
end;
Probably worth noting that GDI may not be the best graphics system when scaling is involved. For quick reference, here's what the above yields:
Assuming you're using the VCL framework, all controls across Delphi are Integer based. You simply cannot assign a float value, not without first converting it to an integer.
The Firemonkey framework on the other hand is widely based on float values.
I have an RTF document with defined page settings:
(...}\paperw16840\paperh11907\margl794\margt709\margr794\margb983\viewkind4\\uc1\trowd\....)
In my app I use a TRichEdit to show the document.
The TRichEdit has a TPanel as its Parent, and is using Align=alClient and AlignWithMargins=True.
I set the Panel's Width to 16840 * PixelsPerInch/1440 (1123 pixels) and I see that is equal to the page's width, as shown in MSWord (scale=100%).
Setting the RichEdit's Margins to 794 * PixelsPerInch/1440 (53 pixels), the Width of the RichEdit is smaller than it must be, or the margins are bigger than they must be (compared with MSWord).
No borders, no other margins, except what I set in code:
function pixelsOf(prop : string) : integer;
var
i,j,l : integer;
begin
result := -1;
l := length(prop);
i := pos(prop,s);
if i > 0 then begin
j := i+l;
while s[j] in ['0'..'9'] do inc(j);
result := round(strToIntDef(copy(s,i+l,j-i-l),-1)*PixelsPerInch/1440);
end;
end;
paperW := pixelsOf('\paperw'); // pixelsOf() calcs twips*pixelsPerInch/1440
PanelPreview.Width := paperW;
Lm := pixelsOf('\margl');
RichEdit1.Margins.Left := Lm;
Rm := pixelsOf('\margr');
RichEdit1.Margins.Right := Rm;
Tm := pixelsOf('\margt');
RichEdit1.Margins.Top := Tm;
The value of paperW gives the correct Panel width (compared with MSWord), but the values of Lm and Rm give bigger margins, so the RichEdit becomes narrower.
How can I calculate the correct margins so the RichEdit has the same layout as MSWord?
This maybe helps. I noticed that :
TRichedit leaves a space about 10 pixels at the left side (the rendering is starting after this space). Is there a parameter that can be fix this other than margins.left ?
TRicheditdoesn't render any table wider than its width (MSword do
this adjusting the margins). So the TRichedit trancates everything
outside its margins.
The result of the above is that the left margin seems wider than must be and truncates the right side of the table if it is winder.
see the image
I'm working on an app for sending bills. The client must have the ability to add a leter in rich format. Using Debenu, I scan and then print char by char to retrieve the font styles. This works pretty good, accept for the printing.
The Picture to the left is my richedit and to the right is my PDF. As you can see, character spacing is a little bid off
This is a small bit of code that does the copying and character spacing.
'XPos' is the left position of the line. After each printed char, I'm adding the width of the selected char to XPos
XPos := 42;
for I := 0 to length(RichEdit.Text) - 1 do
begin
RichEdit.SelStart := I;
RichEdit.SelLength := 1;
PDF.DrawText(XPos, YPos, RichEdit.SelText);
XPos := XPos + Canvas.TextWidth(RichEdit.SelText);
end;
The X and YPos for the PDF are floats, while the Canvas.TextWidth are in integers. Is it possible the actual positions on the canvas are a bit more accurate? If that is the case, how do I get those positions in floats?
I'm using Delphi XE-5 and Debenu Quick PDF Library 9.1 for creating the pdf's.
Edit:
As pointed out by LU RD. In FireMonkey the Canvas.Textwidth are floats. I did a quick check with an EditBox, Button and Label:
Label1.Text := floattostr(Canvas.TextWidth(edit1.seltext));
In this test the Label shows a very specific number for char width.
This leaves me to believe that I was right that char width's are indeed floats and I need to start implementing mjn's sugestion of transferring a full string with the same properties to my PDF.
Debenu has this function:
function TDebenuPDFLibrary0915.CharWidth(CharCode: Integer): Integer;
If you devide the return value by 1000 and multiply it by the text size, you will get the right width for the char.
This is the solution to my own question:
XPos := 42;
for I := 0 to length(RichEdit.Text) do
begin
RichEdit.SelStart := I;
RichEdit.SelLength := 1;
PDF.DrawText(XPos, YPos, RichEdit.SelText);
str := RichEdit.SelText;
if length(str) > 0 then
chr := char(str[1]);
XPos := XPos + (PDF.CharWidth(Ord(chr)) / 1000 * 12);
end;
Mind you, this is still work in progress. But the essential is here.
I have two questions about KOL.
I have a main form. As I see this placed on the TForm's position.
I wanna put it to the screen center.
How can I access it's coordinates, or the handle for "SetWindowPos"?
I have 4 labels in the form. For 2 of them I want to use smaller fonts. But I don't found any Font Size property... How to do it then?
1.1. How to center form on screen ?
Use the CenterOnForm method. As description says, if it's applied to a form, centers form on screen:
Form.CenterOnForm(nil);
1.2. How to get form position ?
Just like in Delphi by the Left and Top properties or e.g. by the Position property. The following pseudo-code results to the same:
ShowMessage('Form pos.: [' +
Int2Str(Form.Left) + '; ' +
Int2Str(Form.Top) + ']'
);
ShowMessage('Form pos: [' +
Int2Str(Form.Position.X) + '; ' +
Int2Str(Form.Position.Y) + ']'
);
Note, that unless you change the form position by yourself or until the applet is running, both properties returns 0. The form position, if you didn't set it, is adjusted when the applet starts.
1.3. How to get form handle ?
Use either the Handle property or the GetWindowHandle method. The following pseudo-code results to the same:
ShowMessage(Int2Str(Form.Handle));
ShowMessage(Int2Str(Form.GetWindowHandle));
2. How to change the font size ?
As #David already mentioned in his post, use the Font.FontHeight property. Just one sidenote, the default font when you create e.g. that label is set to System to which is not possible to change the size, so don't be surprised when the size changes won't be applied. Here's a quote from the source:
Value 0 (default) says to use system default value, negative values
are to represent font height in "points", positive - in pixels. In XCL
usually positive values (if not 0) are used to make appearance
independent from different local settings.
And here's a sample usage shown on positioned label creation:
Label1 := NewLabel(Form, 'Label1').SetPosition(8, 8);
Label1.Font.FontName := 'Tahoma';
Label1.Font.FontHeight := -11;
3. Example project
program Project1;
uses
KOL;
type
PForm1 = ^TForm1;
TForm1 = object(TObj)
Form, Label1, Label2, Label3, Label4: PControl;
end;
var
Form1: PForm1;
procedure CreateForm(var Result: PForm1; AParent: PControl);
begin
New(Result, Create);
with Result^ do
begin
Form := NewForm(AParent, 'Caption').SetSize(320, 240);
Form.CenterOnForm(nil);
Label1 := NewLabel(Form, 'Label1').SetPosition(8, 8);
Label1.Font.FontName := 'Tahoma';
Label1.Font.FontHeight := -11;
Label2 := NewLabel(Form, 'Label2').SetPosition(72, 8);
Label2.Font.FontName := 'Tahoma';
Label2.Font.FontHeight := -11;
Label3 := NewLabel(Form, 'Label3').SetPosition(136, 8);
Label3.Font.FontName := 'Tahoma';
Label3.Font.FontHeight := -15;
Label4 := NewLabel(Form, 'Label4').SetPosition(200, 8);
Label4.Font.FontName := 'Tahoma';
Label4.Font.FontHeight := -15;
end;
end;
begin
Applet := NewApplet('Test');
CreateForm(Form1, Applet);
Run(Applet);
end.
Question 1
Call the SetPosition and SetSize methods on the form.
Form.SetPosition(x,y);
Form.SetSize(w,h);
Question 2
Use Font.FontHeight.
MyControl.Font.FontHeight := ...;
If you want to call SetWindowPos, you can retrieve the handle calling the GetWindowHandle method of the returned pointer.
Please, don't ask more than 1 question per question.
When you edit a TLabel's caption in the form designer, it resizes the TLabel for you. Is there any way I can get a TMemo to do that, at runtime?
I'd like to be able to take a TMemo, assign something to its .lines.text property, and then tell it to resize itself and not exceed a certain width, though it can get as tall as it wants to. Anyone know how to do that?
This works just fine for me. The constant added (8) might vary on whether you are using a border and/or bevel, experiment with it.
procedure TForm1.Memo1Change(Sender: TObject);
var
LineHeight: Integer;
DC: HDC;
SaveFont : HFont;
Metrics : TTextMetric;
Increase: Integer;
LC: Integer;
begin
DC := GetDC(Memo1.Handle);
SaveFont := SelectObject(DC, Memo1.Font.Handle);
GetTextMetrics(DC, Metrics);
SelectObject(DC, SaveFont);
ReleaseDC(Memo1.Handle, DC);
LineHeight := Metrics.tmHeight;
Increase := Memo1.Height;
LC := Memo1.Lines.Count;
if LC < 1 then
LC := 1;
Memo1.Height := LC * LineHeight + 8;
Increase := Memo1.Height - Increase;
Memo1.Parent.Height := Memo1.Parent.Height + Increase;
end;
Set the WordWrap property of the TMemo to true, dump your text into it, count the lines, and set the height to the product of the line count and the line height, but you need to know the line height.
The TMemo does not expose a line height property, but if you're not changing the font or font size at runtime, you can determine the line height experimentally at design time.
Here's the code I used to set the height of the TMemo that had a line height of 13 pixels. I also found that I needed a small constant to account for the TMemo's top and bottom borders. I limited the height to 30 lines (396 pixels) to keep it on the form.
// Memo.WordWrap = True (at design time)
Memo.Text := <ANY AMOUNT OF TEXT>;
Memo.Height := Min(19 + Memo.Lines.Count * 13, 396);
If you absolutely must extract the line height from the object at runtime, then you might use Someone's answer. Or, you can use TRichEdit, which has the SelAttributes property containing a Height property giving the line height.
-Al.
I've implemented a self-growing TMemo as a nice example of LiveBindings (one of the few useful examples I could come up with for LiveBindings in VCL).
A quote From my Delphi XE2 Development Essentials courseware manual:
"To build this example, place a TMemo component on a VCL form, open the LiveBindings property, and select the “New LiveBinding” option. Pick the TBindExpression choice. Open BindExpressionMemo11 in the Object Inspector and set SourceComponent to Memo1 and SourceExpression to Lines.Count * 22.
To get a better result at runtime, set SourceExpression to the more exact expression
Font.Size - 4 + (Lines.Count + 1) * -1 * (Font.Height - 3)
Finally, in the OnChange event handler of the TMemo, write one line of code:
BindingsList1.Notify(Sender, '');
That’s it. Compile and run to see the growing memo in action.
[screenshot]
Initially, the TMemo control will be two lines high (the line with the contents, and a next line), and whenever we hit enter or word wrapping advances us to the next line, the TMemo control will grow in height (growing down actually, so make sure to leave enough space on the form for the TMemo to expand itself)."
Groetjes, Bob Swart
procedure TTmpMessage.edMsgChange (Sender: TObject);
var
LineHeight : Integer;
DC : HDC;
SaveFont : HFont;
Metrics : TTextMetric;
begin
DC := GetDC ( TRxRichEdit (Sender).Handle );
SaveFont := SelectObject ( DC, TRxRichEdit (Sender).Font.Handle );
GetTextMetrics (DC, Metrics);
SelectObject (DC, SaveFont);
ReleaseDC ( TRxRichEdit (Sender).Handle, DC );
LineHeight := Metrics.tmHeight;
Height := TRxRichEdit (Sender).Lines.Count * LineHeight + 32;
end;
And why not just:
Memo1.Height := Memo1.ContentBounds.Height + 5;