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
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:
BYTE BSSID; WCHAR SSID; int RSSI; int Channel; int Infastructure; int Auth; };
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);
GetAdapters function can be used to query names of network adapters. It calls the built-in
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.
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 wireless OIDs. Notes on the returned station data:
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.
Infrastructure field can be
Ndis802_11IBSS (a peer) or
- The value of the
Auth field is
Ndis802_11AuthModeOpen for unsecured stations.
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
- Then add
#include "ColoredDlg.h" to your dialog header files.
That's it! Colors should be set up in the
OnInitDialog function with
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:
COLORREF crWndBkg; COLORREF crBkgInactive; COLORREF crBkgActive; COLORREF crTxtInactive; COLORREF crTxtActive; COLORREF crDarkLine; COLORREF crLightLine; };
You can see which is which on this image:
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:
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:
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
COptionsDlg in the demo application.
- Follow Andrzej's instructions on adding the tab control to your application. Proceed exactly as if you were adding
- Change the member variable type in your dialog header from
- Add member vars for child dialog pointers, like
m_pOptionsDlg in PPDlg.h.
- Set up container mode --
CTCS_CONTAINER style -- and colors in your
- Add tabs and child dialogs with
AddDialog; have a look at the
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
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:
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.
#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
- Alternatively, for dynamic creation, add
m_Slider.Create(_T("CustomSliderClass"), strTitle, strStyle, sliderRect, this, IDC_SLIDER) to your
- Link up the variable with the control via a
DDX_Control call in
- 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:
The slider has the following properties, which should be set up in
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);
void SetRange(DWORD dwMin, DWORD dwMax);
void SetRangeMin(DWORD dwMin);
void SetRangeMax(DWORD dwMax);
void SetPos(DWORD dwPos);
void SetReverse(bool bRev);
void SetBkgColor(COLORREF crBkg);
void SetBitmaps(HBITMAP hBmpChannel, HBITMAP hBmpThumbInactive, HBITMAP hBmpThumbActive = NULL);
- The slider is horizontal by default. To make it vertical, specify the
- 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(
... use nPos
} case IDC_FONTSIZE_SLIDER:
... use nPos
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
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_AUTORADIOBUTTON for radios
BS_AUTOCHECKBOX for checkboxes
BS_GROUPBOX for group boxes
- If none of the above is specified, it will be a pushbutton
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
- You can use
You can specify additional pushbutton properties with the
- Use one of
bfTextBottom to specify where the text will be drawn relative to the bitmap. It's like the
SetAlign function of
bfHGradient to have a gradient color background.
SetGradientColors to specify used colors (start, end).
Here are two buttons with gradient background and custom font:
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
- You can use
- To use custom bitmaps for active/inactive states, use the
BS_BITMAP style and specify bitmaps with the
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
bfTextRight to specify where the text will be drawn relative to the bitmap.
The following checkboxes have the default
Using group boxes
The following styles can be used with group boxes:
BS_CENTER for text formatting.
You can specify additional properties with the
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:
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:
"Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | BS_BITMAP,48,54,60,10
"Check2",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | BS_BITMAP,48,72,60,10
PUSHBUTTON "Flat button",IDC_FLATBTN,35,27,90,19,BS_FLAT
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!
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.
- 25 June, 2007 -- Original version posted
- 16 July, 2007 -- Updated
- 30 July, 2007 -- Article edited and moved to the main CodeProject.com article base