However when I click to the caption of a child frame
with the left mouse button (single click!!) this frame
gets maximized immediately. Using SPY++ I see a
WM_NCLBUTTONDBLCLK, which is handled correctly (i.e.
frame gets maimized).
I cannot figure it out what causes the WM_NCLBUTTONDBLCLK
message instead of a WM_NCLBUTTONDOWN message.
When I build a new project using appwizard I cannot
reproduce this wrong behaviour. So it must be something
in my code.
Could anybody please have a look at my code and try to
find out what I am doing wrong. Just see the source code
at http://www.8ung.at/kotyczka/opengl_en.html. Don't worry
about the OpenGL stuff.
I know that I could fix it by overwriting the WM_NCLBUTTONDBLCLK
message. But then the child frame would not maximize, even if I
really double click it's caption. This is not a solution either.
What I want is to catch the wrong WM_NCLBUTTONDBLCLK message.
TIA Uwe.
> Could anybody please have a look at my code and try to
> find out what I am doing wrong. Just see the source code
it's this one in COpenGLSampleView::OnActivateView that causes the trouble:
m_bLastActivateWasDoneByLButton = PeekMessage(&msg, m_hWnd, WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, PM_NOREMOVE);
I've removed the assignment to m_bLastActivateWasDoneByLButton to make
sure there is no side effect, but that does not solve the problem. It's the mere
PeekMessage call. Not really an idea what's wrong with it, just some speculation:
MSDN states "During this call, the system delivers pending messages that were
sent to windows owned by the calling thread" and "The system may also process
internal events." Probably some message or internal event is processed out of
order and causes confusion to the MDI child window management.
Looking at the spy message trace there actually are some notable differences. In the
good case immediately after WM_NCLBUTTONDOWN message there is a
WM_LBUTTONUP message followed by a WM_CAPTURECHANGED
message indicating release of a capture. In the bad case you can see the
WM_NCLBUTTONDOWN message as well but there is no WM_LBUTTONUP
message and no WM_CAPTURECHANGED message. Instead an additional
WM_MOUSEACTIVATE message occurs. I suspect the button click is processed
twice due to some confusion caused by the PeekMessage call. But as I said before:
that's all speculation.
Regards,
Frank
"Uwe Kotyczka" <uwe.ko...@web.de> wrote in message
news:4cd8ac7a.04060...@posting.google.com...
Thanks for puzzling this out. I could even reproduce this behaviour
in new MDI project: I built a new MDI project (using app wizard).
The only change I made was that I put a "PeekMessage" call into the
virtual OnActivateView function of the view class.
This was enough to reproduce that weird message mess.
Any idea how to get around that?
As far as I understand your code, the intention is to ignore a mouse click if it is the one that
activates the MDI child window. The problem is that you don't know if OnActivateView has
been called due to a mouse click activation or due to some other cause (e.g. Ctrl+Tab).
Unfortunately there are lots of places in the MFC where OnActivateView is called and
you cannot easily tell them from each other. The interesting call is the one that takes place
in CMDIChildWnd::OnMDIActivate but that still doesn't help too much as there is no
information passed to OnMDIActivate concerning the activation cause.
We will have to have a closer look at what happens in MDI child window activation to
find a possible solution. The MDI client window listens to WM_PARENTNOTIFY
messages (not WM_MOUSEACTIVATE as one could think) to get to know about
mouse clicks into a descendant window. WM_PARENTNOTIFY messages are sent
to all ancestors of a clicked window (not the clicked window itself; that's why it has
PARENT in its name) beginning from the immediate parent up to the top level parent.
In contrast to other messages this is done sequentially and not recursively. As soon as
the MDI client receives a WM_PARENTNOTIFY message it activates the MDI child
window that is parent of the clicked window.
A possible solution now is to implement a WM_PARENTNOTIFY message handler
on your MDI client class, set a flag, call the base class and clear the flag again:
void CClientFrame::OnParentNotify (UINT message, LPARAM)
{
if (message == WM_LBUTTONDOWN)
m_bInMouseActivate = true;
CWnd::OnParentNotify ();
m_bInMouseActivate = false;
}
In OnActivateView you can tell from the flag if this is a mouse activation or some other
kind.
I'm not sure if this will cover all thinkable cases. You will have to play a little to find out.
Regards,
Frank
> As far as I understand your code, the intention is to ignore a mouse click if it is the one that
> activates the MDI child window. The problem is that you don't know if OnActivateView has
> been called due to a mouse click activation or due to some other cause (e.g. Ctrl+Tab).
You are absolutely right. I use the mouse to start/stop zooming or rotating
an OpenGL view. Of course it is very annoying, if zooming starts immediately
even if the user just wants to activate the view by a mouse click.
> Unfortunately there are lots of places in the MFC where OnActivateView is called and
> you cannot easily tell them from each other. The interesting call is the one that takes place
> in CMDIChildWnd::OnMDIActivate but that still doesn't help too much as there is no
> information passed to OnMDIActivate concerning the activation cause.
>
> We will have to have a closer look at what happens in MDI child window activation to
> find a possible solution. The MDI client window listens to WM_PARENTNOTIFY
> messages (not WM_MOUSEACTIVATE as one could think) to get to know about
> mouse clicks into a descendant window. WM_PARENTNOTIFY messages are sent
> to all ancestors of a clicked window (not the clicked window itself; that's why it has
> PARENT in its name) beginning from the immediate parent up to the top level parent.
> In contrast to other messages this is done sequentially and not recursively. As soon as
> the MDI client receives a WM_PARENTNOTIFY message it activates the MDI child
> window that is parent of the clicked window.
>
> A possible solution now is to implement a WM_PARENTNOTIFY message handler
> on your MDI client class, set a flag, call the base class and clear the flag again:
>
> void CClientFrame::OnParentNotify (UINT message, LPARAM)
> {
> if (message == WM_LBUTTONDOWN)
> m_bInMouseActivate = true;
>
> CWnd::OnParentNotify ();
>
> m_bInMouseActivate = false;
> }
>
> In OnActivateView you can tell from the flag if this is a mouse activation or some other
> kind.
>
> I'm not sure if this will cover all thinkable cases. You will have to play a little to find out.
Well, it sounds that your workaround could work. However it is not really what
I am looking for. Let me explain you why: I use these views in several programs.
Some programs are designed like OpenGLSampleMDI, here I have each view in one
child frame. I can switch the active view by mouse or by Ctrl+Tab.
Other Programs have more than one view in the same child frame
(using nested splitter windows containing the views). Here I can switch
the active view by mouse, by Ctrl+Tab (if I have more than one document open)
or by F6 (views for one document, all in the same child frame).
So I think that it is not a good idea to let the parent window handle it,
it should be the view itself which makes the decision. Everything else
strongly contradicts OO design. (OKOK, MFC is not strictly OO, I know.)
And I would really like to have a unique solution.
And yet another note.
I had the idea of combining OnActivateView and OnMouseActivate.
OnMouseActivate is called every time I click to the view, even if it is
already the active view. And it's parameters do not seem to hold any
information about the active state before clicking.
So my idea was that I set a helper flag in OnActivateView, and in
OnMouseActivate I use that helper flag to set the actual flag.
This would work in a MDI environtemt, here I see OnActivateView followed by
OnMouseActivate. In a SDI environment however first OnMouseActivate is
being called and then OnActivateView. So this idea does not work either.
Sorry, I do not understand your answer at all. Could you explain it?
Guess I understand the problem: you would not want to touch the various involved
window classes in all the different applications that reuse the view. Thought about
having some kind of generic MFC framework extension that could provide the
solution to all applications? Something like CMyGenericMDIClient, CMyGenericMDIChild,
CMyGenericMDIView, ...
> So I think that it is not a good idea to let the parent window handle it,
> it should be the view itself which makes the decision. Everything else
> strongly contradicts OO design. (OKOK, MFC is not strictly OO, I know.)
> And I would really like to have a unique solution.
I agree, the view should decide whether to start zooming, rotating, ... The information
however on which this decision is based upon IMO does not necessarily have to
emerge from the view itself. Actually the OnActivateView call itself is some kind
of information that is transported into the view from the outside. If this call had
another parameter e.g. called bMouseActivate, would you banish this one only
because it was evaluated somewhere outside the view? Combining the idea of
an extra parameter with the framework extension approach from above could
result in something like this:
class CMyGenericView : public CView
{
virtual void OnActivateView (BOOL bActivate, CView* pActivateView, CView* pDeactiveView);
virtual void OnActivateViewExt (BOOL bActivate, CView* pActivateView, CView* pDeactiveView, bool bMouseActivate);
bool IsMouseActivation ();
};
void CMyGenericView::OnActivateView (BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
{
OnActivateViewExt (BOOL bActivate, CView* pActivateView, CView* pDeactiveView, IsMouseActivation ());
}
bool CMyGenericView::IsMouseActivation ()
{
in case of MDI ask MDI client if inside mouse activation
in case of SDI do whatever is necessary to find out
}
How to implement IsMouseActivation in the various cases (without breaking
too many OO design rules) is left to the fantasy of the implementor. The decision
MDI or SDI e.g. is niftily made by involving the (once again generic) top level
frame class.
I would not dare to claim this really meets your requirements. Just take it as
kinda "design study".
And if finally you find a solution that is way off from what we were talking about
here please let me know. Sounds like this problem could eventually strike me as
well.
Use some other (non-zero) ID. Zero ID is not allowed and may interfere with
special timers, for example, double click detection.
From SetTimer docs:
nIDEvent
[in] Specifies a nonzero timer identifier
"Uwe Kotyczka" <uwe.ko...@web.de> wrote in message
news:4cd8ac7a.04060...@posting.google.com...
Thanks for pointing that out. I corrected it.
Unfortunately it was not the reason of my original problem.
You are right, I do not really want to rewrite/extend the framework.
However after playing around a lot I found this solution
which works for me. I have _no_idea_ why it works, but it does.
Really strange.
Take a look at my end version for this peace of code:
void CGlView::OnActivateView(BOOL bActivate, CView* pActivateView,
CView* pDeactiveView)
{
if (bActivate)
{
MSG msg;
m_bLastActivateWasDoneByLButton = PeekMessage(&msg, m_hWnd,
WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, PM_NOREMOVE);
// Note: PeekMessage is in here for correct parent frame behaviour
// (strange, but true)
//
// The first PeekMessage call yields some message confusion in the
// parent frame. If the user _single_ clicks to the parent frame's
// caption, then the parent frame gets a WM_NCLBUTTONDBLCLK msg.
// No idea why the above PeekMessage call causes this confusion.
// And no idea why the next PeekMessage call does repair it.
PeekMessage(&msg, GetParentFrame()->GetSafeHwnd(),
WM_NCLBUTTONDBLCLK, WM_NCLBUTTONDBLCLK, PM_NOREMOVE);
}
...
CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
...
}
Thanks again for finding the reason of the mismatch.