Align middle text string inside the button - c#

I'm making some custom control for a small project. I have created a TP1CustomFlatButton like this:
It was easy for me if I added a label with text to bottom of my TP1CustomFlatButton. I didn't want to handle events for that label so I used event onPaint to draw the text. I followed the turtorial of Microsoft and I got the custom flat button like the picture I attached. What I'm trying to get is to make the text align center at the bottom of my TP1CustomFlatButton.
This is my code for TP1CustomFlatButton:
// constructor
public TP1CustomFlatButton()
{
this.FlatStyle = FlatStyle.Flat;
this.FlatAppearance.BorderSize = 0;
this.BackColor = Color.MediumSeaGreen;
this.ForeColor = Color.White;
this.Text = "TP1CustomButton";
}
protected override void OnPaint(PaintEventArgs pevent)
{
pevent.Graphics.FillRectangle(new SolidBrush(this.BackColor), new Rectangle(0, 0, this.Width, this.Height));
TextFormatFlags flags = TextFormatFlags.Bottom;
//render text
TextRenderer.DrawText(pevent.Graphics, this.Text, this.Font, new Point((int)(this.Width - Text.Length)/2,this.Height), this.ForeColor, flags);
//draw image
Image img = this.BackgroundImage;
//create rectangle to display image
Rectangle imgRec = new Rectangle(this.Width - 32 /3, this.Height - 32/ 3, 32, 32);
if(img!=null)
pevent.Graphics.DrawImage(img, imgRec);
}
I'm really confused with the coordinate X and Y. As you can see the code I tried to make the "SETTINGS" text string to align center at bottom of my TP1CustomFlatButton. I spent 5 hours to read more information about coordinate and location of controls in Windows Form. But now I'm really tired.
Hope someone can give me any advice or any solution for my custom control.

You need to use the MeasureString()method in order to calculate the middle.
also see the changes i have madein order to find the middle (calculated in drawPoint field).
see my example based on your code:
public TP1CustomFlatButton()
{
this.FlatStyle = FlatStyle.Flat;
this.FlatAppearance.BorderSize = 0;
this.BackColor = Color.MediumSeaGreen;
this.ForeColor = Color.White;
this.Text = "middle";
}
protected override void OnPaint(PaintEventArgs pevent)
{
pevent.Graphics.FillRectangle(new SolidBrush(this.BackColor), new Rectangle(0, 0, this.Width, this.Height));
TextFormatFlags flags = TextFormatFlags.Bottom;
//render text
String drawString = this.Text;
SizeF size = pevent.Graphics.MeasureString(drawString, this.Font);
Point drawPoint = new Point((int)this.Size.Width / 2 - (int)size.Width / 2,this.Height);
TextRenderer.DrawText(pevent.Graphics, this.Text, this.Font, drawPoint, this.ForeColor, flags);
//draw image
Image img = this.BackgroundImage;
//create rectangle to display image
Rectangle imgRec = new Rectangle(this.Width - 32 / 3, this.Height - 32 / 3, 32, 32);
if (img != null)
pevent.Graphics.DrawImage(img, imgRec);
}
Output:

Try Changing the TextFormatFlags as below:
TextFormatFlags flags = TextFormatFlags.Bottom | TextFormatFlags.VerticalCenter;
Also, checkout this link

Related

Outer Glow Effect for Text C#

