Drawing a transparent button - c#

I'm trying to create a transparent button in C# (.NET 3.5 SP1) to use in my WinForms application. I've tried everything to get the button to be transparent (it should show the gradient background underneath the button) but it's just not working.
Here is the code I'm using:
public class ImageButton : ButtonBase, IButtonControl
{
public ImageButton()
{
this.SetStyle(
ControlStyles.SupportsTransparentBackColor |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
this.BackColor = Color.Transparent;
}
protected override void OnPaint(PaintEventArgs pevent)
{
Graphics g = pevent.Graphics;
g.FillRectangle(Brushes.Transparent, this.ClientRectangle);
g.DrawRectangle(Pens.Black, this.ClientRectangle);
}
// rest of class here...
}
The problem is that the button seems to be grabbing random UI memory from somewhere and filling itself with some buffer from Visual Studio's UI (when in design mode). At runtime it's grabbing some zero'd buffer and is completely black.
My ultimate goal is to paint an image on an invisible button instead of the rectangle. The concept should stay the same however. When the user hovers over the button then a button-type shape is drawn.
Any ideas?
EDIT: Thanks everybody, the following worked for me:
public class ImageButton : Control, IButtonControl
{
public ImageButton()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
SetStyle(ControlStyles.ResizeRedraw, true);
this.BackColor = Color.Transparent;
}
protected override void OnPaint(PaintEventArgs pevent)
{
Graphics g = pevent.Graphics;
g.DrawRectangle(Pens.Black, this.ClientRectangle);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
// don't call the base class
//base.OnPaintBackground(pevent);
}
protected override CreateParams CreateParams
{
get
{
const int WS_EX_TRANSPARENT = 0x20;
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_TRANSPARENT;
return cp;
}
}
// rest of class here...
}

WinForms (and underlying User32) does not support transparency at all. WinForms however can simulate transparency by using control style you provide - SupportsTransparentBackColor, but in this case all that "transparent" control does, it to allow drawing parent its background.
ButtonBase uses some windows styles that prevent working this mechanism. I see two solutions: one is to derive your control from Control (instead of ButtonBase), and second is to use Parent's DrawToBitmap to get background under your button, and then draw this image in OnPaint.

In winforms there are some tricks to allow a control having its background correctly painted when using transparency. You can add this code to the OnPaint or OnPaintBackground to get the controls you have in the background being painted:
if (this.Parent != null)
{
GraphicsContainer cstate = pevent.Graphics.BeginContainer();
pevent.Graphics.TranslateTransform(-this.Left, -this.Top);
Rectangle clip = pevent.ClipRectangle;
clip.Offset(this.Left, this.Top);
PaintEventArgs pe = new PaintEventArgs(pevent.Graphics, clip);
//paint the container's bg
InvokePaintBackground(this.Parent, pe);
//paints the container fg
InvokePaint(this.Parent, pe);
//restores graphics to its original state
pevent.Graphics.EndContainer(cstate);
}
else
base.OnPaintBackground(pevent); // or base.OnPaint(pevent);...

I'm not sure ButtonBase supports transparency... have you checked that out?
I've written a number of transparent controls, but I have always inherited from Control or UserControl.
When you want to block out a control painting it's background - you should override OnPaintBackground instead of OnPaint and not call the base class.
Filling a rectangle with Brushes.Transparent is funny though - you're painting with an invisible color over what's aready there. Or to put it another way: it does nothing!

I know this question is old, but if someone doesn't want to create a control to do this I came up with this code from a different article and changed it an extension method.
public static void ToTransparent(this System.Windows.Forms.Button Button,
System.Drawing.Color TransparentColor)
{
Bitmap bmp = ((Bitmap)Button.Image);
bmp.MakeTransparent(TransparentColor);
int x = (Button.Width - bmp.Width) / 2;
int y = (Button.Height - bmp.Height) / 2;
Graphics gr = Button.CreateGraphics();
gr.DrawImage(bmp, x, y);
}
And the call like:
buttonUpdate.ToTransparent(Color.Magenta);

Related

Panel onPaint render artefacts

