How to truncate a string to fit in a container? - c#

There are a lot of questions (EG: 1, 2, 3, 4, 5) asking how you can truncate a string to a desired amount of characters. But I want a piece of text truncated to fit in a container. (IE: crop the string by it's width in pixels, not characters).
This is easy if you use WPF, but not so much in WinForms...
So: how can you truncate a string to make it fit in a container?

After a day of coding I found a solution and I wanted to share it with the community.
First of all: there is no native truncate function for a string or winforms TextBox. If you use a label, you can use the AutoEllipsis property.
FYI: An ellipsis is a punctuation mark that consist of three dots. IE: …
That's why I made this:
public static class Extensions
{
/// <summary>
/// Truncates the TextBox.Text property so it will fit in the TextBox.
/// </summary>
static public void Truncate(this TextBox textBox)
{
//Determine direction of truncation
bool direction = false;
if (textBox.TextAlign == HorizontalAlignment.Right) direction = true;
//Get text
string truncatedText = textBox.Text;
//Truncate text
truncatedText = truncatedText.Truncate(textBox.Font, textBox.Width, direction);
//If text truncated
if (truncatedText != textBox.Text)
{
//Set textBox text
textBox.Text = truncatedText;
//After setting the text, the cursor position changes. Here we set the location of the cursor manually.
//First we determine the position, the default value applies to direction = left.
//This position is when the cursor needs to be behind the last char. (Example:"…My Text|");
int position = 0;
//If the truncation direction is to the right the position should be before the ellipsis
if (!direction)
{
//This position is when the cursor needs to be before the last char (which would be the ellipsis). (Example:"My Text|…");
position = 1;
}
//Set the cursor position
textBox.Select(textBox.Text.Length - position, 0);
}
}
/// <summary>
/// Truncates the string to be smaller than the desired width.
/// </summary>
/// <param name="font">The font used to determine the size of the string.</param>
/// <param name="width">The maximum size the string should be after truncating.</param>
/// <param name="direction">The direction of the truncation. True for left (…ext), False for right(Tex…).</param>
static public string Truncate(this string text, Font font, int width, bool direction)
{
string truncatedText, returnText;
int charIndex = 0;
bool truncated = false;
//When the user is typing and the truncation happens in a TextChanged event, already typed text could get lost.
//Example: Imagine that the string "Hello Worl" would truncate if we add 'd'. Depending on the font the output
//could be: "Hello Wor…" (notice the 'l' is missing). This is an undesired effect.
//To prevent this from happening the ellipsis is included in the initial sizecheck.
//At this point, the direction is not important so we place ellipsis behind the text.
truncatedText = text + "…";
//Get the size of the string in pixels.
SizeF size = MeasureString(truncatedText, font);
//Do while the string is bigger than the desired width.
while (size.Width > width)
{
//Go to next char
charIndex++;
//If the character index is larger than or equal to the length of the text, the truncation is unachievable.
if (charIndex >= text.Length)
{
//Truncation is unachievable!
//Throw exception so the user knows what's going on.
throw new IndexOutOfRangeException("The desired width of the string is too small to truncate to.");
}
else
{
//Truncation is still applicable!
//Raise the flag, indicating that text is truncated.
truncated = true;
//Check which way to text should be truncated to, then remove one char and add an ellipsis.
if (direction)
{
//Truncate to the left. Add ellipsis and remove from the left.
truncatedText = "…" + text.Substring(charIndex);
}
else
{
//Truncate to the right. Remove from the right and add the ellipsis.
truncatedText = text.Substring(0, text.Length - charIndex) + "…";
}
//Measure the string again.
size = MeasureString(truncatedText, font);
}
}
//If the text got truncated, change the return value to the truncated text.
if (truncated) returnText = truncatedText;
else returnText = text;
//Return the desired text.
return returnText;
}
/// <summary>
/// Measures the size of this string object.
/// </summary>
/// <param name="text">The string that will be measured.</param>
/// <param name="font">The font that will be used to measure to size of the string.</param>
/// <returns>A SizeF object containing the height and size of the string.</returns>
static private SizeF MeasureString(String text, Font font)
{
//To measure the string we use the Graphics.MeasureString function, which is a method that can be called from a PaintEventArgs instance.
//To call the constructor of the PaintEventArgs class, we must pass a Graphics object. We'll use a PictureBox object to achieve this.
PictureBox pb = new PictureBox();
//Create the PaintEventArgs with the correct parameters.
PaintEventArgs pea = new PaintEventArgs(pb.CreateGraphics(), new System.Drawing.Rectangle());
pea.Graphics.PageUnit = GraphicsUnit.Pixel;
pea.Graphics.PageScale = 1;
//Call the MeasureString method. This methods calculates what the height and width of a string would be, given the specified font.
SizeF size = pea.Graphics.MeasureString(text, font);
//Return the SizeF object.
return size;
}
}
Usage:
This is a class you can copy and paste in the namespace that contains your winforms form. Make sure you include "using System.Drawing;"
This class has two extensions methods, both called Truncate. Basically you can now do this:
public void textBox1_TextChanged(object sender, EventArgs e)
{
textBox1.Truncate();
}
You can now type something in textBox1 and if needed, it will automatically truncate your string to fit in the textBox and it will add an ellipsis.
Overview:
This class currently contains 3 methods:
Truncate (extension for TextBox)
Truncate (extension for string)
MeasureString
Truncate (extension for TextBox)
This method will automatically truncates the TextBox.Text property. The direction of truncation is determent by the TextAlign property. (EG: "Truncation for left alignm…", "…ncation for right alignment".) Please note: this method might need some altering to work with other writing systems such as Hebrew or Arabic.
Truncate (extension for string)
In order to use this method you must pass two parameters: a font and a desired width. The font is used to calculate the width of the string and the desired width is used as the maximum width allowed after truncation.
MeasureString
This method is private in the code snippet. So if you want to use it, you must change it to public first. This method is used to measure the height and width of the string in pixels. It requires two parameters: the text to be measured and the font of the text.
I hope I helped someone with this. Perhaps there is an other way to do this, I found this answer by Hans Passant, which truncates a ToolTipStatusLabel, which is quite impressive. My .NET skills are nowhere near that of Hans Passant so I haven't managed to convert that code to work with something like a TextBox... But if you did succeeed, or have another solution I would love to see it! :)

I tested the code of Jordy and compared the result with this code of mine, there is no difference, they both trim/truncate fairly OK but not well in some cases, that may be the size measured by MeasureString() is not exact. I know this code is just a simplified version, I post it here if someone cares about it and uses it because it's short and I tested: there is no difference in how exactly this code can trim/truncate string compared with the Jordy's code, of course his code is some kind of full version with 3 methods supported.
public static class TextBoxExtension
{
public static void Trim(this TextBox text){
string txt = text.Text;
if (txt.Length == 0 || text.Width == 0) return;
int i = txt.Length;
while (TextRenderer.MeasureText(txt + "...", text.Font).Width > text.Width)
{
txt = text.Text.Substring(0, --i);
if (i == 0) break;
}
text.Text = txt + "...";
}
//You can implement more methods such as receiving a string with font,... and returning the truncated/trimmed version.
}

Related

Detecting forced line split in RichTextBox

I have an application which analyses data messages in a piped format (HL7), and for that, it has a DataGridView that's synced with a RichTextBox. Specifically, when you click on a property in the DataGridView, it jumps to the corresponding position in the RichTextBox, and vice versa.
The RichTextBox has word wrap disabled, so I can easily match the lines in the editor to the lines in the actual data.
However, I currently have to deal with messages containing a Base64 dump of a binary file in some parts of it, and the large content is making the rich text box wrap the lines anyway. This makes the calculations mess up, and when matching the returned position in the actual message text, I get wrong data, the analysis fails, and, usually, I get an ArgumentOutOfRangeException when the actual next line is shorter than the clicked position on that line.
This is the code:
/// <summary>Gets the cursor position as Point, with Y as line number and X as index on that line.</summary>
/// <returns>The cursor position as Point, with Y as line number and X as index on that line</returns>
protected Point GetCursorPosition()
{
Int32 selectionStart = this.rtxtMessage.SelectionStart;
Int32 currentLine = this.rtxtMessage.GetLineFromCharIndex(selectionStart);
Int32 currentPos = selectionStart - this.rtxtMessage.GetFirstCharIndexFromLine(currentLine);
return new Point(currentPos, currentLine);
}
Correct behaviour:
On this click, the function will return point [28, 4].
Incorrect behaviour on force-wrapped line:
On this click, the function will return point [6,5], where it should actually be [2813,4]. This causes it to show analysis for the next line, and, as mentioned, if the click is on a location in the line that's beyond the end of that next analysed line, it causes an ArgumentOutOfRangeException.
Is there any way to compensate for this forced line split? I need to be able to accurately determine the position in the actual text to do the analysis.
Note, the line splits don't seem to be predictable; I don't know what the maximum length is after which it tries to split, or the characters on which it decides a split is possible.
Also note, the two called RichTextBox functions, namely GetLineFromCharIndex and GetFirstCharIndexFromLine, correctly correspond to what's actually shown on the screen... but what's shown on the screen is an incorrect representation of the real data. In fact, it doesn't even correspond to the output of the RichTextBox's own .Lines property, which gives me the content in an array of plain text lines.
I'd rather avoid using that .Lines property though, since I've noticed that in general, the functions to extract text from the rich text box are rather slow.
It appears that if you enable the richedit control's advanced typography option via sending the EM_SETTYPOGRAPHYOPTIONS message that the forced wrapping of long text lines does not occur with the RichTextBox.WordWrap property set to false.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
richTextBox1.HandleCreated += RTBHandledCreated;
FillRTB();
}
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
private void RTBHandledCreated(object sender, EventArgs e)
{
const Int32 WM_USER = 0x400;
const Int32 EM_SETTYPOGRAPHYOPTIONS = WM_USER + 202;
const Int32 EM_GETTYPOGRAPHYOPTIONS = WM_USER + 203;
const Int32 TO_ADVANCEDTYPOGRAPHY = 1;
const Int32 TO_SIMPLELINEBREAK = 2;
SendMessage(richTextBox1.Handle, EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
}
private void FillRTB()
{
for (Int32 i = 0; i <= 3; i++)
{
richTextBox1.AppendText($"Line {i}: ");
if (i == 1 || i == 3 )
{
StringBuilder sb = new StringBuilder(100000);
for (Int32 j = 0; j < sb.Capacity; j += 10)
{
for (Int32 k = 0; k <= 9; k++)
{
sb.Append(k.ToString());
}
}
richTextBox1.AppendText(sb.ToString());
}
if (i != 3)
{
richTextBox1.AppendText($"{Environment.NewLine}");
}
}
richTextBox1.SelectionStart = 0;
}
private void richTextBox1_SelectionChanged(object sender, EventArgs e)
{
label1.Text = richTextBox1.GetLineFromCharIndex(richTextBox1.SelectionStart).ToString();
}
}
Note that I originally mentioned in a comment to the OP's self-answer that a solution using the "Text Object Model" was possible. On more thorough testing of this technique, I found that it was only accurate for the first line of forced wrapped text, after that it included previous wrapped lines in the determining the line position. Therefore, I am not showing that method.
In my search for solutions, I found that, as I feared, RichTextBox.Lines is not a simple harmless pointer to an array, but in fact an elaborate operation to convert the rich text box contents to plain text, and using that in this situation would severely impact performance.
However, this made me realise that a lot of my code throughout the project was already accessing that property for random operations, especially in the analysis part that occurs on any line change.
I decided to make a cache variable for that lines array, which is cleared in the TextChanged event of the RichTextBox. All instances that fetched the lines on the RichTextBox were then replaced by a call to this little function:
private String[] GetTextboxLines()
{
if (this.m_LineCache != null)
return this.m_LineCache;
String[] lines = this.rtxtMessage.Lines;
this.m_LineCache = lines;
return lines;
}
This is still rather heavy when simply typing text into the rich text editor, since any key stroke will basically clear the array and then the analyser operation will fetch it again, but since the tool is an analyser first, and only an editor second, this is not a huge issue. And even then, RichTextBox.Lines was called multiple times in the analysis following such a key stroke, so overall the result is still vastly optimised.
With this system in place, the use of the lines array became viable again in my little GetCursorPosition() function, so I adapted it to make use of the new cached value as well:
/// <summary>Gets the cursor position as Point, with Y as line number and X as index on that line.</summary>
/// <returns>The cursor position as Point, with Y as line number and X as index on that line</returns>
protected Point GetCursorPosition()
{
Int32 selectionStart = this.rtxtMessage.SelectionStart;
String[] lines = this.GetTextboxLines();
Int32 nrOfLines = lines.Length;
Int32 y;
for (y = 0; y < nrOfLines; y++)
{
Int32 lineLen = lines[y].Length;
// Can be equal if at the very end of a line.
if (selectionStart <= lineLen)
return new Point(selectionStart, y);
// +1 to compensate for the line break character,
// which is only one byte in a rich text box.
selectionStart -= (lineLen + 1);
}
return new Point(0, nrOfLines - 1);
}

