Applications: Creating a simple UI application from scratch, Process Viewer, Part 3

Let me paste the code for ProcessViewerDlgProc function, and then we will see how each part works. Here is the code:

BOOL CALLBACK ProcessViewerDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)

{

    int wmID, wmEvent, procCount;

    PAINTSTRUCT ps;

    HDC hdc;



    switch(uMessage)

    {

        case WM_INITDIALOG:

            {

                SHINITDLGINFO shidi;

                SHMENUBARINFO mbi;



                memset(&shidi, 0, sizeof(shidi));

                memset(&mbi, 0, sizeof(mbi));



                shidi.dwMask = SHIDIM_FLAGS;

                shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;

                shidi.hDlg = hDlg;

                SHInitDialog(&shidi);



                mbi.cbSize = sizeof(mbi);

                mbi.hwndParent = hDlg;

                mbi.nToolBarId = IDR_MENU_PROCVIEWER;

                mbi.hInstRes = g_hInst;



                if(!SHCreateMenuBar(&mbi))

                {

                    printf("ProcViewer: Error creating menu bar, errcode:0x%x\n", GetLastError());

                }

                else

                {

                    g_hWndMenuBar = mbi.hwndMB;

                }



                AllignComponents(hDlg);



                //initialise the list view control column headers

                InitializeListViewControl(hDlg);



                procCount = GetRunningProcesses();



                RefreshProcessListView(hDlg, procCount);

            }

            return TRUE;



        case WM_COMMAND:

            {

                wmID = LOWORD(wParam);

                wmEvent = HIWORD(wParam);



                switch(wmID)

                {

                    case IDM_EXIT:

                        EndDialog(hDlg, IDM_EXIT);

                        break;



                    case IDM_REFRESH:

                        UpdateProcessView(hDlg);

                        break;

                }



            }

            break;



        case WM_PAINT:

            {

                hdc = BeginPaint(hDlg, &ps);



                //add drawing code here



                EndPaint(hDlg, &ps);

            }

            break;



        case WM_NOTIFY:

            {

                switch(((LPNMHDR)lParam)->code)

                {

                    case LVN_ITEMCHANGED:

                        {

                            LPNMLISTVIEW lpnm = (LPNMLISTVIEW)lParam;

                           

                            //get info about which item was selected.

                            int temp = lpnm->uNewState ^ lpnm->uOldState;



                            /*

                            when you select an item you get about 3 LVN_ITEMCHANGED notification.

                            So process only one of them, i.e when the item is selected and focused, both.

                            */

                            if((temp & LVIS_SELECTED) && (temp & LVIS_FOCUSED))

                            {

                                if(lpnm->iItem < MAX_PROCESSES)

                                {

                                    ShowProcInfo(hDlg, &ProcessList[lpnm->iItem]);

                                }

                            }



                        }

                        break;

                }

            }

            break;



        case WM_CLOSE:

            {

                EndDialog(hDlg, uMessage);

                return TRUE;

            }



    }



    return FALSE;

}

I will concentrate only on WM_INITDIALOG and WM_NOTIFY and leave the other messages. They are pretty much self explanatory and you probably know what they are for already.

 

The WM_INITDIALOG message is sent when our dialog is to be initialized just before it is shown to the user. This is where we set up the UI controls in the dialog, move them around, align them and of course associate a menu bar with our dialog.  The SHInitDialog and SHCreateMenuBar functions are standard and are used to display the dialog in full screen and add a menubar, respectively. The menu bar here has "Exit" as its left softkey and "Refresh" the right softkey. The next function I call is AllignComponents(). This is basically to beautify the dialog and move around the controls so they look nice. Before going to the AllignComponents function, here are a few globals that I use:

HINSTANCE g_hInst;

HWND g_hWndMenuBar;

PROCESSENTRY32 ProcessList[MAX_PROCESSES];

g_hInst, holds the instance handle of our application. g_hWndMenuBar stores the handle to the menubar created in WM_INITDIALOG. And finally, ProcessList is an array of PROCESSENTRY32 structures, which will hold the list of current running processes. MAX_PROCESSES is defined as:

#define MAX_PROCESSES    32

Now back to the AllignComponents() function, here is the code:



//this function alligns the controls on the dialog, beautifies.

BOOL AllignComponents(HWND hWnd)

