optimalfill of a label - pascal

I have two forms in my application, form1 sending a long string (more than 300 characters) or short string (less than 50 characthers) to a label on form2 with a click of a button, I'm using a label because I can't find a memo component with transparent background, and Canvas stuff requires a long code compared with the label properties.
Some label properties
Align == alClient
Alignment = taCenter
Layout = tlCenter
WordWrap = True
AutoSize = True
OptimalFill = True
The idea is to adjust the height of the text with OptimalFill, but when I send a long string to the label, the font size, goes all wrong, sometimes the label fills right (when I repeat a long string after a short string for example).
With a:
showMessage(IntToStr(label.Font.Size));
I've got some weird changes on the font size, if I send a string with 100 chars for the first time I got a font like 18, if I send a string with 20 I got a font like 30 and if I send the same string with 100 chars, I got a 22 (numbers are just an example, not the real / right ones), and not even what they should be btw
IF I change the Caption, the text height goes bad (still the font size of the first string that I've used), it only changes when I send a small string, things just go strange.
Following the above example will be something like:
string with 100 chars returns a font size of 20
string with 70 chars returns a font size of 20 //should be arround 25 or so
string with 10 chars returns a font size of 40
string with 100 chars returns a font size of 22
Already tried:
// to 'reset' the font size then put the string on the caption and later the real string
Caption: = '';
Caption: = # 0;
Caption: = ' ';
Caption: = '-';
Label.Update; // to update the 'math'
Label.Refresh; // to update the 'math'
Label.Repaint; //one by one and all togheter
Label.AdjustSize
Application.ProcessMessages
Form2.Update
Label.UpdateBaseBound
Labe.AdjustFontForOptimalFill;
I tried after and before the 'on click sent string to'
label.Caption: = stringGrid.Cells [0, stringGrid.Row];
Also tried enable and disable the label to 'reset' everything.
Nothing works right, things have a strange value (font size) and the font size depends on the length of the sequence.
How can I make it work right and create the right source calculation each time Legend changes?
Arch Linux X64 with the latest Lazarus (QT) using Gnome 3.22.3 (not sure if this is relevant)

Related

How to deteriminate max string length in on line in edit box in Lazarus

I was wondering how can i make that when user input longer string than box's width cursor goes to a new line and when there is more text that can fit into editbox, showbar shows itself and user can input 'infinite' lenght string?

Multi-line headers for TTabSheet?

I have a TPageControl with several tabs. On one page, the header is a bit too long and appears clipped on a single line. Is it possible to have the tab header be multi-line (ie. like the WordWrap property on buttons)?
In the image below the caption has been set to "This text is too long to fit onto one row". Ideally, this should appear on two rows:
This text is too long to fit
onto one row
For the example below I have set:
TabWidth := 185;
TabHeight := 60;
* EDIT *
The example above does not apply any theme. When a skin is applied (in this case the 3rd party product VCLSkin) and I insert a newline character at runtime, then it works:
Product_Sheet.Caption := (
'This text is too long' + #13#10 + 'to fit onto one row');
Based on the comments below, this suggests that in the absence of a skin, this will need to be custom-drawn.

How to properly set the size of a ListView column according to its content?

I have a number of list view controls (TListView) that are used to display data. All these list view are set to "Detail" mode and all have TImageList assigned to their "SmallIcons" properties.
I'm trying to set the width of these column based on their contents exactly in the same way as if the user double-clicked on the separator slider at the end of each of the column headers.
First, I tried to set the column width to "-1" and "-2" for auto-sizing them: not only did that fail to work perfectly (some columns containing local characters - I'm using D6 and that means ANSI strings - are too low) but it also made the display of the column extremely slow (up to 30 seconds to display a list view with 6 column and 150 items when it's instantaneous with fixed width).
I have tried to use GetTextExtent on each cell to obtain the expected width of the text, adding some margin (from 2 to 10 pixels) and the expand the width of the column if it is lower than the calculated text width. Special treatment is applied to the first column (Items.caption) to take into account the display of the icon (I add the width of the icon, plus margin, to the width of the cell's text).
That didn't work either: in many cases (for instance, displaying the date in "yyyy/mm/dd hh:nn:ss" format results in a text too large to fit in the column).
Thinking that the issue could come from the window theme engine, I've switched to use GetThemeTextExtent instead of GetTextExtent but obtained the same result.
The only thing that seems to work is to add an arbitrary large margin (20 pixels) to each column width but, of course, that produces columns that are larger than they should be.
So, is there any alternative strategy ? I don't need anything but something that will calculate the correct width once: when the list is first populated. The code behind "clicking the column separator" works just fine but I can't find how to trigger it by code (well, I guess I could send the double click messages to the header directly as a hack)
For clarification, here are the things I tried the following code:
(in call case, there is a call made to ListView.canvas.Font.Assign(ListView.font). It is not in theses functions because a single assignment is enough but the code loops on all non-autosized columns of the listview).
Edit
My initial attempt using Windows Theme API:
function _GetTextWidth1(AText: widestring; IsHeader: boolean = false): Integer;
var
ATheme: HTheme;
rValue: TRect;
iPartID: integer;
AWidetext: WideString;
const
LVP_GROUPHEADER = 6;
begin
// try to get text width using theme API
ZeroMemory(#rValue, SizeOf(rValue));
ATheme := OpenThemeData(ListView.Handle, 'LISTVIEW');
try
if not IsHeader then
iPartID := LVP_LISTITEM
else
iPartID := LVP_GROUPHEADER;
AWidetext := AText;
GetThemeTextExtent( ATheme,
ListView.Canvas.Handle,
iPartID,
LIS_NORMAL,
PWideChar(AWidetext),
-1,
DT_LEFT or DT_SINGLELINE or DT_CALCRECT,
nil,
rValue
);
finally // wrap up
CloseThemeData(ATheme);
end; // try/finally
result := rValue.Right;
end;
next attempt using DrawText/DrawTextW:
function _GetTextWidth2(AText: widestring; IsHeader: boolean = false): Integer;
var
rValue: TRect;
lFlags: Integer;
begin
// try to get text width using DrawText/DrawTextW
rValue := Rect(0, 0, 0, 0);
lFlags := DT_CALCRECT or DT_EXPANDTABS or DT_NOPREFIX or DT_LEFT or DT_EXTERNALLEADING;
DrawText(ListView.canvas.Handle, PChar(AText), Length(AText), rValue, lFlags);
//DrawTextW(ListView.canvas.Handle, PWideChar(AText), Length(AText), rValue, lFlags);
result := rValue.Right;
end;
Third attempt using delphi's TextWidth function
function _GetTextWidth3(AText: widestring; IsHeader: boolean = false): Integer;
begin
// try to get text width using delphi wrapped around GetTextExtentPoint32
result := ListView.canvas.TextWidth(Atext);
end;
In all cases, I add a margin to the resulting width: I tried values as high as 20 pixels. I also take into account the possibility that the view use icons (in which case I add the width of the icon plus the margin again to the first column).
You could use canvas.TextWidth method. But be sure to use TListView canvas (not other, i.e. TForm) and first assign a font to canvas from TListView.
For example:
var
s: integer;
begin
ListView1.AddItem('test example item', nil);
ListView1.canvas.Font.Assign(ListView1.font);
s := ListView1.canvas.TextWidth(ListView1.Items[0].Caption) + 10; //this "+10" is a small additional margin
if s > ListView1.Columns[0].Width then
ListView1.Columns[0].Width := s;
It works fine for me.

ZPL - zebra: print justified text block without overwriting last line

I'm using the following command to print a justified text:
^FB1800,3,0,J^FT100,200^A0B,26,26^FH\^FDLONG TEXT TO BE PRINTED, WHICH DOESNT FIT IN ONLY 3 LINES...^FS
The command ^FB1800,3,0,J prints a field block in a width of 1800 dots, maximum 3 lines, justified.
The problem is that if the text exceeds the maximum number of lines, it overwrites the last line! :( That of course makes the text of the last line unreadable.
How can I avoid that? Does anybody know if is there a way to cut the exceeding text?
The documentation says exactly that this happens:
Text exceeding the maximum number of lines overwrites the last line. Changing the font size automatically increases or decreases the size of the block.
For reference: I'm using printer Zebra 220Xi4.
Any help would be appreciated. Thank you!
Take a look at the ^TB command. It is preferred over the ^FB command and truncates if the text exceeds the size defined in the TB params
I had just about the same problem, what fixed it in my case - although not the most elegant way - is to specify a higher number of maximum lines, and then formatting it in a way that only the first 3 are in the visible area.
In your case it would be for example ^FB1800,7,0,J instead of ^FB1800,3,0,J
This at least fixed it for me right away, because I print this text at the bottom of the label. If you need to have it somewhere in the middle or top, there might be some tricks with putting a (white) box on top of the overflow-area, since the Zebra printers seem to render before printing. Hope it helps.
Depending on the higher-level programming language you're using (assuming that you are), you could accomplish the same thing (truncate the text to be printed to a specified number of characters) with code like this (C# shown here):
public void PrintLabel(string price, string description, string barcode)
{
const int MAX_CAPS_DESC_LEN = 21;
const int MAX_LOWERCASE_DESC_LEN = 32;
try
{
bool descAllUpper = HHSUtils.IsAllUpper(description);
if (descAllUpper)
{
if (description.Length > MAX_CAPS_DESC_LEN)
{
description = description.Substring(0, MAX_CAPS_DESC_LEN);
}
}
else // not all upper
{
if (description.Length > MAX_LOWERCASE_DESC_LEN)
{
description = description.Substring(0, MAX_LOWERCASE_DESC_LEN);
}
}
. . .
This is what I'm using; is there any reason to prefer the "raw" ^TB command over this?

Issue with algorithm to shorten sentences

I have a webpage which displays multiple textual entries which have no restriction on their length. They get automatically cut if they are too long to avoid going to a new line. This is the PHP function to cut them:
function cutSentence($sentence, $maxlen = 16) {
$result = trim(substr($sentence, 0, $maxlen));
$resultarr = array(
'result' => $result,
'islong' => (strlen($sentence) > $maxlen) ? true : false
);
return $resultarr;
}
As you can see in the image below, the result is fine, but there are a few exceptions. A string containing multiple Ms (I have to account for those) will go to a newline.
Right now all strings get cut after just 16 characters, which is already very low and makes them hard to read.
I'd like to know if a way exists to make sure sentences which deserve more spaces get it and those which contain wide characters end up being cut at a lower number of characters (please do not suggest using the CSS property text-overflow: ellipsis because it's not widely supported and it won't allow me to make the "..." click-able to link to the complete entry, and I need this at all costs).
Thanks in advance.
You could use a fixed width font so all characters are equal in width. Or optionally get how many pixels wide every character is and add them together and remove the additional character wont the pixel length is over a certain amount.
If the style of your application isn't too important, you could simply use a font in the monospace family such as Courier.
Do it in Javascript rather than in PHP. Use the DOM property offsetWidth to get the width of the containing element. If it exceeds some maximum width, then truncate accordingly.
Code copied from How can I mimic text-overflow: ellipsis in Firefox? :
function addOverflowEllipsis( containerElement, maxWidth )
{
var contents = containerElement.innerHTML;
var pixelWidth = containerElement.offsetWidth;
if(pixelWidth > maxWidth)
{
contents = contents + "…"; // ellipsis character, not "..." but "…"
}
while(pixelWidth > maxWidth)
{
contents = contents.substring(0,(contents.length - 2)) + "…";
containerElement.innerHTML = contents;
pixelWidth = containerElement.offsetWidth;
}
}
Since you are asking for a web page then you can use CSS text-overflow to do that.
It seems to be supported enough, and for firefox there seems to be css workarounds or jquery workarounds...
Something like this:
span.ellipsis {
white-space:nowrap;
text-overflow:ellipsis;
overflow:hidden;
width:100%;
display:block;
}
If you fill more text than it fits it will add the three dots at the end.
Just cut the text if it is really too long so you don't waste html space.
More info here:
https://developer.mozilla.org/En/CSS/Text-overflow
Adding a 'see more' link at the end is easy enough, as appending another span with fixed width, containing the link to see more. text will be truncated with ellipsis before that.

Resources