i want to have outer glow text in a label for my winform application some thing like:
i searched for it in stackoverflow and I found this:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
//Create a bitmap in a fixed ratio to the original drawing area.
Bitmap bm=new Bitmap(this.ClientSize.Width/5, this.ClientSize.Height/5);
//Create a GraphicsPath object.
GraphicsPath pth=new GraphicsPath();
//Add the string in the chosen style.
pth.AddString("Text Halo",new FontFamily("Verdana"),(int)FontStyle.Regular,100,new Point(20,20),StringFormat.GenericTypographic);
//Get the graphics object for the image.
Graphics g=Graphics.FromImage(bm);
//Create a matrix that shrinks the drawing output by the fixed ratio.
Matrix mx=new Matrix(1.0f/5,0,0,1.0f/5,-(1.0f/5),-(1.0f/5));
//Choose an appropriate smoothing mode for the halo.
g.SmoothingMode=SmoothingMode.AntiAlias;
//Transform the graphics object so that the same half may be used for both halo and text output.
g.Transform=mx;
//Using a suitable pen...
Pen p=new Pen(Color.Yellow,3);
//Draw around the outline of the path
g.DrawPath(p,pth);
//and then fill in for good measure.
g.FillPath(Brushes.Yellow,pth);
//We no longer need this graphics object
g.Dispose();
//this just shifts the effect a little bit so that the edge isn't cut off in the demonstration
e.Graphics.Transform=new Matrix(1,0,0,1,50,50);
//setup the smoothing mode for path drawing
e.Graphics.SmoothingMode=SmoothingMode.AntiAlias;
//and the interpolation mode for the expansion of the halo bitmap
e.Graphics.InterpolationMode=InterpolationMode.HighQualityBicubic;
//expand the halo making the edges nice and fuzzy.
e.Graphics.DrawImage(bm,ClientRectangle,0,0,bm.Width,bm.Height,GraphicsUnit.Pixel);
//Redraw the original text
e.Graphics.FillPath(Brushes.Black,pth);
//and you're done.
pth.Dispose();
}
but the PROBLEM IS I CAN NOT MOVE IT please help me i need it to be movable and I want to be able to change it's size. the code above, just adds it automatically to somewhere in my form but I want to move that.
thank you
A better approach is to create a custom control for this to use/add some relevant drawing properties. Mainly, the Font and color of the text, the size and color of the outline. Then, you can lay out the custom control in any container at any location and with any size.
Here's a simple example.
[DesignerCategory("Code")]
public class GlowTextLabel : Control
{
private Color outlineColor = SystemColors.Highlight;
private int outlineSize = 1;
public GlowTextLabel() : base()
{
SetStyle(ControlStyles.Selectable, false);
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.SupportsTransparentBackColor, true);
}
[DefaultValue(typeof(Color), "Highlight")]
public Color OutlineColor
{
get => outlineColor;
set
{
if (outlineColor != value)
{
outlineColor = value;
Invalidate();
}
}
}
[DefaultValue(1)]
public int OutlineSize
{
get => outlineSize;
set
{
if (outlineSize != value)
{
outlineSize = Math.Max(1, value);
Invalidate();
}
}
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(BackColor);
var w = Math.Max(8, ClientSize.Width / 5);
var h = Math.Max(8, ClientSize.Height / 5);
using (var bmp = new Bitmap(w, h))
using (var gp = new GraphicsPath())
using (var sf = new StringFormat(StringFormat.GenericTypographic))
{
sf.Alignment = sf.LineAlignment = StringAlignment.Center;
gp.AddString(Text,
Font.FontFamily, (int)Font.Style, GetEmFontSize(Font),
ClientRectangle, sf);
using (var g = Graphics.FromImage(bmp))
using (var m = new Matrix(1.0f / 5, 0, 0, 1.0f / 5, -(1.0f / 5), -(1.0f / 5)))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Transform = m;
using (var pn = new Pen(OutlineColor, OutlineSize))
{
g.DrawPath(pn, gp);
g.FillPath(pn.Brush, gp);
}
}
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Optional for wider blur...
// e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.DrawImage(bmp,
ClientRectangle, 0, 0, bmp.Width, bmp.Height,
GraphicsUnit.Pixel);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var br = new SolidBrush(ForeColor))
e.Graphics.FillPath(br, gp);
}
}
}
private float GetEmFontSize(Font fnt) =>
fnt.SizeInPoints * (fnt.FontFamily.GetCellAscent(fnt.Style) +
fnt.FontFamily.GetCellDescent(fnt.Style)) / fnt.FontFamily.GetEmHeight(fnt.Style);
Rebuild, find the GlowTextLabel control on the ToolBox under your project's components group, drop an instance, try the Font, ForeColor, OutlineColor, and OutlineSize properties with different values.
Pen width 1.
Pen width 10.
Pen width 20.

How can I resize the width and height of a UserControl according to a child Label?