Adapt the height of a TextBox

I am working on a UserControl that contains a multiline TextBox.
When using my control, one will be able to set the text that will be displayed. The TextBox should then adapt its Height to make the text fit, the Width cannot change.
So here is the property that handles the text :
[Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
public string TextToDisplay
{
get
{
return internalTextBox.Text;
}
set
{
internalTextBox.Text = value;
AdaptTextBoxSize();
}
}
My first attempt was rather simple :
private void AdaptTextBoxSize()
{
int nbLignes = internalTextBox.Lines.Length;
float lineHeight = internalTextBox.Font.GetHeight();
internalTextBox.Height = (int)((nbLignes) * lineHeight);
}
This did not work as it doesn't take into account spacing between two lines of text. So the more lines I have in the text, the more I get clipped.
So I tried this :
private void AdaptTextBoxSize()
{
Size textSize = internalTextBox.GetPreferredSize(new Size(internalTextBox.Width, 0));
internalTextBox.Height = textSize.Height;
}
This does work when all the lines in the textbox are shorter than the Width. But when one line is longer and should be clipped to the next line, GetPreferredSize() returns a larger width than the one I passed, and therefore the height is too small.
So I changed again and tried this one:
private void AdaptTextBoxSize()
{
Size textSize = TextRenderer.MeasureText(
internalTextBox.Text,
internalTextBox.Font,
new Size(internalTextBox.Width, 0),
TextFormatFlags.WordEllipsis
);
internalTextBox.Height = textSize.Height;
}
This time the returned Width is correct, as it does not exceed the one I passed, but the height is the same as the previous trial. So it doesn't work either. I tried different combinations for TextFormatFlags, but could not manage to find the winning one...
Is this a bug from the framework?
The real question here is, is there another thing I can try, or another to achieve what I want (i.e. auto-adapt the height when setting the TextToDisplay property)?
TextBox.GetPositionFromCharIndex returns the pixel position of a character. Position here means top/left so we need to add one more line..
This seems to work here:
textBox.Height = textBox.GetPositionFromCharIndex(textBox4.Text.Length - 1).Y + lineHeight;
I get the line height like this:
int lineHeight = -1;
using (TextBox t = new TextBox() { Font = textBox.Font }) lineHeight = t.Height;
I set the Height instead of the ClientSize.Height, which is slightly wrong unless BorderStyle is None. You can change to textBox.ClientSize = new Size(textBox.ClientSize.Width, l + lh);

How can I keep the RadioButton text and an additional Label in alignment?

I'm working on a C# project using .NET 3.5 and Windows Forms. I need to design a decision step with multiple options that require a bit of explanatory text. For this, I want to have a set of RadioButtons to choose an option, followed by an additional Label each that contains the explanation.
I want to keep the label of the radio buttons and the label containing the explanatory text aligned - I've added red lines to the image to illustrate this. I could probably tweak some margins or other settings on the second label, but that would probably start to look weird as soon as the user chooses a different theme or changes some other settings. What is the canonical (and most robust) way to do this?
Your question boils down to two partial problems:
How large is the RadioButton (or the CheckBox when thinking ahead)..
How large is the gap between the glyph and the Text.
The first question is trivial:
Size s = RadioButtonRenderer.GetGlyphSize(graphics,
System.Windows.Forms.VisualStyles.RadioButtonState.CheckedNormal);
..using a suitable Graphics object. Note that I use the RadioButtonState CheckedNormal as I don't you want the Lables to align differently when the Buttons are checked or unchecked..
The second one is anything but trivial. The gap may or may not be constant and there is another gap to the left of the glyph! If I really wanted to get it right I guess I would write a routine to measure the text offset at startup:
public Form1()
{
InitializeComponent();
int gapRB = getXOffset(radioButton1);
int gapLB = getXOffset(label1);
label1.Left = radioButton1.Left + gapRB - gapLB;
}
Here is the measurement function. Note that is doesn't even use the Glyph measurement. Also note that it isn't enough to measure the text offset of the RadioButton. You also need to measure the offset of the Label!
int getXOffset(Control ctl)
{
int offset = -1;
string save = ctl.Text; Color saveC = ctl.ForeColor; Size saveSize = ctl.Size;
ContentAlignment saveCA = ContentAlignment.MiddleLeft;
if (ctl is Label)
{
saveCA = ((Label)ctl).TextAlign;
((Label)ctl).TextAlign = ContentAlignment.BottomLeft;
}
using (Bitmap bmp = new Bitmap(ctl.ClientSize.Width, ctl.ClientSize.Height))
using (Graphics G = ctl.CreateGraphics() )
{
ctl.Text = "_";
ctl.ForeColor = Color.Red;
ctl.DrawToBitmap(bmp, ctl.ClientRectangle);
int x = 0;
while (offset < 0 && x < bmp.Width - 1)
{
for (int y = bmp.Height-1; y > bmp.Height / 2; y--)
{
Color c = bmp.GetPixel(x, y);
if (c.R > 128 && c.G == 0) { offset = x; break; }
}
x++;
}
}
ctl.Text = save; ctl.ForeColor = saveC; ctl.Size = saveSize;
if (ctl is Label) { ((Label)ctl).TextAlign = saveCA; }
return offset;
}
Now the Texts do align pixel perfect..:
Note that I use two original controls from my form. Therefore much of the code is simply storing and restoring the properties I need to manipulate for the measurement; you can save a few lines by using two dummies.. Also note that I wrote the routine so that it can measure RadioButtons and Labels and probably CheckBoxes as well..
Is it worth it? You decide..!
PS: You could also owner-draw the RadioButton and the Label text in one.. this would have the interesting side-effect, that the whole text would be clickable..:
Here is a quick and dirty implementation of owner drawing a CheckBox: Prepare it by setting AutoSize = false and by adding the real text together with the extra text into the Tag, separated by a e.g. "§". Feel free to change this setup, maybe using the Label control..
I clear the Text to prevent it from drawing it and I decide on an offset. To measure it, you could use the GetGlyphSize from above.. Note how the DrawString method honors embedded '\n' characters.
The Tag contained this string:
A Rose is a Rose is a Rose..§A Rose is a rose is a rose is a rose is /
A rose is what Moses supposes his toes is / Couldn't be a lily or a
taffy daphi dilli / It's gotta be a rose cuz it rhymes with mose!
And I for the screenshot I actually used this line:
e.Graphics.DrawString(texts[1].Replace("/ ", "\n"), ...
Here is the Paint event:
private void checkBox1_Paint(object sender, PaintEventArgs e)
{
checkBox1.Text = "";
string[] texts = checkBox1.Tag.ToString().Split('§');
Font font1 = new Font(checkBox1.Font, FontStyle.Regular);
e.Graphics.DrawString(texts[0], checkBox1.Font, Brushes.Black, 25, 3);
if (texts.Length > 0)
{
SizeF s = e.Graphics.MeasureString(texts[1], checkBox1.Font, checkBox1.Width - 25);
checkBox1.Height = (int) s.Height + 30;
e.Graphics.DrawString(texts[1], font1, Brushes.Black,
new RectangleF(new PointF(25, 25), s));
}
}
The simplest out-of-the-box solution (it seems to me) would be to use 3 controls instead of 2: a radio button (with the text set to ""), a label (to go beside the radio button) and another label (to go below them). This would allow you easier configuration in designer, but (far more importantly) simpler run-time evaluation and adjustment, if necessary, to keep them in alignment should styles change.
I do understand that this takes away the benefit of clicking the label to select the radio button, but you could add that behavior in the label's Click event if you need it.
Alternatively, you could create a UserControl containing the text-free radio button and the label, and handle the behavior within that UserControl while exposing the label's location.
If you don't care about the radiobutton's text being bold, you could set it's label to a multiline string, and set CheckAlign to TopLeft:
radioButton2.CheckAlign = ContentAlignment.TopLeft;
radioButton2.Text = #"Radiobutton
Explanation text";
Don't know why I didn't think of this earlier, but the following approach seems to work:
Use a TableLayoutPanel with two columns that are set to adjust their width automatically.
Place all RadioButtons in the first column and set them to span both columns.
Place all Labels in the second column, setting all margins to 0.
Add a disabled, but visible (!) "spacer" RadioButton without text in an additional row at the end of the layout.
When displaying the form, convert the first column to a fixed size and hide the "spacer".
The key point seems to be that the "spacer" has to be visible initially - otherwise the column will get a size of 0.
This is my test form in the designer:
To change the layout, I used the following Load handler:
private void TestForm_Load(object sender, EventArgs e)
{
// find the column with the spacer and back up its width
int column = tableLayoutPanel.GetColumn(radioButtonSpacer);
int width = tableLayoutPanel.GetColumnWidths()[column];
// hide the spacer
radioButtonSpacer.Visible = false;
// set the column to the fixed width retrieved before
tableLayoutPanel.ColumnStyles[column].SizeType = SizeType.Absolute;
tableLayoutPanel.ColumnStyles[column].Width = width;
}
And this is the result at runtime:
You could add an invisible dummy label having the same text as the radiobutton. Then, get the length of that label and calculate the correct position of the explanation label.
labelDummy.Text = radioButton1.Text;
labelExplanation.Left = radioButton1.Right - labelDummy.Width;
However, this still appears to be some pixels off, even though I the label's margin to 0, maybe some additional tweaking can fix this. Here's a screenshot to show what I mean. The label's background is green to be able to see the extra margin.

C# TextBox Line Spacing

I'm working on a plugin for Paint.net that converts the current image to ASCII art. I have the conversion working fine, and it outputs the ASCII art into a TextBox control, with a fixed width font. My problem is, the ASCII art is stretched vertically, because of the line spacing in a TextBox. Is there any way to set the line spacing of a TextBox?
A TextBox simply shows single or multiline text with no formatting options - it can have a font but that applies to the TextBox and not to the text, so you can't have paragraph settings like line spacing as far as I know.
My first suggestion would be to use a RichTextBox, but then again, RTF doesn't have a code for line spacing so I believe that would be impossible as well.
So my final suggestions is to use an owner-drawn control. It shouldn't be too difficult with a fixed-width font - you know the location of each character is (x*w, y*h) where x and y are the character index and w and h are the size of one character.
Edit: Thinking about it a bit more, it's even simpler - simply separate the string to lines and draw each line.
Here's a simple control that does just that. When testing it I found that for Font = new Font(FontFamily.GenericMonospace, 10, FontStyle.Regular), the best value for Spacing was -9.
/// <summary>
/// Displays text allowing you to control the line spacing
/// </summary>
public class SpacedLabel : Control {
private string[] parts;
protected override void OnPaint(PaintEventArgs e) {
Graphics g = e.Graphics;
g.Clear(BackColor);
float lineHeight = g.MeasureString("X", Font).Height;
lineHeight += Spacing;
using (Brush brush = new SolidBrush(ForeColor)) {
for (int i = 0; i < parts.Length; i++) {
g.DrawString(parts[i], Font, brush, 0, i * lineHeight);
}
}
}
public override string Text {
get {
return base.Text;
}
set {
base.Text = value;
parts = (value ?? "").Replace("\r", "").Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
}
}
/// <summary>
/// Controls the change in spacing between lines.
/// </summary>
public float Spacing { get; set; }
}

AutoSize for Label / TextBox in .NET Compact Framework

I'm quite simply going totally bonkers with the omission of the AutoSize-property for the Label and TextBox controls in .NET Compact Framework. I have a simple app, that's supposed to list a bunch of text data (generally between one-liners to a few paragraphs of text) in a TabControl. Everything else works smoothly, but my attempts at dynamically resizing the Label / TextBox -controls I use to display the text are failing miserably.
Here's the way I've tried doing it:
/*
Variables:
s = The text intended for the TextBox
NewTB = TextBox object
width = Intended width
whiteSpaceAdjustment = amount of pixels per line to adjust "wasted" whitespace due to wrapping
*/
String[] linesArray = s.Replace(Environment.NewLine, "\n").Split(new char[] { '\n' });
int lines = 0;
int lineHeight = g.MeasureString(
s.Replace("\n", "").Replace("\r", ""),
LabelFont
).ToSize().Height;
foreach (String str in linesArray) {
if (str.Length == 0) {
lines++;
continue;
}
szz = g.MeasureString(str, LabelFont).ToSize();
lines += szz.Width / (width - whiteSpaceAdjustment);
lines += (szz.Width % width) != 0 ? 1 : 0;
}
NewTB.Height = lines * lineHeight;
NewTB.Width = width;
...but the problem is that the range needed for whiteSpaceAdjustment is too huge. When it's large enough to actually work on the most extreme cases (paragraphs made mostly up of really long words), most boxes end up being a line or two too tall.
I'm probably going to have to implement word wrapping myself, but before I go there, is there anybody with a nice clean solution ready for this?
I'd be forever grateful!
Try this article
www.mobilepractices.com/2007/12/multi-line-graphicsmeasurestring.html
Make sure you also look at the link at the bottom of the article to be able to use different fonts.
If you are using .Net CF 3.5 you may be able to turn their example into an extension method. Otherwise I'd suggest that you create a new control inheriting from the framework control.
This is what I developed for auto re-size width of label in WinCE.
/// <summary>
/// This class provides dynamic size labels, i.e. as the text grows lable width will grow with it.
/// </summary>
public partial class AutoSizeLabel : UserControl
{
private string _strText;
private const int padding = 10;
public AutoSizeLabel()
{
InitializeComponent();
}
public override string Text
{
get
{
return _strText;
}
set
{
_strText = value;
Refresh();
}
}
protected override void OnPaint(PaintEventArgs pe)
{
SizeF size = pe.Graphics.MeasureString(this.Text, this.Font);
this.Size = new Size((int)size.Width + padding, this.Height);
if (this.Text.Length > 0)
{
pe.Graphics.DrawString(this.Text,
this.Font,
new SolidBrush(this.ForeColor),
(this.ClientSize.Width - size.Width) / 2,
(this.ClientSize.Height - size.Height) / 2);
}
// Calling the base class OnPaint
base.OnPaint(pe);
}
}

Categories

Resources