Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Wifi scanner + custom MFC controls

0.00/5 (No votes)
30 Jul 2007 3  
A Wifi scanner with custom slider, tab control, buttons and checkboxes

Screenshot - pp.gif

Introduction

I set up wireless at home a couple of weeks ago and became interested in wireless tools. I wanted to know how AP scanner software works. I did a bit of research and it turned out that getting Wifi information is rather simple with the NDISUIO driver that is available on PocketPC 2003 and newer systems. So, I coded a simple scanner that has two view modes. The first is a colored list of all nearby APs. The other mode displays the signal strength of a selected AP with large numbers. This way it has an acceptable outdoor visibility and you can follow a single signal without having to see the APs of your neighbours flashing on your screen. I also included different custom MFC controls I coded before: a bitmapped slider, a tab control and buttons. I hope you find these classes useful, too.

This article features the following classes:

  • CWifiPeek - a class that can be used to list nearby Wifi devices
  • CColoredDlg - a dialog base class that supports custom colors for child controls
  • CCustomTabCtrl - a custom Tab control that runs fine under WinCE/PocketPC
  • CCustomSlider - a custom Slider (trackbar) control with BMP support
  • CCustomButton - custom Button controls (button, checkbox, radio, etc.)

Using the application

PeekPocket is fairly simple to use. Just turn your WLAN on, run the application and see the APs listed. Secure APs are printed in red, while inactive ones are greyed out. An inactive AP is one that is not currently visible. If you tap an AP name, you can "go large," i.e. follow one signal without seeing any other APs. You can set the list font size and scanning speed on the Options tab. You can also set network type and security filters there. Your Wifi adapter should be displayed in the combo. If it is not, something's wrong. In such a case, you can try the following:

  • Disable and then re-enable wireless.
  • If the PDA has an ActiveSync connection to your PC, disconnect it.
  • If you have configured an automatic connection to an AP, try removing it.

If these do not help, sometimes a reset will. You can compile the application with Visual Studio 2005 and the PocketPC 2003 SDK. PeekPocket should run fine on WM5 and WM6 devices, too. If you add other SDKs to the demo project, you might need to change a couple of compiler/linker settings.

Access Point listing made easy

The CWifiPeek class does all the Wifi query stuff. It can be used in non-MFC applications, too. You have to add CWifiPeek.h and CWifiPeek.cpp to your project. Don't forget to disable use of precompiled headers for this CPP file! The class uses the NDIS User mode I/O driver (NDISUIO) to perform AP scanning. The following structure will be used to return station info:

struct BSSIDInfo
{
    BYTE BSSID[6];            //MAC Address
    WCHAR SSID[32];           //Station name    
    int RSSI;                 //Signal strength    
    int Channel;              //Used channel    
    int Infastructure;        //Type: AP or peer
    int Auth;                 //Open or secure?
};

The class provides the following functions:

  • bool GetAdapters(LPWSTR pDest, DWORD &dwBufSizeBytes);
  • bool RefreshBSSIDs(LPWSTR pAdapter);
  • bool GetBBSIDs(LPWSTR pAdapter, struct BSSIDInfo *pDest, DWORD &dwBufSizeBytes, DWORD &dwReturnedItems);

The GetAdapters function can be used to query names of network adapters. It calls the built-in NDIS (not NDISUIO) driver. You have to pass the address of a WCHAR buffer and the buffer size. The function fills the buffer with adapter names separated by commas. It also sets the DWORD to the number of bytes returned. It filters out some adapter names, e.g. infrared, GPRS, ActiveSync connection. If the function returns true, but indicates that it copied zero bytes, something might be wrong. Your Wifi might be turned off or might not be operational.

The function uses the IOCTL_NDIS_GET_ADAPTER_NAMES IOCTL call. Adapter names must be retrieved because the NDISUIO calls require this parameter. The RefreshBSSIDs function requests that the driver initiate an AP survey. It takes one parameter: an adapter name. Upon success, it returns true. This function is called frequently so that we have an up-to-date list.

