C# 如何创建一个在其他控件之上工作的透明控件?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/592538/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 09:32:34  来源:igfitidea点击:

How to create a transparent control which works when on top of other controls?

c#winformstransparent-control

提问by Ed S.

I have a control (derived from System.Windows.Forms.Control) which needs to be transparent in some areas. I have implemented this by using SetStyle():

我有一个控件(派生自 System.Windows.Forms.Control)在某些区域需要透明。我已经通过使用 SetStyle() 实现了这一点:

public TransparentControl()
{
    SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    this.BackColor = Color.Transparent.
}

Now, this works if there are no controls between the form and the transparent control. However, if there happens to be another control below the transparent control (which is the use case here), it does not work. The intermediate control is not draw, but the form below does show through. I can get the effect that I need by overriding CreateParams and setting the WS_EX_TRANSPARENT flasg like so:

现在,如果表单和透明控件之间没有控件,这将起作用。但是,如果透明控件下面恰好有另一个控件(这是这里的用例),则不起作用。中间控件不是绘制,但下面的表格确实显示出来了。我可以通过覆盖 CreateParams 并像这样设置 WS_EX_TRANSPARENT flasg 来获得我需要的效果:

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

The problem here is that it reallyslows down the painting of the control. The control is already double buffered, so nothing to do there. The performance hit is so bad that it is unacceptable. Has anyone else encountered this problem? All of the resources that I can find suggest using method #1, but again, that does not work in my case.

这里的问题是它确实减慢了控件的绘制速度。控件已经是双缓冲的,所以没有什么可做的。性能损失如此之大,令人无法接受。有没有其他人遇到过这个问题?我能找到的所有资源都建议使用方法 #1,但同样,这在我的情况下不起作用。

EDIT: I should note that I do have a workaround. The child (transparent) controls could simply draw themselves onto the parent's Graphics object, but it is really ungly and I don't like the solution at all (though it may be all I have).

编辑:我应该注意到我确实有一个解决方法。子(透明)控件可以简单地将自己绘制到父对象的 Graphics 对象上,但它真的很丑,我根本不喜欢该解决方案(尽管我可能只有它)。

EDIT2: So, following the advice that I got on how transparency works in .NET, I have implemented the IContainer interface in my user control which contains the transparent controls. I have created a class which implements ISite, I add my child controls to the Components collection of the UserControl, the Container property lines up correctly in the debugger, but I still do not get a transparency effect. Does anyone have any ideas?

EDIT2:因此,按照我在 .NET 中获得的关于透明度如何工作的建议,我在包含透明控件的用户控件中实现了 IContainer 接口。我创建了一个实现 ISite 的类,我将我的子控件添加到 UserControl 的 Components 集合中,Container 属性在调试器中正确排列,但我仍然没有得到透明效果。有没有人有任何想法?

采纳答案by Ed S.

I decided to simply paint the parent under my child controls manually. Here is a good article.

我决定简单地在我的子控件下手动绘制父级。 这是一篇很好的文章。

回答by snarf

Transparent controls in DotNet are implemented by having the transparent control's containerdraw itself in the transparent control's window and then having the transparent control draw itself. This process doesn't take into account the possibility of overlapping controls. So you will need to use some sort of work-around to make it work.

DotNet 中的透明控件是通过让透明控件的容器在透明控件的窗口绘制自身,然后让透明控件绘制自身来实现的。这个过程没有考虑重叠控制的可能性。因此,您需要使用某种变通方法来使其工作。

In some cases I've had success with complex nesting, but that's mostly only good for quick-and-dirty layering of bitmaps, and it doesn't solve any issues with partially overlapping controls.

在某些情况下,我在复杂的嵌套方面取得了成功,但这主要仅适用于位图的快速和肮脏的分层,并且它不能解决部分重叠控件的任何问题。

回答by user71617

Some suggestions (apologies for the VB code).

一些建议(为 VB 代码道歉)。

Try to avoid painting the background:

尽量避免绘制背景:

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    If m.Msg = &H14 Then
        Return
    End If
    MyBase.WndProc(m)
End Sub

Protected Overrides Sub OnPaintBackground(ByVal pevent As System.Windows.Forms.PaintEventArgs)
    Return
End Sub

Don't call the controls base paint method:

不要调用控件基础绘制方法:

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
    'MyBase.OnPaint(e) - comment out - do not call
End Sub

回答by takrl

Drawing the siblings under the control is possible, but it's ugly. The code below works reasonably well for me, it expands on the code given in the link in Ed S.' answer.

在控制下绘制兄弟姐妹是可能的,但它很难看。下面的代码对我来说工作得相当好,它扩展了Ed S链接中给出的代码回答

Possible pitfalls:

