Transparent Controls In VSTO - c#

I'm trying to add some windows forms controls to a worksheet using vsto. I'd like them to be transparent though (so that the actual content in Excel is visible).
My winforms user control constructor looks like this:
public Tag()
{
InitializeComponent();
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.Transparent;
}
I'm adding the control like this:
void Application_WorkbookOpen(Excel.Workbook Wb)
{
var nativeSheet = Wb.ActiveSheet as Excel.Worksheet;
if (nativeSheet != null)
{
var tag = new Tag();
var vstoSheet = nativeSheet.GetVstoObject();
var range = nativeSheet.Range["A1", missing];
vstoSheet.Controls.AddControl(tag, range, Guid.NewGuid().ToString());
}
}
If there's some content cell A1, it will be covered by the control (the cell will just appear as plain white).
Anybody have any ideas on this?

As mentioned over at MSDN:
When a Windows form control are hosted on Office document, the control
is not directly embedded in the document. An ActiveX control called
the Windows Forms control host is added to document surface first and
the control host acts as a host for each Windows Form control added
too the document.
You can see for yourself:
var btn = new ImageButton();
btn.Name = "link1";
btn.Text = controlText;
btn.Click += new EventHandler(btn_Click);
vstoWorksheet.Controls.AddControl(pic, nativeWorksheet.Range[address], controlText);
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...
}

Related

Removing the button's background?

Based on my previous question (Making the panel transparent?), I wished to create a button with transparent background around it:
internal class TransparentButton : Button
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20; // WS_EX_TRANSPARENT
return cp;
}
}
public TransparentButton()
{
SetStyle(ControlStyles.Opaque | ControlStyles.SupportsTransparentBackColor, true);
}
}
Now, when I add this button to my form:
I wish it to have transparent background, i.e. to not draw the gray border on top of the black background.
Can I do it with my TransparentButton class, without major changes (like drawing the text and background, and border manually)?
This is my desired goal to reach, so the button itself shall stay as is, but the rectangular background with the color of "Control" shall be transparent:
This edited TransparentButton improves on my previous answer and no longer overrides OnPaint. It requires no custom drawing. Instead, it uses Graphics.CopyFromScreen to make a screenshot of the rectangle behind the button and sets its own Button.BackgroundImage to the snipped bitmap. This way it's effectively camouflaged and appears transparent while still drawing as a Standard styled button.
Requirements:
Create a button with transparent background.
Do it without drawing the background manually in OnPaint.
Keep the Button.FlatStyle as FlatStyle.Standard.
Do not disturb the rounded edges of the standard button.
class TransparentButton : Button
{
public TransparentButton() => Paint += (sender, e) =>
{
// Detect size/location changes
if ((Location != _prevLocation) || (Size != _prevSize))
{
Refresh();
}
_prevLocation = Location;
_prevSize = Size;
};
Point _prevLocation = new Point(int.MaxValue, int.MaxValue);
Size _prevSize = new Size(int.MaxValue, int.MaxValue);
public new void Refresh()
{
if (!DesignMode)
{
bool isInitial = false;
if ((BackgroundImage == null) || !BackgroundImage.Size.Equals(Size))
{
isInitial = true;
BackgroundImage = new Bitmap(Width, Height);
}
if (MouseButtons.Equals(MouseButtons.None))
{
// Hide button, take screenshot, show button again
Visible = false;
BeginInvoke(async () =>
{
Parent?.Refresh();
if (isInitial) await Task.Delay(250);
using (var graphics = Graphics.FromImage(BackgroundImage))
{
graphics.CopyFromScreen(PointToScreen(new Point()), new Point(), Size);
}
Visible = true;
});
}
else
{
using (var graphics = Graphics.FromImage(BackgroundImage))
graphics.FillRectangle(Brushes.LightGray, graphics.ClipBounds);
}
}
else base.Refresh();
}
protected override void OnMouseUp(MouseEventArgs mevent)
{
base.OnMouseUp(mevent);
Refresh();
}
/// <summary>
/// Refresh after a watchdog time delay. For example
/// in response to parent control mouse moves.
/// </summary>
internal void RestartWDT(TimeSpan? timeSpan = null)
{
var captureCount = ++_wdtCount;
var delay = timeSpan ?? TimeSpan.FromMilliseconds(250);
Task.Delay(delay).GetAwaiter().OnCompleted(() =>
{
if (captureCount.Equals(_wdtCount))
{
Debug.WriteLine($"WDT {delay}");
Refresh();
}
});
}
int _wdtCount = 0;
}
Design Mode Example
Main form BackgroundImage to main form with a Stretch layout. Then overlay a TableLayoutPanel whose job it is to keep the button scaled correctly as the form resizes. TransparentButton is now placed in one of the cells.
Test
Here's the code I used to test the basic transparent button as shown over a background image:
public partial class MainForm : Form
{
public MainForm() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
buttonTransparent.ForeColor = Color.White;
buttonTransparent.Click += onClickTransparent;
}
private void onClickTransparent(object? sender, EventArgs e)
{
MessageBox.Show("Clicked!");
buttonTransparent.RestartWDT();
}
protected override CreateParams CreateParams
{
get
{
const int WS_EX_COMPOSITED = 0x02000000;
// https://stackoverflow.com/a/36352503/5438626
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_COMPOSITED;
return cp;
}
}
}
Revision 2.0 Demo
I've uploaded a demo to my GitHub repo that has a draggable, clickable transparent button over a RichTextBox control.

