NO

Author Topic: Can someone please suggest a better way...  (Read 7673 times)

CommonTater

  • Guest
Can someone please suggest a better way...
« on: September 29, 2011, 05:07:52 AM »
I've just spent a whole day struggling with WM_ACTIVATE...  I have a window with a treeview and a listview in the more or less standard explorer configuration... The goal is to have it place the keyboard focus back where it came from after the main window loses forgeround status... It's insane!  Half the time GetFocus() returns NULL.  When the window is minimized it returns different values than if you just click on the desktop... worse still, if you don't handle the message at all, it comes back to foreground with *nothing* selected and you have to click it to get it going again...

Here's the handler code I came up with.  It mostly works but there remains a problem that if you launch the program and immediately minimize it... or if you minimize it before it loses focus some other way, it resets back to the treeview control...   (As you can see from my notes, I'm convinced there has to be a better way)
Code: [Select]
// set keyboard focus
BOOL DoWMActivate(WPARAM Action)                // note this is a horrible Kludge
  { static HWND Focus;                          // it seriously needs fixing
    switch (Action)                             // Microsoft seriously needs fixing
     { case WA_INACTIVE :
        Focus = GetFocus();
        break;
      case WA_ACTIVE :
      case WA_CLICKACTIVE :
        SetFocus( Focus ? Focus : gTreeView );
        break;
      default :
        SetFocus(gTreeView); }
    return FALSE; }

EDIT:  It would be amazingly helpful if GetFocus() were to return the last focused child in an app, instead of NULL when the window is not in the foreground.  But I suppose Microsoft throught that would be too convenient.
« Last Edit: September 29, 2011, 05:19:19 AM by CommonTater »

JohnF

  • Guest
Re: Can someone please suggest a better way...
« Reply #1 on: September 29, 2011, 10:10:11 AM »
You said,

The goal is to have it place the keyboard focus back where it came from after the main window loses forgeround status

Did you mean - when your app regains the focus?

John

CommonTater

  • Guest
Re: Can someone please suggest a better way...
« Reply #2 on: September 29, 2011, 10:53:07 AM »
Yes... I shold have said "loses then regains foreground status" ...
Sorry about the poor wording. 
I'm beyond frustrated with this business; a whole day to write 10 lines of code???



Offline TimoVJL

  • Global Moderator
  • Member
  • *****
  • Posts: 2091
Re: Can someone please suggest a better way...
« Reply #3 on: September 29, 2011, 11:03:48 AM »
Global handle for focus window ?
Code: [Select]
void OnNotify(HWND hwnd, int idCtrl, NMHDR* pNMHdr)
{
if (pNMHdr->code == NM_SETFOCUS)
g_hWndFocus = pNMHdr->hwndFrom;
}
Code: [Select]
void OnActivate(HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized)
{
static HWND hWndF;

switch(state) {
case WA_INACTIVE:
hWndF = GetFocus();
break;
case WA_ACTIVE:
case WA_CLICKACTIVE :
if (hWndF) {
SetFocus(hWndF);
} else SetFocus(g_hWndFocus);
break;
}
}
EDIT: Example for testing.
Code: [Select]
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>

#define IDC_LISTVIEW 4002
#define IDC_TREEVIEW 4003

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct);
static void OnDestroy(HWND hwnd);
static void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);
//static void OnSize(HWND hwnd, UINT state, int cx, int cy);
static void OnNotify(HWND hwnd, int idCtrl, NMHDR* pNMHdr);
static void OnActivate(HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized);

static HWND MakeListView(HWND hWnd, UINT wId);
static HWND MakeTreeView(HWND hWnd, UINT wId);

char *szAppName = "WinFrame";
char *szFrameClass = "cWinFrame";
HWND hFrame;
HANDLE hInst;
HWND g_hWndTV, g_hWndLV;
HWND g_hWndFocus;

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcx;
MSG msg;

wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = (WNDPROC) WndProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.hInstance = hInstance;
wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground= (HBRUSH)COLOR_APPWORKSPACE+1;
wcx.lpszMenuName = NULL;
wcx.lpszClassName= szFrameClass;
wcx.hIconSm = 0;

if (!RegisterClassEx(&wcx))
return 0;
hInst = hInstance;

hFrame = CreateWindowEx(0, szFrameClass, szAppName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInst, NULL);
if(!hFrame) return 0;
ShowWindow(hFrame, nCmdShow);
UpdateWindow(hFrame);