The GetBBSIDs function returns the list of available stations, i.e. peers and Access Points. It takes an adapter name and a pointer to the destination buffer, along with the buffer size. If it succeeds, it returns true and fills dwReturnedItems with the number of returned structures, not bytes. These two methods use the OID_802_11_BSSID_LIST_SCAN and OID_802_11_BSSID_LIST wireless OIDs. Notes on the returned station data:

  • The BSSID field contains the MAC address of the station.
  • The station name is returned in SSID. This can be an empty string.
  • The signal strength is returned in dBm units in RSSI. A -50 dBm signal is stronger than -60, which is stronger than -70, and so on. Usually, signals below -75 are considered very poor. A signal level of -50 dBm is fine.
  • The Infrastructure field can be Ndis802_11IBSS (a peer) or Ndis802_11Infrastructure (AP).
  • The value of the Auth field is Ndis802_11AuthModeOpen for unsecured stations.

Colored dialogs

The base class of all dialogs is CColoredDlg instead of CDialog. This class provides support for custom colors with an OnCtlColor handler. During the paint cycle of dialog controls, this handler is invoked and the parent dialog can set the background and foreground colors of the controls being drawn. This feature can be used to make the GUI look a bit different. However, it's very simple if you look at the code. You can use this class in a few simple steps:

  • add ColoredDlg.h and ColoredDlg.cpp to your project.
  • In your dialog header and CPP files, replace every occurrence of CDialog with CColoredDlg.
  • Then add #include "ColoredDlg.h" to your dialog header files.

That's it! Colors should be set up in the OnInitDialog function with SetBkgColor and SetFrgColor calls. These take a COLORREF value that you can create with an RGB macro, for instance. A two-minute dialog with colored controls can be seen here:

You can draw different controls with different colors using an OnCtlColor handler. Controls don't have to be owner-drawn to have their colors changed.

The Tab control

The Tab control in CustomTabCtrl.h and CustomTabCtrl.cpp is a WinCE / PocketPC compatible version of Andrzej Markowski's excellent Custom Tab Control. I've made it WinCE-compatible by removing everything Win32-specific, such as XP themes support, tooltips, etc. I've also made the tabs rectangular, as it was a lot simpler to draw these with WinCE GDI. Left and right orientations are present, but they are experimental and very slow, i.e. no PlgBlt on WinCE. In general, it's recommended to use the top and bottom orientations only. The class is still compatible with Win32 and can be used with Embedded Visual C++ 4, too.

Have a look at Andrzej's article to see how to use this control in your applications. Just don't forget to disable use of precompiled headers for this version. The control can be created both dynamically and from a template. The control supports custom colors. To get and set these colors, use the following functions:

  • void GetColors(TabItemColors *pColorsOut);
  • void SetColors(TabItemColors *pColorsIn, BOOL fRedraw=FALSE);

These calls use the following structure:

struct TabItemColors
{       
    COLORREF crWndBkg;            //window background color
    COLORREF crBkgInactive;       //background color for inactive tab item
    COLORREF crBkgActive;         // .. active tab item
    COLORREF crTxtInactive;       //text color for active tab item
    COLORREF crTxtActive;         // .. active tab item
    COLORREF crDarkLine;          //darker line
    COLORREF crLightLine;         //lighter line
};

You can see which is which on this image:

Screenshot - tabs1.jpg

The "Scanner" tab is active and the "Options" tab is inactive. The lines around tabs are "dark lines." The crLightLine field is reserved for future use. The rectangular area on the right, marked with "1," is "window background." That is, a part of the control window where no tabs are drawn. It's a good idea to call GetColors first, update the colors you wish and then call SetColors. Have a look at the CPPDlg::OnInitDialog function. You can set the tab font too, just as with the original control. One thing to keep in mind is that if you use CFont, you should not declare it on OnInitDialog, but as a member variable in the dialog header. This way the font won't be freed until the dialog is closed.

