Создание полупрозрачных панелей / элементов управления. Есть ли надежный способ?

#c# #.net #custom-controls

#c# #.net #пользовательские элементы управления

Вопрос:

Я пытаюсь создать полупрозрачный элемент управления, производный от System.Windows.Forms.Panel . [Редактировать: В основном, я хочу достичь этого]:

введите описание изображения здесь

Я просмотрел многочисленные веб-статьи, а также вопросы SO и пришел к этому:

 class SeeThroughPanel : Panel
{
    public SeeThroughPanel()
    {
    }

    protected override CreateParams CreateParams {
        get {
            var cp = base.CreateParams;
            cp.ExStyle |= 0x00000020;
            return cp;
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        //base.OnPaint(e);
        e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(50, 0, 0, 0)), this.ClientRectangle);
    }
}
  

Что приводит к этому (обратите внимание, что этот код тоже не делает его действительно полупрозрачным. Кнопка отображается полноцветной, в отличие от изображения выше):

Пользовательский элемент управления показывает прозрачность, как и ожидалось.

Однако, как только я делаю что-то, что вызывает перерисовку любых других элементов управления в пределах полупрозрачной панели, она перерисовывается (в данном случае я прокрутил базовый элемент управления):

Пользовательский элемент управления неправильно перерисован.

Итак, мой вопрос в том, как я могу избежать этого? Должен быть какой-то способ. В конце концов, веб-браузеры постоянно обрабатывают такого рода рендеринг.

Комментарии:

1. Похоже, что кнопка находится на панели и ее прокрутили, не так ли? Вы проверили Button.Parent ?

2. Это не решит вашу проблему, но я бы посоветовал переключиться на WPF: фреймворк намного мощнее Winforms. С WPF то, что вы хотите сделать, будет тривиальным.

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

4. @MarioVernari: Не удается переключиться на WPF, потому что я работаю с фреймворком, который построен на winforms.

5. @TaW: Проблема не в кнопке. Проблема в том, что полупрозрачная панель перед ним перерисовывается.

Ответ №1:

Сейчас я провел несколько тестов.

Вы объединяете несколько проблем в одну, так что давайте разберем их по отдельности:

  • Ни один веб-браузер не написан на C # с использованием Winforms. Так что это не причина, по которой это должно быть возможно.

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

  • При прокрутке вы увидите любой отчет элемента управления прокруткой в качестве его поверхности.

Вот два скриншота:

Это после запуска:

скриншот1

.. и это после того, как я прокрутил немного вправо:

скриншот1

Я использую этот код, чтобы убедиться, что Z-порядок правильный:

 button1.Parent = this;
panel1.BringToFront();
button1.BringToFront();
seeThroughPanel1.BringToFront();
  

Обратите внимание, как экономится пространство кнопки; это показывает, как используется старая поверхность.

Чтобы обойти это, вам нужно было бы получить доступ к текущей форме surface (возможно, с помощью Control.Нарисуйте tobitmap), а затем используйте правую часть этого, чтобы нарисовать вашу «полупрозрачную» панель. Конечно, это должно было бы скрыться, прежде чем вы захватите текущую поверхность формы.

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

* Хитрость заключается в том, чтобы перемещать его по контейнеру с помощью клавиатуры, не с помощью мыши; с помощью этого трюка он просто перемещается, не изменяя свой родительский контейнер. Также полезно для размещения элемента управления поверх вкладок TabControl… Но я был слишком ленив для этого, поэтому я его закодировал..

Комментарии:

1. 1 за написание кода и самостоятельное выполнение тестов. Спасибо. No WebBrowser is written in C# using Winforms. Я с удовольствием изучу Win32 API, если потребуется. Your button almost certainly is not in the Form's Controls collection Правильно. Это было добавлено с помощью кода. Но повторный эксперимент с вложенной панелью с большими размерами воспроизводит поведение. Я не прошу другого ответа. С вашим ответом и многими другими, которые я прочитал, поэтому я соглашусь на «это невозможно с WinForms». В любом случае спасибо.

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

Ответ №2:

Функция прозрачности элемента управления Windows Forms оставляет желать лучшего и является вопиющей выдумкой. Элемент управления на самом деле не прозрачный, он просто притворяется прозрачным, глядя на фон своего родительского элемента управления и копируя соответствующую часть изображения или фона на свою собственную поверхность во время метода OnPaintBackground.

Это означает, что «прозрачный» элемент управления, размещенный поверх другого в том же родительском элементе, фактически затенит другие дочерние элементы управления. На рисунке 1 показан этот эффект в действии.

введите описание изображения здесь

Элемент управления panel справа от формы закрывает элемент управления PictureBox и показывает только фон родительского элемента.

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

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

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

 protected void InvalidateEx()
{
  if(Parent==null)
    return;
  Rectangle rc=new Rectangle(this.Location,this.Size);
  Parent.Invalidate(rc,true);
}
  

Наконец, нам нужно убедиться, что процедура отрисовки фона не испортит недавно перерисованное содержимое родительской формы, отключив метод OnPaintBackground .

 protected override void OnPaintBackground(PaintEventArgs pevent)
{
  //do not allow the background to be painted 
}
  

Теперь элемент управления готов для остальной части его модификации. На этом этапе важно отметить, что прозрачные элементы управления не подходят для двойной буферизации с использованием стандартного метода setStyle. Растровое изображение памяти, которое предоставляется вашему коду, имеет непрозрачный фон и не позволяет просвечивать тщательно сохраненным родительским пикселям.

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

 using System;
using System.Drawing;
using System.Windows.Forms;

internal class SeeThroughPanel : Panel
{

    public SeeThroughPanel()
    {
    }

    protected void TickHandler(object sender, EventArgs e)
    {
        this.InvalidateEx();
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;

            cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT

            return cp;
        }
    }

    protected void InvalidateEx()
    {
        if (Parent == null)
        {
            return;
        }

        Rectangle rc = new Rectangle(this.Location, this.Size);

        Parent.Invalidate(rc, true);
    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {

    }

    private Random r = new Random();

    protected override void OnPaint(PaintEventArgs e)
    {
        
        e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(128, 0, 0, 0)), this.ClientRectangle);
    }
}
  

введите описание изображения здесь

Комментарии:

1. Спасибо. Но, к сожалению, это не мое требование. Это моя вина, что я сделал свой вопрос неясным. (С тех пор я обновил вопрос). Что я хочу, так это элемент управления с полупрозрачным фоном . Но в статье описывается элемент управления, который является прозрачным.

2. У меня это сработало, с небольшими изменениями, в частности, с отменой OnPaintBackground. Спасибо, что поделились. VS Community 2013 / Winforms