ToolStripDropDown transparent background and smooth Region

I'm trying to create a custom popup window in my winforms project, which looks like so:
The problem with this is that the edges are smooth...
I'm achieving this like so:
public partial class BubblePopup : ToolStripDropDown
{
private const int BORDERWIDTH = 6;
private SolidBrush _backgroundBrush;
private int _borderRadius = 20;
public BubblePopup()
{
this.BackColor = Color.Transparent;
InitializeComponent();
//Method 1
Region = new Region(ControlUtilities.CreateBubblePath(new Rectangle(BORDERWIDTH - 1, BORDERWIDTH - 1, ClientSize.Width - (BORDERWIDTH * 2), ClientSize.Height - (BORDERWIDTH * 2)), _borderRadius));
/////////////
_backgroundBrush = new SolidBrush(Color.Blue);
}
//Method 1
protected override void OnSizeChanged(EventArgs e)
{
Region = new Region(ControlUtilities.CreateBubblePath(new Rectangle(BORDERWIDTH - 1, BORDERWIDTH - 1, ClientSize.Width - (BORDERWIDTH * 2), ClientSize.Height - (BORDERWIDTH * 2)), _borderRadius));
base.OnSizeChanged(e);
}
////////
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
}
}
If I don't use the code enclosed by Method1 comments and I'm setting the background to transparent and setting the CreateParams flag to support transparency im getting the following result(I also tried overriding the OnPaintBackground event as well not to call the base.OnPaintBackground I got a black background then):
So I would either need a popup with my custom path with smooth region, or a transparent background would also work for me.
Also here's the code how I'm showing the popup:
private GeneralPopup _popupItem = new GeneralPopup();
private ToolStripControlHost _popupControlHost;
private BubblePopup _popup;
public Form1()
{
InitializeComponent();
_popupControlHost = new ToolStripControlHost(_popupItem);
_popupControlHost.Padding = new Padding(0);
_popupControlHost.Margin = new Padding(0);
_popupControlHost.AutoSize = false;
_popupControlHost.BackColor = Color.Transparent;
_popup = new BubblePopup();
_popup.Padding = new Padding(0);
_popup.Margin = new Padding(0);
_popup.AutoSize = true;
_popup.DropShadowEnabled = false;
_popup.Items.Add(_popupControlHost);
}
private void button1_Click(object sender, EventArgs e)
{
_popup.Show(button1,10,10);
}
Thanks in advance

Move transparent image over another transparent image

