I am currently developing a simple image editing tool using Winforms and .NET 3.5 (work environment).
I have a requirement that when the user clicks a select tool button, a square (rectangle in C#) will appear that they can scale between 100x100 and 400x400. I have this bit fixed - the issue comes with making the background of the rectangle transparent.
I'm a little unclear on if transparency is supported in .NET 3.5, I've tried the following:
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
pnlSelectArea.BackColor = Color.Transparent;
pnlSelectArea.ForeColor = Color.Transparent;
selectArea1.BackColor = Color.Transparent;
selectArea1.ForeColor = Color.Transparent;
But this has no effect - any advice would be appreciated.
This is my special Control which contains an opacity property, it 100% works:
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;
public class TranspCtrl : Control
{
public bool drag = false;
public bool enab = false;
private int m_opacity = 100;
private int alpha;
public TranspCtrl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
this.BackColor = Color.Transparent;
}
public int Opacity
{
get
{
if (m_opacity > 100)
{
m_opacity = 100;
}
else if (m_opacity < 1)
{
m_opacity = 1;
}
return this.m_opacity;
}
set
{
this.m_opacity = value;
if (this.Parent != null)
{
Parent.Invalidate(this.Bounds, true);
}
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle bounds = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
Color frmColor = this.Parent.BackColor;
Brush bckColor = default(Brush);
alpha = (m_opacity * 255) / 100;
if (drag)
{
Color dragBckColor = default(Color);
if (BackColor != Color.Transparent)
{
int Rb = BackColor.R * alpha / 255 + frmColor.R * (255 - alpha) / 255;
int Gb = BackColor.G * alpha / 255 + frmColor.G * (255 - alpha) / 255;
int Bb = BackColor.B * alpha / 255 + frmColor.B * (255 - alpha) / 255;
dragBckColor = Color.FromArgb(Rb, Gb, Bb);
}
else
{
dragBckColor = frmColor;
}
alpha = 255;
bckColor = new SolidBrush(Color.FromArgb(alpha, dragBckColor));
}
else
{
bckColor = new SolidBrush(Color.FromArgb(alpha, this.BackColor));
}
if (this.BackColor != Color.Transparent | drag)
{
g.FillRectangle(bckColor, bounds);
}
bckColor.Dispose();
g.Dispose();
base.OnPaint(e);
}
protected override void OnBackColorChanged(EventArgs e)
{
if (this.Parent != null)
{
Parent.Invalidate(this.Bounds, true);
}
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
this.Invalidate();
base.OnParentBackColorChanged(e);
}
}
You will need to use Opacity property and set it to zero to make form invisible.
If you want to make a control Transparent, as you have tried in your example, See this article
How to: Give Your Control a Transparent Background
It say the code you have written, must be in constructor of the control. Hence, I guess, you will need to create a custom control derived from your pnlSelectArea 's type most probaably a button. In in that custom control's constructor you can write code to set its style and color.
Here is what worked for me with because the other solutions did not work.
This is with transparent UserControl added to ListView/TreeView Control Collection
I know it says ButtonRenderer but it should work for any controls.
In the UserControl:
protected override void OnPaint(PaintEventArgs e)
{
ButtonRenderer.DrawParentBackground(e.Graphics, this.ClientRectangle, this);
}
in the Parent control:
protected override void WndProc(ref Message m)
{
if(m.Msg == 0xF)
foreach(Control c in this.Controls) { c.Invalidate(); c.Update(); }
base.WndProc(ref m);
}
great!!
I finally managed to draw transparent shapes.
I've added a virtual method
Draw(g);
right before
bckColor.Dispose();
g.Dispose();
base.OnPaint(e);
and at the end the declaration of the virtual method
protected virtual void Draw(Graphics g){ }
Now I can continue creating my own Transparent shapes, graphics etc ...
There is one simple workaround for this. You can create an image with a transparent background (PNG) and add it for the Image property of the icon. This works fine as information does not have much flexibility in styling. Sometime this might not be suitable for everyone. Remember this is only a workaround.
PS:
Add where ever the text on the image and keep blank for the text property.
Related
I am working on a project wherein I need to add a Control with the shape of a Circle with some text in the middle.
My problem is the circle is too small, when I resize it, it overlaps other controls. I want to draw the circle same width as the square.
Otherwise. how can I make the Control's background transparent?
I am using the code below:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (Bitmap bitmap = new Bitmap(this.Width, this.Height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.Clear(this.BackColor);
using (SolidBrush brush = new SolidBrush(this._FillColor))
{
graphics.FillEllipse(brush, 0x18 - 6, 0x18 - 6, (this.Width - 0x30) + 12, (this.Height - 0x30) + 12);
}
Brush FontColor = new SolidBrush(this.ForeColor);
SizeF MS = graphics.MeasureString(Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value)), Font);
graphics.DrawString(Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value)), Font, FontColor, Convert.ToInt32((Width / 2 - MS.Width / 2) + 2), Convert.ToInt32((Height / 2 - MS.Height / 2) + 3));
bitmap.MakeTransparent(this.BackColor);
e.Graphics.DrawImage(bitmap, 0, 0);
graphics.Dispose();
bitmap.Dispose();
}
}
}
This is a Custom Control derived from Control, which can be made translucent.
The interface is a colored circle which can contain a couple of numbers.
The Control exposes these custom properties:
Opacity: The level of opacity of the control BackGround [0, 255]
InnerPadding: The distance between the inner rectangle, which defines the circle bounds and the control bounds.
FontPadding: The distance between the Text and the Inner rectangle.
Transparency is obtained overriding CreateParams, then setting ExStyle |= WS_EX_TRANSPARENT;
The Control.SetStyle() method is used to modify the control behavior, adding these ControlStyles:
▶ ControlStyles.Opaque: prevents the painting of a Control's BackGround, so it's not managed by the System. Combined with CreateParams to set the Control's Extended Style to WS_EX_TRANSPARENT, the Control becomes completely transparent.
▶ ControlStyles.SupportsTransparentBackColor the control accepts Alpha values for it's BackGround color. Without also setting ControlStyles.UserPaint it won't be used to simulate transparency. We're doing that ourselves with other means.
To see it at work, create a new Class file, substitute all the code inside with this code preserving the NameSpace and build the Project/Solution.
The new Custom Control will appear in the ToolBox. Drop it on a Form. Modify its custom properties as needed.
A visual representation of the control:
Note and disclaimer:
This is a prototype Control, the custom Designer is missing (cannot post that here, too much code, also connected to a framework).
As presented here, it can be used to completely overlap other Controls in a Form or other containers. Partial overlapping is not handled in this simplified implementation.
The Font is hard-coded to Segoe UI, since this Font has a base-line that simplifies the position of the text in the middle of the circular area.
Other Fonts have a different base-line, which requires more complex handling.
See: TextBox with dotted lines for typing for the base math.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class RoundCenterLabel : Label, INotifyPropertyChanged, ISupportInitialize
{
private const int WS_EX_TRANSPARENT = 0x00000020;
private bool IsInitializing = false;
private Point MouseDownLocation = Point.Empty;
private readonly int fontPadding = 4;
private Font m_CustomFont = null;
private Color m_BackGroundColor;
private int m_InnerPadding = 0;
private int m_FontPadding = 25;
private int m_Opacity = 128;
public event PropertyChangedEventHandler PropertyChanged;
public RoundCenterLabel() => InitializeComponent();
private void InitializeComponent()
{
SetStyle(ControlStyles.Opaque |
ControlStyles.SupportsTransparentBackColor |
ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
m_CustomFont = new Font("Segoe UI", 50, FontStyle.Regular, GraphicsUnit.Pixel);
BackColor = Color.LimeGreen;
ForeColor = Color.White;
}
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
cp.ExStyle |= WS_EX_TRANSPARENT;
return cp;
}
}
public new Font Font
{
get => m_CustomFont;
set {
m_CustomFont = value;
if (IsInitializing) return;
FontAdapter(value, DeviceDpi);
NotifyPropertyChanged();
}
}
public override string Text {
get => base.Text;
set { base.Text = value;
NotifyPropertyChanged();
}
}
public int InnerPadding {
get => m_InnerPadding;
set {
if (IsInitializing) return;
m_InnerPadding = ValidateRange(value, 0, ClientRectangle.Height - 10);
NotifyPropertyChanged(); }
}
public int FontPadding {
get => m_FontPadding;
set {
if (IsInitializing) return;
m_FontPadding = ValidateRange(value, 0, ClientRectangle.Height - 10);
NotifyPropertyChanged();
}
}
public int Opacity {
get => m_Opacity;
set { m_Opacity = ValidateRange(value, 0, 255);
UpdateBackColor(m_BackGroundColor);
NotifyPropertyChanged();
}
}
public override Color BackColor {
get => m_BackGroundColor;
set { UpdateBackColor(value);
NotifyPropertyChanged();
}
}
protected override void OnLayout(LayoutEventArgs e)
{
base.OnLayout(e);
base.AutoSize = false;
}
private void NotifyPropertyChanged([CallerMemberName] string PropertyName = null)
{
InvalidateParent();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
MouseDownLocation = e.Location;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left) {
var loc = new Point(Left + (e.X - MouseDownLocation.X), Top + (e.Y - MouseDownLocation.Y));
InvalidateParent();
BeginInvoke(new Action(() => Location = loc));
}
}
private void InvalidateParent()
{
Parent?.Invalidate(Bounds, true);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
using (var format = new StringFormat(StringFormatFlags.LineLimit | StringFormatFlags.NoWrap, CultureInfo.CurrentUICulture.LCID))
{
format.LineAlignment = StringAlignment.Center;
format.Alignment = StringAlignment.Center;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
using (var circleBrush = new SolidBrush(m_BackGroundColor))
using (var foreBrush = new SolidBrush(ForeColor))
{
FontAdapter(m_CustomFont, e.Graphics.DpiY);
RectangleF rect = InnerRectangle();
e.Graphics.FillEllipse(circleBrush, rect);
e.Graphics.DrawString(Text, m_CustomFont, foreBrush, rect, format);
};
};
}
public void BeginInit() => IsInitializing = true;
public void EndInit()
{
IsInitializing = false;
Font = new Font("Segoe UI", 50, FontStyle.Regular, GraphicsUnit.Pixel);
FontPadding = m_FontPadding;
InnerPadding = m_InnerPadding;
}
private RectangleF InnerRectangle()
{
(float Min, _) = GetMinMax(ClientRectangle.Height, ClientRectangle.Width);
var size = new SizeF(Min - (m_InnerPadding / 2), Min - (m_InnerPadding / 2));
var position = new PointF((ClientRectangle.Width - size.Width) / 2,
(ClientRectangle.Height - size.Height) / 2);
return new RectangleF(position, size);
}
private void FontAdapter(Font font, float dpi)
{
RectangleF rect = InnerRectangle();
float fontSize = ValidateRange(
(int)(rect.Height - m_FontPadding), 6,
(int)(rect.Height - m_FontPadding)) / (dpi / 72.0F) - fontPadding;
m_CustomFont.Dispose();
m_CustomFont = new Font(font.FontFamily, fontSize, font.Style, GraphicsUnit.Pixel);
}
private void UpdateBackColor(Color color)
{
m_BackGroundColor = Color.FromArgb(m_Opacity, Color.FromArgb(color.R, color.G, color.B));
base.BackColor = m_BackGroundColor;
}
private int ValidateRange(int Value, int Min, int Max)
=> Math.Max(Math.Min(Value, Max), Min); // (Value < Min) ? Min : ((Value > Max) ? Max : Value);
private (float, float) GetMinMax(float Value1, float Value2)
=> (Math.Min(Value1, Value2), Math.Max(Value1, Value2));
}
I want to add text labels onder the tick marks of a trackbar control. Initially everything appears fine but when a drag the thumb of the trackbar my text labels disappear. What is happening here ?
Here is my code :
public class DateTimeTrackBar : TrackBar
{
public DateTimeTrackBar()
{
:
SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
}
:
protected override void OnPaint(PaintEventArgs e)
{
base.SetStyle(ControlStyles.UserPaint, false);
base.Refresh();
if (ShowLabels)
DrawLabels(e);
base.SetStyle(ControlStyles.UserPaint, true);
//base.OnPaint(e);
}
protected virtual void DrawLabels(PaintEventArgs e)
{
int nNumTicks = GetNumTicks(this);
if (nNumTicks > 0)
{
PointF[] TickLocs = GetTickLocations(nNumTicks);
string[] TickLabels = GetTickLabels(nNumTicks);
using (Font ArialFnt = new Font("Arial", 6, FontStyle.Regular))
{
using (Brush GrayBrush = new SolidBrush(Color.Gray))
{
float fTickLabelLocOffset;
for (int i = 0; i < nNumTicks; i++)
{
if (!ShowMinMaxLabels && ((i == 0) || (i == (nNumTicks - 1))))
continue;
if (i == 0)
fTickLabelLocOffset = 0.0f;
else
{
SizeF Size = e.Graphics.MeasureString(TickLabels[i], ArialFnt);
if (i == (nNumTicks - 1))
fTickLabelLocOffset = Size.Width;
else
fTickLabelLocOffset = (Size.Width / 2.0f);
}
PointF TickLabelLoc = new PointF(TickLocs[i].X - fTickLabelLocOffset, TickLocs[i].Y + 8);
e.Graphics.DrawString(TickLabels[i], ArialFnt, GrayBrush, TickLabelLoc);
}
}
}
}
}
:
}
The standard convention here would be to not set the UserPaint style, to call base.OnPaint() in your override method, and then render your custom graphics after the call to base.OnPaint() returns, similar to this:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (ShowLabels)
DrawLabels(e);
}
Based on the documentation for Control.SetStyle() at https://msdn.microsoft.com/en-us/library/system.windows.forms.control.setstyle(v=vs.110).aspx as well as the ControlStyles enumeration at https://msdn.microsoft.com/en-us/library/system.windows.forms.controlstyles(v=vs.110).aspx, consider the following:
You should not be calling SetStyle() repeatedly to change the flags (as happens in your OnPaint() method). These should be set once, when the control is initialized.
When you set the UserPaint flag to true, you are telling Windows the following. This is likely not your intent:
If true, the control paints itself rather than the operating system
doing so. If false, the Paint event is not raised. This style only
applies to classes derived from Control.
I have 3 pictures, each one has a colored circle in it. The 3 pictures are red, green and yellow.
I'm putting it in a PictureBox in a windows form. I want to switch these images from green to yelow to red or otherwise.
Is there anything I can make them fade to each other instead of switching them the normal way?
I know this could be done using flash/j-query easily, but I was wondering how far I can achieve .
Something similar in windows forms using normal windows forms functionality.
Note: I'm using .net framework 4 and windows forms.
See Transition of images in Windows Forms Picture box. There is a solution that transitions the images using a timer on this page.
Code from site:
public class BlendPanel : Panel
{
private Image mImg1;
private Image mImg2;
private float mBlend;
public BlendPanel()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint |
ControlStyles.OptimizedDoubleBuffer, true);
}
public Image Image1
{
get { return mImg1; }
set { mImg1 = value; Invalidate(); }
}
public Image Image2
{
get { return mImg2; }
set { mImg2 = value; Invalidate(); }
}
public float Blend
{
get { return mBlend; }
set { mBlend = value; Invalidate(); }
}
protected override void OnPaint(PaintEventArgs e)
{
if (mImg1 == null || mImg2 == null)
{
e.Graphics.FillRectangle(new SolidBrush(this.BackColor),
new Rectangle(0, 0, this.Width, this.Height));
}
else
{
Rectangle rc = new Rectangle(0, 0, this.Width, this.Height);
ColorMatrix cm = new ColorMatrix();
ImageAttributes ia = new ImageAttributes();
cm.Matrix33 = mBlend;
ia.SetColorMatrix(cm);
e.Graphics.DrawImage(mImg2, rc, 0, 0, mImg2.Width,
mImg2.Height, GraphicsUnit.Pixel, ia);
cm.Matrix33 = 1F - mBlend;
ia.SetColorMatrix(cm);
e.Graphics.DrawImage(mImg1, rc, 0, 0, mImg1.Width,
mImg1.Height, GraphicsUnit.Pixel, ia);
}
base.OnPaint(e);
}
}
Build your project. You can now drop a BlendPanel from the top of the toolbox onto your form. Here's a sample program that uses it:
namespace WindowsApplication1
{
public partial class Form1 : Form
{
private float mBlend;
private int mDir = 1;
public Form1()
{
InitializeComponent();
timer1.Interval = 30;
timer1.Tick += BlendTick;
blendPanel1.Image1 = Bitmap.FromFile(#"c:\temp\test1.bmp");
blendPanel1.Image2 = Bitmap.FromFile(#"c:\temp\test2.bmp");
timer1.Enabled = true;
}
private void BlendTick(object sender, EventArgs e)
{
mBlend += mDir * 0.02F;
if (mBlend < 0) { mBlend = 0; mDir = 1; }
if (mBlend > 1) { mBlend = 1; mDir = -1; }
blendPanel1.Blend = mBlend;
}
}
}
You'll need to modify the Bitmap.FromFile() calls. Build and run. You should see the displayed image smoothly morph from your first image to your second image without any flickering. Lots of ways to tweak the code, have fun.
I don't know if it is a good idea, but i would go for 2 image boxes, one to fade in, other to fade out, and change alpha when time passes.
Like what #Igoris suggested, You need to use two controls overlap each other, and you should define a timer so when you want to fade in or out, start the timer and in its tick decrease the Transparent of the first control and increase it on the second one... , the problem is that the ordinary controls does not support transparent by default. so you have to inherit it and apply transparent here is a custom TransparentPictureBox that inherited from PictureBox:
public class TransparentPictureBox : System.Windows.Forms.PictureBox
{
/// <summary>
/// Initialize new instance of this class.
/// </summary>
public TransparentPictureBox()
: base()
{
DoubleBuffered = true;
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.BackColor = Color.Transparent;
}
}
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):
Was thinking it should be pretty easy to create a ProgressBar that drew some text upon itself. However, I am not quite sure what is happening here...
I added the following two overrides:
protected override void OnPaintBackground(PaintEventArgs pevent)
{
base.OnPaintBackground(pevent);
var flags = TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.SingleLine | TextFormatFlags.WordEllipsis;
TextRenderer.DrawText(pevent.Graphics, "Hello", Font, Bounds, Color.Black, flags);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var flags = TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.SingleLine | TextFormatFlags.WordEllipsis;
TextRenderer.DrawText(e.Graphics, "Hello", Font, Bounds, Color.Black, flags);
}
However, I get no text, and the methods doesn't even seem to be called. What is going on here?
Update: Thanks to the two answers so far, I have gotten it to actually call the OnPaint by using SetStyle(ControlStyles.UserPaint, true), and I have gotten it to draw the text in the right place by sending in new Rectangle(0, 0, Width, Height) instead of Bounds.
I do get text now, but the ProgressBar is gone... and the point was kind of to have the text on top of the ProgressBar. Any idea how I can solve this?
You could override WndProc and catch the WmPaint message.
The example below paints the Text property of the progressbar in its center.
public class StatusProgressBar : ProgressBar
{
const int WmPaint = 15;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case WmPaint:
using (var graphics = Graphics.FromHwnd(Handle))
{
var textSize = graphics.MeasureString(Text, Font);
using(var textBrush = new SolidBrush(ForeColor))
graphics.DrawString(Text, Font, textBrush, (Width / 2) - (textSize.Width / 2), (Height / 2) - (textSize.Height / 2));
}
break;
}
}
}
I needed to do this myself and I thought that I would post a simplified example of my solution since I could not find any examples. It is actually pretty simple if you use the ProgressBarRenderer class:
class MyProgressBar : ProgressBar
{
public MyProgressBar()
{
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rect = this.ClientRectangle;
Graphics g = e.Graphics;
ProgressBarRenderer.DrawHorizontalBar( g, rect );
rect.Inflate(-3, -3);
if ( this.Value > 0 )
{
Rectangle clip = new Rectangle( rect.X, rect.Y, ( int )Math.Round( ( ( float )this.Value / this.Maximum ) * rect.Width ), rect.Height );
ProgressBarRenderer.DrawHorizontalChunks(g, clip);
}
// assumes this.Maximum == 100
string text = this.Value.ToString( ) + '%';
using ( Font f = new Font( FontFamily.GenericMonospace, 10 ) )
{
SizeF strLen = g.MeasureString( text, f );
Point location = new Point( ( int )( ( rect.Width / 2 ) - ( strLen.Width / 2 ) ), ( int )( ( rect.Height / 2 ) - ( strLen.Height / 2 ) ) );
g.DrawString( text, f, Brushes.Black, location );
}
}
}
Your problem is that you're passing in Bounds as your Rectangle parameter. Bounds contains the Height and Width of your control, which is what you want, but it also contains the Top and Left properties of your control, relative to the parent form, so your "Hello" is being offset on the control by however much your control is offset on its parent form.
Replace Bounds with new Rectangle(0, 0, this.Width, this.Height) and you should see your "Hello".
It seems that if you call 'SetStyle(ControlStyles.UserPaint, true)' the standard OnPaint method implemented for ProgressBar could not be invoked (using base.OnPaint(e) does not work at all). The strangest thing is that even if you actually create a UserControl, and try to draw draw some text upon the progress bar... it doesn't seem to work too... Of course you may place a Label on top of it... but I suppose it is not actually what you wanted to achieve.
Ok, it seems that I have managed to solve this problem. It is although a little complicated. First you need to create a transparent Label control. Code below:
public class TransparentLabel : System.Windows.Forms.Label
{
public TransparentLabel()
{
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
}
Second thing is to create UserControl, place a ProgressBar on it (Dock=Fill) - this will be the control that we will use instead of standard ProgressBar. Code:
public partial class UserControl2 : UserControl
{
public UserControl2()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
this.progressBar1.SendToBack();
this.transparentLabel1.BringToFront();
this.transparentLabel1.Text = this.progressBar1.Value.ToString();
this.transparentLabel1.Invalidate();
}
public int Value
{
get { return this.progressBar1.Value; }
set
{
this.progressBar1.Value = value;
}
}
}
The strange thing with ProgressBar is that it 'overdraws' the controls that are being placed upon it, so it is needed to send progressbar to back, and bring the label control to front. I haven't found more elegant solution at the moment.
This works, the label is being displayed on the progressbar, the background of the label control is transparent, so I think it looks like you wanted it to look :)
I may share my sample code if you wish...
Oh, btw. this strange behaviour of ProgressBar control that I have mentioned, is responsible for that it is not possible to use Graphics object to draw anything on a control that derives from ProgressBar. The text (or whatever you draw using Graphics object) is actually being drawn but... behind the ProgressBar control (if you take a closer look, you may see this user drawn things flickering when the Value of the ProgressBar changes and it need to repaint itself).
Here's another solution along with other people's suggestions. I subclassed the progressbar control to make this work. I mixed and matched codes from various places for this. The paint event could be cleaner, but that's for you to do ;)
public class LabeledProgressBar: ProgressBar
{
private string labelText;
public string LabelText
{
get { return labelText; }
set { labelText = value; }
}
public LabeledProgressBar() : base()
{
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.Paint += OnLabelPaint;
}
public void OnLabelPaint(object sender, PaintEventArgs e)
{
using(Graphics gr = this.CreateGraphics())
{
string str = LabelText + string.Format(": {0}%", this.Value);
LinearGradientBrush brBG = new LinearGradientBrush(e.ClipRectangle,
Color.GreenYellow, Color.Green, LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(brBG, e.ClipRectangle.X, e.ClipRectangle.Y,
e.ClipRectangle.Width * this.Value / this.Maximum, e.ClipRectangle.Height);
e.Graphics.DrawString(str, SystemFonts.DefaultFont,Brushes.Black,
new PointF(this.Width / 2 - (gr.MeasureString(str, SystemFonts.DefaultFont).Width / 2.0F),
this.Height / 2 - (gr.MeasureString(str, SystemFonts.DefaultFont).Height / 2.0F)));
}
}
}