I'm coding a clone messaging app. I have a user control called "Bubble" that I use as a message bubble. And its user control layout is like this:
It contains only lblMessage, lblTime and pictureBox. lblMessage's AutoSize property off and Anchor is top-left-right. My goal is to size or wrap this according to the content. I am able to resize vertically using this code.
void SetHeight()
{
//SizeF maxSize = new Size(500, int.MaxValue);
Graphics g = CreateGraphics();
SizeF size = g.MeasureString(lblMessage.Text, lblMessage.Font, lblMessage.Width);
lblMessage.Height = int.Parse(Math.Round(size.Height + 2, 0).ToString());
lblTime.Top= lblMessage.Bottom +10;
this.Height = lblTime.Bottom + 10;
}
My result is like this:
I can resize vertically, but not horizontally at the same time. The way I resize it vertically is to use width property of lblMessage as the limit size. I want to resize user control according to size of short text. I thought about creating a maxSize for MeasureString but I can't get it to work. I'm trying to find a function that continues from a bottom line when it reaches a certain width value. I look forward to your help.
I searched all stackowerflow questions in depth but none of them worked in my case.
**Edit after Jimi comments
I tried to apply his solution to my project. I don't think I did everything right, but with a little bit of comment, something came up that worked for me at least.
private void Bubble_Paint(object sender, PaintEventArgs e)
{
float minimumWidth = lblTime.Width + pictureBox1.Width + 35;
float maximumWidth = 500;
string measureString = lblMessage.Text;
Font stringFont = new Font("Helvetica", 12.0F);
CharacterRange[] characterRanges = { new CharacterRange(0, measureString.Length) };
float layautWidth = maximumWidth - minimumWidth;
RectangleF layoutRect = new RectangleF(10, 6, layautWidth, this.Height);
StringFormat stringFormat = new StringFormat();
stringFormat.SetMeasurableCharacterRanges(characterRanges);
Color myColor = Color.FromArgb(211, 212, 212);
SolidBrush myBrush = new SolidBrush(myColor);
Region[] stringRegions = e.Graphics.MeasureCharacterRanges(measureString, stringFont, layoutRect, stringFormat);
RectangleF measureRect1 = stringRegions[0].GetBounds(e.Graphics);
//When I gave Rectangle to the DrawString, some last letter was truncated, so I used plain text printing until I got to the bottom line.
if (measureRect1.Height < 30)
e.Graphics.DrawString(measureString, stringFont, myBrush, 10, 6, stringFormat);
else e.Graphics.DrawString(measureString, stringFont, myBrush, measureRect1, stringFormat);
e.Graphics.DrawRectangle(new Pen(Color.Transparent, 0), Rectangle.Round(measureRect1));
this.Width = Convert.ToInt32(measureRect1.Width + minimumWidth);
lblTime.Top = Convert.ToInt32(measureRect1.Height) + 10;
this.Height = lblTime.Bottom + 10;
}
Result in container preview:
user control that goes down one line from the specified limit
and for one line
dynamically result inside the application

How can I clear the Graphics before drawing another letter?

