Page 1 of 2

How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 04 Jul 2019, 09:33
by nikobarli
Hi,

Inside Win32Display.cpp, the code to activate/deactivate Noesis' View is by hooking into WM_ACTIVATE message.
However, this message is not sent if the window is a child window (not Top-level window).
        case WM_ACTIVATE:
        {
            switch (LOWORD(wParam))
            {
                case WA_INACTIVE:
                {
                    mDeactivated(this);
                    TSF::DeactivateWindow(mWindowHandle);
                    break;
                }

                default:
                {
                    TSF::ActivateWindow(mWindowHandle);
                    mActivated(this);

                    // Sync key states (they are reset each time window is deactivated)
                    for (uint32_t i = 0; i < NS_COUNTOF(mKeyTable); i++)
                    {
                        if (mKeyTable[i] != 0 && GetAsyncKeyState(i))
                        {
                            mKeyDown(this, (Key)mKeyTable[i]);
                        }
                    }

                    break;
                }
            }

            return false;
        }
What is the recommended way to write the integration layer when the hosting window is a child window ?

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 09 Jul 2019, 21:47
by sfernandez
I started to investigate this but I have a question, is the child window like a borderless popup or are you interested in a MDI scenario with child document windows?

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 10 Jul 2019, 03:59
by nikobarli
I started to investigate this but I have a question, is the child window like a borderless popup or are you interested in a MDI scenario with child document windows?
The child window is a window created with WS_CHILD style.

We are mixing other contents with Noesis. The top-level window is an MFC window, which has WS_POPUP style. One or more Noesis window is layout on top of it, each window is created with WS_CHILD style.
Noesis_WS_CHILD.PNG

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 10 Jul 2019, 14:02
by sfernandez
I guess the only way to activate one of those Noesis child windows is by clicking with the mouse in them.

In that case to determine when a child window is being activated you might use WM_MOUSEACTIVATE, but that message only provides information about the newly activated window, so the integration layer should keep track of that to properly raise Deactivated event on the previous active window, something like this:
case WM_MOUSEACTIVATE:
{
  Win32Display* activeDisplay = GetActiveDisplay();
  if (activeDisplay!= 0)
  {
    activeDisplay->mDeactivated(this);
    TSF::DeactivateWindow(activeDisplay->mWindowHandle);
  }
  SetActiveDisplay(this);
  TSF::ActivateWindow(mWindowHandle);
  mActivated(this);
  ...
  return MA_ACTIVATE;
}

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 11 Jul 2019, 02:09
by nikobarli
Suppose the next WM_MOUSEACTIVATE-ed window is not a Noesis window (or a window that is not in our control), then there is no chance to deactivate the currently active Noesis View.

At least I observed the following problems:
1. The blinking cursor inside a texbox is not disabled when the active window is switched to another top-level window which is not a Noesis window.
2. Context menu is not closed when the active window is switched to another top-level window which is not a Noesis window.

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 12 Jul 2019, 13:06
by sfernandez
I can't find in Win32 other way to handle activation/focus of child windows than tracking the active child window from the application code using WM_MOUSEACTIVATE. But not only for Noesis windows, you should track WM_MOUSEACTIVATE for all application windows (top-level included).

Is that something you can do?

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 16 Jul 2019, 09:17
by nikobarli
I can't find in Win32 other way to handle activation/focus of child windows than tracking the active child window from the application code using WM_MOUSEACTIVATE. But not only for Noesis windows, you should track WM_MOUSEACTIVATE for all application windows (top-level included).

Is that something you can do?
I can control all Noesis windows and it's top-level window, but ufortunately I cannot control other application's windows or MFC controls that are mixed with Noesis...

