透明用户控件的刷新显示

Refresh display of transparent user control

我已经编写了一个支持透明背景(除其他外)的用户控件。
但是,我发现一个问题,当背景是透明的,并且你改变了用户控件的文本,之前的文本仍然显示在屏幕上,在新文本的下面,导致无法阅读。
我已经用谷歌搜索了半天,找到了各种对我的情况不起作用的建议,其中大部分涉及将父控件绘制到位图上并在我的控件表面上绘制该位图。
但是,在我的例子中,父控件也是透明的,所以我尝试按照建议 here 上升到表单级别,但我得到了一个 InvalidArgumentException, 我试过按照建议 here 使父控件无效,但也没有成功。

我的代码基本上是这样的(截断到最低限度):

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);
    }
}

我想我们在这方面已经很接近了。

首先,您不能像那样抑制 OnPaintBackground 并期望其余的 winforms 像预期的那样 运行,这意味着根本没有任何东西被绘制到那个区域,所以我们离开了 GDI+ 或 windows或者可能是 "fill in the blanks" 的其他构造。表单上不透明的控件可能需要调用 UserControl 来获取它的背景,因此我们必须绘制一些东西...

所以我们总是调用base.OnPaintBackground,但是如果背景是透明的,那么我们需要使整个表面无效的控件重新绘制。

    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();
            }
        }
    }

这仍然不完整,但保存了我在 UserControl 上方和下方的控件具有文本框时遇到的大部分闪烁,这些文本框的值在计时器上刷新。

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

我一次又一次地了解到,简单地向别人解释问题通常可以帮助您解决问题。

所以,结合我所有搜索的一些答案后, 主要是 this one 关于使父级的指定矩形无效, this one 关于获取控件在窗体上的位置,
我想到了这个:

// 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();
}

我仍然需要处理当前控件和表单之间的父控件,但现在这对我来说已经足够了。