0

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:

enter image description here

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:

enter image description here

Daniel
  • 2,318
  • 2
  • 22
  • 53
  • Does this answer your question? https://stackoverflow.com/questions/10763640/c-sharp-windows-form-application-transparent-button – Jord van Eldik Jan 23 '23 at 07:54
  • Unfortunately, no :( I wish to keep the button's *FlatStyle* as Standard. – Daniel Jan 23 '23 at 08:59
  • 1
    As this is your second question concerning transparency you should rethink your approach with winforms and choose something that is stronger in that area like WPF. Winforms is a rather old technology not created with transparency in mind. Trying to "fake" transparency the way you wish will mostly end up in quirky solutions working more or less against the UI Framework and those solutions are nor easy to implement. – Ralf Jan 23 '23 at 09:20
  • 2
    In winform in my experience there is no true transparent. The transparent that you get is by getting the parent background and making the own. – lork6 Jan 23 '23 at 11:17
  • I believe the answer to "can I do it with my `TransparentButton` class, without [...] drawing the [...] background manually?" is No. For example, if the button's parent has a background image and you want to "see through" the button, setting `BackgroundColor=Color.Transparent` won't work. What _will_ work is overriding TransparentButton.OnPaint` and painting the button with an image of what that rectangle would look like on the parent if the button weren't there. – IVSoftware Jan 24 '23 at 18:56
  • While this is not that difficult, can I somehow override the OnPaint while keeping the overall look of a standard button? I know I can take a screencapture of it, and draw it in OnPaint, but in general, can I *grab* a standard button's *paint* and reuse it in my custom button's OnPaint with rounded edges? – Daniel Jan 24 '23 at 19:11
  • 1
    Perhaps this looks like a job for [ButtonRenderer](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.buttonrenderer) class in `System.Windows.Forms`? – IVSoftware Jan 24 '23 at 19:18

1 Answers1

0

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.

clickable-draggable-over-richtextbox

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.

designer

runtime


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.

IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • [clone](https://github.com/IVSoftware/transparent-button-00.git) – IVSoftware Jan 24 '23 at 02:23
  • Thank you. I appreciate your efforts solving this. However, my problem is not button's backround inside the "button", but its background outside. Here: https://imgur.com/a/SpXE3VQ – Daniel Jan 28 '23 at 06:51