如何创建一个透明控件,在其他控件之上时仍能正常工作?

20

我有一个控件(派生自System.Windows.Forms.Control),需要在某些区域上是透明的。我通过使用SetStyle()来实现:

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

如果在透明控件和表单之间没有其他控件,那么这个方法是有效的。然而,如果在透明控件下方有另一个控件(就像这里的情况一样),它将不起作用。中间的控件不会被绘制,但是下面的表单会透过来显示。您可以通过覆盖CreateParams并设置WS_EX_TRANSPARENT标志来获得所需的效果,代码如下:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x20; // WS_EX_TRANSPARENT
        return cp;
    }
}
这里的问题在于它会使得控件的绘制速度变得非常缓慢。该控件已经具有双缓冲功能,因此在这方面无事可做。效能的损失非常严重,是无法接受的。是否还有其他人遇到过这个问题?我能找到的所有资源都建议使用方法#1,但在我的情况下,它仍然不起作用。
编辑:我应该指出,我确实有一个解决方法。子(透明)控件可以简单地将自己绘制到父级的Graphics对象上,但这样做真的很丑陋,我不喜欢这种解决方案(尽管这可能是我唯一拥有的解决方案)。
编辑2:因此,根据我从.NET中获取的关于透明度如何工作的建议,在包含透明控件的用户控件中实现了IContainer接口。我创建了一个实现ISite的类,将我的子控件添加到UserControl的Components集合中,容器属性在调试器中正确对齐,但我仍然没有获得透明效果。有人有什么想法吗?
7个回答

9

这只是我想出来的一个简单的东西...唯一的问题是当相交的控件被更新时它不会更新。

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

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

6
DotNet 中的透明控件是通过让透明控件的容器在透明控件的窗口中绘制自身,然后让透明控件自己绘制来实现的。这个过程没有考虑到重叠控件的可能性,所以你需要使用一些解决方法才能使其正常工作。
在某些情况下,我曾经尝试使用复杂嵌套的方式取得了成功,但这主要只适用于快速、简单地制作位图层,并且不能解决部分重叠控件的问题。

谢谢,我会研究一下。这些控件实际上是用户控件的一部分,所以也许我可以实现IContainer接口? - Ed S.
似乎容器属性为空。我会修复它并发布我的结果。 - Ed S.
我已经编辑了帖子并添加了更多信息,您能否告诉我是否有任何想法? - Ed S.

5
可以将兄弟控件绘制在控件下方,但这样做很丑。以下代码对我来说运行得相当好,它在Ed S.的答案链接中给出的代码基础上进行了扩展。
可能会遇到以下问题:
  • DrawToBitmap 是在.net 2.0 中引入的,因此不要期望它能在旧版本中使用。但即使如此,也可以通过向兄弟控件发送 WM_PRINT 来实现类似的效果;据我所知,这就是 DrawToBitmap 在内部执行的操作。
  • 如果你的控件使用了 WS_EX_TRANSPARENT,可能也会出现问题;根据msdn的说法,该窗口样式会干涉绘画顺序。我没有使用任何使用该样式的控件,所以无法确定。
  • 我在 XP SP3 上使用 VS2010 运行,因此这种方法在 Vista 或 W7 上可能会有其他问题。
以下是代码:
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);
        }
}

5
我发现以下修改可以使事情变得更快一些:
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();
}

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


1
谢谢,这个可行。 但是 this.Width / this.Height 不起作用。我不知道为什么,想问问有没有人知道原因。 - Hesham Eraqi

4

这对于在您控制下绘制父级元素是有效的。但是,您有没有想过如何在您的控制下绘制兄弟元素? - dtroy
谢谢你的回答,它确实让我朝着实现类似“透明控件”的方向迈进了一步。 - Alex
遇到了同样的问题,但在这个过程中,也发现了这个https://dev59.com/XGjWa4cB1Zd3GeqPudbv?lq=1,其中有一个非常好的答案。 - Arunas

2

以下是一些建议(抱歉使用了VB代码)。

尽量避免绘制背景:

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

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

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

谢谢,但我已经尝试过了(虽然我没有说)。 - Ed S.

-2
这个方法很管用,至少对我来说是这样的:
protected override void OnPaintBackground(PaintEventArgs e)
{
    //base.OnPaintBackground(e);
    e.Graphics.DrawRectangle(new Pen(Color.Transparent, 1), new Rectangle(0, 0, this.Size.Width, this.Size.Height));
}

这将不允许您“透过”控件看到其他内容。此外,您的PaintEventArgs会提供一个Graphics对象,无需创建和泄漏一个。 - Ed S.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接