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);
}
}
Related
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);
I am adding a new way to distinguish the user privileges in my program.
It is a small circular panel that appears after the username and that changes color depending on its privileges and that is shown after the user nick leaving a spacing of 5 pixels
:
private void SetNick(string nick)
{
this.NickLabel.Text = nick;
this.NickLabel.Left = ((this.ProfilePicturePanel.ClientSize.Width - this.NickLabel.Width) / 2) - 5;
Hector.Framework.Utils.Ellipse.Apply(this.BadgePanel, 6);
this.BadgePanel.Top = this.NickLabel.Top + 3;
this.BadgePanel.Left = this.NickLabel.Width + this.BadgePanel.Width + 5;
}
The nick of the user has a minimum of 3 characters and a maximum of 6 characters, then when the nickname has 6 characters (example: Jhon S), the panel is aligned correctly:
But if the nickname have 3 characters (example: Ben), then this happens:
It is assumed that the panel should always be shown near the label leaving a space of 5 pixels even if the label changes its content.
Could you tell me what I'm doing wrong?
You can override the Label Control and write your own implementation that draws your badge directly in the Label. Here's a simple example.
public class LabelWithBadge : Label
{
public Color BadgeColor { get; set; }
private Size BadgeSize { get; set; }
public LabelWithBadge()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
if (BadgeColor == null)
BadgeColor = Color.Red;
if (BadgeSize == null)
BadgeSize = new Size(20, 20);
}
protected override Size SizeFromClientSize(Size clientSize)
{
var textSize = TextRenderer.MeasureText("doesn't matter", this.Font);
this.BadgeSize = new Size(textSize.Height, textSize.Height);
var baseSize = base.SizeFromClientSize(clientSize);
return new Size(baseSize.Width + BadgeSize.Width, baseSize.Height);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(new SolidBrush(this.BadgeColor), this.ClientSize.Width - this.BadgeSize.Width, 0, this.BadgeSize.Width, this.BadgeSize.Height);
}
}
By overriding SizeFromClientSize you can control the AutoSize ability of the label, and pad it to make room for your badge.
If you want to support manual sizing for the badge, then you'll need to tweak this to work with AutoSize off.
Then I set Styles on the control to handle painting. Overriding OnPaint allows you to draw in the extra padded on in the SizeFromClientSize override.
I added a property for the Badge Color. The Badge Size is determined by the font on the control using TextRenderer.MeasureText. So if you make the font bigger, the badge get's bigger with it.
This control will show up in your Toolbox when you build. Then you can use it like any other label but this one has a badge in it.
I am an hobbyist .NET developer. I am developing a Windows Forms app and this picture shows want I want to use a chart control for:
Basically, I want to change the upper part of every column which is taller than a certain threshold (400 in the example above). I have searched around a bit, but no joy yet. Any ideas?
EDIT: I have a graceful workaround, but it's still frustrating not to have something available in the control itself. I have created two series, red and blue. When I add a point I check if the height is bigger than the threshold. If yes, I add the point to the red series, and I also add a point of threshold height to the blue series. If not, I add it to the blue series as is. The code can explain better than me:
private void AddPointToChart(Chart chart, int x, int y)
{
if (this.threshold < y)
{
chart.Series[1].Points.AddXY(x, y);
y = this.threshold;
}
chart.Series[0].Points.AddXY(x, y);
}
As per the original question, StackedColumn is the way to go!
And for your edit, on option (not an easy one) is to override virtual methods of Chart to add your own behaviour. I came up with an example after some experimentation:
public class ThresholdColumnChart : Chart
{
private double _threshold = 50d;
public double Threshold
{
get { return _threshold; }
set { _threshold = value; Invalidate(); }
}
public ThresholdColumnChart() : base() { }
protected override void OnCustomize()
{
base.OnCustomize();
if (Series.Count != 1)
return;
Series.Add(new Series());
foreach (var dataPoint in Series[0].Points)
{
var newDataPoint = new DataPoint();
newDataPoint.XValue = dataPoint.XValue;
newDataPoint.YValues[0] = (dataPoint.YValues[0] > _threshold ?
dataPoint.YValues[0] - _threshold : 0);
Series[1].Points.Add(newDataPoint);
if (dataPoint.YValues[0] > _threshold)
dataPoint.YValues[0] = _threshold;
}
Series[0].ChartType = SeriesChartType.StackedColumn;
Series[1].ChartType = SeriesChartType.StackedColumn;
Series[1].Color = Color.Red;
}
protected override void OnPostPaint(ChartPaintEventArgs e)
{
base.OnPostPaint(e);
if (!(e.ChartElement is ThresholdBarChart))
return;
if (Series.Count != 2)
return;
for (int i = 0; i < Series[0].Points.Count; i++)
Series[0].Points[i].YValues[0] += Series[1].Points[i].YValues[0];
Series.Remove(Series[1]);
}
}
In short the OnCustomize(...) method is called just before the chart is drawn (before OnPrePaint(...)) at which point it creates a second series to be used for drawing. The new series is constructed with the threshold in mind, and the first series is reduced not to exceed the threshold just like the example in your edit.
After the chart is done painting OnPostPaint(...) will be called and the first series will be restored to it's original values, and the "extra" series is removed.
Both OnPrePaint(...) and OnPostPaint are actually called multiple times when a chart is drawn, hence why I had to put if (!(e.ChartElement is ThresholdBarChart)) return; in; to make sure we only remove the series once the chart has finnished painting.
After adding the code and compiling once you will get a new usercontrol added to the design toolbox called "ThresholdColumnChart". You can set the threshold from the Property "Threshold" (also in design). I wouldnt trust this code with my life but it should serve as a starting point.
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.
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; }
}