I've added something else to the control: the container mode. Andrzej's tab control is actually a bar with buttons. The bar sends various notifications to its parent when the user clicks something, but it's the application's task to show and hide child windows accordingly. A container, on the other hand, can host child dialogs and displays or hides them automatically. The container mode can be enabled by setting the CTCS_CONTAINER style This mode is supported by a derived class, CCustomTabContainer. New functions related to the container mode are:

  • int GetTabsHeight();
  • void SetTabsHeight(int nHeight);
  • void AdjustRect(BOOL bLarger, LPRECT lpRect);
  • void AddDialog(int nIndex, CString strText, CDialog *pDlg);
  • void RemoveDialog(int nIndex);

When in container mode, the height of the entire tab control window is obviously larger than just the height of the tabs:

Screenshot - tabs2.jpg

Tabs' height can be set with the SetTabsHeight function. To get the current value, use the GetTabsHeight function. The control has an AdjustRect function that is similar to CTabCtrl::AdjustRect. To use the container in an application, proceed as follows:

  • Add dialog resources to your application. The dialog should have no title bar, no border and make sure they have the "Child" style.
  • Add classes for these dialogs, like CScannerDlg and COptionsDlg in the demo application.
  • Follow Andrzej's instructions on adding the tab control to your application. Proceed exactly as if you were adding CCustomTabCtrl!
  • Change the member variable type in your dialog header from CCustomTabCtrl to CCustomTabContainer.
  • Add member vars for child dialog pointers, like m_pScannerDlg and m_pOptionsDlg in PPDlg.h.
  • Set up container mode -- CTCS_CONTAINER style -- and colors in your OnInitDialog function.
  • Add tabs and child dialogs with AddDialog; have a look at the CPPDlg::OnInitDialog function.

Note that the container calls delete on the dialogs automatically when either the container is destructed, the close button is tapped or when you call RemoveDialog.

The Slider control

This is a bitmapped slider control and is implemented as a CWnd-derived class. It can be used on WinCE / PocketPC and Win32 platforms, too. It has horizontal and vertical orientations, as well as a reversed mode:

Screenshot - slider1.jpg

To use it in your application, proceed as follows:

  • Add CustomSlider.h and CustomSlider.cpp to your project. Precompiled headers should be turned off for the CPP file.
  • Add #include "CustomSlider.h" to the appropriate dialog header.
  • Add a member variable of type CCustomSlider to the dialog class.
  • For template-based creation, add a Custom control with class set to CustomSliderClass.
  • Alternatively, for dynamic creation, add m_Slider.Create(_T("CustomSliderClass"), strTitle, strStyle, sliderRect, this, IDC_SLIDER) to your OnInitDialog function.
  • Link up the variable with the control via a DDX_Control call in DoDataExchange.
  • Add scroll handlers to your dialog; see the example below.

As you probably know, a slider control has two distinct parts: the so-called thumb -- i.e. the object that actually slides -- and the channel, i.e. the area where the thumb moves. The following picture shows a channel, a thumb and the slider control they make:

Screenshot - slider2.jpg

The slider has the following properties, which should be set up in OnInitDialog:

  • BkgColor determines what color is used to erase the control background.
  • RangeMin specifies the minimum value the control can return.
  • RangeMax specifies the maximum value the control can return.
  • Pos is the current position of the thumb.
  • There are 3 bitmaps: one for the channel image, one for the thumb when it is inactive (not tapped) and one for the active (tapped, dragged) thumb.
  • There is a Reverse flag that determines whether the slider behaves reversed.

You can use the following functions to get and set these properties:

  • void GetRange(DWORD& dwMin, DWORD& dwMax);
  • DWORD GetRangeMin();
  • DWORD GetRangeMax();
  • void SetRange(DWORD dwMin, DWORD dwMax);
  • void SetRangeMin(DWORD dwMin);
  • void SetRangeMax(DWORD dwMax);
  • DWORD GetPos();
  • void SetPos(DWORD dwPos);
  • void SetReverse(bool bRev);
  • bool GetReverse();
  • void SetBkgColor(COLORREF crBkg);
  • COLORREF GetBkgColor();
  • void SetBitmaps(HBITMAP hBmpChannel, HBITMAP hBmpThumbInactive, HBITMAP hBmpThumbActive = NULL);

