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

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

Ответы [ 7 ]

8 голосов
/ 19 мая 2012

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

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

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 голосов
/ 27 февраля 2009

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

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

5 голосов
/ 11 сентября 2012

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

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

4 голосов
/ 17 мая 2011

Рисование братьев и сестер под контролем возможно, но безобразно. Приведенный ниже код работает достаточно хорошо для меня, он расширяет код, указанный в ссылке в 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);
        }
}
3 голосов
/ 27 февраля 2009

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

2 голосов
/ 27 февраля 2009

Некоторые предложения (извинения за код 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
0 голосов
/ 09 августа 2013

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

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