while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
switch (wMsg)
{
case WM_COMMAND:
return OnCommand(hWnd, (int)(LOWORD(wParam)), (HWND) (lParam), (UINT)HIWORD(wParam)), 0;
// case WM_SIZE:
// return OnSize(hWnd, (UINT) (wParam), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam)), 0;
case WM_NOTIFY:
return (OnNotify(hWnd, (int)(wParam), (NMHDR *) (lParam)), 0);
case WM_ACTIVATE:
return (OnActivate(hWnd,(UINT)LOWORD(wParam),(HWND)lParam,(BOOL)HIWORD(wParam)),0);
case WM_CREATE:
return OnCreate(hWnd, (LPCREATESTRUCT) (lParam)), 0;
case WM_DESTROY:
return OnDestroy(hWnd), 0;
default:
return DefWindowProc(hWnd, wMsg, wParam, lParam);
}
}

static BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
InitCommonControls();
g_hWndTV = MakeTreeView(hWnd, IDC_TREEVIEW);
g_hWndLV = MakeListView(hWnd, IDC_LISTVIEW);
MoveWindow(g_hWndTV, 1, 1, 200, 100, TRUE);
MoveWindow(g_hWndLV, 205, 1, 200, 100, TRUE);

TVINSERTSTRUCT tvins;
memset(&tvins, 0, sizeof(tvins));
tvins.item.mask = TVIF_TEXT;
tvins.item.pszText = "Root";
TreeView_InsertItem(g_hWndTV, &tvins);
SetFocus(g_hWndTV);
g_hWndFocus = g_hWndTV;

LVCOLUMN lvc;
lvc.mask = LVCF_TEXT | LVCF_WIDTH;
lvc.cx = 50;
for (int i=0; i<2; i++) {
lvc.iSubItem = i;
lvc.pszText = "C";
ListView_InsertColumn(g_hWndLV, i, &lvc);
}
LVITEM lvi;
lvi.mask = LVIF_TEXT;
lvi.pszText = "row1";
lvi.iItem = 0;
lvi.iSubItem = 0;
ListView_InsertItem(g_hWndLV, &lvi);

return 0;
}

static void OnDestroy(HWND hwnd)
{
PostQuitMessage(0);
}

static void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
switch(id) {
/*
case IDM_EXIT:
PostMessage(hwnd, WM_CLOSE, 0, 0L);
return;
*/
}
}

static void OnNotify(HWND hwnd, int idCtrl, NMHDR* pNMHdr)
{
char szTmp[100];

if (pNMHdr->code == NM_SETFOCUS) {
wsprintf(szTmp, "%Xh TV:%Xh LV:%Xh", pNMHdr->hwndFrom, g_hWndTV, g_hWndLV);
OutputDebugString(szTmp);
g_hWndFocus = pNMHdr->hwndFrom;
}
}

static void OnActivate(HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized)
{
static HWND hWndF;

switch(state) {
case WA_INACTIVE:
hWndF = GetFocus();
break;
case WA_ACTIVE:
case WA_CLICKACTIVE :
if (hWndF) {
SetFocus(hWndF);
} else SetFocus(g_hWndFocus);
break;
}
}

static HWND MakeListView(HWND hWnd, UINT wId)
{
HWND hWndLV;
hWndLV = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT
,0, 0, 0, 0,
hWnd, (HMENU)wId, hInst, NULL);
if (hWndLV) {
SendMessage(g_hWndLV,LVM_SETEXTENDEDLISTVIEWSTYLE,
LVS_EX_GRIDLINES, LVS_EX_GRIDLINES);
SendMessage(hWndLV, LVM_SETBKCOLOR, 0, CLR_NONE);
}
return hWndLV;
}

static HWND MakeTreeView(HWND hWnd, UINT wId)
{
HWND hWndTV = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, NULL,
WS_CHILD | WS_VISIBLE | WS_TABSTOP |
TVS_HASLINES | TVS_LINESATROOT |
TVS_HASBUTTONS
,0, 0, 0, 0,
hWnd, (HMENU)wId, hInst, NULL);
return hWndTV;
}
EDIT: edit controls.
Code: [Select]
static void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
if (codeNotify == EN_SETFOCUS) {
g_hWndFocus = hwndCtl;
}

}
« Last Edit: September 29, 2011, 03:29:33 PM by timovjl »
May the source be with you