I created Panel class "GpanelBorder" which draw border in custom panel with code:
namespace GetterControlsLibary
{
public class GpanelBorder : Panel
{
private Color colorBorder;
public GpanelBorder()
{
SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawRectangle(
new Pen(
new SolidBrush(colorBorder), 8),
e.ClipRectangle);
}
public Color BorderColor
{
get
{
return colorBorder;
}
set
{
colorBorder = value;
}
}
}
}
Works fine but when i in design mode, mouse click inside panel and move mouse or drag other control over this panel, artifacts are created (picture below)
How to fix it?
The .ClipRectangle parameter does NOT necessarily represent the entire area of your control to be painted within. It may represent a SMALLER portion of your control indicating just that portion needs to be repainted. You can use the "clip rectangle" to redraw only a portion of your control in situations where it would be too costly to compute and repaint the entire control. If that situation doesn't apply to you, then use the ClientRectangle to get the bounds of your entire control and use that to draw your border. Also, you are LEAKING a PEN and a SOLIDBRUSH. You need to .Dispose() of those resources when you're done with them. This is best done with a using block:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (SolidBrush sb = new SolidBrush(colorBorder), 8))
{
using (Pen p = new Pen(sb))
{
e.Graphics.DrawRectangle(p, this.ClientRectangle);
}
}
}
You may need to create a new Rectangle based on ClientRectangle and adjust that to your liking before drawing.
Thanks, Works great!
but corectly code is
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (SolidBrush sb = new SolidBrush(colorBorder))
{
using (Pen p = new Pen(colorBorder, 2))
{
e.Graphics.DrawRectangle(p, this.ClientRectangle);
}
}
}

why my custom control textbox's onpaint overided method never gets called?