Notes:

  • The slider is horizontal by default. To make it vertical, specify the TBS_VERT style.
  • The slider has no default graphics, so in all cases you must set up bitmaps.
  • If you omit the 3rd parameter of SetBitmaps, the same bitmap will be used for thumb active and inactive states.
  • The control supports transparency. The black color will be treated as transparent.
  • The control will automatically free the bitmap handles you specify.
  • The control supports non-negative range and position values, i.e. zero and above. If you need negative values, you have to offset the returned position.
  • It's a good idea if the slider rectangle has the same dimensions as the channel bitmap.
  • Used bitmaps should have widths and heights divisible by 2.
  • If in doubt, have a look at COptionsDlg::OnInitDialog. The sample code works, so there must be a way.

Here's how to handle slider events. Add an OnHScroll or OnVScroll handler to your dialog class. The handler should look like this:

void COptionsDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    if(pScrollBar != NULL && pScrollBar->IsKindOf(
        RUNTIME_CLASS(CCustomSlider)))
    {
        //which slider was it?
        switch(pScrollBar->GetDlgCtrlID())
        {
            case IDC_SCANSPEED_SLIDER:
            {
                  ... use nPos
                break;
            }//case scanspeed slider
            case IDC_FONTSIZE_SLIDER:
            {
                  ... use nPos
                break;
            }//case fontsize slider
        }
    }
}

Button-based controls

There are myriad owner-drawn buttons for Win32, but a lot less for WinCE. The CCeButtonST is an excellent control, but it does not support a couple of things I wanted. For example, it does not support setting different captions for active/inactive buttons or modifying group box and radio button behaviour. Activating a radio button unchecks all other radios in the same control group. The CCustomButton is an owner-drawn MFC button class that has the following characteristics:

  • Works on WinCE / PocketPC / Win32, with Embedded Visual C++ 4 and Visual Studio 2005
  • Very easy to add to an existing project
  • Supports buttons, radios, checkboxes and group boxes
  • Supports BMP images with transparency (no icons)
  • Supports custom fonts
  • Supports text-only or bitmap-only buttons, too
  • Can have different caption and/or image for pressed/normal states
  • Supports regular pushbuttons, pushlike buttons and flat buttons
  • Supports gradient background colors

To use it in your application, add CustomButton.h and CustomButton.cpp to your application. Guess what: just like the controls above, this one requires you to turn off precompiled headers for the CPP file, too. Add buttons to your dialogs and add member variables of type CCustomButton. There's no need to make your buttons owner-drawn. You can also create the control dynamically with its Create method. The following properties can be set for buttons, checkboxes and radios:

  • Text color for idle (inactive - not pressed) control
  • Text color for active (pressed) control
  • Background color for idle (inactive - not pressed) control
  • Background color for active (pressed) control
  • Caption for inactive and active control
  • Bitmap for inactive and/or active control
  • Font to be used
  • Optionally, alignment for text and bitmap

Colors can be set with the following methods:

  • void SetBkgIdleColor(COLORREF crBkgIdle);
  • void SetBkgActiveColor(COLORREF crBkgActive);
  • void SetFrgIdleColor(COLORREF crFrgIdle);
  • void SetFrgActiveColor(COLORREF crFrgActive);

Captions, font and used bitmaps can be changed with:

  • void SetCaption(CString strCaption, CString strActiveCaption = _T(""));
  • void SetFont(HFONT hFont);
  • void SetBitmaps(HBITMAP hBmpInactive, HBITMAP hBmpActive = NULL);

The font and bitmaps will be freed automatically. If you omit the second caption or bitmap parameter, the same text/bitmap will be used for active (pressed) and inactive states. As with the slider above, the black color in bitmaps will be treated as transparent. If you set a custom font, make sure it is not freed until the dialog is closed. So if you use CFont for font creation, declare the variable as a dialog class member var in the header file and not in OnInitDialog.

