在 MFC 对话框和自己的线程上的 OpenGL 控件之间传递消息
Passing messages between an MFC Dialog and an OpenGL control on it's own thread
我正在尝试将OpenGL控件添加到MFC对话框中。 对话框从也运行 OpenGL 的窗口启动。这使它变得复杂。 由于一次只能有一个 OpenGL 上下文进入一个线程,因此我要么必须拥有整个对话框,要么只需要在其自己的线程上设置控件。 我选择尝试后者,因为它会使我们项目中的其他对话框也更容易实现。
我对MFC的了解大多参差不齐,因为我一直在学习,所以如果我错过了一些重要的东西,请道歉。 我真的可以使用一些 MFC 帮助来提供传达消息的最佳方式,或者从对话框中为控件设置属性。 运气好的话,我似乎有一些东西在起作用。
我使用 OnSize 作为一个例子。我在控件包装类中创建了这个函数,以尝试将消息从对话框推送到控件。
void AddOpenGLControl::PostMessageToControl(UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (dialogHandle != NULL)
PostMessage(dialogHandle, Msg, wParam, lParam);
}
但这让我试图在要发送到控件的对话框中模拟可能已经存在的虚假窗口消息。
WPARAM wp = nType;
LPARAM lp = MAKELPARAM(ctrlRect.Width(), ctrlRect.Height());
openGlControlOnThread.PostMessageToControl(WM_SIZE, wp, lp);
我知道我会想要像普通控件一样访问以触发更多消息,并且这种方法会很快变得繁琐。
有没有办法简单地传递消息? 很难问我不知道的东西,但我正在考虑继承,或包装类中的消息映射,或预翻译消息,或从隐藏的正常控件中窃取消息以发送或......好吧,我会接受任何可以跨线程工作的东西。
这是所有代码,以填补空白。 我将代码与我所能配对的,但我确实必须使用我们的消息循环(对不起! 这是我从窗口的标准启动:
//Main thread (with existing openGL window)
class TCADTLSandbox2
{
public:
virtual bool DoCmd(); // Main Entry Point
};
bool TCADTLSandbox2::DoCmd()
{
TCADTLSandbox2Dialog dialog;
dialog.Create(TCADTLSandbox2Dialog::IDD);
dialog.ShowWindow(SW_SHOW);
// We have to use the main window's accessors to get any messages from the window
WPARAM dialogMessage;
while (!GetUITools()->WaitForCommandMessage("", dialogMessage) || (dialogMessage != TIMsgIDCancel))
{
if (dialogMessage == TIMsgIDOK)
{
break;
}
}
dialog.ShowWindow(SW_HIDE);
dialog.DestroyWindow();
return true;
}
这是对话框类。 类成员 openGlControlOnThread 和 OnSize() 是两个有趣的东西。
//Dialog
class TCADTLSandbox2Dialog : public CDialog
{
DECLARE_DYNAMIC(TCADTLSandbox2Dialog)
public:
TCADTLSandbox2Dialog(CWnd* pParent = NULL);
virtual ~TCADTLSandbox2Dialog();
enum { IDD = IDD_SANDBOX2 };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
private:
AddOpenGLControl openGlControlOnThread;
virtual BOOL OnInitDialog();
afx_msg void OnBnClickedOk();
afx_msg void OnBnClickedCancel();
afx_msg void OnSize(UINT nType, int cx, int cy);
};
IMPLEMENT_DYNAMIC(TCADTLSandbox2Dialog, CDialog)
TCADTLSandbox2Dialog::TCADTLSandbox2Dialog(CWnd* pParent /*=NULL*/): CDialog(TCADTLSandbox2Dialog::IDD, pParent){}
TCADTLSandbox2Dialog::~TCADTLSandbox2Dialog(){}
void TCADTLSandbox2Dialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(TCADTLSandbox2Dialog, CDialog)
ON_BN_CLICKED(IDOK, &TCADTLSandbox2Dialog::OnBnClickedOk)
ON_BN_CLICKED(IDCANCEL, &TCADTLSandbox2Dialog::OnBnClickedCancel)
ON_WM_SIZE()
END_MESSAGE_MAP()
BOOL TCADTLSandbox2Dialog::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rect;
GetDlgItem(IDC_CADTL_SANDBOX_OPENGLTEST)->GetWindowRect(rect);
ScreenToClient(rect);
openGlControlOnThread.Create(rect, this);
initialized = true;
return TRUE;
}
void TCADTLSandbox2Dialog::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if (initialized)
{
CRect ctrlRect(0, 0, 0, 0);
GetDlgItem(IDC_CADTL_SANDBOX_OPENGLTEST)->GetWindowRect(ctrlRect);
ScreenToClient(ctrlRect);
//int buttonWidth = ctrlRect.Width();
ctrlRect.left = 20;
ctrlRect.right = cx - 20;
ctrlRect.bottom = cy - 50;
WPARAM wp = nType;
LPARAM lp = MAKELPARAM(ctrlRect.Width(), ctrlRect.Height());
openGlControlOnThread.PostMessageToControl(WM_SIZE, wp, lp);
}
}
void TCADTLSandbox2Dialog::OnBnClickedOk()
{
PostQuitMessage(0);
}
void TCADTLSandbox2Dialog::OnBnClickedCancel()
{
PostQuitMessage(0);
CDialog::OnCancel();
}
这就是它变得有趣的地方。 这就是我为将 OpenGL 控件包装到新线程上所做的。 PostMessageToControl是我希望用更简单的东西取代的东西。
// Class to kickoff the control thread
class AddOpenGLControl
{
public:
AddOpenGLControl();
~AddOpenGLControl();
void Create(CRect rect, CWnd *parent);
// You can send something along the lines of (WM_DESTROY, NULL, NULL)
void PostMessageToControl(UINT Msg, WPARAM wParam, LPARAM lParam);
private:
void StartThread(CRect rect, CWnd *parent);
std::thread controlThread;
};
// *** The thread class ***
AddOpenGLControl::AddOpenGLControl(){}
AddOpenGLControl::~AddOpenGLControl()
{
WaitForSingleObject(controlThread.native_handle(), INFINITE); // Okay?
controlThread.join(); // Also okay?
}
void AddOpenGLControl::PostMessageToControl(UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (dialogHandle != NULL)
PostMessage(dialogHandle, Msg, wParam, lParam);
}
void AddOpenGLControl::Create(CRect rect, CWnd *parent)
{
std::thread initThread(&AddOpenGLControl::StartThread, this, rect, std::ref(parent));
controlThread = std::move(initThread);
}
void AddOpenGLControl::StartThread(CRect rect, CWnd *parent)
{
COpenGLControl *openGLControl = new COpenGLControl;
openGLControl->Create(rect, parent);
openGLControl->m_unpTimer = openGLControl->SetTimer(1, 1000, 0);
HWND threadHandle = (HWND)openGLControl->GetSafeHwnd();
// This is where I start getting lost. Taken from MSDN
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, threadHandle, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
下面是控件本身:
// OpenGL Dialog Control thread (now it can have it's own openGL context)
class COpenGLControl : public CWnd
{
public:
UINT_PTR m_unpTimer;
COpenGLControl();
virtual ~COpenGLControl();
void Create(CRect rect, CWnd *parent);
void UpdateOpenGLDispay();
afx_msg void OnSize(UINT nType, int cx, int cy);
void ParentDialogResize(UINT nType, int cx, int cy);
private:
CWnd *hWnd;
HDC hDC;
HGLRC openGLRC;
CString className;
void Initialize();
void DrawCRD(double centerX, double centerY, double centerZ, double crdScale);
afx_msg void OnPaint();
afx_msg void OnDraw(CDC *pDC);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT_PTR nIDEvent);
DECLARE_MESSAGE_MAP()
};
// *** The actual control ***
COpenGLControl::COpenGLControl()
{
initialized = false;
display = new Display2d;
}
COpenGLControl::~COpenGLControl()
{
CleanUp();
}
BEGIN_MESSAGE_MAP(COpenGLControl, CWnd)
ON_WM_PAINT()
ON_WM_CREATE()
ON_WM_TIMER()
ON_WM_SIZE()
ON_WM_DESTROY()
END_MESSAGE_MAP()
int COpenGLControl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
Initialize();
return 0;
}
void COpenGLControl::Initialize()
{
// Initial Setup:
//
//Set up the pixal descriptor for using openGL on the graphics card
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // Size of this structure
1, // Version of this structure
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24, // Want 24bit color
0, 0, 0, 0, 0, 0, // Not used to select mode
0, 0, // Not used to select mode
0, 0, 0, 0, 0, // Not used to select mode
32, // Size of depth buffer
0, // Not used to select mode
0, // Not used to select mode
PFD_MAIN_PLANE, // Draw in main plane
0, // Not used to select mode
0, 0, 0 }; // Not used to select mode
// Store the device context
if (!(hDC = GetDC()->m_hDC)) // From the dialog?
{
AfxMessageBox("Can't Create A GL Device Context.");
//May not be able to unregister class?? May not need to?
return;
}
// Choose a pixel format that best matches that described in pfd
int nPixelFormat; // Pixel format index
nPixelFormat = ChoosePixelFormat(hDC, &pfd);
// Set the pixel format for the device context
if (!SetPixelFormat(hDC, nPixelFormat, &pfd))
AfxMessageBox("Setting pixel format failed");
// Create the rendering context and make it current
if (!(openGLRC = wglCreateContext(hDC)))
{
AfxMessageBox("Can't Create A GL Rendering Context.");
//May not be able to unregister class?? May not need to?
return;
}
if (!wglMakeCurrent(hDC, openGLRC))
AfxMessageBox("WGLFailed");
// Set color to use when clearing the background.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearDepth(1.0f);
// Turn on backface culling
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
// Turn on depth testing
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
// Enable alpha blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
UpdateOpenGLDispay();
initialized = true;
}
void COpenGLControl::Create(CRect rect, CWnd *parent)
{
className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_OWNDC, NULL, (HBRUSH)GetStockObject(BLACK_BRUSH), NULL);
CreateEx(0, className, "OpenGL", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, rect, parent, 0);
hWnd = parent;
}
void COpenGLControl::OnPaint()
{
// Attempt to help with flickering
UpdateOpenGLDispay();
CPaintDC dc(this);
ValidateRect(NULL);
}
void COpenGLControl::OnDraw(CDC *pDC)
{
UpdateOpenGLDispay();
}
void COpenGLControl::UpdateOpenGLDispay()
{
DrawCRD(); // ... just draw something
SwapBuffers(hDC);
}
void COpenGLControl::DrawCRD(double centerX, double centerY, double centerZ, double crdScale)
{
// X CRD
glColor4f(1.0f, 0.0f, 0.0f, 1);//R
glBegin(GL_LINES);
glVertex3f(centerX, centerY, centerZ);
glVertex3f(centerX + 1 * crdScale, centerY, centerZ);
glEnd();
// Y CRD
glColor4f(0.0f, 0.0f, 1.0f, 1);//B
glBegin(GL_LINES);
glVertex3f(centerX, centerY, centerZ);
glVertex3f(centerX, centerY + 1 * crdScale, centerZ);
glEnd();
// Z CRD
glColor4f(0.0f, 1.0f, 0.0f, 1);//G
glBegin(GL_LINES);
glVertex3f(centerX, centerY, centerZ);
glVertex3f(centerX, centerY, centerZ + 1 * crdScale);
glEnd();
}
void COpenGLControl::OnTimer(UINT_PTR nIDEvent)
{
switch (nIDEvent)
{
case 1:
UpdateOpenGLDispay();
break;
}
CWnd::OnTimer(nIDEvent);
}
void COpenGLControl::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
if (0 >= cx || 0 >= cy || nType == SIZE_MINIMIZED) return;
if (nType == SIZE_RESTORED)
ParentDialogResize(nType, cx, cy);
}
void COpenGLControl::ParentDialogResize(UINT nType, int cx, int cy)
{
if (initialized)
{
if (0 >= cx || 0 >= cy || nType == SIZE_MINIMIZED) return;
this->SetWindowPos(NULL, 20, 20, cx, cy, SWP_SHOWWINDOW); // fixed for this example
}
}
void COpenGLControl::OnDestroy()
{
CWnd::OnDestroy();
PostQuitMessage(0);
}
void COpenGLControl::CleanUp()
{
if (!wglDeleteContext(openGLRC))
AfxMessageBox("Deleting OpenGL Rendering Context failed");
if (!wglMakeCurrent(hDC, NULL))
AfxMessageBox("Removing current DC Failed");
//ReleaseDC(hDC); // The internets say the device context should automatically be deleted since it's CS_OWNDC
}
// Sorry for the heap of code. Or is it stack?
谢谢!
似乎我错过了MFC的一些重要方面。 如果有人遵循类似的路径,最好在尝试创建 opengl 控件之前完成对常规控件进行子类化的过程。
有几种方法可以做到这一点。 如果我使用 OnWndMsg 或类似的 WindowProc,我可以在消息仍处于 wParam/lParam 状态时获取消息,并将其传递出去。
BOOL TCADTLSandbox2Dialog::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
//This is one way to just pass the message along. For some cases, we may need to check the mouse position as well.
if (message == WM_MOUSEWHEEL)
{
openGlControlOnThread.PostMessageToControl(message, wParam, lParam);
return true;
}
if (wParam == WM_LBUTTONDOWN)
{
openGlControlOnThread.PostMessageToControl(message, wParam, lParam);
return true;
}
return CDialog::OnWndMsg(message, wParam, lParam, pResult);
}
此外,事实证明PreTranslateMessage是我可以使用的选项,尽管当时我没有看到如何。
BOOL TCADTLSandbox2Dialog::PreTranslateMessage(MSG* pMsg)
{
// Send the message to the control (the dialog gets it by default)
if (pMsg->message == WM_MOUSEWHEEL)
{
openGlControlOnThread.PostMessageToControl(pMsg->message, pMsg->wParam, pMsg->lParam);
return true;
}
return MCDialog::PreTranslateMessage(pMsg);
}
希望这对其他人有所帮助!
- 在createdialog创建的窗口中捕获用于编辑控件的OnMouseMove消息
- WinAPI 在单击第一个对话框上的按钮控件并销毁第一个对话框后创建第二个对话框
- 在编译时,C++项目抛出错误 C2228,这是预期的,因为控件在运行时未达到该点
- 如何更改窗体上所有控件的标题?[C++生成器]
- 双击更改 mfc 中列表控件中的行的颜色
- 派生的 wxPanel 控件如何访问其中包含 wxDialog 中的数据?
- 如何从代码本身向 wxwidgets 中的文本控件插入字符?
- 如何在MFC中的静态文本控件上插入图标?
- 我的主窗口在创建时或单击更新区域时是否会收到编辑控件?
- 如何在Qt C++中向自定义控件添加属性?
- C/C++ 检测双击 TVItem 的常用控件
- 从C++标头中导入常量而不是硬编码它们:扩展 .net 控件?
- 控件不会在选择函数旁边移动
- MFC:我们能否扩展CEditView中存在的CEdit控件类行为
- 通过嵌入式 IWebBrowser2 控件中的链接打开 youtube 搜索失败
- 如何在 ActiveX 控件中正确初始化 OpenGL
- 用c++和OpenGL创建一个带有简单控件的简单窗口
- 如何在Win32(C++)静态控件中正确渲染OpenGL
- 在 MFC 对话框和自己的线程上的 OpenGL 控件之间传递消息
- 在Windows桌面应用程序的OpenGL窗口上显示WPF控件