I have overridden the OnPaint method of my Label control in VS2008:
void Label_OnPaint(object sender, PaintEventArgs e) {
base.OnPaint(e);
dim lbl = sender as Label;
if (lbl != null) {
string Text = lbl.Text;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
if (myShowShadow) { // draw the shadow first!
e.Graphics.DrawString(Text, lbl.Font, new SolidBrush(myShadowColor), myShadowOffset, StringFormat.GenericDefault);
}
e.Graphics.DrawString(Text, lbl.Font, new SolidBrush(lbl.ForeColor), 0, 0, StringFormat.GenericDefault);
}
}
This works, but I really want to find out how to center the text both vertically and horizontally. I've heard of the MeasureString() method, but my "Text" complicates matters because it could include page breaks.
Could someone guide me with how to do this?
Alternatively you can create your own StringFormat object and pass it in using an overload of DrawString that supports a RectangleF:
StringFormat formatter = new StringFormat();
formatter.LineAlignment = StringAlignment.Center;
formatter.Alignment = StringAlignment.Center;
RectangleF rectangle = new RectangleF(0, 0, lbl.Width, lbl.Height);
e.Graphics.DrawString(Text, lbl.Font, new SolidBrush(lbl.ForeColor), rectangle, formatter);
You can call TextRenderer.DrawText with the HorizontalCenter and VerticalCenter flags.
Here is the code i'm using at the moment,
SizeF size;
string text = "Text goes here";
size = e.Graphics.MeasureString(text, font);
x = (lineWidth / 2) - (size.Width / 2);
y = top;
e.Graphics.DrawString(text, font, Brushes.Black, x, y);
I just wanted to add (a year later) a tool I created because StringAlignment turned out to be not very dependable. It turns out to be very similar to Neo's version.
The code below does an excellent job of centering the text both vertically and horizontally. Also, I wrote it with various overloads so that different options could be supplied to make this control behave exactly like I want.
Here are my overloads:
private static void DrawCenter(Label label, Graphics graphics) {
DrawCenter(label.Text, label, label.Location, label.ForeColor, graphics);
}
private void DrawCenter(string text, Label label, Graphics graphics) {
DrawCenter(text, label, label.Location, label.ForeColor, graphics);
}
private static void DrawCenter(string text, Label label, Point location, Graphics graphics) {
DrawCenter(text, label, location, label.ForeColor, graphics);
}
private static void DrawCenter(string text, Label label, Point location, Color fontColor, Graphics graphics) {
Rectangle rect = new Rectangle(location, label.Size);
SizeF lSize = graphics.MeasureString(text, label.Font, rect.Width);
PointF lPoint = new PointF(rect.X + (rect.Width - lSize.Width) / 2, rect.Y + (rect.Height - lSize.Height) / 2);
graphics.DrawString(text, label.Font, new SolidBrush(fontColor), lPoint);
}
To use these for the Label's OnPaint event, simply modify my original code in the question to following:
private void Label_OnPaint(object sender, PaintEventArgs e) {
base.OnPaint(e);
Label lbl = sender as Label;
if (lbl != null) {
string txt = lbl.Text;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
if (myShowShadow) { // draw the shadow first!
Point offset = new Point(lbl.Location.X - 1, lbl.Location.Y - 1)
DrawCenter(txt, lbl, offset, myShadowColor, e.Graphics);
}
DrawCenter(lbl, e.Graphics);
}
}
For a Print_Document event, I have a version that will also print a box around the label if there is already a box around it in the designer:
private static void DrawCenter(string text, Label label, Point location, Color fontColor, Graphics graphics) {
Rectangle rect = new Rectangle(location, label.Size);
SizeF lSize = graphics.MeasureString(text, label.Font, rect.Width);
PointF lPoint = new PointF((rect.Width - lSize.Width) / 2, (rect.Height - lSize.Height) / 2);
graphics.DrawString(text, label.Font, new SolidBrush(fontColor), lPoint);
if (label.BorderStyle != BorderStyle.None) {
using (Pen p = new Pen(Color.Black)) {
graphics.DrawRectangle(p, rect);
}
}
}
If you find this at all useful, give me a +1.
~Joe
Related
I have found this question (as a few others), but this is the one I have implemented so far:
Crosshair cursor with additional lines in C#
As it states, I can use a stock cursor "cross" directly in the IDE. This is a really good way to do things. The answer specified in the answer above draws a cross on the screen at the given width / height. Eg:
private Cursor crossCursor(Pen pen, Brush brush, int x, int y)
{
var pic = new Bitmap(x, y);
Graphics gr = Graphics.FromImage(pic);
var pathX = new GraphicsPath();
var pathY = new GraphicsPath();
pathX.AddLine(0, y / 2, x, y / 2);
pathY.AddLine(x / 2, 0, x / 2, y);
gr.DrawPath(pen, pathX);
gr.DrawPath(pen, pathY);
IntPtr ptr = pic.GetHicon();
var c = new Cursor(ptr);
return c;
}
My issue is that I want my cross hairs to extend to the Bounds of the viewing area. To provide context here, I have:
//Form
//TableLayoutPanel
//UserControl (fills the TableLayoutPanel visible area)
So how can I adjust my cursor so that the lines extend (much like in CAD pacakages)?
Thanks.
Update: I have tried calling the method from here:
protected override void OnLoad(System.EventArgs e)
{
Cursor = crossCursor(Pens.WhiteSmoke, Brushes.WhiteSmoke, Bounds.Width, Bounds.Height);
}
But it is not Ok because at this point in time Bounds is returning a dimension of 150 by 150 which is not the size of the TableLayoutPanel.
Update: I have adjuted it to use the Resize handler instead and it does improve things:
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Cursor = crossCursor(Pens.WhiteSmoke, Brushes.WhiteSmoke, Bounds.Width, Bounds.Height);
}
The only problem now (and it kind of makes sense I suppose) is that the cursor will only take the full width and height of the view when it is central to the view. As soon as I move about in the view that cursor does not adjust. I always want a horizontal/vertical line through the mouse position (not just the initial cross).
See:
The crosshairs need extending (the thicker red lines). Either I need to constantly create the cursor as the mouse moves or construct the two lines another way. What to do?
I came across this:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/7bdbad6d-1f65-461b-8f0c-6ef4f243fa6b/crosshair-cursor-using-c?forum=csharpgeneral
So, instead of changing the cursor object I now draw lines in the controls MouseMove handler:
Region r = new Region();
r.Union(new Rectangle(0, lastY, this.Width, 1));
r.Union(new Rectangle(lastX, 0, 1, this.Height));
this.Invalidate(r);
this.Update();
Graphics g = Graphics.FromHwnd(this.Handle);
g.DrawLine(Pens.White, 0, e.Y, this.Width, e.Y);
g.DrawLine(Pens.White, e.X, 0, e.X, this.Height);
int intDiameter = 20;//the diameter of this circle
g.DrawEllipse(Pens.White, e.X - intDiameter / 2, e.Y - intDiameter / 2, 20, 20);
//to draw the circle
lastX = e.X;
lastY = e.Y;
It works, but I get noticiable screen flicker doing it this way.
You don't need to create a cursor. You can create a double buffered control and draw cross over control.
using System;
using System.Drawing;
using System.Windows.Forms;
public class DrawingSurface : Control
{
Pen crossPen;
Pen rectanglePen;
Brush rectangleBrush;
public DrawingSurface()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;
crossPen = new Pen(Color.Red, 2);
rectangleBrush = new SolidBrush(Color.FromArgb(50, Color.Blue));
rectanglePen = new Pen(Color.Blue, 1);
}
bool mouseDown = false;
Point startPoint = Point.Empty;
Point endPoint = Point.Empty;
protected override void OnMouseDown(MouseEventArgs e)
{
startPoint = e.Location;
mouseDown = true;
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
mouseDown = false;
base.OnMouseUp(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
endPoint = e.Location;
this.Invalidate();
base.OnMouseMove(e);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
if (this.ClientRectangle.Contains(endPoint))
DrawCross(e.Graphics, endPoint);
if (mouseDown)
DrawRectangle(e.Graphics, startPoint, endPoint);
}
void DrawCross(Graphics g, Point point)
{
g.DrawLine(crossPen, new Point(0, point.Y), new Point(Width, point.Y));
g.DrawLine(crossPen, new Point(point.X, 0), new Point(point.X, Height));
}
void DrawRectangle(Graphics g, Point point1, Point point2)
{
var rectangle = new Rectangle(
Math.Min(point1.X, point2.X), Math.Min(point1.Y, point2.Y),
Math.Abs(point1.X - point2.X), Math.Abs(point1.Y - point2.Y));
g.FillRectangle(rectangleBrush, rectangle);
g.DrawRectangle(rectanglePen, rectangle);
}
protected override void Dispose(bool disposing)
{
crossPen.Dispose();
rectanglePen.Dispose();
rectangleBrush.Dispose();
base.Dispose(disposing);
}
}
I am making a custom progress bar in C#, and I want to show the percent on top of the bar. I need it so that when the bar reaches the text, it changes color. Take for example the image I made below:
Pretend that the orange rectangle on the left is the progress bar, and the black rectangle is blank space.
Is there anyway I can recreate this using GDI?
Thanks in advance,
Pat
You can do it by overriding paint on the control of your choice,
First draw the Black background and orange text
e.Graphics.FillRectangle(Brushes.Black, panel1.ClientRectangle);
e.Graphics.DrawString("StackOverflow", Font, Brushes.Orange, panel1.ClientRectangle);
Then Draw the overlay and clip to the size of the progress value
var clipRect = new Rectangle(0, 0, (panel1.Width / 100) * _progress, panel1.Height);
e.Graphics.SetClip(clipRect);
e.Graphics.FillRectangle(Brushes.Orange, clipRect);
e.Graphics.DrawString("StackOverflow", Font, Brushes.Black, 0, 0);
This is a working example using Panel as the control to override paint on (Just add a panel to a Form)
Example:
public partial class Form1 : Form
{
private Timer _progresstimer = new Timer();
private int _progress = 0;
public Form1()
{
InitializeComponent();
panel1.Paint += new PaintEventHandler(panel1_Paint);
_progresstimer.Interval = 250;
_progresstimer.Tick += (s, e) =>
{
if (_progress < 100)
{
_progress++;
panel1.Invalidate();
return;
}
_progress = 0;
panel1.Invalidate();
};
_progresstimer.Start();
}
void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Black, panel1.ClientRectangle);
e.Graphics.DrawString("StackOverflow", Font, Brushes.Orange, panel1.ClientRectangle);
var clipRect = new Rectangle(0, 0, (panel1.Width / 100) * _progress, panel1.Height);
e.Graphics.SetClip(clipRect);
e.Graphics.FillRectangle(Brushes.Orange, clipRect);
e.Graphics.DrawString("StackOverflow", Font, Brushes.Black, 0, 0);
}
}
You will want to set DoubleBuffering etc as this will flicker without, but this should be a good starting example.
Result:
I applied the solution provided by an user to solve the problem, but it's happening another problem. Previously I owned an icon along with the text, but after I used the code described here, my icon no longer appears. What can it be?
And how can I use the icon while using a different color other than the default on tab header?
The solution I used is:
private void tabControl1_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
TabPage CurrentTab = tabControl1.TabPages[e.Index];
Rectangle ItemRect = tabControl1.GetTabRect(e.Index);
SolidBrush FillBrush = new SolidBrush(Color.Red);
SolidBrush TextBrush = new SolidBrush(Color.White);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
//If we are currently painting the Selected TabItem we'll
//change the brush colors and inflate the rectangle.
if (System.Convert.ToBoolean(e.State & DrawItemState.Selected))
{
FillBrush.Color = Color.White;
TextBrush.Color = Color.Red;
ItemRect.Inflate(2, 2);
}
//Set up rotation for left and right aligned tabs
if (tabControl1.Alignment == TabAlignment.Left || tabControl1.Alignment == TabAlignment.Right)
{
float RotateAngle = 90;
if (tabControl1.Alignment == TabAlignment.Left)
RotateAngle = 270;
PointF cp = new PointF(ItemRect.Left + (ItemRect.Width / 2), ItemRect.Top + (ItemRect.Height / 2));
e.Graphics.TranslateTransform(cp.X, cp.Y);
e.Graphics.RotateTransform(RotateAngle);
ItemRect = new Rectangle(-(ItemRect.Height / 2), -(ItemRect.Width / 2), ItemRect.Height, ItemRect.Width);
}
//Next we'll paint the TabItem with our Fill Brush
e.Graphics.FillRectangle(FillBrush, ItemRect);
//Now draw the text.
e.Graphics.DrawString(CurrentTab.Text, e.Font, TextBrush, (RectangleF)ItemRect, sf);
//Reset any Graphics rotation
e.Graphics.ResetTransform();
//Finally, we should Dispose of our brushes.
FillBrush.Dispose();
TextBrush.Dispose();
}
You need to paint the Icon yourself also. An example would be to add something like the following after the painting of the tabs Background in you code (i assume that an image list was used here)
int imageLeftOffset = 4;
Point imagePos = new Point(imageLeftOffset, ItemRect.Top + (ItemRect.Height - tabControl1.ImageList.ImageSize.Height + 1) / 2);
tabControl1.ImageList.Draw(e.Graphics, imagePos, CurrentTab.ImageIndex);
You may need to reajust the drawing of the Text so that text and image aren't overlapping.
Greetings,
I have a tab control and I want to have 1 of the tabs have it's text color changed on a event.
I've found answers like C# - TabPage Color event
and C# Winform: How to set the Base Color of a TabControl (not the tabpage)
but using these sets all colors instead of one.
So I was hoping there is a way to implement this with the tab I wish to change as a method instead of a event?
Something like:
public void SetTabPageHeaderColor(TabPage page, Color color)
{
//Text Here
}
If you want to color the tabs, try the following code:
this.tabControl1.DrawMode = TabDrawMode.OwnerDrawFixed;
this.tabControl1.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.tabControl1_DrawItem);
private Dictionary<TabPage, Color> TabColors = new Dictionary<TabPage, Color>();
private void SetTabHeader(TabPage page, Color color)
{
TabColors[page] = color;
tabControl1.Invalidate();
}
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
//e.DrawBackground();
using (Brush br = new SolidBrush (TabColors[tabControl1.TabPages[e.Index]]))
{
e.Graphics.FillRectangle(br, e.Bounds);
SizeF sz = e.Graphics.MeasureString(tabControl1.TabPages[e.Index].Text, e.Font);
e.Graphics.DrawString(tabControl1.TabPages[e.Index].Text, e.Font, Brushes.Black, e.Bounds.Left + (e.Bounds.Width - sz.Width) / 2, e.Bounds.Top + (e.Bounds.Height - sz.Height) / 2 + 1);
Rectangle rect = e.Bounds;
rect.Offset(0, 1);
rect.Inflate(0, -1);
e.Graphics.DrawRectangle(Pens.DarkGray, rect);
e.DrawFocusRectangle();
}
}
For WinForms users reading this - This ONLY works if you set your tab control's DrawMode to OwnerDrawFixed - the DrawItem event never fires if it's set to Normal.
To add to Fun Mun Pieng's answer which works beautifully on Horizontal tabs, if you were to use Vertical tabs (like I was) then you would need something like this:
private void tabControl2_DrawItem(object sender, DrawItemEventArgs e)
{
using (Brush br = new SolidBrush(tabColorDictionary[tabControl2.TabPages[e.Index]]))
{
// Color the Tab Header
e.Graphics.FillRectangle(br, e.Bounds);
// swap our height and width dimensions
var rotatedRectangle = new Rectangle(0, 0, e.Bounds.Height, e.Bounds.Width);
// Rotate
e.Graphics.ResetTransform();
e.Graphics.RotateTransform(-90);
// Translate to move the rectangle to the correct position.
e.Graphics.TranslateTransform(e.Bounds.Left, e.Bounds.Bottom, System.Drawing.Drawing2D.MatrixOrder.Append);
// Format String
var drawFormat = new System.Drawing.StringFormat();
drawFormat.Alignment = StringAlignment.Center;
drawFormat.LineAlignment = StringAlignment.Center;
// Draw Header Text
e.Graphics.DrawString(tabControl2.TabPages[e.Index].Text, e.Font, Brushes.Black, rotatedRectangle, drawFormat);
}
}
I will echo the point that ROJO1969 made, if this is in WinForms - then you must set DrawMode to OwnerDrawFixed.
Special thanks goes out to this wonderful blog entry which described how to do a rotation of text on a form.
private void MainForm_Load(object sender, EventArgs e)
{
...
this.tabControl1.DrawMode = TabDrawMode.OwnerDrawFixed;
this.tabControl1.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.tabControl1_DrawItem);
...
}
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
try
{
// Draw the background of the control for each item.
//e.DrawBackground();
if (e.Index == this.tabControl1.SelectedIndex)
{
Brush _BackBrush = new SolidBrush(tabControl1.TabPages[e.Index].BackColor);
Rectangle rect = e.Bounds;
e.Graphics.FillRectangle(_BackBrush, (rect.X) + 4, rect.Y, (rect.Width) - 4, rect.Height);
SizeF sz = e.Graphics.MeasureString(tabControl1.TabPages[e.Index].Text, e.Font);
e.Graphics.DrawString(tabControl1.TabPages[e.Index].Text, e.Font, Brushes.Black,
e.Bounds.Left + (e.Bounds.Width - sz.Width) / 2,
e.Bounds.Top + (e.Bounds.Height - sz.Height) / 2 + 1);
}
else
{
// 파스톤계 배경색 없앨려면 FromArgb 를 없애면 된다.
Brush _BackBrush = new SolidBrush(Color.FromArgb(50, tabControl1.TabPages[e.Index].BackColor));
Rectangle rect = e.Bounds;
e.Graphics.FillRectangle(_BackBrush, rect.X, (rect.Y)-0, rect.Width, (rect.Height)+6);
SizeF sz = e.Graphics.MeasureString(tabControl1.TabPages[e.Index].Text, e.Font);
e.Graphics.DrawString(tabControl1.TabPages[e.Index].Text, e.Font, Brushes.Black,
e.Bounds.Left + (e.Bounds.Width - sz.Width) / 2,
e.Bounds.Top + 5);
}
}
catch (Exception Ex)
{
MessageBox.Show(Ex.Message, "Error Occured", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
If any one need to put color for tab header use try this. My tab name tabControl
tabControl.DrawMode = TabDrawMode.OwnerDrawFixed;
tabControl.DrawItem += tabControl1_DrawItem;
declear this under main class
then,
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
Color tabTextColor = Color.FromArgb(0x000001);
var color = Color.FromArgb(tabTextColor.R, tabTextColor.G, tabTextColor.B);
TextRenderer.DrawText(e.Graphics, tabControl.TabPages[e.Index].Text, e.Font, e.Bounds, color);
}
declare this function it will generate output
final out
I have the following code under a TabConttrols DrawItem event that I am trying to extract into a class file. I am having trouble since it is tied to an event. Any hints or pointers would be greatly appreciated.
private void tabCaseNotes_DrawItem(object sender, DrawItemEventArgs e)
{
TabPage currentTab = tabCaseNotes.TabPages[e.Index];
Rectangle itemRect = tabCaseNotes.GetTabRect(e.Index);
SolidBrush fillBrush = new SolidBrush(Color.Linen);
SolidBrush textBrush = new SolidBrush(Color.Black);
StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
//If we are currently painting the Selected TabItem we'll
//change the brush colors and inflate the rectangle.
if (System.Convert.ToBoolean(e.State & DrawItemState.Selected))
{
fillBrush.Color = Color.LightSteelBlue;
textBrush.Color = Color.Black;
itemRect.Inflate(2, 2);
}
//Set up rotation for left and right aligned tabs
if (tabCaseNotes.Alignment == TabAlignment.Left || tabCaseNotes.Alignment == TabAlignment.Right)
{
float rotateAngle = 90;
if (tabCaseNotes.Alignment == TabAlignment.Left)
rotateAngle = 270;
PointF cp = new PointF(itemRect.Left + (itemRect.Width / 2), itemRect.Top + (itemRect.Height / 2));
e.Graphics.TranslateTransform(cp.X, cp.Y);
e.Graphics.RotateTransform(rotateAngle);
itemRect = new Rectangle(-(itemRect.Height / 2), -(itemRect.Width / 2), itemRect.Height, itemRect.Width);
}
//Next we'll paint the TabItem with our Fill Brush
e.Graphics.FillRectangle(fillBrush, itemRect);
//Now draw the text.
e.Graphics.DrawString(currentTab.Text, e.Font, textBrush, (RectangleF)itemRect, sf);
//Reset any Graphics rotation
e.Graphics.ResetTransform();
//Finally, we should Dispose of our brushes.
fillBrush.Dispose();
textBrush.Dispose();
}
Depends on what you're trying to achieve. You could always sub class TabControl or you could encapsulate the drawing code in a class that you pass a TabControl to.
public class TabRenderer
{
private TabControl _tabControl;
public TabRenderer(TabControl tabControl)
{
_tabControl = tabControl;
_tabControl.DrawMode = TabDrawMode.OwnerDrawFixed;
_tabControl.DrawItem += TabControlDrawItem;
}
private void TabControlDrawItem(object sender, DrawItemEventArgs e)
{
// Your drawing code...
}
}