wxWidgets is awesome. Thanks for all the hard work over the years.
I'm developing a cross-platform app that utilizes multiple DC animations and OpenGL canvas, often running/shown at the same time. I've been mainly developing on WIndows (Seven & Eight). I haven't done any OSX or Linux testing in quite a while. I'm not sure if this issue exists on other platforms.
When I click a wxFrame on the title bar, the main GUI render loop freezes up, also causing my animations (DC and OpenGL) to freeze up. Left-click freezes for about a second, and right-click freezes until I release the right mouse button (brutal). Actual movement of the frame after left-clicking does not affect the GUI loop.
Poking around on the web reveals that this isn't specific to wxWidgets. I also remember experiencing the same issue with various programs.
That said, I have a work-around (posted below) that allows me to skip the hangup by bypassing the relevant windows message via MSWWindowProc and manually moving the window using calculated mouse positions. I use a wxTimer to kick off the move and a wxStopWatch to help with precision and fast mouse movements (windows wxTimer ~15ms limitation).
The tricky parts:
> Working nicely with the caption / titlebar buttons (min, max, close). I still needed to be able to catch those events accurately.
> Still allow for resizing via the borders.
> Locations returned by DefWindowProc() after WM_NCHITTEST aren't accurate. Results seem based on which direction the mouse came from. If coming from under the title bar, the returned value is HTMENU (if using a menu bar), even though coordinates are within the HTCAPTION area. Similarly, HTTOP is returned if having come from the top of the window.
Are there any other solutions out there that are more elegant? This works fine for now but is untested on any other windows versions or themes than the ones I use.
Thanks!
Code: Select all
// class header declarations
#ifdef WIN32
public:
bool Create(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxDEFAULT_FRAME_STYLE);
private:
void OnTimerMSWMove(wxTimerEvent &event);
WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam);
wxTimer _mswTimerMove;
wxStopWatch _mswSWMove;
wxPoint _mswMoveStart;
int _mswBorderSize;
int _mswCaptionBtnsWidth;
int _mswCaptionBarHeight;
short _mswNCHitTestLoc;
bool _mswIsToolWindow;
#endif
// source definitions
#ifdef WIN32
#include <Windows.h>
#define MSWMOVE_OUTER_DELAY_MS 15
#define MSWMOVE_INNER_DELAY_MS 1
bool DerivedFrame::Create(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos, const wxSize &size, long style){
bool ret = wxFrame::Create(parent, id, title, pos, size, style);
// standard caption buttons: min, max, close
// tool window caption buttons: 1 close button
_mswIsToolWindow = (style & wxFRAME_TOOL_WINDOW) == wxFRAME_TOOL_WINDOW;
_mswTimerMove.Connect(wxEVT_TIMER, wxTimerEventHandler(DerivedFrame::OnTimerMSWMove), NULL, this);
RECT cRect;
RECT wRect;
::GetClientRect(GetHandle(), &cRect);
::GetWindowRect(GetHandle(), &wRect);
_mswBorderSize = ((wRect.right - wRect.left) - (cRect.right -cRect.left)) / 2;
_mswCaptionBtnsWidth = GetSystemMetrics(SM_CXSIZE) * (_mswIsToolWindow ? 1 : 3);
_mswCaptionBarHeight = GetSystemMetrics(SM_CYCAPTION);
_mswNCHitTestLoc = 0;
return ret;
}
void DerivedFrame::OnTimerMSWMove(wxTimerEvent &event){
_mswSWMove.Start();
while(_mswSWMove.Time() < MSWMOVE_INNER_DELAY_MS)
if (GetKeyState(VK_LBUTTON) < 0){
wxPoint mousePos = wxGetMousePosition();
Move(wxPoint(
mousePos.x - _mswMoveStart.x,
mousePos.y - _mswMoveStart.y));
} else _mswTimerMove.Stop();
}
WXLRESULT DerivedFrame::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam){
if (IsShown()){
switch(nMsg){
// only process left-down mouse clicks...
case WM_NCLBUTTONDBLCLK:
case WM_NCMBUTTONDBLCLK:
case WM_NCRBUTTONDBLCLK:
case WM_NCXBUTTONDBLCLK:
case WM_NCMBUTTONDOWN:
case WM_NCRBUTTONDOWN:
case WM_NCXBUTTONDOWN:
nMsg = 0;
break;
case WM_NCLBUTTONDOWN:
if (_mswNCHitTestLoc == HTCLOSE){
// do manual close here
} else if (_mswNCHitTestLoc == HTMINBUTTON){
// do manual iconize here
} else if (_mswNCHitTestLoc == HTMAXBUTTON){
nMsg = 0; // already disabled in wxWidgets
} else {
// find the title bar area that is acceptable for moves:
// consider borders and control/caption button box size...
wxPoint pStart = GetScreenPosition();
pStart.x += _mswBorderSize;
pStart.y += _mswBorderSize;
wxPoint pEnd(wxPoint(
pStart.x + GetClientSize().GetWidth() - _mswCaptionBtnsWidth + _mswBorderSize, // extra border for caption button spacing
pStart.y + _mswCaptionBarHeight));
wxPoint mousePos = wxGetMousePosition();
// if in normal move area...
bool inside_move_area =
mousePos.x > pStart.x && mousePos.x < pEnd.x &&
mousePos.y > pStart.y && mousePos.y < pEnd.y;
// if not, check in the dead space under caption buttons (skip tool windows)...
if (!_mswIsToolWindow && !inside_move_area)
inside_move_area =
mousePos.x > pEnd.x && mousePos.x < pStart.x + GetClientSize().GetWidth() &&
mousePos.y > pStart.y + _mswCaptionBarHeight - _mswBorderSize && mousePos.y < pEnd.y;
// inside area for moving window...
if (inside_move_area){
SetFocus();
_mswTimerMove.Start(MSWMOVE_OUTER_DELAY_MS);
nMsg = 0;
}
}
break;
// update the mouse location when inside the non-client area...
case WM_NCMOUSEMOVE:
if (!_mswTimerMove.IsRunning()){
wxPoint mousePos = wxGetMousePosition();
_mswMoveStart = wxPoint(
mousePos.x - GetScreenPosition().x,
mousePos.y - GetScreenPosition().y);
}
break;
// get location of non-client area...
case WM_NCHITTEST:
_mswNCHitTestLoc = DefWindowProc(GetHandle(), nMsg, wParam, lParam);
break;
}
}
return wxFrame::MSWWindowProc(nMsg, wParam, lParam);
}
#endif