private void timer1_Tick(object sender, EventArgs e)
{
counter--;
DrawLetter();
if (counter == 0)
{
t.Stop();
TakeScreenShot();
}
}
private void DrawLetter()
{
var letter = counter.ToString();
Graphics g = Graphics.FromHdc(GetDC(IntPtr.Zero));
float width = ((float)this.ClientRectangle.Width);
float height = ((float)this.ClientRectangle.Width);
float emSize = height;
Font font = new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular);
font = FindBestFitFont(g, letter.ToString(), font, this.ClientRectangle.Size);
SizeF size = g.MeasureString(letter.ToString(), font);
g.DrawString(letter, font, new SolidBrush(Color.White), (width - size.Width) / 2, 0);
}
private Font FindBestFitFont(Graphics g, String text, Font font, Size proposedSize)
{
// Compute actual size, shrink if needed
while (true)
{
SizeF size = g.MeasureString(text, font);
// It fits, back out
if (size.Height <= proposedSize.Height &&
size.Width <= proposedSize.Width) { return font; }
// Try a smaller font (90% of old size)
Font oldFont = font;
font = new Font(font.Name, (float)(font.Size * .9), font.Style);
oldFont.Dispose();
}
}
void TakeScreenShot()
{
bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);
gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
bmpScreenshot.Save(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + #"\ScreenCaptures\newfile.png", ImageFormat.Png);
}
I am able to draw the string but it is writing on top of itself.
How can I clear it? Basically I want the countdown to appear on the screen then take a screenshot.
Right now the number is overwritten by another.
You can do the following: create an additional transparent form, and it will display timer values. This will allow you to erase the previous value. In addition, this will allow to get rid of the function call GetDC via PInvoke.
Form timerForm; // main form field
// Create and show additional transparent form before starting the timer
timerForm = new Form
{
FormBorderStyle = FormBorderStyle.None,
WindowState = FormWindowState.Maximized,
TransparencyKey = SystemColors.Control,
ShowInTaskbar = false
};
timerForm.Show();
timer.Start();
Change the method DrawLetter as follows
private void DrawLetter()
{
var letter = counter.ToString();
Graphics g = timerForm.CreateGraphics();
float width = ClientRectangle.Width;
float height = ClientRectangle.Width;
float emSize = height;
using (Font font1 = new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular))
using (Font font2 = FindBestFitFont(g, letter, font1, ClientRectangle.Size))
using (var brush = new SolidBrush(Color.White))
{
SizeF size = g.MeasureString(letter, font2);
g.Clear(SystemColors.Control);
g.DrawString(letter, font2, brush, (width - size.Width) / 2, 0);
}
}
We must release all used resources like fonts and brushes. For this I applied using.
Change the timer tick event handler as follows
private void timer1_Tick(object sender, EventArgs e)
{
counter--;
DrawLetter();
if (counter == 0)
{
timer.Stop();
TakeScreenShot();
timerForm.Dispose(); // must release
}
}
FindBestFitFont and TakeScreenShot methods remain unchanged.
Draw your font to a different bitmap. Transparent background (or whatever doesn't invert, see below - perhaps black).
(now you could also draw it with a different colored shadow to mitigate drawing on similar colored background - but the natures of SRCINVERT/XOR, below, will mitigate this as well)
Use BitBlt to copy it to the screen
Use the SRCINVERT raster op.
(note: the colors may be different as it is XORing it with pixels underneath)
Now when is is time to erase, just make the same bitblt with the same contents as previous, the double XOR effect caused by SRCINVERT will have the effect of erasing it.
Then draw the next font.
Note: if desktop is updated between calls, all bets are off.
better...
Rather than attempting a transparent background, draw it on a white background. This will eliminate contrast issues with the font, eliminate concern with dynamic updates, and eliminate problems with erasing. Sometimes you have to admit - the method & code isn't the problem, the requirements are the problem. This all depends of course on the source of the requirements, etc.
If it needs to look professional, don't put the content on the screen, draw it after you take the screen capture.
If you end up using the transparent window approach, the screen shot may miss the transparent window. To get it, see this question:
Capture screenshot Including Semitransparent windows in .NET. (could be fixed by newer .net / newer windows versions)
You need to invalidate all the windows on the desktop by using the InvalidateRect function to erase the previously drawn letter.
See additional codes below for the DrawLetter method.
[DllImport("user32")]
private static extern bool InvalidateRect(IntPtr hwnd, IntPtr rect, bool bErase);
private void DrawLetter()
{
var letter = counter.ToString();
Graphics g = Graphics.FromHdc(GetDC(IntPtr.Zero));
float width = ((float)this.ClientRectangle.Width);
float height = ((float)this.ClientRectangle.Width);
float emSize = height;
Font font = new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular);
font = FindBestFitFont(g, letter.ToString(), font, this.ClientRectangle.Size);
SizeF size = g.MeasureString(letter.ToString(), font);
// Invalidate all the windows.
InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
// Sometimes, the letter is drawn before the windows are invalidated.
// To fix that, add a small delay before drawing the letter.
System.Threading.Thread.Sleep(100);
// Finally, draw the letter.
g.DrawString(letter, font, new SolidBrush(Color.White), (width - size.Width) / 2, 0);
}
A solution is:
You must take a snapshot of that area you want to show counter before all things. Then call DrawImage function to draw snapshot image before call DrawString function every time.

Painting text on Button - Difference in look

