MFC托管的Windows窗体UserControl的透明背景

bit*_*onk 0 mfc transparency interop winforms

我正在使用CWinFormsControl在MFC对话框中托管Windows窗体UserControl.我已将该属性设置DoubleBufferd为true.根据文档,这会导致AllPaintingInWmPaintUserPaint设置为真(不确定这是否重要).如何强制(或伪造)UserControl将其背景透明化?

这是我在UserControl的构造函数中设置的:

this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.Transparent;
this.DoubleBuffered = true;
Run Code Online (Sandbox Code Playgroud)

mek*_*ian 5

我有一个可能有用的潜在解决方案,尽管我需要更多关于动画控件如何工作的信息.在我的解决方案中有一个不幸的副作用,那就是DoubleBuffering属性只能在.NET控件容器中正常工作.当在MFC中托管时,您的控件将在调整大小和其他类似的显示撕裂刷新时闪烁.这可能会导致动画控件出现问题,具体取决于它们执行绘图工作的方式.

首先,我首先在MFC中托管.NET UserControl时查找问题.经过一段时间阅读CWinFormsControl :: CreateControl()的实例化代码以及下面的所有内容,没有任何异常现象出现.实际上,除了加载托管引用的怪癖之外,代码与加载透明ActiveX控件的方式相同.

在了解了这条信息之后,我使用Spy ++来查看.NET控件是否使用窗口化容器进行实例化.的确是.经过相当长时间的调查后,这个控件容器似乎由一个实用程序类System.Windows.Forms.Control.AxSourcingSite的实例控制,该实例没有文档,几乎没有可见性.这对我来说有点令人惊讶,因为通常情况恰恰相反.MFC和较少使用的WTL非常支持就地激活,并且通常控件可以与主机设置的任何设备一起使用,无论是否有窗口.

从这里,我检查.NET控件托管在.NET控件容器中时是否存在相同的容器.我假设控件可能有自己的窗口,没有任何特殊的适配器.事实证明,我错了.控件的工作方式与就地非窗口控件的工作方式相同.这意味着为了保持行为,解决方案必须允许常规.NET激活正常进行,并且当窗口化时,它应该执行其他操作.

仔细查看MFC托管版本可以看到.NET UserControl绘制的灰白色背景.在更多的spading和测试之后,这个灰白色的背景肯定是由窗口消息处理链中的隐藏层绘制的.这意味着我们可以通过使用AllPaintingInWmPaint来破解解决方案.

为了演示这一点,这里是UserControl的源代码,可以在.NET和MFC托管容器中托管.此控件依赖于以下事项来解决透明度问题.

  1. 添加一个成员变量m_ReroutePaint,以便我们知道何时需要覆盖默认的WM_PAINT行为.
  2. 覆盖base.CreateParams并添加WS_EX_TRANSPARENT标志.调用此属性时,将m_ReroutePaint设置为true.在.NET容器中激活Control时未调用此属性.
  3. 如果我们重新路由绘画活动,则覆盖WndProc()方法,并根据我们的喜好修补WM_PAINT.
  4. 通过Interop使用BeginPaint()/ EndPaint()来设置/拆除WM_PAINT.使用提供的HDC作为Graphics对象的初始化程序.

以下是一些警告:

  1. 在实例化控件之后,无法通过BackColor .NET属性更改控件的背景颜色.可以为此添加变通方法,但为了保持样本简洁,我省略了代码来执行此操作,因为预期目标是透明控件.但是,如果您使用不透明的背景颜色,则不需要解决方法.我确实为这种情况留下了代码.
  2. 在通过Graphics.FromHdc()将HDC附加到WM_PAINT处理程序中的Graphics对象时,文档建议应该调用Graphics.ReleaseHdc().但是,通过执行此操作,会发生GDI句柄泄漏.我已经把它留在这里评论了,但也许有GDI +内部知识的人可以解决这个问题.

此UserControl是在名为"UserCtrlLibrary1"的项目中创建的.可以安全地删除DebugPrintStyle()项.此外,还添加了处理程序以进行调整大小和绘制,这些处理程序都在一个单独的设计器文件中,但很容易添加.AllPaintingInWmPaint应该在控件的生命周期内为true.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果OP以外的任何人想测试这个,这里有详细介绍,以便在MFC中启动并运行.我创建了一个MFC SDI项目,没有文档视图架构,支持ActiveX控件.这导致生成典型的"project-name"类,ChildView类和MainFrm类.

在ChildView.h标题内,在类之前添加以下标题材料(但在#pragma之后添加一次).如果您的名称不同,请更改.NET控件库的名称.

#include <afxwinforms.h>
#using "UserCtrlLibrary1.dll"
using namespace UserCtrlLibrary1;
Run Code Online (Sandbox Code Playgroud)

为.NET控件主机添加成员变量.任意地,我把它放在属性部分下面.

// Attributes
public:
  CWinFormsControl<CircleControl> m_Circle;
Run Code Online (Sandbox Code Playgroud)

另外,我为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);
Run Code Online (Sandbox Code Playgroud)

在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);
}
Run Code Online (Sandbox Code Playgroud)

这些更改创建UserControl的实例,并将其锚定在视图的上半部分.OnPaint()处理程序在视图的左半部分绘制一个粉红色条带.在视图的左上象限中,透明度应该是明显的.

要使MFC项目编译和运行,需要将UserCtrlLibrary1输出的副本放在与UserCtrlMFCHost的可执行文件相同的位置.另外,需要将另一个副本放在与#using语句的项目源代码文件相同的目录中.最后,应修改MFC项目以使用/ clr编译脚本.在"配置属性"部分的"常规"子部分中,此开关列在"项目默认值"下.

值得注意的一点是,这允许^后缀访问托管类.在开发此解决方案的某些方面,我讨论了添加仅在从MFC实例化时调用的方法,但考虑到有方法可以检测窗口/非窗口激活,这不是必需的.但是,其他实现可能需要这样做,所以我觉得指出这一点很好.

如何:使用/ clr编译MFC和ATL代码