JohnF

  • Guest
Re: Can someone please suggest a better way...
« Reply #4 on: September 29, 2011, 11:53:13 AM »
You said,

The goal is to have it place the keyboard focus back where it came from after the main window loses forgeround status

Did you mean - when your app regains the focus?

John

Usually the system sorts things out so that the window/control that had focus will regain the focus again later when things are appropriate. That's my experience anyway.

John

CommonTater

  • Guest
Re: Can someone please suggest a better way...
« Reply #5 on: September 29, 2011, 02:25:49 PM »
@Timo ... WM_NOTIFY ... OF COURSE!  Thank you so much!  I see your scheme... a global focus handle and use WM_ACTIVATE to reset the focus to the global handle... Perfect!   Looking at the API it seems most controls do signal focus activation to the parent, so this is worth trying. 

@Johnf... actually when a top level window loses then regains focus, the DefWindowProc() resets the focus to the first child with the WS_TABSTOP style... In my case, that's a toolbar... which is not very helpful if the user left things in the ListView, three tabs away... Since this is indexing close to 100,000 files on a lan, I really do want this to come back to exactly where the user left it.

Thanks guys... This is a big help and it is appreciated.

In case you're curious... here's a screenshot.  The toolbar is a bit rough still, but that's next on my list...




« Last Edit: September 29, 2011, 02:40:04 PM by CommonTater »

CommonTater

  • Guest
Re: Can someone please suggest a better way...
« Reply #6 on: September 30, 2011, 04:08:18 AM »
The struggle continues... 

Here's the latest generation of the code...

Message Tosser...
Code: [Select]
        case WM_ACTIVATE :
          return 0;
        case WM_ACTIVATEAPP :
          return DoWMActivateApp(wParm);
        case WM_SYSCOMMAND :
          switch(LOWORD(wParm & 0xFFF0))
            { case SC_MINIMIZE :
                DoSCMinimize();
                return DefWindowProc(hWind,Msg,wParm,lParm);
              default :
                return DefWindowProc(hWind,Msg,wParm,lParm); }

Functions...

Code: [Select]
// catch focus from WM_SYSCOMMAND
VOID DoSCMinimize( void )
  { FocusWin = GetFocus(); }


// set keyboard focus
BOOL DoWMActivateApp(WPARAM Action)               
  { HWND ft = GetFocus();

    if(Action)
      SetFocus(FocusWin);
    else
      FocusWin = ft ? ft : FocusWin;

// debug log
fprintf(f,"Action = %llX\tft = %P\tFocusWin = %P\n",Action,ft,FocusWin);
fflush(f);

    return FALSE; }

And the debug log...

Code: [Select]
Pass 1 : launch then exit.

Action = 0 ft = 0000000000110184 FocusWin = 0000000000110184 <-- tree

Pass 2 : launch, lose focus, get focus

Action = 0  ft = 00000000001702D2 FocusWin = 00000000001702D2 <-- tree
Action = 1   ft = 0000000000000000 FocusWin = 00000000001702D2
Action = 0   ft = 00000000001702D2 FocusWin = 00000000001702D2

Pass 3 : launch, lose focus, get focus, select list, lose focus, get focus

Action = 0   ft = 00000000001001E2 FocusWin = 00000000001001E2 <-- tree
Action = 1   ft = 0000000000000000 FocusWin = 00000000001001E2
Action = 0   ft = 00000000001802EA FocusWin = 00000000001802EA <-- list
Action = 1   ft = 0000000000000000 FocusWin = 00000000001802EA
Action = 0   ft = 00000000001802EA FocusWin = 00000000001802EA

Pass 4 : launch, lose focus, get focus, minimize, restore

Action = 0   ft = 00000000001401C2 FocusWin = 00000000001401C2 <-- tree
Action = 1   ft = 0000000000000000 FocusWin = 00000000001401C2
Action = 0   ft = 0000000000000000 FocusWin = 00000000001401C2
Action = 1   ft = 0000000000000000 FocusWin = 00000000001401C2
Action = 0   ft = 0000000000220118 FocusWin = 0000000000220118 <-- main window


As you can see , the system itself sabotages me on that last pass...

Any suggestions?

