bit*_*onk 0 mfc transparency interop winforms
我正在使用CWinFormsControl在MFC对话框中托管Windows窗体UserControl.我已将该属性设置DoubleBufferd
为true.根据文档,这会导致AllPaintingInWmPaint
并UserPaint
设置为真(不确定这是否重要).如何强制(或伪造)UserControl将其背景透明化?
这是我在UserControl的构造函数中设置的:
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.Transparent;
this.DoubleBuffered = true;
Run Code Online (Sandbox Code Playgroud)
我有一个可能有用的潜在解决方案,尽管我需要更多关于动画控件如何工作的信息.在我的解决方案中有一个不幸的副作用,那就是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托管容器中托管.此控件依赖于以下事项来解决透明度问题.
以下是一些警告:
此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实例化时调用的方法,但考虑到有方法可以检测窗口/非窗口激活,这不是必需的.但是,其他实现可能需要这样做,所以我觉得指出这一点很好.