I want to create user control that will display text.
I need a way to resize control at run-time so that it will adjust height to show all the text.
I've created control that looks like this:
As You can see I have icon and text drawn using TextRenderer.DrawText
. Unfortunately when I resize my control (only to left or right) my text is sometimes cut, ass shown below:
I'm measuring text using TextRenderer.MeasureText
based on that I'm counting number of lines and then I'm drawing that text. Here is code I'm using:
[Designer(typeof(MyTextBoxDesigner))]
public partial class MyTextBox : UserControl
{
public MyTextBox()
{
InitializeComponent();
}
[DefaultValue("Demo"), Description("Text of control"), Category("Appearance"),Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text
{
get
{
return base.Text;
}
set
{
if (base.Text == value) return;
base.Text = value;
Invalidate();
}
}
Image _image;
[Description("Image shown on the left side of the control"),
Category("Appearance")]
public Image Image
{
get
{
return _image;
}
set
{
if (_image == value) return;
_image = value;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
int imagePadding = 0;
if (_image != null)
{
imagePadding = 25;
e.Graphics.DrawImage(_image,new Rectangle(5,5,16,16));
}
Size textSize1 = TextRenderer.MeasureText(e.Graphics, Text, Font);
SizeF textSize2 = e.Graphics.MeasureString(Text, Font);
Debug.WriteLine(textSize2);
int maxTextWidth = Width - Padding.Left-Padding.Right-imagePadding;
int lineHeight = textSize1.Height + 2;
int numLines = 1;
if (textSize1.Width > maxTextWidth)
{
numLines = textSize1.Width / maxTextWidth + 1;
}
Rectangle textRect = new Rectangle
{
Width = Width - Padding.Left-Padding.Right-imagePadding,
Height = (numLines * lineHeight),
X = Padding.Left+imagePadding,
Y = 5
};
TextRenderer.DrawText(e.Graphics, Text, Font, textRect, ForeColor, TextFormatFlags.WordBreak | TextFormatFlags.Left | TextFormatFlags.Top);
e.Graphics.DrawRectangle(Pens.CadetBlue, textRect);
base.OnPaint(e);
}
}
internal class MyTextBoxDesigner : ControlDesigner
{
public override SelectionRules SelectionRules
{
get
{
//all minus bottom and top
return (base.SelectionRules & ~(SelectionRules.BottomSizeable | SelectionRules.TopSizeable));
}
}
}
Ideally I'd like my control to auto-adjust height based on text length, so if text is short (or control long enough) in will be for example 20px height, but if text is long my control should be (for example) 40px height (2 lines of text).
How should I change my text measurement to calculate textRect
correctly?
Should I update height of my control using Size property or is there a better way?
EDIT:
Basically I want to draw text that can wrap inside rectangle, but has fixed width, so when text is longer that rectangle will adjust its height. Then based on that height I want to adjust control height.
Here is an auto-height control. If you change the width of control, the height of control will changes in way which the whole text can be shown.
You can create such control using different approaches including:
Approach 1: Auto-size Composite Control hosting a Label
This approach is based on hosting an auto-size Label
with dynamic maximum width in an auto-size Control
. In this approach we set maximum width of label based on width of control and since the label is auto-size, its height will be automatically set to show all texts and then we set height of control based on height of label.
Approach 2: Auto-Size Simple Control from scratch without Label
This approach is based on overriding SetBoundsCore
and setting size of control based on its Text
size. In this approach we calculate the size of text based on width of control using TextRenderer.MeasureText
and then set calculated height as height of control. In this approach you should handle text format flags and rendering yourself.
In both approaches a ControlDesigner
is used to disable all size grab handles except left and right.
Please note
These are not the only approaches available but are good examples. As another option you can inherit from a Label
and change it's behavior.
Approach 1: Auto-size Composite Control hosting a Label
It works based on setting AutoSize
property of label to true
and then setting MaximumSize
of label based on contol Width
. Also the height of control is set based on height of label.
You can simply draw the image in OnPaint
method. Also you can add a PictureBox
for image.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
[Designer(typeof(MyLabelDesigner))]
public partial class MyLabel : Control
{
public MyLabel() { InitializeComponent(); }
private System.Windows.Forms.Label textLabel;
private void InitializeComponent()
{
this.textLabel = new System.Windows.Forms.Label();
this.textLabel.AutoSize = true;
this.textLabel.Location = new System.Drawing.Point(0, 0);
this.textLabel.Name = "label1";
textLabel.SizeChanged += new EventHandler(textLabel_SizeChanged);
this.AutoSize = true;
this.Controls.Add(this.textLabel);
}
void textLabel_SizeChanged(object sender, EventArgs e)
{
this.Height = this.textLabel.Bottom + 0;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
this.textLabel.MaximumSize = new Size(this.Width, 0);
}
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text
{
get { return this.textLabel.Text; }
set { this.textLabel.Text = value; }
}
}
Approach 2: Auto-Size Simple Control from scratch without Label
This approach works based on setting size of control in SetBoundsCore
based on current width and calculated height of its Text
. To calculate height of control. You can simply draw the Image
.
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms.Design;
[Designer(typeof(MyLabelDesigner))]
public class ExLabel : Control
{
public ExLabel()
{
AutoSize = true;
DoubleBuffered = true;
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void OnTextChanged(System.EventArgs e)
{
base.OnTextChanged(e);
SetBoundsCore(Left, Top, Width, Height, BoundsSpecified.Size);
Invalidate();
}
protected override void SetBoundsCore(int x, int y, int width, int height,
BoundsSpecified specified)
{
var flags = TextFormatFlags.Left | TextFormatFlags.WordBreak;
var proposedSize = new Size(width, int.MaxValue);
var size = TextRenderer.MeasureText(Text, Font, proposedSize, flags);
height = size.Height;
base.SetBoundsCore(x, y, width, height, specified);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var flags = TextFormatFlags.Left | TextFormatFlags.WordBreak;
TextRenderer.DrawText(e.Graphics, Text, Font, ClientRectangle,
ForeColor, BackColor, flags);
}
}
Designer
Here is the ControlDesigner
which used to limit size grab handles in designer to left and right for both implementations:
using System.Windows.Forms.Design;
public class MyLabelDesigner : ControlDesigner
{
public override SelectionRules SelectionRules
{
get
{
return (base.SelectionRules & ~(SelectionRules.BottomSizeable |
SelectionRules.TopSizeable));
}
}
}