I'm tring to build a Control derived class which supports an Opcacity property.
This control could host both text and image and will beable to fade them out and in.
Here is my code:
internal class FadeControl : Control
{
private int opacity = 100;
public FadeControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
public int Opacity
{
get
{
return opacity;
}
set
{
if (value > 100) opacity = 100;
else if (value < 1) opacity = 1;
else opacity = value;
if (Parent != null)
Parent.Invalidate(Bounds, true);
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//do nothing
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
protected override void OnPaint(PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Rectangle bounds = new Rectangle(0, 0, Width - 1, Height - 1);
int alpha = (opacity * 255) / 100;
using (Brush bckColor = new SolidBrush(Color.FromArgb(alpha, BackColor)))
{
if (BackColor != Color.Transparent)
g.FillRectangle(bckColor, bounds);
}
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.Matrix33 = (float)alpha / 255;
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
if (BackgroundImage != null)
g.DrawImage(BackgroundImage, bounds, 0, 0, Width, Height, GraphicsUnit.Pixel, imageAttr);
if (Text != string.Empty)
{
using (Brush txtBrush = new SolidBrush(Color.FromArgb(alpha, ForeColor)))
{
g.DrawString(Text, Font, txtBrush, 5, 5);
}
}
}
}
protected override void OnBackColorChanged(EventArgs e)
{
if (Parent != null)
Parent.Invalidate(Bounds, true);
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
Invalidate();
base.OnParentBackColorChanged(e);
}
}
I've putted the control on a form which has a timer on it.
The timer set the control's opacity from 0 to 100 and back and its working well.
The problem I'm trying to solved is that the control flickers while changing its opacity.
Setting the control toControlStyles.DoubleBuffer will make the control invisible on the form.
Any advice will be welcome.
I was unable to use both a double buffer and WS_EX_TRANSPARENT (0x20) for the transparent background. So I decided to implement the transparent background by copying the content of the parent control and use double buffer to prevent flicker.
The following is the final source code, tested and working:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
internal class FadeControl : Control
{
private int opacity = 100;
private Bitmap backgroundBuffer;
private bool skipPaint;
public FadeControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
}
public int Opacity
{
get
{
return opacity;
}
set
{
if (value > 100) opacity = 100;
else if (value < 1) opacity = 1;
else opacity = value;
if (Parent != null)
Parent.Invalidate(Bounds, true);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//do nothig
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
private void CreateBackgroundBuffer(Control parent)
{
int offsetX;
int offsetY;
GetOffsets(out offsetX, out offsetY, parent);
backgroundBuffer = new Bitmap(Width + offsetX, Height + offsetY);
}
protected override void OnResize(EventArgs e)
{
var parent = Parent;
if (parent != null)
{
CreateBackgroundBuffer(parent);
}
base.OnResize(e);
}
private void GetOffsets(out int offsetX, out int offsetY, Control parent)
{
var parentPosition = parent.PointToScreen(Point.Empty);
offsetY = Top + parentPosition.Y - parent.Top;
offsetX = Left + parentPosition.X - parent.Left;
}
private void UpdateBackgroundBuffer(int offsetX, int offsetY, Control parent)
{
if (backgroundBuffer == null)
{
CreateBackgroundBuffer(parent);
}
Rectangle parentBounds = new Rectangle(0, 0, Width + offsetX, Height + offsetY);
skipPaint = true;
parent.DrawToBitmap(backgroundBuffer, parentBounds);
skipPaint = false;
}
private void DrawBackground(Graphics graphics, Rectangle bounds)
{
int offsetX;
int offsetY;
var parent = Parent;
GetOffsets(out offsetX, out offsetY, parent);
UpdateBackgroundBuffer(offsetX, offsetY, parent);
graphics.DrawImage(backgroundBuffer, bounds, offsetX, offsetY, Width, Height, GraphicsUnit.Pixel);
}
private void Draw(Graphics graphics)
{
Rectangle bounds = new Rectangle(0, 0, Width, Height);
DrawBackground(graphics, bounds);
int alpha = (opacity * 255) / 100;
using (Brush bckColor = new SolidBrush(Color.FromArgb(alpha, BackColor)))
{
if (BackColor != Color.Transparent)
{
graphics.FillRectangle(bckColor, bounds);
}
}
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.Matrix33 = (float)alpha / 255;
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
if (BackgroundImage != null)
{
graphics.DrawImage(BackgroundImage, bounds, 0, 0, Width, Height, GraphicsUnit.Pixel, imageAttr);
}
if (Text != string.Empty)
{
using (Brush txtBrush = new SolidBrush(Color.FromArgb(alpha, ForeColor)))
{
graphics.DrawString(Text, Font, txtBrush, 5, 5);
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (!skipPaint)
{
Graphics graphics = e.Graphics;
Draw(graphics);
}
}
protected override void OnBackColorChanged(EventArgs e)
{
if (Parent != null)
{
Parent.Invalidate(Bounds, true);
}
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
Invalidate();
base.OnParentBackColorChanged(e);
}
}
Note that the method CreateParams is no longer present, also I've changed the contructor.
The field skipPaint is to know when not to paint in order to be able to able to tell the parent to draw itself to a bitmap during OnPaint without having infinite recursion.
backgroundBuffer is not to implement double buffering, but to keep a copy of the contents of the parent without the control rendered. It is updated each paint, I know there are more efficient solutions...* yet this approach keeps it simple and shouldn't be a bottleneck unless you have too many of these controls on the same container.
*: A better solution would be to update it each time the parent invalidates. Futhermore shared it among all the FadeControls in the same parent.
Related
I have a custom CheckListBox control that is supposed to function the same way as the CheckedListBox control but it is not. The problem is when I scroll down, the drawing gets all messed up. The container won't actually scroll down, it will just "jitter" the check boxes around, draw random lines, etc.
Update: I changed the code so the location of each CheckBox was set in the OnControlAdded method instead of the OnPaint method. It now scrolls fine, but the drawing is still messed up! The border is missing, the BackColor changes, the lines for the check boxes are not straight; just a whole mess of things. It works perfectly in the designer (the scrolling and drawing), but not when I run the program.
Here's the code for the control:
public class ChromeCheckListBox : ChromeContainerControl
{
[Description("Determines what corner(s) will be rounded.")]
public Utilities.RoundedRectangle.RectangleCorners Corners { get; set; }
private int cornerRadius;
[Description("Determines the radius of the the corners")]
public int CornerRadius
{
get { return cornerRadius; }
set
{
if (value < 1)
Utilities.ThrowError("The radius cannot be less than 1. If you want no radius, set Corners to None.");
else
cornerRadius = value;
}
}
[Description("Determines the list of ChromeRadioButton controls that are displayed.")]
private ChromeCheckBox[] items;
public ChromeCheckBox[] Items
{
get { return items; }
set
{
items = value;
Controls.Clear();
Controls.AddRange(items);
}
}
public ChromeCheckListBox()
{
this.AutoScroll = true;
this.Corners = Utilities.RoundedRectangle.RectangleCorners.All;
this.CornerRadius = 1;
this.Items = new ChromeCheckBox[0];
this.Size = new Size(100, 100);
}
protected override void OnControlAdded(ControlEventArgs e)
{
for (int i = 0; i < Items.Length; i++)
{
if (i == 0)
Items[i].Location = new Point(2 + Padding.Left, 2 + Padding.Top);
else
Items[i].Location = new Point(2 + Padding.Left, Items[i - 1].Location.Y + Size.Ceiling(this.CreateGraphics().MeasureString(Items[i - 1].Text, Items[i - 1].Font)).Height);
}
base.OnControlAdded(e);
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics canvas = e.Graphics;
canvas.SmoothingMode = SmoothingMode.HighQuality;
Rectangle region = new Rectangle(0, 0, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
GraphicsPath path = Utilities.RoundedRectangle.Create(region, CornerRadius, Corners);
canvas.FillPath(new LinearGradientBrush(region, fillColors[0], fillColors[1], 90), path);
canvas.DrawPath(new Pen(borderColor), path);
}
}
I got it! Instead of setting the location of the check boxes in the OnPaint method, I should set them in the OnControlAdded method. Also, on the OnScroll method, I need to Invalidate the control causing it to redraw.
So, it should (well maybe not should, but could) look like this:
public class ChromeCheckListBox : ChromeContainerControl
{
[Description("Determines what corner(s) will be rounded.")]
public Utilities.RoundedRectangle.RectangleCorners Corners { get; set; }
private int cornerRadius;
[Description("Determines the radius of the the corners")]
public int CornerRadius
{
get { return cornerRadius; }
set
{
if (value < 1)
Utilities.ThrowError("The radius cannot be less than 1. If you want no radius, set Corners to None.");
else
cornerRadius = value;
}
}
[Description("Determines the list of ChromeRadioButton controls that are displayed.")]
private ChromeCheckBox[] items;
public ChromeCheckBox[] Items
{
get { return items; }
set
{
items = value;
Controls.Clear();
Controls.AddRange(items);
}
}
public ChromeCheckListBox()
{
this.AutoScroll = true;
this.Corners = Utilities.RoundedRectangle.RectangleCorners.All;
this.CornerRadius = 1;
this.Items = new ChromeCheckBox[0];
this.Size = new Size(100, 100);
}
protected override void OnControlAdded(ControlEventArgs e)
{
for (int i = 0; i < Items.Length; i++)
{
if (i == 0)
Items[i].Location = new Point(2 + Padding.Left, 2 + Padding.Top);
else
Items[i].Location = new Point(2 + Padding.Left, Items[i - 1].Location.Y + Size.Ceiling(this.CreateGraphics().MeasureString(Items[i - 1].Text, Items[i - 1].Font)).Height);
}
base.OnControlAdded(e);
}
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
base.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics canvas = e.Graphics;
canvas.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle region = new Rectangle(0, 0, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
GraphicsPath path = Utilities.RoundedRectangle.Create(region, CornerRadius, Corners);
canvas.FillPath(new LinearGradientBrush(region, fillColors[0], fillColors[1], 90), path);
canvas.DrawPath(new Pen(borderColor), path);
}
I have a large TIFF picture (5.9 MB, 13k X 16k resolution) that the user loads into a scrollable panel that he then can zoom in/out of, scroll and mark points, regions etc. on.
For the scrollable double-buffered panel I am using a modification of Bob Powell's awesome ZoomPicBox
The panel displays only the part of the picture currently in view.
The stutter occurs when scrolling the image when zoomed out (even if the interpolationMode is set to low).
Is there anything that can be done about it (preferably without hardware acceleration)?
The paint event of the panel:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
if (_image == null)
{
base.OnPaintBackground(e);
return;
}
//scale
System.Drawing.Drawing2D.Matrix ScaleMat = new System.Drawing.Drawing2D.Matrix(_zoom, 0, 0, _zoom, 0, 0);
//move to position of scrollbas
ScaleMat.Translate(this.AutoScrollPosition.X / (_zoom), this.AutoScrollPosition.Y / (_zoom));
e.Graphics.Transform = ScaleMat;
e.Graphics.InterpolationMode = _interpolationMode;
e.Graphics.DrawImage(_image, new Rectangle(0, 0, _image.Width, _image.Height), 0, 0, _image.Width, _image.Height, GraphicsUnit.Pixel);
base.OnPaint(e);
}
* _zoom ,_image and _interpolationMode are private fields of the control.
The constructor:
public PicBoxPlus()
{
MouseMove += PicBoxPlus_MouseMove;
KeyDown += PicBoxPlus_KeyDown;
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);
this.AutoScroll = true;
}
Then I tried implementing Sinatr's code but something is wrong, because all I get is a black image (of the right size). Anyone has an idea what could be wrong?
The new paint event:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
if (mCachedImage == null)
{
base.OnPaintBackground(e);
return;
}
//scale
System.Drawing.Drawing2D.Matrix ScaleMat = new System.Drawing.Drawing2D.Matrix(mZoom, 0, 0, mZoom, 0, 0);
//move to position of scrollbas
ScaleMat.Translate(this.AutoScrollPosition.X / (mZoom), this.AutoScrollPosition.Y / (mZoom));
try
{
if (mCachedImage == null)
{
mCachedImage = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
using (var cacheGraphics = Graphics.FromImage(mCachedImage))
{
cacheGraphics.Transform = ScaleMat;
cacheGraphics.InterpolationMode = _interpolationMode;
cacheGraphics.DrawImage(mCachedImage, new Rectangle(0, 0, mCachedImage.Width, mCachedImage.Height), 0, 0, mCachedImage.Width, mCachedImage.Height, GraphicsUnit.Pixel);
}
e.Graphics.DrawImage(mCachedImage, Point.Empty);
}
}
catch (Exception ex)
{
throw ex;
}
base.OnPaint(e);
}
The Image and zoom properties:
public Bitmap Image
{
get { return mCachedImage; }
set
{
mCachedImage = value;
UpdateScaleFactor();
this.Invalidate();
}
}
public Single Zoom
{
get { return mZoom; }
set
{
if (value <= 0||value < 0.001)
{
value = 0.001f;
}
mZoom = value;
UpdateScaleFactor();
ResetCache(); // Sinatr's function
this.Invalidate();
}
}
Loading the image from the main form:
panelMap.Image = (Bitmap)Image.FromFile("pic.tiff");
Is not tested, but should give an idea.
Bitmap _cached = null;
override void OnPaint(PaintEventArgs e)
{
if(_cached == null)
{
_cached = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
using(var graphics = Graphics.FromImage(_cached)
{
// draw into this graphics once -> it will be cached in _cached bitmap
}
}
e.Graphics.DrawImage(_cached, Point.Empty);
}
// call this if _zoom or ClientSize is changed
void ResetCache()
{
_cache = null;
this.Invalidate(); // mandatory for _zoom change
}
Also, I don't know how to do you present your zoomed-in picture, but usually there is an offset so that you can move (pan) image.
I have been playing around with customizing the WinForm combobox...So far I have the following:
Using this code:
public class ComboBoxWithBorder : ComboBox
{
private Color _borderColor = Color.Black;
private ButtonBorderStyle _borderStyle = ButtonBorderStyle.Solid;
private static int WM_PAINT = 0x000F;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
Graphics g = Graphics.FromHwnd(Handle);
Rectangle bounds = new Rectangle(0, 0, Width, Height);
ControlPaint.DrawBorder(g, bounds, _borderColor, _borderStyle);
}
}
[Category("Appearance")]
public Color BorderColor
{
get { return _borderColor; }
set
{
_borderColor = value;
Invalidate(); // causes control to be redrawn
}
}
[Category("Appearance")]
public ButtonBorderStyle BorderStyle
{
get { return _borderStyle; }
set
{
_borderStyle = value;
Invalidate();
}
}
}
However, I am trying to achieve something similar to this:
Is it possible to change the background color of the white dropdown box to a darker color?
Is it possible to change the dropdown list border from white to a different color?
Try This Class
Refer
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class FlatCombo : ComboBox
{
private Brush BorderBrush = new SolidBrush(SystemColors.Window);
private Brush ArrowBrush = new SolidBrush(SystemColors.ControlText);
private Brush DropButtonBrush = new SolidBrush(SystemColors.Control);
private Color _ButtonColor = SystemColors.Control;
public Color ButtonColor {
get { return _ButtonColor; }
set {
_ButtonColor = value;
DropButtonBrush = new SolidBrush(this.ButtonColor);
this.Invalidate();
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(m);
switch (m.Msg) {
case 0xf:
//Paint the background. Only the borders
//will show up because the edit
//box will be overlayed
Graphics g = this.CreateGraphics;
Pen p = new Pen(Color.White, 2);
g.FillRectangle(BorderBrush, this.ClientRectangle);
//Draw the background of the dropdown button
Rectangle rect = new Rectangle(this.Width - 15, 3, 12, this.Height - 6);
g.FillRectangle(DropButtonBrush, rect);
//Create the path for the arrow
Drawing2D.GraphicsPath pth = new Drawing2D.GraphicsPath();
PointF TopLeft = new PointF(this.Width - 13, (this.Height - 5) / 2);
PointF TopRight = new PointF(this.Width - 6, (this.Height - 5) / 2);
PointF Bottom = new PointF(this.Width - 9, (this.Height + 2) / 2);
pth.AddLine(TopLeft, TopRight);
pth.AddLine(TopRight, Bottom);
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
//Determine the arrow's color.
if (this.DroppedDown) {
ArrowBrush = new SolidBrush(SystemColors.HighlightText);
} else {
ArrowBrush = new SolidBrush(SystemColors.ControlText);
}
//Draw the arrow
g.FillPath(ArrowBrush, pth);
break;
default:
break; // TODO: might not be correct. Was : Exit Select
break;
}
}
//Override mouse and focus events to draw
//proper borders. Basically, set the color and Invalidate(),
//In general, Invalidate causes a control to redraw itself.
#region "Mouse and focus Overrides"
protected override void OnMouseEnter(System.EventArgs e)
{
base.OnMouseEnter(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
protected override void OnMouseLeave(System.EventArgs e)
{
base.OnMouseLeave(e);
if (this.Focused)
return;
BorderBrush = new SolidBrush(SystemColors.Window);
this.Invalidate();
}
protected override void OnLostFocus(System.EventArgs e)
{
base.OnLostFocus(e);
BorderBrush = new SolidBrush(SystemColors.Window);
this.Invalidate();
}
protected override void OnGotFocus(System.EventArgs e)
{
base.OnGotFocus(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
protected override void OnMouseHover(System.EventArgs e)
{
base.OnMouseHover(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
#endregion
}
I created a custom control which I want to use to overlay part of my form with status information on demand. It should display a text and have a background color depending on the type of information. Here is the still incomplete code.
public partial class StatusPanel : UserControl
{
public enum PanelStyle
{
Info,
Warning,
Error
}
public PanelStyle Style { get; set; }
public StatusPanel()
{
InitializeComponent();
this.imgGreen = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
using(Graphics g = Graphics.FromImage(this.imgGreen))
{
g.Clear(Color.Transparent);
Brush bg = new SolidBrush(Color.FromArgb(200, Color.Green));
g.FillRectangle(bg, 0, 0, 256, 256);
}
this.imgYellow = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
using(Graphics g = Graphics.FromImage(this.imgYellow))
{
g.Clear(Color.Transparent);
Brush bg = new SolidBrush(Color.FromArgb(10, Color.Yellow));
g.FillRectangle(bg, 0, 0, 256, 256);
}
}
protected readonly Font font = new Font("Arial", 12.0F);
protected readonly Brush textBrush = new SolidBrush(Color.Black);
protected readonly Image imgGreen;
protected readonly Image imgYellow;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// do not draw background
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
Image img = GetImage();
e.Graphics.DrawImage(img, 0, 0, this.Width, this.Height);
e.Graphics.DrawString("ABC", this.font, this.textBrush, 1.0F, 1.0F);
}
protected Image GetImage()
{
return (this.Style == PanelStyle.Info) ? this.imgGreen : this.imgYellow;
}
}
This works quite fine. But when I put some buttons on a form and one of this controls infront of them, they will "overdraw" when the mouse moves over the button and the highlight effects redraws them.
How will my component be notified that it needs to redraw because underlying control have redrawn?
register a paint-handler to all controls under Parent.Control and a handler for Parent.ControlAdded that registers your paint-handler
something like this:
private void myDummyUserControl_Load(object sender, EventArgs e)
{
var uc = (DummyUserControl)sender;
uc.Parent.ControlAdded += new ControlEventHandler(Parent_ControlAdded);
foreach (Control c in uc.Parent.Controls)
{
if (uc == c)
continue;
c.Paint += new PaintEventHandler(c_Paint);
}
}
void c_Paint(object sender, PaintEventArgs e)
{
//checks & paint stuff here
}
void Parent_ControlAdded(object sender, ControlEventArgs e)
{
e.Control.Paint += new PaintEventHandler(c_Paint);
}
//edit
not sure if you need to use recursion to go down the tree of child controls and need to add handlers on those too ...
Is it possible to display a label vertically in a Windows Forms?
Labels are easy, all you have to do is override the Paint event and draw the text vertically. Do note that GDI is optimised for Drawing text horizontally. If you rotate text (even if you rotate through multiples of 90 degrees) it will looks notably worse.
Perhaps the best thing to do is draw your text (or get a label to draw itself) onto a bitmap, then display the bitmap rotated.
Some C# code for drawing a Custom Control with vertical text. Note that ClearType text NEVER works if the text is not horizontal:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public partial class VerticalLabel : UserControl
{
public VerticalLabel()
{
InitializeComponent();
}
private void VerticalLabel_SizeChanged(object sender, EventArgs e)
{
GenerateTexture();
}
private void GenerateTexture()
{
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
format.Trimming = StringTrimming.EllipsisCharacter;
Bitmap img = new Bitmap(this.Height, this.Width);
Graphics G = Graphics.FromImage(img);
G.Clear(this.BackColor);
SolidBrush brush_text = new SolidBrush(this.ForeColor);
G.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
G.DrawString(this.Name, this.Font, brush_text, new Rectangle(0, 0, img.Width, img.Height), format);
brush_text.Dispose();
img.RotateFlip(RotateFlipType.Rotate270FlipNone);
this.BackgroundImage = img;
}
}
Create a class myLabel which can rotate it's Text on any angle specified by you.
You can use it by code or simply dragging from ToolBox
using System.Drawing;
class myLabel:System.Windows.Forms.Label
{
public int RotateAngle { get; set; } // to rotate your text
public string NewText { get; set; } // to draw text
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
Brush b =new SolidBrush(this.ForeColor);
e.Graphics.TranslateTransform(this.Width / 2, this.Height / 2);
e.Graphics.RotateTransform(this.RotateAngle);
e.Graphics.DrawString(this.NewText, this.Font,b , 0f, 0f);
base.OnPaint(e);
}
}
Now this custom control is used into your form.
You have to set below properties
1. mylbl.Text = ""; //which can be changed by NewText property
2. mylbl.AutoSize = false; // adjust according to your text
3. mylbl.NewText = "Hello"; // whatever you want to display
4. mylbl.ForeColor = Color.Red; // color to display
5. mylbl.RotateAngle = -90; //angle to rotate
I expanded on Javed Akram's answer to resize the widget automatically (I needed this feature). It works for both positive and negative angles, the way that Javed states:
1. mylbl.Text = ""; // which can be changed by NewText property
2. mylbl.AutoSize = false; // adjust according to your text
3. mylbl.NewText = "Hello"; // whatever you want to display
4. mylbl.ForeColor = Color.Red; // color to display
5. mylbl.RotateAngle = -90; // angle to rotate
Here is the code:
public class RotatingLabel : System.Windows.Forms.Label
{
private int m_RotateAngle = 0;
private string m_NewText = string.Empty;
public int RotateAngle { get { return m_RotateAngle; } set { m_RotateAngle = value; Invalidate(); } }
public string NewText { get { return m_NewText; } set { m_NewText = value; Invalidate(); } }
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
Func<double, double> DegToRad = (angle) => Math.PI * angle / 180.0;
Brush b = new SolidBrush(this.ForeColor);
SizeF size = e.Graphics.MeasureString(this.NewText, this.Font, this.Parent.Width);
int normalAngle = ((RotateAngle % 360) + 360) % 360;
double normaleRads = DegToRad(normalAngle);
int hSinTheta = (int)Math.Ceiling((size.Height * Math.Sin(normaleRads)));
int wCosTheta = (int)Math.Ceiling((size.Width * Math.Cos(normaleRads)));
int wSinTheta = (int)Math.Ceiling((size.Width * Math.Sin(normaleRads)));
int hCosTheta = (int)Math.Ceiling((size.Height * Math.Cos(normaleRads)));
int rotatedWidth = Math.Abs(hSinTheta) + Math.Abs(wCosTheta);
int rotatedHeight = Math.Abs(wSinTheta) + Math.Abs(hCosTheta);
this.Width = rotatedWidth;
this.Height = rotatedHeight;
int numQuadrants =
(normalAngle >= 0 && normalAngle < 90) ? 1 :
(normalAngle >= 90 && normalAngle < 180) ? 2 :
(normalAngle >= 180 && normalAngle < 270) ? 3 :
(normalAngle >= 270 && normalAngle < 360) ? 4 :
0;
int horizShift = 0;
int vertShift = 0;
if (numQuadrants == 1)
{
horizShift = Math.Abs(hSinTheta);
}
else if (numQuadrants == 2)
{
horizShift = rotatedWidth;
vertShift = Math.Abs(hCosTheta);
}
else if (numQuadrants == 3)
{
horizShift = Math.Abs(wCosTheta);
vertShift = rotatedHeight;
}
else if (numQuadrants == 4)
{
vertShift = Math.Abs(wSinTheta);
}
e.Graphics.TranslateTransform(horizShift, vertShift);
e.Graphics.RotateTransform(this.RotateAngle);
e.Graphics.DrawString(this.NewText, this.Font, b, 0f, 0f);
base.OnPaint(e);
}
}
I found a way to simply do it without adding code or classes to your project!
When you create your label, simply add:
this.label1.text = "V\nE\nR\nT\nI\nC\nA\nL\n";
This worked for me!
You can rotate text instead of the label control in the OnPaint event or Paint method:
private void uc1_Paint(object sender, PaintEventArgs e)
{
string Name;
var g = e.Graphics;
g.DrawString(Name, new Font("Tahoma", 8), Brushes.Black, 0, 0,
new StringFormat(StringFormatFlags.DirectionVertical));
}
2015 update on an old post. Since most of the other answers seem to heavily affect VS2013's designer in terms of usability, I'd suggest this solution:
http://www.codeproject.com/Articles/19774/Extended-Vertical-Label-Control-in-C-NET
It absolutely works. I found it on net and little changed
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.ComponentModel;
public class VerticalLabel : System.Windows.Forms.Label
{
private bool bFlip = true;
public VerticalLabel()
{
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.Trimming = StringTrimming.None;
stringFormat.FormatFlags = StringFormatFlags.DirectionVertical;
Brush textBrush = new SolidBrush(this.ForeColor);
Matrix storedState = g.Transform;
if (bFlip)
{
g.RotateTransform(180f);
g.TranslateTransform(-ClientRectangle.Width,-ClientRectangle.Height);
}
g.DrawString(
this.Text,
this.Font,
textBrush,
ClientRectangle,
stringFormat);
g.Transform = storedState;
}
[Description("When this parameter is true the VLabel flips at 180 degrees."),Category("Appearance")]
public bool Flip180
{
get
{
return bFlip;
}
set
{
bFlip = value;
this.Invalidate();
}
}
}
Used pieces from others
Jeremy
public partial class VerticalLabel_UserControl : UserControl
{
private IComponentChangeService _changeService;
private string strPropertyText = "Vertical Text";
public VerticalLabel_UserControl()
{
InitializeComponent();
}
[EditorBrowsable(EditorBrowsableState.Always)]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Bindable(true)]
public override string Text { get { return base.Text; } set { base.Text = value; this.Invalidate(); } }
private void VerticalLabel_UserControl_SizeChanged(object sender, EventArgs e)
{
GenerateTexture();
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
}
private void GenerateTexture()
{
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
// format.Trimming = StringTrimming.EllipsisCharacter;
Bitmap img = new Bitmap(this.Height, this.Width);
Graphics G = Graphics.FromImage(img);
G.Clear(this.BackColor);
SolidBrush brush_text = new SolidBrush(this.ForeColor);
G.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
G.DrawString(this.strPropertyText, this.Font, brush_text, new Rectangle(0, 0, img.Width, img.Height), format);
img.RotateFlip(RotateFlipType.Rotate270FlipNone);
this.BackgroundImage = img;
brush_text.Dispose();
}
public override System.ComponentModel.ISite Site
{
get
{
return base.Site;
}
set
{
_changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
if (_changeService != null)
_changeService.ComponentChanged -= new ComponentChangedEventHandler(OnComponentChanged);
base.Site = value;
if (!DesignMode)
return;
_changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
if (_changeService != null)
_changeService.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged);
}
}
private void OnComponentChanged(object sender, ComponentChangedEventArgs ce)
{
VerticalLabel_UserControl label = ce.Component as VerticalLabel_UserControl;
if (label == null || !label.DesignMode)
return;
if (((IComponent)ce.Component).Site == null || ce.Member == null || ce.Member.Name != "Text")
return;
//Causes the default text to be updated
string strName = this.Name.ToLower();
string strText = this.Text.ToLower();
if (strText.Contains(strName))
{
this.Text = "Vertical Text";
}
else
{
strPropertyText = this.Text;
}
//Prints the text vertically
GenerateTexture();
}
}
I just turned off the AutoSize property and resized the label vertically. I made the label wide enough for only one character. Then I changed TextAlign to center to make the alignment look better. This worked great for me.