I have my control MyLabel and when I change the Font Size must perform this code in the constructor. How to make this code worked?
protected override void OnFontChanged(EventArgs e)
{
if (AutoSize_)
{
this.AutoSize = true;
remember_size = this.Size;
this.AutoSize = false;
this.Size = new Size(remember_size.Width, remember_size.Height);
remember_size = this.Size;
}
...
this.Invalidate();
}
But don't work. For example this code work:
protected override void OnFontChanged(EventArgs e)
{
if (AutoSize_)
{
this.AutoSize = true;
}
...
this.Invalidate();
}
If your goal is to resize the label so the text is visible, regardless of the font size, the AutoSize property will do this for you.. However if you for some reason wish to handle this with your own code, you could try setting the AutoSize property to false (and not change it..)
The following code method can be called from any Form, UserControl, or Control. It will return the size of the specified text in the specified font.
public static Size MeasureText(Graphics graphicsDevice, String text, Font font)
{
System.Drawing.SizeF textSize = graphicsDevice.MeasureString(text, font);
int width = (int)Math.Ceiling(textSize.Width);
int heigth = (int)Math.Ceiling(textSize.Height);
Size size = new Size(width, heigth);
return size;
}
Now you will need to check that the label has not outgrown the parent container, which would cause some of the labels text to be cut off. Something like the following will accomplish this:
private void ResizeParentAccordingToLabelSize(Label resizedLabel)
{
int necessaryWidth = resizedLabel.Location.X + resizedLabel.Width;
int necessaryHeight = resizedLabel.Location.Y + resizedLabel.Height;
if (necessaryWidth > this.Width)
{
this.Width = necessaryWidth;
}
if (necessaryHeight > this.Height)
{
this.Height = necessaryHeight;
}
}
Related
I have records in my .NET WinForms app that I lay out with enhanced TextBox controls on panels when the records are editable, but I set the TextBoxes to ReadOnly when the records are not editable. Clicking the save button on an editable record saves the text to the database, and then it is displayed as an un-editable record (until the edit button is clicked). Please see the following screen grab:
As you can hopefully see, the first record is not editable, but the second one is. The problem I have is that I would like the TextBox to grow in Height if the text is too much to fit. It seems that the TextBox is doing the WordWrap, but it either only shows one line of the text or only the first two. Something is always cut off at the bottom.
I have looked at several other posts on this site, including, especially, Expandable WinForms TextBox.
Here is some sample code for the panel:
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
...
Field1 = new ExpandoField { Multiline = true, WordWrap = true };
Field1.Location = new System.Drawing.Point(42, 3);
if (CanEdit)
{
Field1.BackColor = System.Drawing.Color.White;
Field1.TabIndex = 20;
}
else
{
((ExpandoField) Field1).ReadOnly = true;
Field1.ForeColor = System.Drawing.Color.FromArgb(0, 50, 0);
Field1.BackColor = System.Drawing.Color.Snow;
Field1.TabIndex = 0;
Field1.TabStop = false;
}
Field1.Text = Text1;
Field1.Dock = DockStyle.None;
Field1.Size = new System.Drawing.Size(538 - 25, 34);
Field1.MinimumSize = Field1.Size;
Field1.AutoSize = true;
Controls.Add(Field1);
As you can see, I have AutoSize set to true for the panel. The code for Field2 is similar to Field1.
ExpandoField is based on sample code I saw from a response by dstran in Expandable WinForms TextBox. It seemed to be the most complete implementation of the suggestion marked as the answer to that post. Here's the code:
class ExpandoField : TextBox
{
private double m_growIndex = 0.0;
private Timer m_timer;
public ExpandoField()
{
AutoSize = false;
this.Height = 20;
// Without the timer, I got a lot of AccessViolationException in the System.Windows.Forms.dll.
m_timer = new Timer();
m_timer.Interval = 1;
m_timer.Enabled = false;
m_timer.Tick += new EventHandler(m_timer_Tick);
this.KeyDown += new KeyEventHandler(ExpandoField_KeyDown);
}
void ExpandoField_KeyDown(object sender, KeyEventArgs e)
{
if (e.Modifiers == Keys.Control && e.KeyCode == Keys.A)
this.SelectAll();
}
void m_timer_Tick(object sender, EventArgs e)
{
var sz = new System.Drawing.Size(Width, Int32.MaxValue);
sz = TextRenderer.MeasureText(Text, Font, sz, TextFormatFlags.TextBoxControl);
m_growIndex = (double)(sz.Width / (double)Width);
if (m_growIndex > 0)
Multiline = true;
else
Multiline = false;
int tempHeight = (int) (20 * m_growIndex);
if (tempHeight <= 20)
Height = 20;
else
Height = tempHeight;
m_timer.Enabled = false;
}
public override sealed bool AutoSize
{
get { return base.AutoSize; }
set { base.AutoSize = value; }
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
m_timer.Enabled = true;
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
m_timer.Enabled = true;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
m_timer.Enabled = true;
}
}
This is obviously not quite working. I have the panel set to AutoSize, but it is not growing to accomodate the second TextBox. Also, I need to somehow push the second TextBox down when the first one grows. Is there some good way for the panel to know when ExpandoField gets an OnSizeChanged event? It seems like the growth of that panel would then need to cause the remainder of the list of panels to be redrawn in lower locations. I'm not sure how to get this cascade effect to work right...
I also think the use of the timer seems like an inefficient kluge...
I'm still learning WinForms. Is there some well-designed way I can get the behavior that I want? Is there some event I can catch when the WordWrap takes place (or when the text exceeds the size of the TextBox)? That would allow me to resize the TextBox. And how does the TextBox let the panel know that it has changed? Does it need to call the OnSizeChanged handler for it's parent panel? Does the panel need to call the OnSizeChanged handler for it's parent list?
Any suggestions?
I believe I have the answer, after 3 or 4 failed attempts...
class ExpandoField : TextBox
{
private bool UpdateInProgress = false;
private static System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(#"\r\n");
public delegate void CallbackFn();
CallbackFn VSizeChangedCallback;
public ExpandoField(CallbackFn VSizeChanged)
{
AutoSize = false;
VSizeChangedCallback = VSizeChanged;
this.KeyDown += new KeyEventHandler(ExpandoField_KeyDown);
}
public void ExpandoField_KeyDown(object sender, KeyEventArgs e)
{
if (e.Modifiers == Keys.Control && e.KeyCode == Keys.A)
this.SelectAll();
}
public void UpdateSize()
{
if (UpdateInProgress == false && Text.Length > 0)
{
UpdateInProgress = true;
int numLines = 0;
System.Drawing.Size baseSize = new System.Drawing.Size(Width, Int32.MaxValue);
System.Drawing.Size lineSize = baseSize; // compiler thinks we need something here...
// replace CR/LF with single character (paragraph mark 'ΒΆ')
string tmpText = rgx.Replace(Text, "\u00B6");
// split text at paragraph marks
string[] parts = tmpText.Split(new char[1] { '\u00B6' });
numLines = parts.Count();
foreach (string part in parts)
{
// if the width of this line is greater than the width of the text box, add needed lines
lineSize = TextRenderer.MeasureText(part, Font, baseSize, TextFormatFlags.TextBoxControl);
numLines += (int) Math.Floor(((double) lineSize.Width / (double) Width));
}
if (numLines > 1)
Multiline = true;
else
Multiline = false;
int tempHeight = Margin.Top + (lineSize.Height * numLines) + Margin.Bottom;
if (tempHeight > Height || // need to grow...
Height - tempHeight > lineSize.Height) // need to shrink...
{
Height = tempHeight;
VSizeChangedCallback();
}
UpdateInProgress = false;
}
}
public override sealed bool AutoSize
{
get { return base.AutoSize; }
set { base.AutoSize = value; }
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
UpdateSize();
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
UpdateSize();
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
UpdateSize();
}
}
Note that on the constructor this subclass of TextBox now accepts a delegate callback to let the parent class know that the TextBox has changed its size. (I suppose I should have handled the possibility of a null value here...)
Thankfully, this solution no longer required a timer.
I have tested this code pretty well, and I have watched it both grow & shrink. It respects MaximumSize, and it even handles the presence of carriage-return/line-feed pairs. (This code assumes Windows; it should be trivial to modify it for Linux, etc.) Feel free to suggest improvements.
I implemented a dynamic RichTextBox (it can increase its width based on the content). But when i previously entered some text and double-click it to change the string in in the text turns transparent.
From the moment I start adding text, the characters become visible again.
On double-click, I just do this:
myAnnotationTextBox.Show();
myAnnotationTextBox.Focus();
The code of my custom RichTextBox:
public class AlphaBlendTextBox : RichTextBox
{
private const int _initialWidth = 100;
public AlphaBlendTextBox(Font textBoxFont, Color theCurrentForeColor)
{
this.Font = textBoxFont;
this.ForeColor = theCurrentForeColor;
this.BackColor = Color.White;
this.AutoSize = false;
this.WordWrap = false;
this.Multiline = true;
this.Height = textBoxFont.Height;
this.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None;
this.BorderStyle = BorderStyle.None;
this.HideSelection = false;
}
protected override void OnContentsResized(ContentsResizedEventArgs e)
{
base.OnContentsResized(e);
if (String.IsNullOrEmpty(Text))
{
Width = _initialWidth;
}
else
{
Width = e.NewRectangle.Width;
Height = e.NewRectangle.Height;
}
}
}
I'm in need of a way to make TextBox appear like a parallelogram but i can't figure out how to do so. I currently have this code:
private void IOBox_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Point cursor = PointToClient(Cursor.Position);
Point[] points = { cursor, new Point(cursor.X + 50, cursor.Y), new Point(cursor.X + 30, cursor.Y - 20),
new Point(cursor.X - 20, cursor.Y - 20) };
Pen pen = new Pen(SystemColors.MenuHighlight, 2);
g.DrawLines(pen, points);
}
But apparently it's not working. Either i misplaced/misused it or i'm not doing something right.
This is the method that i use to add it.
int IOCounter = 0;
private void inputOutput_Click(object sender, EventArgs e)
{
IOBox box = new IOBox();
box.Name = "IOBox" + IOCounter;
IOCounter++;
box.Location = PointToClient(Cursor.Position);
this.Controls.Add(box);
}
Any idea how i can fix it? IOBox is a UserControl made by me which contains a TextBox. Is that rightful to do?
If its possible, you should make your application using WPF. WPF is designed to do exactly what you are trying to do.
However, it can be done in WinForms, though not easily. You will need to make a new class that inherits the TextBox WinForm control. Here is an example that makes a TextBox look like a circle:
public class MyTextBox : TextBox
{
public MyTextBox() : base()
{
SetStyle(ControlStyles.UserPaint, true);
Multiline = true;
Width = 130;
Height = 119;
}
public override sealed bool Multiline
{
get { return base.Multiline; }
set { base.Multiline = value; }
}
protected override void OnPaintBackground(PaintEventArgs e)
{
var buttonPath = new System.Drawing.Drawing2D.GraphicsPath();
var newRectangle = ClientRectangle;
newRectangle.Inflate(-10, -10);
e.Graphics.DrawEllipse(System.Drawing.Pens.Black, newRectangle);
newRectangle.Inflate(1, 1);
buttonPath.AddEllipse(newRectangle);
Region = new System.Drawing.Region(buttonPath);
base.OnPaintBackground(e);
}
}
Keep in mind that you will still have to do other things, such as clipping the text, etc. But this should get you started.
How can I change the BorderColor of the Textbox when a user Clicks on it or focuses on it?
You can handle WM_NCPAINT message of TextBox and draw a border on the non-client area of control if the control has focus. You can use any color to draw border:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ExTextBox : TextBox
{
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
private const int WM_NCPAINT = 0x85;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NCPAINT && this.Focused)
{
var dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
g.DrawRectangle(Pens.Red, 0, 0, Width - 1, Height - 1);
}
}
}
}
Result
The painting of borders while the control is focused is completely flicker-free:
BorderColor property for TextBox
In the current post I just change the border color on focus. You can also add a BorderColor property to the control. Then you can change border-color based on your requirement at design-time or run-time. I've posted a more completed version of TextBox which has BorderColor property:
in the following post:
BorderColor property for TextBox
try this
bool focus = false;
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (focus)
{
textBox1.BorderStyle = BorderStyle.None;
Pen p = new Pen(Color.Red);
Graphics g = e.Graphics;
int variance = 3;
g.DrawRectangle(p, new Rectangle(textBox1.Location.X - variance, textBox1.Location.Y - variance, textBox1.Width + variance, textBox1.Height +variance ));
}
else
{
textBox1.BorderStyle = BorderStyle.FixedSingle;
}
}
private void textBox1_Enter(object sender, EventArgs e)
{
focus = true;
this.Refresh();
}
private void textBox1_Leave(object sender, EventArgs e)
{
focus = false;
this.Refresh();
}
This is an ultimate solution to set the border color of a TextBox:
public class BorderedTextBox : UserControl
{
TextBox textBox;
public BorderedTextBox()
{
textBox = new TextBox()
{
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(-1, -1),
Anchor = AnchorStyles.Top | AnchorStyles.Bottom |
AnchorStyles.Left | AnchorStyles.Right
};
Control container = new ContainerControl()
{
Dock = DockStyle.Fill,
Padding = new Padding(-1)
};
container.Controls.Add(textBox);
this.Controls.Add(container);
DefaultBorderColor = SystemColors.ControlDark;
FocusedBorderColor = Color.Red;
BackColor = DefaultBorderColor;
Padding = new Padding(1);
Size = textBox.Size;
}
public Color DefaultBorderColor { get; set; }
public Color FocusedBorderColor { get; set; }
public override string Text
{
get { return textBox.Text; }
set { textBox.Text = value; }
}
protected override void OnEnter(EventArgs e)
{
BackColor = FocusedBorderColor;
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e)
{
BackColor = DefaultBorderColor;
base.OnLeave(e);
}
protected override void SetBoundsCore(int x, int y,
int width, int height, BoundsSpecified specified)
{
base.SetBoundsCore(x, y, width, textBox.PreferredHeight, specified);
}
}
WinForms was never good at this and it's a bit of a pain.
One way you can try is by embedding a TextBox in a Panel and then manage the drawing based on focus from there:
public class BorderTextBox : Panel {
private Color _NormalBorderColor = Color.Gray;
private Color _FocusBorderColor = Color.Blue;
public TextBox EditBox;
public BorderTextBox() {
this.DoubleBuffered = true;
this.Padding = new Padding(2);
EditBox = new TextBox();
EditBox.AutoSize = false;
EditBox.BorderStyle = BorderStyle.None;
EditBox.Dock = DockStyle.Fill;
EditBox.Enter += new EventHandler(EditBox_Refresh);
EditBox.Leave += new EventHandler(EditBox_Refresh);
EditBox.Resize += new EventHandler(EditBox_Refresh);
this.Controls.Add(EditBox);
}
private void EditBox_Refresh(object sender, EventArgs e) {
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(SystemColors.Window);
using (Pen borderPen = new Pen(this.EditBox.Focused ? _FocusBorderColor : _NormalBorderColor)) {
e.Graphics.DrawRectangle(borderPen, new Rectangle(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1));
}
base.OnPaint(e);
}
}
Using OnPaint to draw a custom border on your controls is fine. But know how to use OnPaint to keep efficiency up, and render time to a minimum. Read this if you are experiencing a laggy GUI while using custom paint routines: What is the right way to use OnPaint in .Net applications?
Because the accepted answer of PraVn may seem simple, but is actually inefficient. Using a custom control, like the ones posted in the answers above is way better.
Maybe the performance is not an issue in your application, because it is small, but for larger applications with a lot of custom OnPaint routines it is a wrong approach to use the way PraVn showed.
set Text box Border style to None
then write this code to container form "paint" event
private void Form1_Paint(object sender, PaintEventArgs e)
{
System.Drawing.Rectangle rect = new Rectangle(TextBox1.Location.X,
TextBox1.Location.Y, TextBox1.ClientSize.Width, TextBox1.ClientSize.Height);
rect.Inflate(1, 1); // border thickness
System.Windows.Forms.ControlPaint.DrawBorder(e.Graphics, rect,
Color.DeepSkyBlue, ButtonBorderStyle.Solid);
}
With PictureBox1
.Visible = False
.Width = TextBox1.Width + 4
.Height = TextBox1.Height + 4
.Left = TextBox1.Left - 2
.Top = TextBox1.Top - 2
.SendToBack()
.Visible = True
End With
Here is my complete Flat TextBox control that supports themes including custom border colors in normal and focused states.
The control uses the same concept mentioned by Reza Aghaei https://stackoverflow.com/a/38405319/5514131 ,however the FlatTextBox control is more customizable and flicker-free.
The control handles the WM_NCPAINT window message in a better way to help eliminate flicker.
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = WindowMessage.WM_NCPAINT AndAlso _drawBorder AndAlso Not DesignMode Then 'Draw the control border
Dim w As Integer
Dim h As Integer
Dim clip As Rectangle
Dim hdc As IntPtr
Dim clientRect As RECT = Nothing
GetClientRect(Handle, clientRect)
Dim windowRect As RECT = Nothing
GetWindowRect(Handle, windowRect)
w = windowRect.Right - windowRect.Left
h = windowRect.Bottom - windowRect.Top
clip = New Rectangle(CInt((w - clientRect.Right) / 2), CInt((h - clientRect.Bottom) / 2), clientRect.Right, clientRect.Bottom)
hdc = GetWindowDC(Handle)
Using g As Graphics = Graphics.FromHdc(hdc)
g.SetClip(clip, CombineMode.Exclude)
Using sb = New SolidBrush(BackColor)
g.FillRectangle(sb, 0, 0, w, h)
End Using
Using p = New Pen(If(Focused, _borderActiveColor, _borderNormalColor), BORDER_WIDTH)
g.DrawRectangle(p, 0, 0, w - 1, h - 1)
End Using
End Using
ReleaseDC(Handle, hdc)
Return
End If
MyBase.WndProc(m)
End Sub
I have removed the default BorderStyle property and replaced it with a simple boolean DrawBorder property that controls whether to draw a border around the control or not.
Use the BorderNormalColor property to specify the border color when the TextBox has no focus, and the BorderActiveColor property to specify the border color when the control receives focus.
The FlatTextBox comes with two themes VS2019 Dark and VS2019 Light, use the Theme property to switch between them.
Complete FlatTextBox control code written in VB.NET
https://gist.github.com/ahmedosama007/37fe2004183a51a4ea0b4a6dcb554176
Using C# WinForms I've got a form that I'm painting a gradient background on it from say light blue to dark blue. I've got some buttons and a couple of labels on it which I've already tried setting the backcolor to transparent as I want the background to show through - especially for the labels, but that doesn't seem to work.
But I've noticed that the painting is also paining the labels, even though the code is only on the form_paint event.
How can I make the labels not contain the same grade shading, but to be transparent? e.g. the labels at the top of the form will be light blue, but the ones at the bottom will be dark blue.
private void frmOptions_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rectangle = e.ClipRectangle;
using (Brush aBrush = new LinearGradientBrush(rectangle, Color.LightBlue, Color.DarkBlue, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(aBrush, rectangle);
}
}
Many thanks for any help on this.
EDIT:
I've just been stepping through the lines of code to debug it and noticed that the paint event is being called for EACH object on the form - the labels & buttons, the rectangle on line 2 above has the size of each time. So I'm now really confused why it's getting called for each object on the form.
EDIT 2:
Just been stepping through the code of my quick test project (see comment below) and also noticed that the paint event is beening called for each object. the differenece is that the test project doesn't have the line "Rectangle rectangle = e.ClipRectangle;" - it basically uses the width & height of the form. where my code above sets rectangle to ClipRectangle - which is the size of each label & button... Is this Normal behaviour ? If so, then it looks like I need to folow the answer below by Reniuz.
EDIT 3
I've change the cliprectangle line to "Rectangle rectangle = new Rectangle(0, 0, this.Width, this.Height);" This seems to make the transparent look how it should, but the paint event is still being called for every object on the form - good job there is only 7 of the, so it's drawing 8 times :( Think I'll still look at the "TransparentLabel" code to test that.
Take a look at this article.
Edit:
Also you can use this code:
public class TransparentLabel : Control
{
public TransparentLabel(Label label)
{
//setting default properties
this.Text = label.Text;
this.Font = label.Font;
this.Location = label.Location;
this.Size = label.Size;
this.Parent = label.Parent;
this.BringToFront();
label.Dispose();
TabStop = false;
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// do nothing
}
protected override void OnPaint(PaintEventArgs e)
{
DrawText();
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if(m.Msg == 0x000F)
{
DrawText();
}
}
private void DrawText()
{
using(Graphics graphics = CreateGraphics())
using(SolidBrush brush = new SolidBrush(ForeColor))
{
SizeF size = graphics.MeasureString(Text, Font);
// first figure out the top
float top = 0;
switch(textAlign)
{
case ContentAlignment.MiddleLeft:
case ContentAlignment.MiddleCenter:
case ContentAlignment.MiddleRight:
top = (Height - size.Height) / 2;
break;
case ContentAlignment.BottomLeft:
case ContentAlignment.BottomCenter:
case ContentAlignment.BottomRight:
top = Height - size.Height;
break;
}
float left = -1;
switch(textAlign)
{
case ContentAlignment.TopLeft:
case ContentAlignment.MiddleLeft:
case ContentAlignment.BottomLeft:
if(RightToLeft == RightToLeft.Yes)
left = Width - size.Width;
else
left = -1;
break;
case ContentAlignment.TopCenter:
case ContentAlignment.MiddleCenter:
case ContentAlignment.BottomCenter:
left = (Width - size.Width) / 2;
break;
case ContentAlignment.TopRight:
case ContentAlignment.MiddleRight:
case ContentAlignment.BottomRight:
if(RightToLeft == RightToLeft.Yes)
left = -1;
else
left = Width - size.Width;
break;
}
graphics.DrawString(Text, Font, brush, left, top);
}
}
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
RecreateHandle();
}
}
public override RightToLeft RightToLeft
{
get
{
return base.RightToLeft;
}
set
{
base.RightToLeft = value;
RecreateHandle();
}
}
public override Font Font
{
get
{
return base.Font;
}
set
{
base.Font = value;
RecreateHandle();
}
}
private ContentAlignment textAlign = ContentAlignment.TopLeft;
public ContentAlignment TextAlign
{
get { return textAlign; }
set
{
textAlign = value;
RecreateHandle();
}
}
}
Transparent label will replace your existing label so you can use it like this:
TransparentLabel transparentLabel1 = new TransparentLabel(label1);
TransparentLabel transparentLabel2 = new TransparentLabel(label2);
and then you should see the result(design time on left, run time on right):