Прозрачный фон для размещенной на MFC Windows Forms UserControl - PullRequest
0 голосов
/ 07 декабря 2009

Я использую CWinFormsControl для размещения пользовательского элемента управления Windows Forms в диалоговом окне MFC. Я установил для свойства DoubleBufferd значение true. Согласно документам это приводит к тому, что AllPaintingInWmPaint и UserPaint также будут установлены в true (не уверен, имеет ли это значение). Как заставить (или подделать) UserControl нарисовать фон прозрачным?

Это то, что я установил в конструкторе моего UserControl:

this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.Transparent;
this.DoubleBuffered = true;

1 Ответ

5 голосов
/ 13 декабря 2009

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

Для начала я сначала искал проблемы при размещении .NET UserControl в MFC. После долгого времени чтения кода реализации CWinFormsControl :: CreateControl () и всего остального ничего необычного не пришло. Фактически, за исключением особенностей загрузки управляемых ссылок, код идентичен тому, как загружаются прозрачные элементы управления ActiveX.

Изучив эту информацию, я использовал Spy ++, чтобы посмотреть, создан ли элемент управления .NET с помощью оконного контейнера. Это действительно так. После довольно продолжительного исследования этот контейнер управления, по-видимому, управляется экземпляром служебного класса System.Windows.Forms.Control.AxSourcingSite, у которого нет документации и почти нет видимости. Это было немного удивительно для меня, как обычно, наоборот. MFC и менее используемый WTL имеют отличную поддержку активации на месте, и обычно элементы управления могут работать с любой настройкой хоста, независимо от того, оконная она или нет.

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

При внимательном рассмотрении размещенной версии MFC обнаруживается не совсем белый фон, нарисованный .NET UserControl. После большего количества проб и тестирования этот не совсем белый фон определенно прорисовывается скрытым слоем в цепочке обработки сообщений окна. Это означает, что мы можем взломать решение, используя AllPaintingInWmPaint.

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

  1. Добавьте переменную-член m_ReroutePaint, чтобы мы знали, когда нам нужно переопределить стандартное поведение WM_PAINT.
  2. Переопределить base.CreateParams и добавить флаг WS_EX_TRANSPARENT. Когда это свойство вызывается, установите для m_ReroutePaint значение true. Это свойство не вызывалось при активации элемента управления в контейнере .NET.
  3. Переопределите метод WndProc () и исправьте WM_PAINT по своему вкусу, если мы перенаправляем действия по рисованию.
  4. Используйте BeginPaint () / EndPaint () через Interop для настройки / демонтажа WM_PAINT. Используйте предоставленный HDC в качестве инициализатора для графического объекта.

Вот несколько предостережений:

  1. Цвет фона элемента управления не может быть изменен через свойство BackColor .NET после создания экземпляра элемента управления. Для этого можно добавить обходные пути, но, чтобы образец был коротким и простым, я пропустил код, чтобы сделать это, так как цель - прозрачные элементы управления. Однако, если вы начинаете с непрозрачного цвета фона, обходной путь не требуется. Я оставил код для этого случая.
  2. При подключении HDC к графическому объекту в обработчике WM_PAINT через Graphics.FromHdc () в документации предлагается вызывать Graphics.ReleaseHdc (). Однако при этом происходит утечка дескриптора GDI. Я оставил это закомментированным здесь, но, возможно, кто-то со знанием внутренних дел GDI + сможет понять это.

Этот UserControl был создан в проекте с именем 'UserCtrlLibrary1'. Элементы DebugPrintStyle () могут быть безопасно удалены. Кроме того, были добавлены обработчики для изменения размера и рисования, которые находятся в отдельном файле дизайнера, но их легко добавить. Значение AllPaintingInWmPaint должно быть истинным в течение всего срока действия элемента управления.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;
using System.Diagnostics;

namespace UserCtrlLibrary1
{
    public partial class CircleControl : UserControl
    {
        public CircleControl()
        {
            InitializeComponent();

            DebugPrintStyle(ControlStyles.SupportsTransparentBackColor, "initial");
            DebugPrintStyle(ControlStyles.AllPaintingInWmPaint, "initial");
            DebugPrintStyle(ControlStyles.UserPaint, "initial");

            this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);

            DebugPrintStyle(ControlStyles.SupportsTransparentBackColor, "current");
            DebugPrintStyle(ControlStyles.AllPaintingInWmPaint, "current");
            DebugPrintStyle(ControlStyles.UserPaint, "current");
        }

        public void DebugPrintStyle(ControlStyles cs, string prefix)
        {
            Debug.Print("{0}: {1}={2}", prefix, cs.ToString(), this.GetStyle(cs).ToString());
        }

        bool m_ReroutePaint;
        const int WS_EX_TRANSPARENT = 0x0020;
        protected override CreateParams CreateParams
        {
            get
            {
                if (this.BackColor == Color.Transparent)
                {
                    m_ReroutePaint = true;
                    CreateParams cp = base.CreateParams;
                    cp.ExStyle |= WS_EX_TRANSPARENT;
                    return cp;
                }
                else
                {
                    return base.CreateParams;
                }
            }
        }