I have created a customized textbox which has border in red color. I then launch my application but this OnPaint never gets called.
My code is this:
public partial class UserControl1 : TextBox
{
public string disable, disableFlag;
public string Disable
{
get
{
return disable;
}
set
{
disable = value;
disableFlag = disable;
//OnPaint(PaintEventArgs.Empty);
}
}
public UserControl1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
this.Text = "testing 1";
if (disable == "true")
{
Pen redPen = new Pen(Color.Red);
Graphics graphics = this.CreateGraphics();
graphics.DrawRectangle(redPen, this.Location.X,
this.Location.Y, this.Width, this.Height);
// base.DrawFrame(e.Graphics);
}
}
}
Please let me know what is the problem (Here is the snapshot of the winform http://prntscr.com/ceq7x5 )?
You shouldn't create a new Graphics object. Use e.Graphics.DrawRectangle to be able to draw on the control using the already existing Graphics object like this:
Pen redPen = new Pen(Color.Red);
e.Graphics.DrawRectangle(redPen, this.Location.X,
this.Location.Y, this.Width, this.Height);
Also, to repeat my comment here about the Disabled flag. There's no point adding a custom Disable flag. Use the Enabled property that is already provided by the Windows Forms TextBox control.
Edit: Please notice that the above code doesn't work in TextBox's case, because it handles drawing differently. TextBox is basically just a wrapper around the native Win32 TextBox so you need to listen to quite a few messages that tell it to repaint itself. You also need to get the handle to the device context and transform it to a managed Graphics object to be able to draw.
Take a look at this article for code and explanations on how to draw on top of TextBox. Especially part 2. Drawing Onto the TextBox on the bottom of the page.

How to create transparent controls in Windows Forms when controls are layered

I am trying to implement a "Fillable Form" in which editable text fields appear over top of an image of a pre-preprinted form for a dot matrix printer. (using c# and Windows Forms and targeting .Net 2.0) My first idea was to use the image as the Windows Form background, but it looked horrible when scrolling and also did not scroll properly with the content.
My next attempt was to create a fixed-size window with a panel that overflows the bounds of the window (for scrolling purposes.) I added a PictureBox to the panel, and added my textboxes on top of it. This works fine, except that TextBoxes do not support transparency, so I tried several methods to make the TextBoxes transparent. One approach was to use an odd background color and a transparency key. Another, described in the following links, was to create a derived class that allows transparency:
Transparency for windows forms textbox
TextBox with a Transparent Background
Neither method works, because as I have come to find out, "transparency" in Windows Forms just means that the background of the window is painted onto the control background. Since the PictureBox is positioned between the Window background and the TextBox, it gives the appearance that the TextBox is not transparent, but simply has a background color equal to the background color of the Window. With the transparency key approach, the entire application becomes transparent so that you can see Visual Studio in the background, which is not what I want. So now I am trying to implement a class that derives from TextBox and overrides either OnPaint or OnPaintBackground to paint the appropriate part of the PictureBox image onto the control background to give the illusion of transparency as described in the following link:
How to create a transparent control which works when on top of other controls?
First of all, I can't get it working (I have tried various things, and either get a completely black control, or just a standard label background), and second of all, I get intermittent ArgumentExceptions from the DrawToBitmap method that have the cryptic message "Additional information: targetBounds." Based on the following link from MSDN, I believe that this is because the bitmap is too large - in either event it seems inefficient to capture the whole form image here because I really just want a tiny piece of it.
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.drawtobitmap(v=vs.100).aspx
Here is my latest attempt. Can somebody please help me with the OnPaintBackground implementation or suggest a different approach? Thanks in advance!
public partial class TransparentTextbox : TextBox
{
public TransparentTextbox()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e); // not sure whether I need this
if (Parent != null)
{
foreach (Control c in Parent.Controls)
{
if (c.GetType() == typeof(PictureBox))
{
PictureBox formImg = (PictureBox)c;
Bitmap bitmap = new Bitmap(formImg.Width, formImg.Height);
formImg.DrawToBitmap(bitmap, formImg.Bounds);
e.Graphics.DrawImage(bitmap, -Left, -Top);
break;
}
}
Debug.WriteLine(Name + " didn't find the PictureBox.");
}
}
}
NOTE: This has been tagged as a duplicate, but I referenced the "duplicate question" in my original post, and explained why it was not working. That solution only works if the TextBox sits directly over the Window - if another control (such as my Panel and PictureBox) sit between the window and the TextBox, then .Net draws the Window background onto the TextBox background, effectively making its background look gray, not transparent.
I think I have finally gotten to the bottom of this. I added a Bitmap variable to my class, and when I instantiate the textboxes, I am setting it to contain just the portion of the form image that sits behind the control. Then I overload OnPaintBackground to display the Bitmap, and I overload OnPaint to manually draw the text string. Here is the updated version of my TransparentTextbox class:
public partial class TransparentTextbox : TextBox
{
public Bitmap BgBitmap { get; set; }
public TransparentTextbox()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString(this.Text, this.Font, Brushes.Black, new PointF(0.0F, 0.0F));
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.DrawImage(BgBitmap, 0, 0);
}
}
... and here is the relevant part of how I instantiate:
Bitmap bgImage = (Bitmap)Bitmap.FromStream(Document.FormImage);
PictureBox pb = new PictureBox();
pb.Image = bgImage;
pb.Size = pb.Image.Size;
pb.Top = 0;
pb.Left = 0;
panel1.Controls.Add(pb);
foreach (FormField field in Document.FormFields)
{
TransparentTextbox tb = new TransparentTextbox();
tb.Width = (int)Math.Ceiling(field.MaxLineWidth * 96.0);
tb.Height = 22;
tb.Font = new Font("Courier", 12);
tb.BorderStyle = BorderStyle.None;
tb.Text = "Super Neat!";
tb.TextChanged += tb_TextChanged;
tb.Left = (int)Math.Ceiling(field.XValue * 96.0);
tb.Top = (int)Math.Ceiling(field.YValue * 96.0);
tb.Visible = true;
Bitmap b = new Bitmap(tb.Width, tb.Height);
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(bgImage, new Rectangle(0, 0, b.Width, b.Height), tb.Bounds, GraphicsUnit.Pixel);
tb.BgBitmap = b;
}
panel1.Controls.Add(tb);
}
I still need to work on how the text looks when I highlight it, and other things like that, but I feel like I am on the right track. +1 to Reza Aghaei and Mangist for commenting with other viable solutions!

semi-transparent image on transparent form

i need to have semi-transparent image (by using alpha blending) drawn on fully transparent form - it means that image will be drawn over form transparent content.
Currently image is always drawn over window background color even if window itself is transparent.
This is current state, thanks for any help.
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
MakeTransparent();
}
private void MakeTransparent()
{
NativeMethods.SetLayeredWindowAttributes(Handle, COLORREF.FromColor(BackColor), 255, Constants.ULW_ALPHA | Constants.ULW_COLORKEY);
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style |= (Constants.WS_EX_LAYERED | Constants.WS_EX_TOOLWINDOW);
return cp;
}
}
private void OnPaint(object sender, PaintEventArgs e)
{
using (Bitmap bitmap = new Bitmap("c:\\semi-transparent.png"))
{
e.Graphics.DrawImage(bitmap, 0, 0);
}
}
I guess since a Form doesn't support a transparent background color this may well be impossible. That way the form's background will always have a color, even when drawing an image with alpha channel on it.
Here's a similar question:
Transparent Winform with image
Well, thanks for the answer.
I actually was able to do this by using UpdateLayeredWindow function but i had to always update whole window bitmap even if i really needed to redraw just small portion of window.
Capturing screen content and draw it under image is not really a solution because i need my window to be moveable.

