I am attempting to convert one of my application written in C# - Windows Forms to C++ - wxWidgets.
My app is borderless and has a thin, transparent panel on top of the form which can be used to move the form. (I used the technique from this question: Make a borderless form movable?)
Now, I basically want to do the same thing in wxWidgets, I've searched around the internet on how to handle a mouse down event over a wxPanel and found a couple examples but both used wxPython in their article/question and I have no knowledge about Python at all.
So how to do the same thing in C++ - wxWidgets?
One way is for the window to register an event handler for each of its child windows' mouse down events. That way the window can take control of the mouse if a certain condition is met (e.g. the Alt key is held down while clicking).
Some of this stuff is illustrated in the wxwidgets\samples\shaped\shaped.cpp sample but basically you do this:
Add a method to your window that you call after all the child windows have been added:
void MyFrame::BindChildEvents()
{
visit_recursively(this,
[] (wxWindow *window, MyFrame *thiz) {
// Bind all but the main window's event
if(window != thiz)
{
window->Bind(wxEVT_LEFT_DOWN, &MyFrame::OnChildLeftDown, thiz);
}
},
this
);
}
You can roll your own window tree traversal but I use this little helper function here:
template<typename F, typename... Args>
void
visit_recursively(wxWindow *window, F func, Args&&... args)
{
for(auto&& child : window->GetChildren())
{
visit_recursively(child, func, std::forward<Args>(args)...);
}
func(window, std::forward<Args>(args)...);
}
Then you set up your mouse down event interception handler:
void MyFrame::OnChildLeftDown(wxMouseEvent& event)
{
// If Alt is pressed while clicking the child window start dragging the window
if(event.GetModifiers() == wxMOD_ALT)
{
// Capture the mouse, i.e. redirect mouse events to the MyFrame instead of the
// child that was clicked.
CaptureMouse();
const auto eventSource = static_cast<wxWindow *>(event.GetEventObject());
const auto screenPosClicked = eventSource->ClientToScreen(event.GetPosition());
const auto origin = GetPosition();
mouseDownPos_ = screenPosClicked - origin;
}
else
{
// Do nothing, i.e. pass the event on to the child window
event.Skip();
}
}
And you handle mouse motion by moving the window along with the mouse:
void MyFrame::OnMouseMove(wxMouseEvent& event)
{
if(event.Dragging() && event.LeftIsDown())
{
const auto screenPosCurrent = ClientToScreen(event.GetPosition());
Move(screenPosCurrent - mouseDownPos_);
}
}
Be sure to call ReleaseMouse() in the wxEVT_LEFT_UP and wxEVT_MOUSE_CAPTURE_LOST events.
"how to fire a mouse down event?". You do not need to worry about 'firing' the event - the OS does that. You need to handle the event which is EVT_LEFT_DOWN. Is your question about how to handle wxWidgets events? Have you looked at the sample programs? http://docs.wxwidgets.org/2.6/wx_samples.html They are all in C++.
There is a description of how to handle events here: http://docs.wxwidgets.org/2.6/wx_eventhandlingoverview.html#eventhandlingoverview
If you question is about something more specific in the details of handling the EVT_LEFT_DOWN event, then please post your code, describe what you want it to do and what it does instead.
Related
(Update, see original question below)
After doing a bit of digging, I'm basically trying to understand the following; In the context of an MDI application, if a menu (which is associated with a specific CChildWnd) has an MF_OWNERDRAW, why are the ON_WM_MEASUREITEM and ON_WM_DRAWITEM events send to the CMainWnd instead of the CChildWnd?
In my InitInstance, the document template is registered and the associated menu is modified to add the MF_OWNERDRAW:
BOOL CMyApp::InitInstance()
{
// ...
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_CHILDFRAME,
RUNTIME_CLASS(CFooDoc),
RUNTIME_CLASS(CFooWnd),
RUNTIME_CLASS(CFooView)
);
if (pDocTemplate->m_hMenuShared != NULL) {
CMenu* pMenu = CMenu::FromHandle(pDocTemplate->m_hMenuShared);
// Add MF_ONWERDRAW to the items that need it.
pMenu->ModifyMenu([item_id], MF_BYCOMMAND | MF_OWNERDRAW, [item_id]);
}
AddDocTemplate(pDocTemplate);
// ...
}
So, once the document template is registered, the menu associated with the document/frame is modified to add the MF_ONWERDRAW flag to each of the required items (the color selection items in my case).
However, why are the OnMeasureItem and OnDrawItem events send to the CMainWnd and not the CFooWnd? And how can I direct the events to the CFooWnd instead?
The reason I'am asking, if I have 5 different types of documents in my MDI application, each needing custom menus, then the CMainWnd basically becomes a mess of message handling. The logical place for the custom menu logic is in the CChildWnd, not the CMainWnd.
Original question:
I'm doing some work on a very old application (MFC 4.2) and I'm running into a problem with drawing in a menu item.
The original application has a menu to select a color and it actually draws the colors in the menu when opened so it easier for the user to select the color.
The behavior for this implemented in CMainWnd using the OnMeasureItem and the OnDrawItem.
class CMainWnd : public CMDIFrameWnd
{
DECLARE_DYNCREATE(CMainWnd)
protected:
afx_msg void OnMeasureItem(int, LPMEASUREITEMSTRUCT);
afx_msg void OnDrawItem(int, LPDRAWITEMSTRUCT);
DECLARE_MESSAGE_MAP()
};
Then, in the implementation (omitted bits and pieces for brevity):
BEGIN_MESSAGE_MAP(CMainWnd, CMDIFrameWnd)
ON_WM_MEASUREITEM()
ON_WM_DRAWITEM()
END_MESSAGE_MAP()
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
lpmis->itemWidth = ::GetSystemMetrics(SM_CYMENU) * 4;
lpmis->itemHeight = ::GetSystemMetrics(SM_CYMENU) * 1;
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CDC dc;
dc.Attach(lpdis->hDC);
CBrush* pBrush;
// draw the hover/selection rectangle
pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT :
COLOR_MENU));
dc.FrameRect(&(lpdis->rcItem), pBrush);
delete pBrush;
// load a checkbox icon into a bitmap
BITMAP bm;
CBitmap bitmap;
bitmap.LoadOEMBitmap(OBM_CHECK);
bitmap.GetObject(sizeof(bm), &bm);
// if color/item selected then draw the checkbox
if (lpdis->itemState & ODS_CHECKED) {
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
dc.BitBlt(
lpdis->rcItem.left + 4,
lpdis->rcItem.top + (((lpdis->rcItem.bottom - lpdis->rcItem.top) - bm.bmHeight) / bm.bmWidth,
bm.bmHeight,
&dcMem,
0,
0,
SRCCOPY
);
dcMem.SelectObject(pOldBitmap);
}
// draw the actual color bar
pBrush = new CBrush(CPaintDoc::m_crColors[lpdis->itemID - ID_COLOR_BLACK]);
CRect rect = lpdis->rcItem;
rect.DeflateRect(6, 4);
rect.left += bm.bmWidth;
dc.FillRect(rect, pBrush);
delete pBrush;
dc.Detach();
}
What the OnDrawItem does is; it draws a horizontal color bar with a color, prefixed by a check icon if that color is selected and the menu item being hovered over is highlighted by a box being drawn around it.
However, since I'm turning this application into a Multidoc application and I don't really feel that this logic should be in the CMainWnd (since none of the other documents will have this type of menu), but that it should be part of the CChildWnd (which inherits from CMDIChildWnd).
But when I move this logic to that class, when I run the application, I get following message in the console logger:
Warning: unknown WM_MEASUREITEM for menu item 0x0082.
And none of the custom menu behavior seems to work.
so, the question is; How can move the custom behavior of a menu into the frame class of an MDI document rather than having it located in the application main frame?
I figured out a work around. Not ideal but I can understand that this is a quirk in the framework, i.e. the menu seems to be part of the MainWnd so from a technical point of view, that is where the ON_WM_MEASUREITEM and ON_WM_DRAWITEM would be handled.
Anyhow, my work around. Basically capture the events in the MainWnd and then delegate the behaviour to the ChildWnd. The trick here (I guess) is to figure out what ChildWnd to delegate to since in an MDI application there can be any number of different ChildWnd's (each with their own Document and View types).
The work around:
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoMeasureItem(nIDCtl, lpmis);
}
}
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoDrawItem(nIDCtl, lpdis);
}
}
}
Pretty straight forward, in the context of the MainWnd, get a pointer to the active MDI ChildWnd, check if it is active, then check the type by using IsKindOf and RUNTIME_CLASS and if so, voila, delegate the behavior to the ChildWnd. To DoMeasureItem and the DoDrawItem are just public methods implemented on the ChildWnd (see question for details).
What I'm trying to achieve
Well, the title might not have explained the problem very well, so here goes:
I am trying to create a Win32 app using MFC that lets you edit and inspect other windows.
I want the user to be able to select other windows.
I got inspired by the "Find Window Process" tool on the toolbar on sysinternals applications such as ProcessExplorer.
The way it works is you click, then the window disappears, and then you drag it over the window you want to select. A border pops up around it and when you let go, it selects the window the mouse is over.
My problem
The problem I was facing is that I don't know how to detect when the user lets go of the mouse on another window.
I detect mouse down using OnClick in CMFCToolBarButton
I tried using SetCapture() but that did nothing.
I tried using OnNcLButtonUp and OnLButtonUp but neither of them worked. (alongside SetCapture)
Here's my code so far (ChildView.cpp):
BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
ON_UPDATE_COMMAND_UI(ID_TB_LOCATEWINDOW, &CChildView::EnableToolbarButton)
ON_UPDATE_COMMAND_UI(ID_TOOLS_MESSAGELAUNCHER, &CChildView::EnableToolbarButton)
ON_WM_XBUTTONUP()
// ON_WM_LBUTTONUP()
ON_WM_NCLBUTTONUP()
END_MESSAGE_MAP()
....
void CChildView::LocateWindow()
{
GetParentFrame()->ShowWindow(SW_MINIMIZE);
SetCapture();
}
void CChildView::OnNcLButtonUp(UINT nHitTest, CPoint point)
{
ReleaseCapture();
GetParentFrame()->ShowWindow(SW_NORMAL);
MessageBox(L"Stuff", L"");
CWnd::OnNcLButtonUp(nHitTest, point);
}
I want to mention that the LocateWindow function gets called when the toolbar button is clicked (as in mouse down, not mouse down AND up)
It is called from the OnClick function.
Here's the code for that:
(I replace the button with OnToolbarReset)
// CLocateWindowButton.cpp : implementation file
//
#include "pch.h"
#include "WindowHacker.h"
#include "MainFrm.h"
#include "CLocateWindowButton.h"
// CLocateWindowButton
IMPLEMENT_SERIAL(CLocateWindowButton, CMFCToolBarButton, 1)
// CLocateWindowButton member functions
CLocateWindowButton::CLocateWindowButton()
{
}
CLocateWindowButton::CLocateWindowButton(CMainFrame* mainFrame, UINT uiCmdID, LPCTSTR lpszText) : CMFCToolBarButton(uiCmdID, NULL, lpszText)
{
this->mainFrame = mainFrame;
}
BOOL CLocateWindowButton::OnClick(CWnd* pWnd, BOOL bDelay = TRUE) {
//(CMainFrame*)m_pWndParent->LocateWindow();
mainFrame->LocateWindow();
return FALSE;
}
void CLocateWindowButton::CopyFrom(const CMFCToolBarButton& src)
{
CMFCToolBarButton::CopyFrom(src);
mainFrame = ((CLocateWindowButton&)src).mainFrame;
}
//void CLocateWindowButton::AssertValid() const
//{
// CMFCToolBarButton::AssertValid();
//
// // TODO: Add your specialized code here and/or call the base class
//}
UPDATE:
It seems to work when I put it inside an LButtonDown event, it just seems to not work when it is being detected from OnClick in CMFCToolBarButton
I found that in CMFCToolBar::OnLButtonUp, after calling OnClick in the button, it recaptures the cursor, invalidating our SetCapture.
BUT if I return TRUE instead of FALSE in OnClick, the mouse is not recaptured.
So changing this:
BOOL CLocateWindowButton::OnClick(CWnd* pWnd, BOOL bDelay = TRUE) {
//(CMainFrame*)m_pWndParent->LocateWindow();
mainFrame->LocateWindow();
//ReleaseCapture();
this->mainFrame->SetCapture();
return FALSE;
}
To this:
BOOL CLocateWindowButton::OnClick(CWnd* pWnd, BOOL bDelay = TRUE) {
//(CMainFrame*)m_pWndParent->LocateWindow();
mainFrame->LocateWindow();
//ReleaseCapture();
this->mainFrame->SetCapture();
return TRUE; // The line is changed here
}
The message gets sent to CMainFrame instead.
This is my code:-
DWORD WINAPI ThreadMouse(void* data){
while (1){
//Check the mouse left button is pressed or not
if ((GetKeyState(VK_LBUTTON) & 0x8000) != 0)
{
MessageBox(NULL, L"Left Clicked", L"", NULL);
break;
}
//Check the mouse right button is pressed or not
if ((GetKeyState(VK_RBUTTON) & 0x100) != 0)
{
MessageBox(NULL, L"Right Clicked", L"", NULL);
break;
}
}
return 0;
}
I am getting the message multiple times instead of once
This looks like a thread, which polls the state of the mouse.
Not something that gets mouse events.
Windows can receive events.
I have a project with :-
class CImageDisplay :
public wxScrolledWindow,
public IToolTarget
{
/// omitted
void OnMouseDClick( wxMouseEvent& mouseEvent );
void OnMouseUp( wxMouseEvent& mouseEvent );
};
Where wxScrolledWindow is derived from wxWindow.
That declares functions for the mouse events.
In the cpp file, I have...
BEGIN_EVENT_TABLE(CImageDisplay, wxScrolledWindow)
EVT_MOTION(CImageDisplay::OnMouseMove )
EVT_LEFT_DOWN(CImageDisplay::OnMouseDown )
EVT_LEFT_DCLICK(CImageDisplay::OnMouseDClick )
EVT_LEFT_UP(CImageDisplay::OnMouseUp )
// omitted
END_EVENT_TABLE()
This tells wx to call my functions when the events (EVT_* occurs).
Then I can track the mouse events as they happen, but otherwise my code is not doing anything.
Check out the wx documentation (e.g. wx documentation mouse events for what events are available, and what limitations they have.
Check out the samples - e.g. plot.cpp which uses mouse events.
Your thread does not have a GUI for the user to interact with, so its key state is never updated. Each thread has its own key state, which is updated during UI message processing.
If you want to monitor the mouse in a thread without a GUI, you have two choices:
use SetWindowsHookEx() to hook the mouse using a WH_MOUSE or WH_MOUSE_LL hook. The thread will need a message loop to service the hook.
Have your thread create a hidden window (and message loop) and then use RegisterRawInputDevices() to have the mouse send WM_INPUT messages to your window.
I am using WM_INPUT to detect my mouse updates, when I click my mouse button 1, I will detect that button 1 is down. However if i hold down the button, the subsequent updates for the mouse will show that button 1 is not depressed.
MSDN shows that usButtonFlags can detect transition state of the mouse buttons. This means rawinput can detect the mouse at the time of press and release but cannot detect the pressed or released state of mouse buttons.
But is it possible for the rawinput to detect the pressed or released state of mouse buttons instead of a transition state?
I used OSG to render a full screen scene, thus, all the mouse clicks should be associated with the current window. I use eventTraversal function of osgViewer::Viewer to update the raw input data:
class CustomViewer : public osgViewer::Viewer
{
public:
CustomViewer() : osgViewer::Viewer() {}
virtual ~CustomViewer() {}
virtual void eventTraversal()
{
RawInputEventRegistry::instance()->updateState( _eventQueue.get() );
osgViewer::Viewer::eventTraversal();
}
}
void RawInputEventRegistry::updateState( osgGA::EventQueue* eventQueue )
{
MSG msg;
if( GetMessage( &msg, c_handle, WM_INPUT, WM_INPUT ) != -1)
{
HRAWINPUT test = (HRAWINPUT)msg.lParam;
add_to_raw_mouse_x_and_y((HRAWINPUT)msg.lParam);
}
osg::ref_ptr<RawInputEvent> event = new RawInputEvent;
event->SetMouseData(raw_mice);
eventQueue->userEvent( event.get() );
}
The add_to_raw_mouse_x_and_y() function is responsible for button click checking, such as:
if (raw->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) raw_mice[i].buttonpressed[0] = 1;
At first my code set up the SDL environment, and proceeded to update the OpenGL context, without performing any SDL_Event processing whatsoever. This causes the window, as long as it was open, to appear to Windows to be unresponsive. The window flickers a bit. The titlebar would get "(Not Responding)" appended to it, and upon clicking inside the window it becomes grayed out, as Windows does this by default on non responsive windows. However in this state (even as and after it becomes grayed out), the OpenGL display continues to update and animate, and here's the kicker, it even does so while the window is being dragged. Clearly in this case the application isn't handling events from windows correctly, causing windows to think that it is in a hanged state. But there is clear evidence that the opengl continues to render.
Now I make one single modification to the code, which is these three lines placed in an appropriate spot inside the loop (which also does the OpenGL draw):
SDL_Event event;
if (SDL_PollEvent(&event) && event.type == SDL_QUIT)
break;
All this is doing is flushing the message queue using SDL.
Now the behavior is that Windows no longer thinks it is "Not Responding" and it does not get grayed out. No flicker. Everything seems to run swimmingly. But once I click and drag the title bar to drag the window, rendering gets blocked. I haven't debugged it to be sure, but I suspect that SDL_PollEvent blocks for the duration of the window drag.
Is there a way around this? This is interesting because part of the behavior exhibited by failing to handle events is proof that what I want is possible in theory.
Update: I found this thread: http://www.gamedev.net/topic/488074-win32-message-pump-and-opengl---rendering-pauses-while-draggingresizing/
The verdict seems to be that it comes down to certain choices that Microsoft made for us... It basically gets stuck in DefWindowProc() till the mouse is released. It would get very messy to hack a fix for this and I might be able to do a work around by rendering in another thread. But I don't even want to begin to think about juggling an OpenGL context from multiple threads, if that's even something that's possible.
Some workaround that works for me - add event filter for SDL_WINDOWEVENT_SIZE_CHANGED event and do additional SetViewport and draw frame.
int SDLApp::eventFilter(void* pthis, const SDL_Event *event)
{
if (event->type == SDL_WINDOWEVENT &&
event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
{
SDLApp* app = (SDLApp*)pthis;
// Note: NULL rectangle is the entire window
SDL_RenderSetViewport(app->renderer_, NULL);
app->DrawFrame();
}
return 1;
}
...
SDL_SetEventFilter((SDL_EventFilter)SDLApp::eventFilter, this);
This question is old, but the solution I'm using doesn't seem to be mentioned anywhere else, so here it is.
I got my inspiration from this answer, and it doesn't use additional threads.
#include <SDL.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <SDL_syswm.h>
#define SIZE_MOVE_TIMER_ID 1
bool sizeMoveTimerRunning = false;
int eventWatch(void*, SDL_Event* event) {
if (event->type == SDL_SYSWMEVENT) {
const auto& winMessage = event->syswm.msg->msg.win;
if (winMessage.msg == WM_ENTERSIZEMOVE) {
// the user started dragging, so create the timer (with the minimum timeout)
// if you have vsync enabled, then this shouldn't render unnecessarily
sizeMoveTimerRunning = SetTimer(GetActiveWindow(), SIZE_MOVE_TIMER_ID, USER_TIMER_MINIMUM, nullptr);
}
else if (winMessage.msg == WM_TIMER) {
if (winMessage.wParam == SIZE_MOVE_TIMER_ID) {
// call your render function
render();
}
}
}
return 0;
}
// rendering function
void render() {
/* do your rendering here */
}
// event loop - call this function after setting up your window to start the event loop
void eventLoop() {
SDL_AddEventWatch(eventWatch, nullptr); // register the event watch function
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); // we need the native Windows events, so we can listen to WM_ENTERSIZEMOVE and WM_TIMER
while (true) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (sizeMoveTimerRunning) {
// modal drag/size loop ended, so kill the timer
KillTimer(GetActiveWindow(), SIZE_MOVE_TIMER_ID);
sizeMoveTimerRunning = false;
}
/* handle the events here */
}
render();
}
}
Of course, if your rendering function needs to keep additional state (e.g. if you're using OOP), use the void* parameter of eventWatch(void*, SDL_Event*) to pass the state.
I had a similar problem in which it would freeze video playback when the window was dragged or resized. The solution I found was to spawn a separate thread for rendering and use the main thread for input.
Example:
DWORD RenderThread(SDL_Window* window)
{
//Rendering stuff here...
}
int main()
{
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window* window = SDL_CreateWindow("Title Here",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, h, w, SDL_WINDOW_RESIZABLE);
HANDLE hRenderThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RenderThread, window, 0, NULL);
SDL_Event event;
while (1)
{
SDL_PollEvent(&event);
switch (event.type)
{
//Event handling here...
}
}
}
Keep in mind that you MUST create the window in the thread that does event handling. If not it won't work. You can create the window in your event handling thread then pass that window pointer to your rendering thread.
I propose you created 2 threads:
Thread 1: loops calling SDL_PollEvent() (without rendering anything)
Thread 2: does OpenGL rendering (without calling SDL_PollEvent())
This way, your OpenGL context would be manipulated from a single thread. The whole solution has a minimum impact the architecture of your application.
Many windows procedures run a separate message loop until a certain event occurs, so you shouldn't rely on your main loop to do the drawing. If possible, application logic and rendering should always be handled in a separate thread.
Your main thread (that only handles message processing) doesn't need GL context at all, so you wouldn't need to worry about sharing.