        private void CircleControl_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            using (SolidBrush b = new SolidBrush(Color.Orange))
            {
                g.FillEllipse(b, 0, 0, this.Width, this.Height);
            }
        }

        private void CircleControl_Resize(object sender, EventArgs e)
        {
            this.Invalidate();
        }

        const int WM_PAINT = 0x000F;
        [DllImport("user32.dll")]
        static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT lpPaint);
        [DllImport("user32.dll")]
        static extern bool EndPaint(IntPtr hWnd, [In] ref PAINTSTRUCT lpPaint);
        [Serializable, StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }
        [StructLayout(LayoutKind.Sequential)]
        struct PAINTSTRUCT
        {
            public IntPtr hdc;
            public bool fErase;
            public RECT rcPaint;
            public bool fRestore;
            public bool fIncUpdate;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public byte[] rgbReserved;
        }

        protected override void WndProc(ref Message m)
        {
            if ((m.Msg == WM_PAINT) && (m_ReroutePaint))
            {
                PAINTSTRUCT ps = new PAINTSTRUCT();
                BeginPaint(this.Handle, out ps);
                using (Graphics g = Graphics.FromHdc(ps.hdc))
                {
                    using (PaintEventArgs e = new PaintEventArgs(g, new Rectangle(ps.rcPaint.Left, ps.rcPaint.Top, ps.rcPaint.Right - ps.rcPaint.Left, ps.rcPaint.Bottom - ps.rcPaint.Top)))
                    {
                        this.OnPaint(e);
                    }
                    // HACK: This is supposed to be required...
                    //       but it leaks handles when called!
                    //g.ReleaseHdc(ps.hdc);
                }
                EndPaint(this.Handle, ref ps);
                return;
            }

            base.WndProc(ref m);
        }
    }
}

В случае, если кто-либо, кроме OP, хотел бы проверить это, вот подробности, как запустить его в MFC. Я создал проект MFC SDI без архитектуры просмотра документов с поддержкой элементов управления ActiveX. Это приводит к генерации типичного класса «имя-проекта», класса ChildView и классов MainFrm.

Внутри заголовка ChildView.h добавьте следующий материал заголовка перед классом (но один раз после #pragma). Измените имя библиотеки управления .NET, если у вас другое.

#include <afxwinforms.h>
#using "UserCtrlLibrary1.dll"
using namespace UserCtrlLibrary1;

Добавьте переменную-член для управляющего хоста .NET. Произвольно я разместил свой в разделе Атрибуты.

// Attributes
public:
  CWinFormsControl<CircleControl> m_Circle;

Также я добавил обработчики для OnCreate () и OnSize (). Общественная / защищенная видимость может быть скорректирована по мере необходимости.

  // Generated message map functions
protected:
  afx_msg void OnPaint();
  DECLARE_MESSAGE_MAP()
public:
  afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
  afx_msg void OnSize(UINT nType, int cx, int cy);

В ChildView.cpp я добавил тела функций для всех элементов, перечисленных выше. Карта сообщений также нуждается в обновлениях, если вы не использовали ClassWizard для добавления обработчиков сообщений Windows.

BEGIN_MESSAGE_MAP(CChildView, CWnd)
  ON_WM_PAINT()
  ON_WM_CREATE()
  ON_WM_SIZE()
END_MESSAGE_MAP()

void CChildView::OnPaint() 
{
  CPaintDC dc(this); // device context for painting

  RECT rt;
  this->GetClientRect(&rt);

  rt.right = (rt.right + rt.left)/2;
  dc.FillSolidRect(&rt, RGB(0xFF, 0xA0, 0xA0));
}

int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if (CWnd::OnCreate(lpCreateStruct) == -1)
    return -1;

  RECT rt;
  this->GetClientRect(&rt);
  m_Circle.CreateManagedControl(WS_VISIBLE, rt, this, 1);

  return 0;
}

void CChildView::OnSize(UINT nType, int cx, int cy)
{
  CWnd::OnSize(nType, cx, cy);

  RECT rt;
  this->GetClientRect(&rt);
  m_Circle.MoveWindow(rt.left, rt.top, rt.right - rt.left, (rt.bottom - rt.top)/2, TRUE);
}

Эти изменения создают экземпляр UserControl и привязывают его к верхней половине представления. Обработчик OnPaint () рисует розовую полосу в левой половине вида. Вместе прозрачность должна быть видна в верхнем левом квадранте представления.

Чтобы проект MFC компилировался и выполнялся, копию вывода UserCtrlLibrary1 необходимо поместить в то же место, что и исполняемые файлы для UserCtrlMFCHost. Кроме того, другая копия должна быть помещена в тот же каталог, что и файлы исходного кода проекта для оператора #using. Наконец, проект MFC должен быть изменен для использования скрипта компиляции / clr. В разделе «Свойства конфигурации» в подразделе «Общие» этот переключатель указан в разделе «Параметры проекта по умолчанию».

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

Как: скомпилировать код MFC и ATL с / clr

...