Set datagrid view background to transparent

I tried to set the background color of a data grid view to be "transparent" from properties but it it said "not a valid property".
How can I do it?
I did this solution to a specific problem (when the grid was contained in a form with background image) with simples modifications you can adapt it to create a generic transparent Grid, just ask if the parent have background image, else just use the parent backcolor to paint your grid, and that is all.
You must inherit from DataGridView and override the PaintBackground method like this:
protected override void PaintBackground(Graphics graphics, Rectangle clipBounds, Rectangle gridBounds)
{
base.PaintBackground(graphics, clipBounds, gridBounds);
Rectangle rectSource = new Rectangle(this.Location.X, this.Location.Y, this.Width, this.Height);
Rectangle rectDest = new Rectangle(0, 0, rectSource.Width, rectSource.Height);
Bitmap b = new Bitmap(Parent.ClientRectangle.Width, Parent.ClientRectangle.Height);
Graphics.FromImage(b).DrawImage(this.Parent.BackgroundImage, Parent.ClientRectangle);
graphics.DrawImage(b, rectDest, rectSource, GraphicsUnit.Pixel);
SetCellsTransparent();
}
public void SetCellsTransparent()
{
this.EnableHeadersVisualStyles = false;
this.ColumnHeadersDefaultCellStyle.BackColor = Color.Transparent;
this.RowHeadersDefaultCellStyle.BackColor = Color.Transparent;
foreach (DataGridViewColumn col in this.Columns)
{
col.DefaultCellStyle.BackColor = Color.Transparent;
col.DefaultCellStyle.SelectionBackColor = Color.Transparent;
}
}
i did this with Deumber's solution and it works, but causes some troubles that i avoided by adding small improvements:
A. scrolling the DGV messes up the background. solution: put this somewhere:
public partial class main : Form
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
}
the background will still scroll, but be corrected immediately after each scroll step. it's noticeable, but was acceptable for me. does anyone know a better solution to support scrolling with this?
B. the designer has troubles using it. solution:
protected override void PaintBackground(Graphics graphics, Rectangle clipBounds, Rectangle gridBounds)
{
base.PaintBackground(graphics, clipBounds, gridBounds);
if (main.ActiveForm != null && this.Parent.BackgroundImage != null)
{
Rectangle rectSource = new Rectangle(this.Location.X, this.Location.Y, this.Width, this.Height);
Rectangle rectDest = new Rectangle(-3, 3, rectSource.Width, rectSource.Height);
Bitmap b = new Bitmap(Parent.ClientRectangle.Width, Parent.ClientRectangle.Height);
Graphics.FromImage(b).DrawImage(this.Parent.BackgroundImage, Parent.ClientRectangle);
graphics.DrawImage(b, rectDest, rectSource, GraphicsUnit.Pixel);
SetCellsTransparent();
}
}
now the designer treats it just like a DGV. it will fail if you ever want to draw the DGV while you have no ActiveForm, but that's not the case usually. it's also possible to just keep the if-line while you might still want to use the designer, and delete it for the release.
Having a transparent color in the DataGridView BackGroundColor property is not possible.
So I decided to sync this property with the parent's BackColor. The good old databinding feature of WinForms is very good at this :
myDataGridView.DataBindings.Add(nameof(DataGrid.BackgroundColor),
this,
nameof(Control.BackColor));
Just after InitializeComponents();
I know this is pretty old, but this works very well.
You need to set all the rows and columns to transparent. Easier way is:
for (int y = 0; y < gridName.Rows[x].Cells.Count; y++)
{
yourGridName.Rows[x].Cells[y].Style.BackColor =
System.Drawing.Color.Transparent;
}
Set datagridview's backcolor same with the form's color. To do this, select datagridview: go to Properties -> RowTemplate -> DefaultCellStyle -> BackColor and choose the color of your form.

Categories