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]; WCHAR SSID[32]; 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);
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; 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:
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:
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:
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:
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)))
{
switch(pScrollBar->GetDlgCtrlID())
{
case IDC_SCANSPEED_SLIDER:
{
... use nPos
break;
} case IDC_FONTSIZE_SLIDER:
{
... use nPos
break;
} }
}
}
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:
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:
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:
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