I experimented with the code, and I found that I can hook into WM_KILLFOCUS to know when I can deactivate the Noesis windows with WS_CHILD.
My activation related code than looks like this (pretty dirty, because I need to handle all WM_ACTIVATE, WM_MOUSEACTIVATE, and WM_KILLFOCUS).
Do you think it's OK to hook to WM_KILLFOCUS ?
            case WM_ACTIVATE:
            {
                switch (LOWORD(wParam)) {
                case WA_INACTIVE:
                    if (g_activeView == m_view) {
                        m_view->Deactivate();
                        g_activeView = nullptr;
                        NoesisApp::TSF::DeactivateWindow(m_hWnd);
                    }
                    break;
                default:
                    if (g_activeView == m_view) break; // do nothing, already activated
                    NoesisApp::TSF::ActivateWindow(m_hWnd);
                    m_view->Activate();
                    g_activeView = m_view;
                    
                    // Sync key states (they are reset each time window is deactivated)
                    // https://www.noesisengine.com/bugs/view.php?id=1418
                    for (auto & entry : keyMap) {
                        if(::GetAsyncKeyState(static_cast<int>(entry.first)) & 0x8000) {
                            m_view->KeyDown(entry.second);
                        }
                    }
                    break;
                }
                break;
            }
            case WM_MOUSEACTIVATE:
            {
                if (g_activeView == m_view) {
                    ret = MA_ACTIVATE;
                    break;
                }
                
                if (g_activeView) {
                    auto hwnd = g_viewMap.GetWnd(g_activeView);
                    g_activeView->Deactivate();
                    g_activeView = nullptr;
                    NoesisApp::TSF::DeactivateWindow(hwnd);
                }
                
                NoesisApp::TSF::ActivateWindow(m_hWnd);
                m_view->Activate();
                g_activeView = m_view;
                
                // Sync key states (they are reset each time window is deactivated)
                // https://www.noesisengine.com/bugs/view.php?id=1418
                for (auto & entry : keyMap) {
                    if (::GetAsyncKeyState(static_cast<int>(entry.first)) & 0x8000) {
                        m_view->KeyDown(entry.second);
                    }
                }
                
                ret = MA_ACTIVATE;
                break;
            }
            case WM_KILLFOCUS:
            {
                if (g_activeView == m_view) {
                    auto hwnd = g_viewMap.GetWnd(g_activeView);
                    g_activeView->Deactivate();
                    g_activeView = nullptr;
                    NoesisApp::TSF::DeactivateWindow(hwnd);
                }
                break;
            }
Also, I need to comment the following line inside TSF.cpp, so that switching back and forth between Noesis and MFC's edit control on the same parent window, doesn't break the MFC IME input.
void TSF::HideKeyboard()
{
    // Need to comment this out !
    // V(gThreadMgr->SetFocus(gDisabledDocumentMgr));

    if (gTextBoxStore->IsEnabled())
    {
        CancelComposition();

        gTextBoxStore->AssociateControl(0, 0);
    }
    else if (gPassBoxStore->IsEnabled())
    {
        gPassBoxStore->AssociateControl(0, 0);
    }
}

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 18 Jul 2019, 11:33
by sfernandez
In my tests I wasn't receiving WM_SETFOCUS on child windows, so I didn't consider that handling focus could be an option for your problem.

After your suggestion of using WM_KILLFOCUS I think you can manually call SetFocus on Noesis child windows when you receive the WM_MOUSEACTIVATE message, and then handle WM_SETFOCUS as the activate and WM_KILLFOCUS as the deactivate.

To restore the focus on your top-level window, it can handle the WM_LBUTTONDOWN message to call SetFocus.

That way you can centralize Activate/Deactivate code in one place, because WM_SETFOCUS and WM_KILLFOCUS will always be called for Noesis child windows. Could that be what you need?
Also, I need to comment the following line inside TSF.cpp, so that switching back and forth between Noesis and MFC's edit control on the same parent window, doesn't break the MFC IME input.
If you comment that line, what happens when you press a key in a Noesis window without a TextBox focused? Is it incorrectly showing the candidate window on desktop top-left corner?

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 19 Jul 2019, 03:12
by nikobarli
In my tests I wasn't receiving WM_SETFOCUS on child windows, so I didn't consider that handling focus could be an option for your problem.
In my integration code, I am calling SetFocus on any WM_{L,R,M}BUTTONDOWN inside the Noesis host window. I think that's why I am receiving WM_SETFOCUS on my child windows. I think your Win32Display.cpp also calling SetFocus as well on those events.
After your suggestion of using WM_KILLFOCUS I think you can manually call SetFocus on Noesis child windows when you receive the WM_MOUSEACTIVATE message, and then handle WM_SETFOCUS as the activate and WM_KILLFOCUS as the deactivate.

To restore the focus on your top-level window, it can handle the WM_LBUTTONDOWN message to call SetFocus.

That way you can centralize Activate/Deactivate code in one place, because WM_SETFOCUS and WM_KILLFOCUS will always be called for Noesis child windows. Could that be what you need?
Thanks for the suggestion. Following your hints, I can simplify the code as follows:
1. Call SetFocus() Inside WM_{L,R,M}BUTTONDOWN
2. Activate Window inside WM_SETFOCUS
3. Deactivate Window inside WM_KILLFOCUS

That way I no longer need to handle WM_ACTIVATE and WM_MOUSEACTIVATE for both top-level windows and child windows.
One draw back is that any other window will lose focus any time we click any Noesis window (even if we only click non-focusable elements).
I think we can live with that for the moment.
If you comment that line, what happens when you press a key in a Noesis window without a TextBox focused? Is it incorrectly showing the candidate window on desktop top-left corner?
No, it is not showing the candidate window on desktop top-left corner. The code inside WM_MOVE handler that calls TSF::MoveWindow(hwnd) is taking care of the case by.

Re: How to Activate/Deactivate Noesis View if the hosting window is a child window

Posted: 19 Jul 2019, 11:56
by sfernandez
Great, thanks for the update.
Should we consider this as solved?