WiFi 扫描仪 + 自定义 MFC 控件
一个带有自定义滑块、选项卡控件、按钮和复选框的 Wi-Fi 扫描仪
引言
几周前我在家里设置了无线网络,并对无线工具产生了兴趣。我想知道 AP 扫描仪软件是如何工作的。我做了一些研究,发现使用 PocketPC 2003 及更高版本系统中可用的 NDISUIO
驱动程序可以非常简单地获取 Wi-Fi 信息。因此,我编写了一个简单的扫描仪,它有两种视图模式。第一种是列出所有附近 AP 的彩色列表。另一种模式以大数字显示选定 AP 的信号强度。这样它就具有可接受的户外可见性,并且您可以跟踪单个信号,而不必看到邻居们的 AP 在屏幕上闪烁。我还包含了之前编写的各种自定义 MFC 控件:一个位图滑块、一个选项卡控件和按钮。我希望您也能发现这些类很有用。
本文特色是以下类
CWifiPeek
- 一个可用于列出附近 Wi-Fi 设备的类CColoredDlg
- 一个支持子控件自定义颜色的对话框基类CCustomTabCtrl
- 一个在 WinCE/PocketPC 下运行良好的自定义选项卡控件CCustomSlider
- 一个支持 BMP 的自定义滑块(轨道条)控件CCustomButton
- 自定义按钮控件(按钮、复选框、单选按钮等)
使用应用程序
PeekPocket 使用起来相当简单。只需打开您的 WLAN,运行应用程序,然后查看列出的 AP。安全 AP 以红色显示,不活动的 AP 以灰色显示。不活动的 AP 是当前不可见的 AP。如果您点击一个 AP 名称,您可以“放大”,即在不显示任何其他 AP 的情况下跟踪一个信号。您可以在“选项”选项卡上设置列表字体大小和扫描速度。您也可以在那里设置网络类型和安全筛选器。您的 Wi-Fi 适配器应显示在组合框中。如果未显示,则说明有问题。在这种情况下,您可以尝试以下操作
- 禁用然后重新启用无线网络。
- 如果 PDA 与您的 PC 有 ActiveSync 连接,请断开连接。
- 如果您已配置与 AP 的自动连接,请尝试删除它。
如果这些方法无效,有时重启会奏效。您可以使用 Visual Studio 2005 和 PocketPC 2003 SDK 编译该应用程序。PeekPocket 在 WM5 和 WM6 设备上也能正常运行。如果您将其他 SDK 添加到演示项目中,您可能需要更改一些编译器/链接器设置。
轻松访问点列表
CWifiPeek
类完成了所有 Wi-Fi 查询工作。它也可以在非 MFC 应用程序中使用。您需要将 CWifiPeek.h 和 CWifiPeek.cpp 添加到您的项目中。不要忘记为该 CPP 文件禁用预编译头文件!该类使用 NDIS 用户模式 I/O 驱动程序 (NDISUIO
) 来执行 AP 扫描。以下结构将用于返回站信息
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?
};
该类提供了以下函数
bool GetAdapters(LPWSTR pDest, DWORD &dwBufSizeBytes);
bool RefreshBSSIDs(LPWSTR pAdapter);
bool GetBBSIDs(LPWSTR pAdapter, struct BSSIDInfo *pDest, DWORD &dwBufSizeBytes, DWORD &dwReturnedItems);
GetAdapters
函数可用于查询网络适配器的名称。它调用内置的 NDIS
(而不是 NDISUIO
)驱动程序。您需要传递一个 WCHAR
缓冲区和缓冲区大小的地址。该函数用逗号分隔的适配器名称填充缓冲区。它还会将 DWORD
设置为返回的字节数。它会过滤掉一些适配器名称,例如红外线、GPRS、ActiveSync 连接。如果函数返回 true
,但指示复制了零字节,则可能存在问题。您的 Wi-Fi 可能已关闭或可能无法运行。
该函数使用 IOCTL_NDIS_GET_ADAPTER_NAMES
IOCTL 调用。必须检索适配器名称,因为 NDISUIO
调用需要此参数。RefreshBSSIDs
函数请求驱动程序启动 AP 扫描。它接受一个参数:适配器名称。成功后,它返回 true
。此函数会频繁调用,以便我们拥有最新的列表。
GetBBSIDs
函数返回可用站点列表,即对等节点和访问点。它接受适配器名称、目标缓冲区指针以及缓冲区大小。如果成功,它返回 true
并将 dwReturnedItems
填充为返回的结构数量,而不是字节数。这两种方法使用 OID_802_11_BSSID_LIST_SCAN
和 OID_802_11_BSSID_LIST
无线 OID。关于返回的站点数据说明
BSSID
字段包含站点的 MAC 地址。- 站点名称在
SSID
中返回。这可能是一个空字符串。 - 信号强度以 dBm 为单位在
RSSI
中返回。-50 dBm 的信号比 -60 强,比 -70 强,依此类推。通常,低于 -75 的信号被认为非常差。-50 dBm 的信号电平还可以。 Infrastructure
字段可以是Ndis802_11IBSS
(对等节点)或Ndis802_11Infrastructure
(AP)。- 对于不安全的站点,
Auth
字段的值是Ndis802_11AuthModeOpen
。
彩色对话框
所有对话框的基类是 CColoredDlg
而不是 CDialog
。该类通过 OnCtlColor
处理程序支持自定义颜色。在对话框控件的绘制周期中,会调用此处理程序,父对话框可以设置正在绘制的控件的背景和前景颜色。此功能可用于使 GUI 看起来略有不同。但是,如果您查看代码,它非常简单。您可以按照几个简单的步骤使用此类
- 将 ColoredDlg.h 和 ColoredDlg.cpp 添加到您的项目中。
- 在您的对话框头文件和 CPP 文件中,将所有
CDialog
替换为CColoredDlg
。 - 然后将
#include "ColoredDlg.h"
添加到您的对话框头文件中。
就是这样!颜色应在 OnInitDialog
函数中使用 SetBkgColor
和 SetFrgColor
调用进行设置。这些调用接受一个 COLORREF
值,例如可以使用 RGB
宏创建。
您可以使用 OnCtlColor
处理程序绘制不同颜色的不同控件。控件不一定需要拥有者绘制才能更改颜色。
选项卡控件
CustomTabCtrl.h 和 CustomTabCtrl.cpp 中的选项卡控件是 Andrzej Markowski 出色的 Custom Tab Control 的 WinCE / PocketPC 兼容版本。我通过删除所有 Win32 特定的内容(如 XP 主题支持、工具提示等)使其与 WinCE 兼容。我还将选项卡制成了矩形,因为使用 WinCE GDI 绘制它们要简单得多。存在左右方向,但它们是实验性的并且非常慢,即 WinCE 上没有 PlgBlt
。总的来说,建议仅使用顶部和底部方向。该类仍然兼容 Win32,也可以与 Embedded Visual C++ 4 一起使用。
请参阅 Andrzej 的文章,了解如何在您的应用程序中使用此控件。只需不要忘记为该版本禁用预编译头文件。该控件既可以动态创建,也可以从模板创建。该控件支持自定义颜色。要获取和设置这些颜色,请使用以下函数
void GetColors(TabItemColors *pColorsOut);
void SetColors(TabItemColors *pColorsIn, BOOL fRedraw=FALSE);
这些调用使用以下结构
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
};
您可以在此图像中看到哪个是哪个
“扫描仪”选项卡处于活动状态,“选项”选项卡处于非活动状态。选项卡周围的线条是“暗线”。crLightLine
字段保留供将来使用。“1”标记的右侧矩形区域是“窗口背景”。也就是说,控件窗口中未绘制选项卡的部分。最好先调用 GetColors
,更新您想要的颜色,然后调用 SetColors
。请查看 CPPDlg::OnInitDialog
函数。您可以像使用原始控件一样设置选项卡字体。有一点需要注意,如果您使用 CFont
,不应该在 OnInitDialog
中声明它,而应在对话框头文件中声明为成员变量。这样,在关闭对话框之前,字体不会被释放。
我向该控件添加了其他内容:容器模式。Andrzej 的选项卡控件实际上是一个带有按钮的栏。当用户单击某项时,该栏会将各种通知发送给其父项,但显示和隐藏子窗口的责任在于应用程序。另一方面,容器可以托管子对话框并自动显示或隐藏它们。可以通过设置 CTCS_CONTAINER
样式来启用容器模式。此模式由派生类 CCustomTabContainer
支持。与容器模式相关的新函数是
int GetTabsHeight();
void SetTabsHeight(int nHeight);
void AdjustRect(BOOL bLarger, LPRECT lpRect);
void AddDialog(int nIndex, CString strText, CDialog *pDlg);
void RemoveDialog(int nIndex);
在容器模式下,整个选项卡控件窗口的高度显然大于选项卡本身的高度
可以使用 SetTabsHeight
函数设置选项卡的高度。要获取当前值,请使用 GetTabsHeight
函数。该控件有一个 AdjustRect
函数,类似于 CTabCtrl::AdjustRect
。要在应用程序中使用容器,请按以下步骤操作
- 将对话框资源添加到您的应用程序。对话框应没有标题栏、边框,并确保它们具有“子”样式。
- 为这些对话框添加类,例如演示应用程序中的
CScannerDlg
和COptionsDlg
。 - 遵循 Andrzej 的 说明将选项卡控件添加到您的应用程序。像添加
CCustomTabCtrl
一样进行! - 在您的对话框头文件中,将成员变量类型从
CCustomTabCtrl
更改为CCustomTabContainer
。 - 为子对话框指针添加成员变量,例如 PPDlg.h 中的
m_pScannerDlg
和m_pOptionsDlg
。 - 在您的
OnInitDialog
函数中设置容器模式(CTCS_CONTAINER
样式)和颜色。 - 使用
AddDialog
添加选项卡和子对话框;请查看CPPDlg::OnInitDialog
函数。
请注意,容器会自动在容器销毁、点击关闭按钮或调用 RemoveDialog
时调用 delete
对话框。
滑块控件
这是一个位图滑块控件,实现为 CWnd
派生类。它也可以在 WinCE / PocketPC 和 Win32 平台上使用。它具有水平和垂直方向,以及反向模式
要在您的应用程序中使用它,请按以下步骤操作
- 将 CustomSlider.h 和 CustomSlider.cpp 添加到您的项目中。应为 CPP 文件关闭预编译头文件。
- 将
#include "CustomSlider.h"
添加到相应的对话框头文件中。 - 在对话框类中添加一个类型为
CCustomSlider
的成员变量。 - 对于基于模板的创建,请添加一个类设置为
CustomSliderClass
的自定义控件。 - 或者,对于动态创建,请在
OnInitDialog
函数中添加m_Slider.Create(_T("CustomSliderClass"), strTitle, strStyle, sliderRect, this, IDC_SLIDER)
。 - 在
DoDataExchange
中的DDX_Control
调用中将变量与控件链接起来。 - 在您的对话框中添加滚动处理程序;请参阅下面的示例。
您可能知道,滑块控件有两个不同的部分:所谓的“拇指”——即实际滑动的对象——和“通道”,即拇指移动的区域。下图显示了一个通道、一个拇指以及它们组成的滑块控件
滑块具有以下属性,应在 OnInitDialog
中设置
BkgColor
确定用于擦除控件背景的颜色。RangeMin
指定控件可以返回的最小值。RangeMax
指定控件可以返回的最大值。Pos
是拇指的当前位置。- 有 3 个位图:一个用于通道图像,一个用于非活动(未点击)的拇指,一个用于活动(点击、拖动)的拇指。
- 有一个
Reverse
标志,用于确定滑块是否反向工作。
您可以使用以下函数来获取和设置这些属性
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);
注释
- 滑块默认是水平的。要使其垂直,请指定
TBS_VERT
样式。 - 滑块没有默认图形,因此在所有情况下都必须设置位图。
- 如果省略
SetBitmaps
的第三个参数,则活动和非活动状态的拇指将使用相同的位图。 - 控件支持透明度。黑色将被视为透明。
- 控件将自动释放您指定的位图句柄。
- 控件支持非负范围和位置值,即零及以上。如果您需要负值,则必须偏移返回的位置。
- 滑块矩形与通道位图尺寸相同是一个好主意。
- 使用的位图的宽度和高度应能被 2 整除。
- 如有疑问,请查看
COptionsDlg::OnInitDialog
。示例代码有效,因此肯定有办法。
以下是如何处理滑块事件。在对话框类中添加 OnHScroll 或 OnVScroll 处理程序。处理程序应如下所示
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
}
}
}
基于按钮的控件
Win32 有大量的拥有者绘制按钮,但 WinCE 的数量要少得多。CCeButtonST
是一个出色的控件,但它不支持我想要的一些功能。例如,它不支持为活动/非活动按钮设置不同的标题,或修改组合框和单选按钮的行为。激活一个单选按钮会取消选中同一控件组中的所有其他单选按钮。CCustomButton
是一个拥有者绘制的 MFC 按钮类,具有以下特点
- 在 WinCE / PocketPC / Win32 上运行,支持 Embedded Visual C++ 4 和 Visual Studio 2005
- 添加到现有项目非常容易
- 支持按钮、单选按钮、复选框和组合框
- 支持带透明度的 BMP 图像(无图标)
- 支持自定义字体
- 也支持纯文本或纯位图按钮
- 可以为活动/非活动按钮(按下/正常)状态设置不同的标题和/或图像
- 支持常规推杆按钮、推杆式按钮和扁平按钮
- 支持渐变背景色
要在您的应用程序中使用它,请将 CustomButton.h 和 CustomButton.cpp 添加到您的应用程序中。您猜怎么着:与上面的控件一样,这个控件也要求您为 CPP 文件禁用预编译头文件。将按钮添加到对话框中,并添加类型为 CCustomButton
的成员变量。无需将按钮设置为拥有者绘制。您也可以使用其 Create
方法动态创建控件。可以为按钮、单选按钮和复选框设置以下属性
- 空闲(非活动 - 未按下)控件的文本颜色
- 活动(按下)控件的文本颜色
- 空闲(非活动 - 未按下)控件的背景颜色
- 活动(按下)控件的背景颜色
- 非活动和活动控件的标题
- 非活动和/或活动的控件的位图
- 使用的字体
- 可选的文本和位图对齐方式
颜色可以使用以下方法设置
void SetBkgIdleColor(COLORREF crBkgIdle);
void SetBkgActiveColor(COLORREF crBkgActive);
void SetFrgIdleColor(COLORREF crFrgIdle);
void SetFrgActiveColor(COLORREF crFrgActive);
标题、字体和使用的位图可以通过以下方式更改
void SetCaption(CString strCaption, CString strActiveCaption = _T(""));
void SetFont(HFONT hFont);
void SetBitmaps(HBITMAP hBmpInactive, HBITMAP hBmpActive = NULL);
字体和位图将自动释放。如果省略第二个标题或位图参数,活动(按下)和非活动状态将使用相同的文本/位图。与上面的滑块一样,位图中的黑色将被视为透明。如果您设置了自定义字体,请确保在关闭对话框之前不要释放它。因此,如果您使用 CFont
创建字体,请在头文件中将变量声明为对话框类的成员变量,而不是在 OnInitDialog
中。
您可以使用 SetFlags
函数设置一些额外的属性。请参阅下面的可用标志。您拥有的是推杆按钮还是复选框取决于使用的按钮样式(BS_XXX
)。如果您使用对话框编辑器添加按钮、单选按钮或复选框,这些样式将自动处理。如果您手动添加控件,则应指定以下样式
BS_RADIOBUTTON
或BS_AUTORADIOBUTTON
用于单选按钮BS_CHECKBOX
或BS_AUTOCHECKBOX
用于复选框BS_GROUPBOX
用于组合框- 如果未指定以上任何一项,则为推杆按钮
使用推杆按钮
以下样式特定于推杆按钮
- 如果您想要一个没有边框的扁平按钮,应使用
BS_FLAT
。 BS_PUSHLIKE
将产生一个“可切换”的推杆按钮。- 如果您想使用位图,应使用
BS_BITMAP
。您需要使用SetBitmaps
函数设置位图。 - 您可以使用
BS_LEFT
、BS_RIGHT
、BS_CENTER
、BS_VCENTER
、BS_BOTTOM
、BS_TOP
、BS_SINGLELINE
或BS_MULTILINE
格式。
您可以使用 SetFlags
函数指定其他推杆按钮属性
- 使用
bfTextLeft
、bfTextRight
、bfTextTop
或bfTextBottom
中的一个来指定文本相对于位图的绘制位置。这类似于CCeButtonST
的SetAlign
函数 - 使用
bfHGradient
来实现渐变色背景。 - 使用
SetGradientColors
指定使用的颜色(开始、结束)。
以下是两个具有渐变背景和自定义字体的按钮
使用复选框和单选按钮
以下样式可用于复选框和单选按钮
- 如果您想使用位图,应使用
BS_BITMAP
。您需要使用SetBitmaps
设置位图 - 您可以使用
BS_LEFT
、BS_RIGHT
、BS_BOTTOM
、BS_TOP
、BS_CENTER
、BS_VCENTER
、BS_SINGLELINE
或BS_MULTILINE
格式。 - 要为活动/非活动状态使用自定义位图,请使用
BS_BITMAP
样式并通过SetBitmaps
函数指定位图。
您无法为这些控件类型在对话框编辑器中指定 BS_BITMAP
,因此如果需要,请手动将其添加到 RC 文件中。您可以使用 SetFlags
函数指定其他属性
- 使用
bfTextLeft
或bfTextRight
来指定文本相对于位图的绘制位置。
以下复选框具有默认的 bfTextRight
对齐方式
使用组合框
以下样式可用于组合框
BS_LEFT
、BS_RIGHT
或BS_CENTER
用于文本格式。
您可以使用 SetFlags
函数指定其他属性
- 使用
bfTextTop
或bfTextBottom
来指定标题的绘制位置。
前两个组合框有一个标题并演示了不同的文本放置和对齐方式。第三个框没有标题;它几乎只是一个填充的矩形
只需三步即可设置一个组合框
SetFrgIdleColor
设置标题文本颜色。SetFrameColor
设置控件边框和标题背景颜色。SetBkgIdleColor
设置控件背景填充颜色。
您也可以为组合框设置自定义字体。请注意,由于 WinCE 绘制控件的方式,您应该注意将组合框放在 RC 文件中的分组控件之后。在对话框编辑器中看到什么并不重要;RC 文件中的对话框应如下所示
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
组合框是最后一个。如果您动态创建控件,则应使用 SetWindowPos
对它们进行排序,以便组合框不会覆盖其他控件。玩得开心!
更新
感谢您敏锐的读者,我在该项目中修复了许多小错误。我还添加了一些功能,包括一个多语言用户界面,最初支持 5 种语言:英语、法语、匈牙利语、波兰语和葡萄牙语。如果您想使用本文介绍的任何类,恳请您从 这里 获取最新的源代码。
历史
- 2007 年 6 月 25 日 -- 发布了原始版本
- 2007 年 7 月 16 日 -- 更新
- 2007 年 7 月 30 日 -- 文章经过编辑并移至 CodeProject.com 的主文章库