(Note this is also posted  at http://cboard.cprogramming.com/windows-programming/141510-wm_activate-woes.html )

Offline TimoVJL

  • Global Moderator
  • Member
  • *****
  • Posts: 2091
Re: Can someone please suggest a better way...
« Reply #7 on: September 30, 2011, 08:46:35 AM »
So only these have to handle ?
Code: [Select]
void OnSysCommand(HWND hwnd, UINT cmd, int x, int y)
{
if (cmd == SC_MINIMIZE)
g_hWndFocus = GetFocus();
}
Code: [Select]
void OnActivate(HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized)
{
switch(state) {
case WA_INACTIVE:
if (!fMinimized)
g_hWndFocus = GetFocus();
break;
case WA_ACTIVE:
case WA_CLICKACTIVE :
if (g_hWndFocus)
SetFocus(g_hWndFocus);
break;
}
}
Code: [Select]
LRESULT CALLBACK WndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
switch (wMsg)
{
case WM_COMMAND:
return OnCommand(hWnd, (int)(LOWORD(wParam)), (HWND) (lParam), (UINT)HIWORD(wParam)), 0;
case WM_SYSCOMMAND:
OnSysCommand(hWnd,(UINT)wParam,(int)(short)LOWORD(lParam),(int)(short)HIWORD(lParam)); break;
case WM_ACTIVATE:
return OnActivate(hWnd,(UINT)LOWORD(wParam),(HWND)lParam,(BOOL)HIWORD(wParam)),0;
case WM_CREATE:
return OnCreate(hWnd, (LPCREATESTRUCT) (lParam)), 0;
case WM_DESTROY:
return OnDestroy(hWnd), 0;
}
return DefWindowProc(hWnd, wMsg, wParam, lParam);
}
May the source be with you

CommonTater

  • Guest
Re: Can someone please suggest a better way...
« Reply #8 on: September 30, 2011, 09:11:30 AM »
Yeah that was an idea from the other board (at the link) and it almost worked.

The WM_Notify method is good for catching focus changes.  I actually found a "plan b" use for that by preventing the user from selecting an empty list view...  It keeps the FocusWin right up to date and the concept you layed out works perfectly except for that cursed main window handle in my last diagnostic sequence.... I suppose I could block it from selecting the main window but now it's getting kinda heavy for a windows message handler.

I've learned that I can reliably restore the focus through WM_ACTIVATE or WM_ACTIVATEAPP in every case except minimize/restore...  Next I'm going to experiment with a parallell handler in the WM_SIZE catching SIZE_MINIMIZED and SIZE_RESTORED...  My thought is that if I prevent WM_ACTIVATE when the HIWORD of WPARAM is non-0 that should let the other handler in WM_SIZE fire without causing a problem...

Fingers crossed.
 
(EDIT: Frankly I'm finding it a tad hard to believe that in a project with 10,000+ lines of code this little 15 line trinket brought me to my knees... )

« Last Edit: September 30, 2011, 09:15:30 AM by CommonTater »

CommonTater

  • Guest
Re: Can someone please suggest a better way...
« Reply #9 on: September 30, 2011, 12:04:18 PM »
GOT IT!  Finally...

This will set the focus window (fwin) for when the window regains focus.  Because it ignores anything but the three specific values the crap during minimize never happens... and it works fine when clicking onto and off of the window, even works if you open another window on top of it.

WM_ACTIVATE handler....
Code: [Select]
// focus manager
BOOL DoWMActivate(WPARAM wParm)
 { static HWND fwin; // focus window
   switch(wParm)
    { case WA_INACTIVE :
        fwin = GetFocus();
        return 0;
      case WA_ACTIVE :
      case WA_CLICKACTIVE :
        SetFocus(fwin);
        return 0;
      default :
        return 0; } }

The problem --finally understood-- was that the value of fwin was not being reliably initialized...
This problem I fixed like this...

In my message proc...
Code: [Select]
        case WM_ACTIVATE :
          return DoWMActivate(wParm);

...

        case WM_NOTIFY :
          switch(((LPNMHDR) lParm)->code)
            { // general
              case NM_SETFOCUS :
                return DoWMActivate(WA_INACTIVE); 
              case NM_CLICK :

Calling the focus handler each time a control sends NM_SETFOCUS initializes the fwin value for me...

It Works!


Thanks for your help guys, much appreciated.
« Last Edit: September 30, 2011, 12:18:00 PM by CommonTater »