I have my custom button where I have overridden the OnPaint() and draw the text in it only. On runtime the text looks differently - spacing between chars is lacking. Here is the image of design & runtime of the button :
The paint methods is as:
protected override void OnPaint(PaintEventArgs pevent)
{
base.OnPaint(pevent);
if (base.ContainsFocus)
{
// Draw inner dotted rectangle when button is on focus
Pen pen = new Pen(Color.Gray, 3);
Point p = base.Location;
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
Rectangle rectangle = new Rectangle(4, 4, Size.Width - 8,
Size.Height - 8);
ControlPaint.DrawFocusRectangle(pevent.Graphics, rectangle);
}
// Draw the string to screen
SizeF sf = pevent.Graphics.MeasureString(displayText, this.Font,
this.Width);
Point ThePoint = new Point();
ThePoint.X = (int)((this.Width / 2) - (sf.Width / 2));
ThePoint.Y = (int)((this.Height / 2) - (sf.Height / 2));
pevent.Graphics.DrawString(displayText, Font,
new SolidBrush(Color.FromArgb(255, 255, 254, 255)), ThePoint);
this.Text = "";
}
Any idea where am I going wrong and how to take care of the same?
You need to set the correct smoothing mode like this:
Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality
Then, the result should look OK.
Devils Child's answer will affect the quality of lines and circles, etc.
But for text rendering, you can use:
e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

c#.Net Progress Bar Issue

I am reading data from a file and would like to attach a progress bar to this operation. I found the following code on stackoverflow - this code is due to William Daniel, Sept 20, stackoverflow post titled, " How to Change Color of Progress Bar in C#.Net 3.5"
class CustomProgressBar : ProgressBar
{
public CustomProgressBar()
{
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
// None... Helps control the flicker.
}
protected override void OnPaint(PaintEventArgs e)
{
const int inset = 2;
using (Image offscreenImage = new Bitmap(this.Width, this.Height))
{
using (Graphics offscreen = Graphics.FromImage(offscreenImage))
{
Rectangle rect = new Rectangle(0, 0, this.Width, this.Height);
if (ProgressBarRenderer.IsSupported)
ProgressBarRenderer.DrawHorizontalBar(offscreen, rect);
rect.Inflate(new Size(-inset, -inset)); // Deflate inner rectangle
rect.Width = (int)(rect.Width * ((double)this.Value / this.Maximum));
if (rect.Width == 0) rect.Width = 1;
LinearGradientBrush brush = new LinearGradientBrush(rect,
this.BackColor, this.ForeColor, LinearGradientMode.Vertical);
offscreen.FillRectangle(brush, inset, inset, rect.Width, rect.Height);
e.Graphics.DrawImage(offscreenImage, 0, 0);
offscreenImage.Dispose();
}
}
}
}
The code works fine except the following:
The gradient does not seem to extend the entire width of the bar. It is there but is much more heavily concentrated near the bottom of the bar and thins out very quickly as we get to the top of the bar. Any suggestions as to how I can fix this?
If I place the progress bar on a form and open an Internet Explorer window over the portion of the form with the progress bar on it, some of the text from the internet window bleeds onto the progress bar in the form. I have no idea why, and how to fix this.
As an aside, how does one estimate the length of an operation that you wish to show via a progress bar?
Any help would be greatly appreciated. Thank you.
As for first part of your question - try using following constructor for your brush:
new LinearGradientBrush(new Point(0, 0), new Point(0, Height - inset * 2), BackColor, ForeColor);
Your current brush has top control point at Y=inset - that's why area from Y=0 to Y=inset is all painted in solid color.
You are not drawing at all in the non-filled portion of your progressbar. Try calling FillRectangle for area from rect.Right to this.Width with background color. It should prevent bleeding from overlapping windows.
try this one, you don't need on each paint to create an image...
class CustomProgressBar : ProgressBar
{
public CustomProgressBar()
{
this.SetStyle(ControlStyles.UserPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.DoubleBuffer, true);
this.UpdateStyles();
}
protected override void OnPaint(PaintEventArgs e)
{
const int inset = 2;
Rectangle rect = this.ClientRectangle;
var offscreen = e.Graphics;
if (ProgressBarRenderer.IsSupported){
ProgressBarRenderer.DrawHorizontalBar(offscreen, rect);
}
rect.Inflate(new Size(-inset, -inset)); // Deflate inner rectangle
rect.Width = Convert.ToInt32(Math.Round((rect.Width * ((double)this.Value / this.Maximum))));
if (rect.Width == 0) rect.Width = 1;
LinearGradientBrush brush = new LinearGradientBrush(rect,this.BackColor, this.ForeColor, LinearGradientMode.Vertical);
offscreen.FillRectangle(brush, inset, inset, rect.Width, rect.Height);
offscreen.DrawString(Value.ToString(), this.Font,Brushes.Black,rect);
}
}
use
const int inset = 1;
for your other question

Categories