You can set some additional properties with the SetFlags function. See the usable flags below. Whether you have a pushbutton or a checkbox depends on the utton styles (BS_XXX) used. These styles will be handled automatically if you add buttons, radios or checkboxes with the dialog editor. If you add controls manually, you should specify the following styles:

  • BS_RADIOBUTTON or BS_AUTORADIOBUTTON for radios
  • BS_CHECKBOX or BS_AUTOCHECKBOX for checkboxes
  • BS_GROUPBOX for group boxes
  • If none of the above is specified, it will be a pushbutton

Using pushbuttons

The following styles are specific for pushbuttons:

  • BS_FLAT should be used if you want a flat button with no border drawn.
  • BS_PUSHLIKE will result in a "togglable" pushbutton.
  • BS_BITMAP should be used if you want to use bitmaps. You need to set up bitmaps with the SetBitmaps function.
  • You can use BS_LEFT, BS_RIGHT, BS_CENTER, BS_VCENTER, BS_BOTTOM, BS_TOP, BS_SINGLELINE or BS_MULTILINE formatting.

You can specify additional pushbutton properties with the SetFlags function:

  • Use one of bfTextLeft, bfTextRight, bfTextTop or bfTextBottom to specify where the text will be drawn relative to the bitmap. It's like the SetAlign function of CCeButtonST
  • Use bfHGradient to have a gradient color background.
  • Use SetGradientColors to specify used colors (start, end).

Here are two buttons with gradient background and custom font:

Screenshot - gradient.jpg

Using checkboxes and radios

The following styles can be used with checkboxes and radios:

  • BS_BITMAP should be used if you want to use bitmaps. You need to set up bitmaps with SetBitmaps
  • You can use BS_LEFT, BS_RIGHT, BS_BOTTOM, BS_TOP, BS_CENTER, BS_VCENTER, BS_SINGLELINE or BS_MULTILINE formatting.
  • To use custom bitmaps for active/inactive states, use the BS_BITMAP style and specify bitmaps with the SetBitmaps function.

You can't specify BS_BITMAP with the dialog editor for these control types, so add it manually to the RC file if required. You can specify additional properties with the SetFlags function:

  • Use bfTextLeft or bfTextRight to specify where the text will be drawn relative to the bitmap.

The following checkboxes have the default bfTextRight alignment:

Screenshot - textright.jpg

Using group boxes

The following styles can be used with group boxes:

  • BS_LEFT, BS_RIGHT or BS_CENTER for text formatting.

You can specify additional properties with the SetFlags function:

  • Use bfTextTop or bfTextBottom to specify where the caption will be drawn.

The first two group boxes have a caption and demonstrate different text placement and alignment. The third box has no caption; it is nothing much more than a filled rectangle:

Screenshot - group.jpg

It takes only three calls to set up a group box:

  • SetFrgIdleColor sets caption text color.
  • SetFrameColor sets control frame and caption background color.
  • SetBkgIdleColor sets control background fill color.

You can set a custom font for the group box, too. Note that because of the way WinCE draws controls, you should take care that the group box is after your grouped controls in the RC file. It does not matter what you see in the dialog editor; the dialog in the RC file should look like this:

BEGIN
    CONTROL    
        "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | BS_BITMAP,48,54,60,10
    CONTROL    
        "Check2",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | BS_BITMAP,48,72,60,10
    PUSHBUTTON "Flat button",IDC_FLATBTN,35,27,90,19,BS_FLAT
    GROUPBOX   "GroupBox",IDC_GB,7,7,142,101
END

The group box is the last one. If you create controls dynamically, you should order them with SetWindowPos so that the group box does not cover other controls. Have fun!

Updates

Thanks to you eagle-eyed readers, I've fixed a number of smaller bugs in this project. I've also added some features, including a multilingual user interface beginning with 5 languages: English, French, Hungarian, Polish and Portuguese. If you wish to use any of the classes featured in this article, I kindly ask you to get the most recent sources from here.

History

  • 25 June, 2007 -- Original version posted
  • 16 July, 2007 -- Updated
  • 30 July, 2007 -- Article edited and moved to the main CodeProject.com article base

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here