Как создать прозрачный элемент управления, который работает поверх других элементов управления?

У меня есть элемент управления (производный от 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, свойство Container правильно отображается в отладчике, но я все еще не получаю эффекта прозрачности. У кого-нибудь есть какие-либо идеи?

Ответов (7)

Решение

Я решил просто вручную нарисовать родительский элемент под моим дочерним элементом управления. Вот хорошая статья.

Это простая вещь, которую я придумал ... Единственная проблема, которую я обнаружил, заключается в том, что она не обновляется при обновлении пересекающихся элементов управления ...

Он работает, рисуя элемент управления, который находится позади / пересекается с текущим элементом управления, в растровое изображение, а затем рисуя это растровое изображение в текущий элемент управления.

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

Я обнаружил, что приведенные ниже модификации делают работу немного быстрее:

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 было бы еще быстрее, но у меня не было времени повозиться с этим.

Это помогает, по крайней мере, для меня:

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

Рисовать братьев и сестер под контролем можно, но это некрасиво. Приведенный ниже код работает для меня достаточно хорошо, он расширяет код, приведенный в ссылке в Ed S. ' ответ .

Возможные подводные камни:

  • DrawToBitmapбыл представлен с .net 2.0, поэтому не ожидайте, что он будет работать с чем-то более ранним, чем это. Но даже в этом случае что-то подобное может быть возможно, отправив WM_PRINT в элемент управления-брат; AFAIK это то, что 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);
        }
}

Прозрачные элементы управления в DotNet реализуются путем рисования контейнера прозрачного элемента управления в окне прозрачного элемента управления и последующего рисования самого прозрачного элемента управления. Этот процесс не учитывает возможность перекрытия элементов управления. Поэтому вам нужно будет использовать какой-то обходной путь, чтобы заставить его работать.

В некоторых случаях мне удавалось сложное вложение, но в основном это хорошо только для быстрого и грязного наложения растровых изображений, и не решает никаких проблем с частично перекрывающимися элементами управления.

Некоторые предложения (извинения за код 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