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):
Related
How would I go about drawing a border with a specified width and color around a listbox?
Can this be done without overriding the OnPaint method?
Following Neutone's suggestion, here is a handy function to add and remove a Panel-based border around any control, even if it is nested..
Simply pass in the Color and size you want, and if you want a BorderStyle. To remove it again pass in Color.Transparent!
void setBorder(Control ctl, Color col, int width, BorderStyle style)
{
if (col == Color.Transparent)
{
Panel pan = ctl.Parent as Panel;
if (pan == null) { throw new Exception("control not in border panel!");}
ctl.Location = new Point(pan.Left + width, pan.Top + width);
ctl.Parent = pan.Parent;
pan.Dispose();
}
else
{
Panel pan = new Panel();
pan.BorderStyle = style;
pan.Size = new Size(ctl.Width + width * 2, ctl.Height + width * 2);
pan.Location = new Point(ctl.Left - width, ctl.Top - width);
pan.BackColor = col;
pan.Parent = ctl.Parent;
ctl.Parent = pan;
ctl.Location = new Point(width, width);
}
}
You can place a list box within a panel and have the panel serve as a border. The panel backcolor can be used to create a colored border. This doesn't require much code. Having a colored border around a form component can be an effective way of conveying status.
The problem with the ListBox control is that it does not raise the OnPaint method so you can not use it to draw a border around the control.
There are two methods to paint a custom border around the ListBox:
Use SetStyle(ControlStyles.UserPaint, True) in the constructor, then you can use the OnPaint method to draw the border.
Override WndProc method that handles operating system messages identified in the Message structure.
I used the last method to paint a custom border around the control, this will eliminate the need to use a Panel to provide a custom border for the ListBox.
public partial class MyListBox : ListBox
{
public MyListBox()
{
// SetStyle(ControlStyles.UserPaint, True)
BorderStyle = BorderStyle.None;
}
protected override void WndProc(ref Message m)
{
base.WndProc(m);
var switchExpr = m.Msg;
switch (switchExpr)
{
case 0xF:
{
Graphics g = this.CreateGraphics;
g.SmoothingMode = Drawing2D.SmoothingMode.Default;
int borderWidth = 4;
Rectangle rect = ClientRectangle;
using (var p = new Pen(Color.Red, borderWidth) { Alignment = Drawing2D.PenAlignment.Center })
{
g.DrawRectangle(p, rect);
}
break;
}
default:
{
break;
}
}
}
}
I have my control MyLabel and when I change the Font Size must perform this code in the constructor. How to make this code worked?
protected override void OnFontChanged(EventArgs e)
{
if (AutoSize_)
{
this.AutoSize = true;
remember_size = this.Size;
this.AutoSize = false;
this.Size = new Size(remember_size.Width, remember_size.Height);
remember_size = this.Size;
}
...
this.Invalidate();
}
But don't work. For example this code work:
protected override void OnFontChanged(EventArgs e)
{
if (AutoSize_)
{
this.AutoSize = true;
}
...
this.Invalidate();
}
If your goal is to resize the label so the text is visible, regardless of the font size, the AutoSize property will do this for you.. However if you for some reason wish to handle this with your own code, you could try setting the AutoSize property to false (and not change it..)
The following code method can be called from any Form, UserControl, or Control. It will return the size of the specified text in the specified font.
public static Size MeasureText(Graphics graphicsDevice, String text, Font font)
{
System.Drawing.SizeF textSize = graphicsDevice.MeasureString(text, font);
int width = (int)Math.Ceiling(textSize.Width);
int heigth = (int)Math.Ceiling(textSize.Height);
Size size = new Size(width, heigth);
return size;
}
Now you will need to check that the label has not outgrown the parent container, which would cause some of the labels text to be cut off. Something like the following will accomplish this:
private void ResizeParentAccordingToLabelSize(Label resizedLabel)
{
int necessaryWidth = resizedLabel.Location.X + resizedLabel.Width;
int necessaryHeight = resizedLabel.Location.Y + resizedLabel.Height;
if (necessaryWidth > this.Width)
{
this.Width = necessaryWidth;
}
if (necessaryHeight > this.Height)
{
this.Height = necessaryHeight;
}
}
In this picture...
... you can see next to each "Line Color" label there is a colored circle.
The colored circle is, in my project, a Swatch. Here is the entire code file for Swatch:
public class Swatch : System.Windows.Forms.Panel
{
/*private int _Radius = 20;
[System.ComponentModel.Category("Layout")]
public int Radius
{
get { return _Radius; }
set { _Radius = value; }
} */
private System.Drawing.Color _BorderColor = System.Drawing.Color.Transparent;
[System.ComponentModel.Category("Appearance")]
public System.Drawing.Color BorderColor
{
get { return _BorderColor; }
set { _BorderColor = value; }
}
private System.Drawing.Color _FillColor = System.Drawing.Color.Blue;
[System.ComponentModel.Category("Appearance")]
public System.Drawing.Color FillColor
{
get { return _FillColor; }
set { _FillColor = value; }
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
System.Drawing.Rectangle RealRect = new System.Drawing.Rectangle(e.ClipRectangle.Location, e.ClipRectangle.Size);
RealRect.Inflate(-1, -1);
int Radius = Math.Min(RealRect.Size.Height, RealRect.Size.Width);
System.Drawing.Rectangle SqRect = new System.Drawing.Rectangle();
SqRect.Location = RealRect.Location;
SqRect.Size = new System.Drawing.Size(Radius, Radius);
System.Drawing.Drawing2D.CompositingQuality PrevQual = e.Graphics.CompositingQuality;
using (System.Drawing.SolidBrush Back = new System.Drawing.SolidBrush(this.FillColor))
{
using (System.Drawing.Pen Pen = new System.Drawing.Pen(new System.Drawing.SolidBrush(this.BorderColor)))
{
//e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.FillEllipse(Back, SqRect);
e.Graphics.DrawEllipse(Pen, SqRect);
}
}
e.Graphics.CompositingQuality = PrevQual;
}
public Swatch()
{
this.SetStyle(System.Windows.Forms.ControlStyles.UserPaint, true);
this.SetStyle(System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(System.Windows.Forms.ControlStyles.ResizeRedraw, true);
this.SetStyle(System.Windows.Forms.ControlStyles.SupportsTransparentBackColor, true);
this.DoubleBuffered = true;
}
}
Each row is a UserControl which consists of a TableLayoutPanel, labels, a Swatch control, and a NumericUpDown box.
There about 10 rows and they are placed in TableLayoutPanel, which sits inside a TabPage on a tab control. The tab page has AutoScroll set to true so that overflow causes the tab page to scroll.
The problem is that whenever I run the application and scroll up and down, the Swatches (the colored circles) tear and show all sorts of artifacts, as seen in the picture above. I'd like to have clean scrolling with no rendering artifacts.
I've tried using SetStyle (as suggested here Painting problem in windows form) but it has had no effect.
The UserControl (each row) has DoubleBuffered set to true, and that too has had no effect either.
I fear I am missing something rather obvious.
The problem is that you calculate the radius of the circle based on the clipping rectangle. So when the line is only partially visible a bad value is resulted.
You should calculate it based on the real rectangle, the one provided by the base class, and let it being clipped normally.
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.
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)));
}
}
}