I define SelecterControl user control (with transparent image in background) that contain a Roll control with transparent image.
I want to move front control over SelecterControl.
when do that, transparent area of SelecterControl don't clear.
My complete code is below:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
namespace TestControl
{
public partial class SelecterControl : UserControl
{
private Roll roll;
public SelecterControl()
{
InitializeComponent();
BackgroundImage = Image.FromStream(new MemoryStream(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAPsAAAAWCAYAAAAGqOr6AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AgFDRwUWvPXdAAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAACxklEQVR42u3cv0sbYRwG8McgucnXpiBCThyCpUMqGa4RobhlKDjKBSLt0EU7tbQQmv4FjQj94aRZOhSSkiKdQqFNJxGkIUOKGQohFMmlBEHrOSVQ0yE5aYJW7jyVO54PhCwX3vDePfd+733vbgBn88G6fRCRWReSuYEzGlQABM7RaJ6BJzId9Mg5Al8FUDwpd4MnNXZj6Lby+uH7pPB7Fb3egvB7TbWm11uYXZ5Y5H4jMicXr+xZyZvwe6HXW9X7b2ZSe61fgW7gq/+Gvj/svsx8OSlkaWEsPITxaYFhWTL9hz8lqtxrRBbdeTRm+jcHWhM7W3rg3eON5PfPP/PPSxGjSjge5Qf7ziprwu9VrTRGRFdnWJYwOTeCybkRCL83kimUfbF0MNUNehEAPMbGG692njHoRO6oDMbCQ8qLUF5FZ97N1z+yJ2/Njdg90eBzWD9d46HS4ze7wJm6WY7gCfLdwOcHu6P6mpAlS9fn/6F0v500Gz/Dw6THBrvAuWW9kCVk5stqLB2sAvAZI7s6Pi1sbezpzbdqWJlShb0nELpc99gFzjU+LaBrTQXp3jLe9nI7rEzhbjJgd7VA5GoXuZLlATrrdAwlkftKeb3e6g07EbmfBwCE34sDrcneIHKRA61p3P3ac1PNvt3X7YXiNyABcIKO6Mrt5+KVzqpYKdtIlrKN9tGfI1s+uXilDWABzltnJ7pSuXjFthyWso12Zr78xcihBwBC0dGErjVZyhO5qITXtSZi6WDCKOOPJ+iELC1ur++yl4hcYHt9F7XC4RI6T76hJ+yh6GhKyFJqc6XGniJysM2VGvR660MsHVzCaY+4hqKji6Vso7i5UlsTsmT5EVdjUoDdTnR5ZfvOlo5a4RAAlmaXJxL925z6pppStpHVtaZ6jpdXXGfgiczJxSttqy+vAJD/uPo1+vLHA+aOiIjI9f4CBEpvEw5QVnUAAAAASUVORK5CYII=")));
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.Transparent;
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnPaint(PaintEventArgs e)
{
if (BackgroundImage != null)
e.Graphics.DrawImage(BackgroundImage, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);
}
#region Roll Horizontal Movement
int strtX;
private void roll_MouseDown(object sender, MouseEventArgs e)
{
strtX = e.X;
}
private void roll_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
roll.Left += e.X - strtX;
}
#endregion
#region Designer generated code
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SelecterControl));
this.roll = new MapControl.Roll();
this.SuspendLayout();
//
// roll
//
this.roll.BackColor = System.Drawing.Color.Transparent;
//this.roll.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("roll.BackgroundImage")));
this.roll.Location = new System.Drawing.Point(87, 1);
this.roll.Name = "roll";
this.roll.Size = new System.Drawing.Size(13, 18);
this.roll.TabIndex = 0;
this.roll.MouseDown += new System.Windows.Forms.MouseEventHandler(this.roll_MouseDown);
this.roll.MouseMove += new System.Windows.Forms.MouseEventHandler(this.roll_MouseMove);
//
// SelecterControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
//this.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("$this.BackgroundImage")));
this.Controls.Add(this.roll);
this.Name = "SelecterControl";
this.Size = new System.Drawing.Size(251, 22);
this.ResumeLayout(false);
}
#endregion
}
class Roll : Control
{
public Roll()
{
BackgroundImage = Image.FromStream(new MemoryStream(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAA0AAAASCAYAAACAa1QyAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AgFDR0RM4ISugAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAA5UlEQVQoz2NkQAWCUMyARMPAexjNAhORjTul5GEhd5cBC1jc4h/249nJ9zCNjAwMDAwaufeVbDU57zIwMLyfnCKKYkPunNcwjek/np28x8DAcI+JgYGBAVnDozd/4BoevfnDMDlFlIGBgYEhtmbjTJizmWAKYBrkROAuZpATYUHRCANM2PygU/iIQafwEQMuwMRABhjVRIkmFmyCV/rl8Ol5z4SUxt7DUgByMpITYYGnP1iChWkSYmBgEIRpRE5GMA3LJ2abQDW9Z4QpSJ32QpCBgeEdNvesnt+p/OF0/3ukPEU6AADyU1AuXNEg+gAAAABJRU5ErkJggg==")));
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.Transparent;
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnPaint(PaintEventArgs e)
{
if (BackgroundImage != null)
e.Graphics.DrawImage(BackgroundImage, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);
}
}
}
What is the cause of this problem?
Thanks for any help.
this.roll.Size = new System.Drawing.Size(13, 18);
That's the core problem in your code, the "roll" control is too small. When you use the WS_EX_TRANSPARENT flag in the CreateParams property, you ask Windows to ensure that the window behind your control always paints first. Which sets the background pixels for your "roll" control so it can just paint its own stuff on top of them.
Trouble is, your roll control is so small that after you moved it, it isn't large enough to overlap the stray pixels that were drawn previously. They are no longer inside the roll control window. So they'll stay there, producing the smearing effect you see.
The only way to avoid this is to make your roll control bigger so it always catches those pixels. Realistically, as large as the range of the slider. Problem is, you now lose all benefit of making "roll" a control of its own. Things like hit testing get complicated, you need to know where the thumb is located to know that the user clicked it.
You might as well create one control that draws both the slider and the thumb. Required code is exactly the same. And it will be more efficient since you use fewer system resources, windows are expensive objects.

Transparent background showing up black

I'm trying to make an image appear on top of another and still show the image underneath via a transparent background. I've got it so the new image appears on top of the other however setting BackColor to Color.Transparent just results in a black background.
Full Code:
public partial class frm_airportApplication : Form
{
PictureBox PicBox;
public frm_airportApplication()
{
InitializeComponent();
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x000000200;
return cp;
}
}
private void button1_Click(object sender, EventArgs e)
{
AllowTransparency = true;
plane p = new plane();
p.getPB().Parent = pb_airport;
this.Controls.Add(p.getPB());
this.Update();
}
protected void InvalidateEx()
{
if (Parent == null)
return;
Rectangle rc = new Rectangle(this.Location, this.Size);
Parent.Invalidate(rc, true);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
//do not allow the background to be painted
}
private void button2_Click(object sender, EventArgs e)
{
AllowTransparency = true;
ResourceManager resourceManager = new ResourceManager("Airport_Application.Properties.Resources", GetType().Assembly);
PicBox = new PictureBox();
PicBox.BackColor = Color.Transparent;
PicBox.Image = (Bitmap)resourceManager.GetObject("plane_icon");
PicBox.Top = 100;
PicBox.Width = 120;
PicBox.Height = 120;
PicBox.Left = 10;
PicBox.SizeMode = PictureBoxSizeMode.Zoom;
PicBox.Parent = pb_airport;
Controls.Add(PicBox);
PicBox.BringToFront();
}
}
public class plane
{
PictureBox pb;
Bitmap image;
ResourceManager resourceManager;
public plane()
{
resourceManager = new ResourceManager("Airport_Application.Properties.Resources", GetType().Assembly);
image=(Bitmap)resourceManager.GetObject("plane_icon");
pb = new PictureBox();
pb.Image = image;
pb.Top = 500;
pb.Width = 100;
pb.Height = 100;
pb.Left = 50;
pb.SizeMode = PictureBoxSizeMode.Zoom;
pb.BackColor = Color.Transparent;
}
public PictureBox getPB()
{
return pb;
}
}
I've found a lot of people who have had similar issues but none of the solutions helped.
It has been awhile but I think you have to set your form to Allow Tranparencies
this.AllowTransparency = true;
or
YourForm.AllowTransparency = true;
that would get rid of the black
I had the same issue but I had just a Panel which should've been transparent so I could see everything underneath it.
The problem was with DoubleBuffered property, it should be set to false.
this.DoubleBuffered = false;
No blackness anymore.
You can create an irregularly shaped form easily by setting its "Region" property. Here's an example:
Irregularly shaped form
As for truly transparent Controls, here's an excellent resource with step-by-step instructions:
Transparent Controls
In simple words, you cannot easily achieve transparency using the default PictureBox control in Windows Forms.
Either you switch to WPF, which by default supports transparency in every bits, or you use a custom control. Once I created such a control called AppIcon, but it is released under GPL, not commercial friendly,
http://mymobilepack.codeplex.com/SourceControl/changeset/view/39314#512415
For forms you can try this:
this.BackColor = System.Drawing.Color.XXX;
this.TransparencyKey = System.Drawing.Color.XXX;
You can try to solve it on the bitmap level:
Make a image in bitmap format and make the backgroundcolor transparant with this method:
bm.MakeTransparent(Color.XXX);
I seemed to solve a similar problem with my splashscreen bij setting a timer every 100ms,
and call DoEvents in it:
private void timer1_Tick(object sender, EventArgs e)
{
//BringToFront();
Application.DoEvents();
}
Hope this helps
If you want to overlay images over images (and not images over form), this would make the trick:
overImage.Parent = backImage;
overImage.BackColor = Color.Transparent;
overImage.Location = thePointRelativeToTheBackImage;
Where overImage and backImage are PictureBox with png (with transparent background).

.NET Custom UserControl's contents are disappearing when the parent is resized

I have derived the .NET UserControl, to make my own PictureList class.
However, when I resize the parent of the control, the contents of the control disappear. No Resize event is issued or whatever. I left out code not relevant to the question.
public ImageList(int width, int height)
{
ClientSize = new Size(width, height);
ResizeRedraw = true;
}
// Ensure background transparency will be handled corretly.
protected override CreateParams CreateParams
{
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20; // WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
MessageBox.Show("This messagebox is only shown upon the first drawing, but not after the magically disappearing of the contents");
img = /*the image to draw*/
rect = /*the size of the image*/
e.Graphics.DrawImage(img, rect);
}
Do you need to enable AutoRedraw?

Categories