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

发布于 2024-07-13 20:34:23 字数 1056 浏览 6 评论 0原文

我有一个控件(派生自 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 对象上,但这确实很丑,而且我根本不喜欢这个解决方案(尽管这可能是我所拥有的全部)。

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

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():

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:

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

The problem here is that it really slows 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.

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).

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

看轻我的陪伴 2024-07-20 20:34:23

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

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

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

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();
    }
}
猫性小仙女 2024-07-20 20:34:23

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

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

Transparent controls in DotNet are implemented by having the transparent control's container draw 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.

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.

來不及說愛妳 2024-07-20 20:34:23

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

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 会更快,但我没有时间修改它。

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.Height instead of Parent.Width / Parent.Height would be even faster, but I didn't have time to tinker with it.

小苏打饼 2024-07-20 20:34:23

将兄弟姐妹置于控制之下是可能的,但它很丑。 下面的代码对我来说效果相当好,它扩展了 Ed S.' 中链接中给出的代码。 回答

可能的陷阱:

  • DrawToBitmap 是在 .net 2.0 中引入的,因此不要指望它可以与比 .net 2.0 更旧的版本一起工作。 但即便如此,通过向同级控件发送 WM_PRINT 也可能实现类似的操作; AFAIK 这就是 DrawToBitmap 内部所做的事情。
  • 根据 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);
        }
}

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.

Possible pitfalls:

  • DrawToBitmap was 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_TRANSPARENT since according to msdn that 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.

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);
        }
}
疯到世界奔溃 2024-07-20 20:34:23

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

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

苏辞 2024-07-20 20:34:23

一些建议(对 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

Some suggestions (apologies for the VB code).

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
影子的影子 2024-07-20 20:34:23

这确实有效,至少对我来说是这样:

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

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

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));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文