1

I've written a user control for that supports transparent background (among other things).
However, I've found a problem that when the background is transparent, and you change the text of the user control, the previous text is still displayed on the screen, under the new text, making it impossible to read.
I've been googling for half a day now, finding all sort of suggestions that didn't work in my case, most of them involving painting the parent control onto a bitmap and draw that bitmap on my control's surface.
However, in my case the parent control is also transparent so I've tried to go up to the form's level like suggested here but i was getting an InvalidArgumentException, I've tried invalidating the parent control like suggested here but no luck either.

my code is basically this (truncated to the bare minimum):

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x20;
        return cp;
    }
}

protected override void OnPaintBackground(PaintEventArgs e)
{
    if(this.BackColor != Color.Transparent)
    {
        base.OnPaintBackground(e);
    }
}
Community
  • 1
  • 1
Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
  • 2
    Winforms /sigh... You can try to invalidate all controls (`form.Invalidate()`), but this would cause a lot of flickering. Then you will try to reduce flickering (double-buffering, bitmaps, etc.). And then you switch to wpf. – Sinatr Sep 09 '15 at 15:09
  • @Sinatr I would love to switch to wpf, unfortunately my boss seems to think that the learning curve is too much and the project's deadline is too short... any other suggestions? – Zohar Peled Sep 09 '15 at 15:17
  • 1
    You are not repainting the background and therefore not over-drawing the pixels of the old text. So this is entirely expected. UserControl supports transparency well, just set its BackColor to Transparent. Remove the two methods you posted. Drawing text on a transparent background is in general unwise, the anti-aliasing pixels will have the wrong colors, making the text look very ugly. Best described as "blobby". You must use TextRenderingHint.SingleBitPerPixelGridFit to avoid this. – Hans Passant Sep 09 '15 at 15:29
  • Why do you need transparency in first place? Maybe instead of using control on top of something you can combine them into one control and have full control over `Paint`? – Sinatr Sep 09 '15 at 15:30
  • @HansPassant: I've already figured out that's the reason for the old text to stay (sorry for forgetting to mention it in my question). I've tried to remove the CreateParams and the override of the OnPaintBackground but it gave me poor results in the display. Anyway, I think I've found the solution thanks to Sinatr's first comment and [this answer](http://stackoverflow.com/a/2884268/3094533). Will post an answer soon – Zohar Peled Sep 09 '15 at 15:57
  • @HansPassant: thanks for the tip about the TextRenderingHint, I'll play around with it and see what looks best (to the client, that is) – Zohar Peled Sep 09 '15 at 16:12

2 Answers2

1

I keep learning again and again that simply explaining the problem to someone else often helps you solve it.

So, After combining a few answers from all my searches, mainly this one about invalidating a specified rectangle of the parent, and this one about getting the location of the control on the form,
I came up with this:

// Make the Text property browsable again, call Refresh when changed.
[Browsable(true), 
 DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text 
{ 
    get { return _Text; }
    set
    { 
        if(_Text != value)
        {
            this.Refresh();
            _Text = value;
        }
    }
}

// Override Refresh to invalidate the relevant part of the parent form 
public override void Refresh()
{
    Form form = this.FindForm();
    // only for transparent controls that has text and no background image
    if (this.BackColor == Color.Transparent &&
        !string.IsNullOrEmpty(this.Text) &&
        (this.Gradient.BackColor2==Color.Transparent || !this.Gradient.IsGradient) && 
        this.BackgroundImage == null && 
        form != null)
    {
        Point locationOnForm = form.PointToClient(
                                   this.Parent.PointToScreen(this.Location)
                               );
        // Invalidate the rectangle of the form that's behind the current control
        form.Invalidate(new Rectangle(locationOnForm, this.Size));
    }
    base.Invalidate();
}

I will still have to deal with parent controls between the current control and the form, but for now this is good enough for me.

Community
  • 1
  • 1
Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
0

I think we are close on this one.

Firstly, you cannot suppress OnPaintBackground like that and expect the rest of winforms to run as expected, this means nothing gets painted to that region at all, so we have left GDI+ or windows or presumably some other construct to "fill in the blanks". The controls on your form that are opaque may want to call your UserControl to get it's background, so we have to paint something...

So we always call base.OnPaintBackground, but if the background is Transparent, then we need to invalidate the entire surface of the control to be repainted.

    public Glass()
    {
        InitializeComponent();
        this.SetStyle(ControlStyles.Opaque, true);
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x20;
            return cp;
        }
    }

    private bool rePaintState = false;
    protected override void OnPaintBackground(PaintEventArgs e)
    {
        base.OnPaintBackground(e);
        if (this.BackColor == Color.Transparent)
        {
            // we want to invalidate and force it to re-paint but just once
            if (rePaintState)
            {
                rePaintState = false;
            }
            else
            {
                rePaintState = true;
                this.Invalidate();
            }
        }
    }

This is still not complete, but saved most of the flickering I was experiencing when controls above and below the UserControl had textboxes with values refreshing on a timer.

I know its not a huge help, but WPF is really good for these types of UX issues ^-^

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
  • Thanks for your answer, but that doesn't help very much. my control also has some other properties such as gradient back color and rounded corners and this doesn't work well with them. – Zohar Peled Sep 10 '15 at 05:30