Confirmation: Windows: Ways to prevent "freezeups" when clicking wxFrames Title Bar?

Do you have a typical platform dependent issue you're battling with ? Ask it here. Make sure you mention your platform, compiler, and wxWidgets version.
Post Reply
spiffy
Earned a small fee
Earned a small fee
Posts: 15
Joined: Wed Dec 13, 2017 6:24 pm

Confirmation: Windows: Ways to prevent "freezeups" when clicking wxFrames Title Bar?

Post by spiffy »

Hi:

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
Last edited by spiffy on Fri Dec 15, 2017 12:53 pm, edited 3 times in total.
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Confirmation: Windows: Ways to prevent "freezeups" when clicking wxFrames Title Bar?

Post by doublemax »

Which wxWidgets version are you using? There is a bug only in wx 3.0.3 that prevents some events from being processed while the user moves the window with the caption bar.
http://trac.wxwidgets.org/ticket/17579

For "normal" wxDC based painting, refreshing the window from a timer event should work. But i don't know about OpenGL based drawings.

Small sample based on the "minimal" sample added (there's a hardcoded path to a bitmap that you need to adjust).
Attachments
minimal.cpp
(7.97 KiB) Downloaded 80 times
Use the source, Luke!
spiffy
Earned a small fee
Earned a small fee
Posts: 15
Joined: Wed Dec 13, 2017 6:24 pm

Re: Confirmation: Windows: Ways to prevent "freezeups" when clicking wxFrames Title Bar?

Post by spiffy »

doublemax wrote:Which wxWidgets version are you using? There is a bug only in wx 3.0.3 that prevents some events from being processed while the user moves the window with the caption bar.
http://trac.wxwidgets.org/ticket/17579

For "normal" wxDC based painting, refreshing the window from a timer event should work. But i don't know about OpenGL based drawings.

Small sample based on the "minimal" sample added (there's a hardcoded path to a bitmap that you need to adjust).
Providing a version number might be helpful, right? :)

I'm running against 3.0.3. I'll try out 3.1 tonight and post the results.

Thanks DoubleMax.
spiffy
Earned a small fee
Earned a small fee
Posts: 15
Joined: Wed Dec 13, 2017 6:24 pm

Re: Confirmation: Windows: Ways to prevent "freezeups" when clicking wxFrames Title Bar?

Post by spiffy »

Alright, so I'm having the same issue with both 3.0.3 and 3.1. Here's what I've tested and my system config:

> Windows 7 and 8.1 x64
> Visual Studio 2013 (v12)
> wxWidgets 3.0.3 and 3.1 (x86) (haven't tried 64-bit)
> Apps Tested: minimal.cpp (from doublemax above) and my own app.
> Link Type: static (non-monolithic)
> Extra Pre-processorr Flags:
_UNICODE;
wxNO_AUI_LIB;
wxNO_HTML_LIB;
wxNO_MEDIA_LIB;
wxNO_NET_LIB;
wxNO_QA_LIB;
wxNO_PROPGRID_LIB;
wxNO_REGEX_LIB;
wxNO_RIBBON_LIB;
wxNO_RICHTEXT_LIB;
wxNO_STC_LIB;
wxNO_WEBVIEW_LIB;
wxNO_XML_LIB;
wxNO_XRC_LIB

> Symptoms: Mouse left-click on title/caption bar causes short freeze. Right-click freezes until released.

I've attached a test solution for VS2013 / VC12. You'll need to change the lib/include dirs to your own wxWidgets path.
Attachments
MSWFrameFreezeTest.zip
Test Solution for VS2013 (v12)
(28.18 KiB) Downloaded 79 times
User avatar
doublemax
Moderator
Moderator
Posts: 19115
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Confirmation: Windows: Ways to prevent "freezeups" when clicking wxFrames Title Bar?

Post by doublemax »

Symptoms: Mouse left-click on title/caption bar causes short freeze. Right-click freezes until released.
Yes, i see pretty much the same (haven't tried the right click before). I don't think there is any easy way to overcome this.

I tried a few triple-A games in windowed mode, all of them freeze when you grab the caption bar. Is that really such a big issue in your application?
Use the source, Luke!
spiffy
Earned a small fee
Earned a small fee
Posts: 15
Joined: Wed Dec 13, 2017 6:24 pm

Re: Confirmation: Windows: Ways to prevent "freezeups" when clicking wxFrames Title Bar?

Post by spiffy »

doublemax wrote:
Symptoms: Mouse left-click on title/caption bar causes short freeze. Right-click freezes until released.
Yes, i see pretty much the same (haven't tried the right click before). I don't think there is any easy way to overcome this.

I tried a few triple-A games in windowed mode, all of them freeze when you grab the caption bar. Is that really such a big issue in your application?
Unfortunately, due to the nature of my application, this freeze-up is something I want to avoid. I have up to 8 frames open at one time, some of which have DC animations or OpenGL graphics. These graphics are displayed to large groups of people for interactive presentations. The user of the application (presenter) may be moving windows around at different times. The freeze-ups would not be very "graceful".

I don't foresee any right-clicking going on, but there is that small delay on left-clicks before the dragging begins, which is somewhat annoying.

My goal here was to just make sure there wasn't another (easier) solution. I have the workaround that I posted above for now, which should suffice until something else comes along.

Thanks doublemax.
Post Reply