{

    HWND hWndListView = NULL;

    HWND hTemp;

    RECT rectMainDlg, rect;

    int x, y, width, height;

   

    memset(&rectMainDlg, 0 , sizeof(RECT));



    //get the client area of the main dialog

    GetClientRect(hWnd, &rectMainDlg);



    hWndListView = GetDlgItem(hWnd, IDC_LISTVIEW_PROCESSES);



    if(!hWndListView)

    {

        printf("GetDlgItem failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

        return FALSE;

    }



    //position List view wrt to main dialog

    x = rectMainDlg.left + 1;

    y = rectMainDlg.top + 1;

    width = rectMainDlg.right – rectMainDlg.left – 2;

    height = (rectMainDlg.bottom – rectMainDlg.top)/2;



    if(!MoveWindow(hWndListView, x, y, width, height, FALSE))

    {

        printf("MoveWindow failed! errcode:%d  line:%d\n", GetLastError(), __LINE__);

    }





    //position GROUPBOX wrt List view

    hTemp = GetDlgItem(hWnd, IDC_STATIC_GROUPBOX);



    if(!hTemp)

    {

        printf("GetDlgItem failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

        return FALSE;

    }



    memset(&rect, 0, sizeof(RECT));

    GetWindowRect(hWndListView, &rect);



    x = rect.left;

    y = rectMainDlg.top + (rect.bottom – rect.top) + 5;

    width = rect.right – rect.left;

    height = (rectMainDlg.bottom – rectMainDlg.top)/2 – 15;



    if(!MoveWindow(hTemp, x, y, width, height, FALSE))

    {

        printf("MoveWindow failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

    }



    //beautify other labels



    memset(&rect, 0, sizeof(RECT));

    GetWindowRect(GetDlgItem(hWnd, IDC_STATIC_GROUPBOX), &rect);



    x = rect.left + 8;

    y = rect.top + 4;

    width = rect.right – rect.left – 20;

    height = 20;



    //beautify Proc ID label wrt to group box

    if(!MoveWindow(GetDlgItem(hWnd, IDC_STATIC_PROCID), x, y, width, height, FALSE))

    {

        printf("MoveWindow failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

    }



    //beautify No of Threads label wrt to PROC ID label

    y = y + height + 4;

    if(!MoveWindow(GetDlgItem(hWnd, IDC_STATIC_NTHREADS), x, y, width, height, FALSE))

    {

        printf("MoveWindow failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

    }



    //beautify Load Addr label wrt No Of Threads label

    y = y + height + 4;

    if(!MoveWindow(GetDlgItem(hWnd, IDC_STATIC_LOADADDR), x, y, width, height, FALSE))

    {

        printf("MoveWindow failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

    }



    return TRUE;

}

This function receives the handle to the dialog and uses the MoveWindow() api to position and size the controls with respect to the dialog and other controls. The x, y co-ordinate values that I have used take into account the screen size (by getting the client area of a full screen dialog) so the UI should not appear terribly bad when the program is run on a device with different screen size and resolution, I haven’t tested it on any device though. Nothing else I see to be explained about this function, so lets move on.

 

The next function I call is InitializeListViewControl(), here is the code:



BOOL InitializeListViewControl(HWND hDlg)

{

    HWND hWndListView = NULL;

    LVCOLUMN lvc;

    RECT rect;



    //column headers

    TCHAR *szColText[2] = { TEXT("S. No"), TEXT("Process Name") };

    const int numCols = 2;



    memset(&lvc, 0, sizeof(lvc));



    hWndListView = GetDlgItem(hDlg, IDC_LISTVIEW_PROCESSES);



    if(!hWndListView)

    {

        printf("+++ Failed to get list view handle! errcode:%d", GetLastError());

        return FALSE;

    }



    //get the window rect of list view, we’ll use this to set column widths

    GetWindowRect(hWndListView, &rect);



    //set list view styles

    SendMessage(hWndListView, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_REPORT);

    //SendMessage(hWndListView, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_GRIDLINES);

    SendMessage(hWndListView, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_SINGLESEL);

    SendMessage(hWndListView, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT);



    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;



    //add columns to list view

    for(int i=0; i < numCols; i++)

    {

        lvc.iSubItem = i;

        lvc.pszText = szColText[i];



        //set 20% of width for S.No and 80% width for Process Name

        if(i == 0)

        {

            lvc.cx = (rect.right – rect.left)/5;

        }

        else if(i == 1)

        {

            lvc.cx = ((rect.right – rect.left)*4)/5;

        }



        lvc.fmt = LVCFMT_LEFT;



        if (ListView_InsertColumn(hWndListView, i, &lvc) == -1)

        {

            printf("+++ ListView_InsertColumn failed! errcode:%d\n", GetLastError());

            return FALSE;

        }   

    }



    //InsertItems(hWndListView);

    //ListView_SetItemState(hWndListView, 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);



    return TRUE;

}

This function sets up our List view control. szColText contains the text of the column headers, you can see that I am using a list control with two columns, first displaying "S. No" and the second column "Process Name". To add a column to the list view control we use the LVCOLUMN structure and ListView_InsertColumn() macro. This macro basically is wrap around the LVM_INSERTCOLUMN message. Get the handle to the list view control and send it a few extended style messages. Setting its view style to Report, making it single select rows and select the full row. The "mask" member of the LVCOLUMN struct specifies, which other members in the struct are valid, in this case, format, column width, text and subitem members. Then in the for loop I set up the iSubItem, pszText, cx (column width) and fmt members. iSubItem is the zero based index of the column, pszText contains the text to be displayed and the text is left alligned in the column. Since there are only two columns I use, I set the width of the first one to 1/5th of the list view controls width and the second to 4/5ths. And finally I use the ListView_InsertColumn macro and pass in the LVCOLUMN structure.

 

Next is the call to GetRunningProcesses(). This function fills up the global, ProcessList, array of PROCESSENTRY32 structure.

//this function fills the ProcessList array with all the current running processes

DWORD GetRunningProcesses()

{

    int index = 0;

    HANDLE hSnapShot;

    PROCESSENTRY32 *pProcess = &ProcessList[0];

    PROCESSENTRY32 prevProcess;

   

    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPNOHEAPS, 0);



    if(hSnapShot == INVALID_HANDLE_VALUE)

    {

        printf("CreateToolhelp32Snapshot failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

        return FALSE;

    }



    memset(pProcess, 0, sizeof(PROCESSENTRY32));

    pProcess->dwSize = sizeof(PROCESSENTRY32);



    if(!Process32First(hSnapShot, pProcess))

    {

        printf("Process32First failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

        return -1;

    }



    memcpy(&prevProcess, pProcess, sizeof(PROCESSENTRY32));

    index = 1;



    while(Process32Next(hSnapShot, &prevProcess))

    {

        if(++index >= MAX_PROCESSES)

            break;



        pProcess++;

        memcpy(pProcess, &prevProcess, sizeof(PROCESSENTRY32));



    }



    CloseToolhelp32Snapshot(hSnapShot);



    return index;

}

If you have read Bruce’s post, then there is nothing much for me to explain here. I use a different algo/logic from what Bruce has used. Well, I was using the same code as Bruce’s first, but I found out a minor flaw with his code that would put the same entry twice in the array at the end. I have to leave a comment on his post ;). Anyways, another thing that is worth mentioning is this, according to the documentation for Process32Next:

BOOL Process32Next(

                                  HANDLE hSnapshot,

                                  LPPROCESSENTRY32 *lppe);



hSnapshot: handle to the snapshot returned from a previous call to CreateToolhelp32Snapshot function.

lppe: Pointer to a PROCESSENTRY32 structure.

What this piece of information doesn’t tell you is the second parameter must point a PROCESSENTRY32 structure returned from a previous call to Process32First or Process32Next. Here’s some work for the Windows Mobile documentation team (;. This function returns the total number of running processes and the ProcessList array is filled with all information about the running processes waiting to be used.

 

 

And the last function I call in WM_INITDIALOG is RefreshProcessListView(). This function uses the data contained in ProcessList array and fills the list view control with this data. The code follows:



BOOL RefreshProcessListView(HWND hDlg, int procCount)

{

    LVITEM lvi;

    TCHAR szSNo[8] = TEXT("");

    HWND hListView = GetDlgItem(hDlg, IDC_LISTVIEW_PROCESSES);



    if(!hListView)

    {

        printf("GetDlgItem failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

        return FALSE;

    }



    memset(&lvi, 0, sizeof(LVITEM));



    //delete all items from list view

    SendMessage(hListView, LVM_DELETEALLITEMS, 0, 0);



    lvi.mask = LVIF_TEXT | LVIF_STATE;

    lvi.state = 0;

    lvi.stateMask = 0;



    for(int i = 0; i < procCount; i++)

    {

        wsprintf(szSNo, L"%d", i+1);

        lvi.iItem = i;

        lvi.iSubItem = 0;

        lvi.pszText = szSNo;

        if(ListView_InsertItem(hListView, &lvi) == -1)

        {

            printf("ListView_InsertItem failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

        }



        lvi.iItem = i;

        lvi.iSubItem = 1;

        lvi.pszText = ProcessList[i].szExeFile;

        if(ListView_SetItem(hListView, &lvi) == -1)

        {

            printf("ListView_SetItem failed errcode:%d  line:%d\n", GetLastError(), __LINE__);

        }

    }



    //select the first item in the list (default)

    ListView_SetItemState(hListView, 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);

    ShowProcInfo(hDlg, &ProcessList[0]);



    return TRUE;

}

To add a row in the list view control we use the LVITEM structure and ListView_InsertItem macro. This is a little interesting and I took some time to figure out ListView_InsertItem and ListView_SetItem. You can see that both these macros are being used in this function. Here is the crux, ListView_InsertItem is used when you are inserting the first item in the row. And once the first element in the row is added, we use ListView_SetItem to add more items in the same row. And that is why, ListView_InsertItem always expects iSubItem to be zero. Other things here are similar to inserting a column, you have mask specifying which fields are valid, iItem is the row number, iSubitem is the column number. ListView_InsertItem inserts an item at i’th row and 0th column and ListView_SetItem adds an item to the i’th row and 1st column (column numbers are zero based). Use the szExeFile member of the PROCESSENTRY32 structure to get the name of the executable file for that program.

ListView_SetItemState(hListView, 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);

Selects the first item in the list view control by default, when the dialog shows up.

 

And finally the ShowProcInfo function:

//update the text control under group box

BOOL ShowProcInfo(HWND hDlg, PROCESSENTRY32 *pProcess)

{

    TCHAR wszStr[64] = TEXT("");

    HWND hTemp;

   

    hTemp = GetDlgItem(hDlg, IDC_STATIC_PROCID);

    if(hTemp)

    {

        //wsprintf(wszStr, L"Process Id: %x", pProcess->th32ProcessID);

        wsprintf(wszStr, L"%-18s 0x%08x", L"Process Id:", pProcess->th32ProcessID);

        SetWindowText(hTemp, wszStr);

    }



    hTemp = GetDlgItem(hDlg, IDC_STATIC_NTHREADS);

    if(hTemp)

    {

        //wsprintf(wszStr, L"No of Threads: %d", pProcess->cntThreads);

        wsprintf(wszStr, L"%-15s %d", L"No of Threads:", pProcess->cntThreads);

        SetWindowText(hTemp, wszStr);

    }



    hTemp = GetDlgItem(hDlg, IDC_STATIC_LOADADDR);

    if(hTemp)

    {

        //wsprintf(wszStr, L"Load Address: %x", pProcess->th32MemoryBase);

        wsprintf(wszStr, L"%-15s 0x%08x", L"Load Address:", pProcess->th32MemoryBase);

        SetWindowText(hTemp, wszStr);

    }



    return TRUE;

}

This function updates the static text controls to show the info related to the currently selcted process in the list view control. We use the th32ProcessID, cntThreads and th32MemoryBase members of PROCESSENTRY32 structure to display the correct info. So by default the first entry in the list view control will be selected and Process Info will show the information about the first entry.

 

I think this post has become a little too long, there is a bit left. I will cover that too (;

 

When the user selects the Refresh softkey, I call UpdateProcessView() function,

BOOL UpdateProcessView(HWND hDlg)

{

    int procCount = 0;



    procCount = GetRunningProcesses();

    RefreshProcessListView(hDlg, procCount);



    return TRUE;

}

This simply calls GetRunningProcesses() and RefreshProcessListView() functions to take a snapshot again and refresh the process list view.

 

And lastly, the WM_NOTIFY message, here it is again:

        case WM_NOTIFY:

            {

                switch(((LPNMHDR)lParam)->code)

                {

                    case LVN_ITEMCHANGED:

                        {

                            LPNMLISTVIEW lpnm = (LPNMLISTVIEW)lParam;

                           

                            //get info about which item was selected.

                            int temp = lpnm->uNewState ^ lpnm->uOldState;



                            /*

                            when you select an item you get about 3 LVN_ITEMCHANGED notification.

                            So process only one of them, i.e when the item is selected and focused, both.

                            */

                            if((temp & LVIS_SELECTED) && (temp & LVIS_FOCUSED))

                            {

                                if(lpnm->iItem < MAX_PROCESSES)

                                {

                                    ShowProcInfo(hDlg, &ProcessList[lpnm->iItem]);

                                }

                            }



                        }

                        break;

                }

            }

            break;

WM_NOTIFY message is sent by any child control to the parent when some property of the control changes, for e.g. the user selected some entry in the list view control. The WM_NOTIFY message is accompanied by the NMHDR structure which contains information about why the notify message was sent. Here, I check whether the notification is LVN_ITEMCHANGED, this is sent whenever there is a change in the selected state of the items in the list view control. You might have read the comment in the code, when the user selects an item in the list view control, LVN_ITEMCHANGED notification will be sent quite a few times, once for the item that was unselected and once for the item that was selected and so on. So I process the message only when the item was selected and focused both. For this we XOR the old state and the new state of the item and then check the bits for selected and focused. We get the index of the item that was both selected and focused and call ShowProcInfo to update the static controls under the group box. So when the user selects entries in the list view control, the process info is updated automatically. Simple.

Leave a Reply

Your email address will not be published. Required fields are marked *