可能的陷阱:

  • DrawToBitmapwas introduced with .net 2.0, so don't expect it to work with anything older than that. But even then something like this may be possible by sending WM_PRINT to the sibling control; AFAIK that's what DrawToBitmap does internally.
  • It may also have problems if you have controls under your control that make use of WS_EX_TRANSPARENTsince according to msdnthat window style fiddles with the painting order. I haven't got any controls that use this style so I can't tell.
  • I'm running XP SP3 with VS2010, therefore this approach may have additional problems on Vista or W7.
  • DrawToBitmap是随 .net 2.0 引入的,所以不要指望它可以与任何比这更旧的东西一起使用。但即便如此,通过将 WM_PRINT 发送到同级控件也可以实现类似的操作;AFAIK 这就是 DrawToBitmap 在内部所做的。
  • 如果您在控制下使用控件,它也可能会出现问题,WS_EX_TRANSPARENT因为根据msdn窗口样式摆弄绘画顺序。我没有任何使用这种风格的控件,所以我不知道。
  • 我在 VS2010 上运行 XP SP3,因此这种方法在 Vista 或 W7 上可能会有其他问题。

Here's the code:

这是代码:

if (Parent != null)
{
    float
        tx = -Left,
        ty = -Top;

    // make adjustments to tx and ty here if your control
    // has a non-client area, borders or similar

    e.Graphics.TranslateTransform(tx, ty);

    using (PaintEventArgs pea = new PaintEventArgs(e.Graphics,e.ClipRectangle))
    {
        InvokePaintBackground(Parent, pea);
        InvokePaint(Parent, pea);
    }

    e.Graphics.TranslateTransform(-tx, -ty);

    // loop through children of parent which are under ourselves
    int start = Parent.Controls.GetChildIndex(this);
    Rectangle rect = new Rectangle(Left, Top, Width, Height);
    for (int i = Parent.Controls.Count - 1; i > start; i--)
    {
        Control c = Parent.Controls[i];

        // skip ...
        // ... invisible controls
        // ... or controls that have zero width/height (Autosize Labels without content!)
        // ... or controls that don't intersect with ourselves
        if (!c.Visible || c.Width == 0 || c.Height == 0 || !rect.IntersectsWith(new Rectangle(c.Left, c.Top, c.Width, c.Height)))
            continue;

        using (Bitmap b = new Bitmap(c.Width, c.Height, e.Graphics))
        {
            c.DrawToBitmap(b, new Rectangle(0, 0, c.Width, c.Height));

            tx = c.Left - Left;
            ty = c.Top - Top;

            // make adjustments to tx and ty here if your control
            // has a non-client area, borders or similar

            e.Graphics.TranslateTransform(tx, ty);
            e.Graphics.DrawImageUnscaled(b, new Point(0, 0));
            e.Graphics.TranslateTransform(-tx, -ty);
        }
}

回答by Lee

This is just a simple thing I cooked up.. The only issue I've found is that it doesn't update when the intersecting controls are updated..

这只是我做的一个简单的事情..我发现的唯一问题是当交叉控件更新时它不会更新..

It works by drawing a control that's behind/intersects with the current control to a bitmap, then drawing that bitmap to the current control..

它的工作原理是将位于当前控件后面/与当前控件相交的控件绘制到位图,然后将该位图绘制到当前控件。

protected override void OnPaint(PaintEventArgs e)
{
    if (Parent != null)
    {
        Bitmap behind = new Bitmap(Parent.Width, Parent.Height);
        foreach (Control c in Parent.Controls)
            if (c.Bounds.IntersectsWith(this.Bounds) & c != this)
                c.DrawToBitmap(behind, c.Bounds);
        e.Graphics.DrawImage(behind, -Left, -Top);
        behind.Dispose();
    }
}

回答by Rubem Pechansky

I found out that the modifications below make things a bit faster:

我发现下面的修改使事情变得更快:

if((this.BackColor == Color.Transparent) && (Parent != null)) {
    Bitmap behind = new Bitmap(Parent.Width, Parent.Height);
    foreach(Control c in Parent.Controls) {
        if(c != this && c.Bounds.IntersectsWith(this.Bounds)) {
            c.DrawToBitmap(behind, c.Bounds);
        }
    }
    e.Graphics.DrawImage(behind, -Left, -Top);
    behind.Dispose();
}

I also think that using this.Width/ this.Heightinstead of Parent.Width/ Parent.Heightwould be even faster, but I didn't have time to tinker with it.

我也认为使用this.Width/this.Height而不是Parent.Width/Parent.Height会更快,但我没有时间去修补它。

回答by Hyperar

This does the trick, at least it has for me:

这可以解决问题,至少对我来说是这样:

protected override void OnPaintBackground(PaintEventArgs e)
{
    //base.OnPaintBackground(e);
    this.CreateGraphics().DrawRectangle(new Pen(Color.Transparent, 1), new Rectangle(0, 0, this.Size.Width, this.Size.Height));
}