2017.5.13插入符

本文介绍了如何在MFC应用程序中创建字符光标和位图光标,包括使用CreateSolidCaret函数创建与字体大小相匹配的插入符,以及如何加载位图并使用CreateCaret函数创建位图光标。

我们知道打开TXT文本后有一个光标闪烁,意味着你可以从这里输入文本。现在我们利用MFC创建这个光标(字符光标和位图光标)。
首先在View窗口下创建一个WM_CREATE消息事件

int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO: Add your specialized creation code here

    return 0;
}

然后利用CreateSolidCaret(int nWidth ,int nHeight)函数来创建插入符。第一个参数是指定插入符的宽度,第二个参数是指定插入符的高度。当这两个参数为零时,系统默认的插入符的宽度和高度为系统窗口边界的宽度和高度。而系统窗口边界的宽度和高度可以通过GetSystemMetrics函数获得。
要注意的是插入符一旦被创建就立即隐藏了,需要调用ShowCaret函数来显示这个插入符。

int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;
    // TODO: Add your specialized creation code here
    /****************wjl************************
        <*字处理程序中的插入符*>
    CClientDC dc(this);
    TEXTMETRIC tm;
    dc.GetTextMetrics(&tm);
    CreateSolidCaret(tm.tmAveCharWidth/8,tm.tmHeight);
    ShowCaret();
    *****************wjl***********************/
    return 0;
}

TEXTMETRIC是一个结构体,里面包含了文本的一些属性,比如字体的两个高度 升序 tmAscent 和降序tmDescent,平均高度之类的。
TEXTMETRIC结构体
为了使插入符能随着字体的大小改变。我们首先要获得字体的大小,这时需要一个函数GetTextMetrics,参数是一个结构体的指针。这里将TEXTMETRIC结构体的对象tm传递进去。


接下来是创建位图插入符,就是用一个位图来代表光标,一闪一闪的(不理解为啥要这样。。。)
这里的bitmap要放在CTextView中,作为全局变量。


class CTextView : public CView
...
private:
    CBitmap bitmap;

首先要创建一个位图,再通过bitmap.LoadBitmap(IDB_BITMAP1);调用位图。

int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO: Add your specialized creation code here
    /****************wjl************************
        <*位图的插入符*>
    *****************wjl***********************/
    CClientDC dc(this);
    bitmap.LoadBitmap(IDB_BITMAP1);
    CreateCaret(&bitmap);
    ShowCaret();

    return 0;
}

函数CreateCaret可以创建位图的插入符,参数是一个位图指针。

<think>我们正在使用MFC在VS2017中实现检测USB设备插入并获取端口号的功能。根据引用[1]的描述,USB设备插入时,系统会有一系列的处理过程,包括获取设备描述、分配地址、读取配置等。在Windows环境下,我们可以通过WindowsAPI来监控USB设备的插拔事件,并获取设备信息,包括端口号。在Windows中,我们可以通过注册设备通知(RegisterDeviceNotification)来接收设备插拔消息,然后通过WM_DEVICECHANGE消息处理函数来响应这些事件。此外,我们可以使用SetupDi系列函数来枚举设备并获取设备信息。以下是一个简单的示例步骤:1.MFC应用程序中(例如,对话框应用程序),重写`OnInitDialog`函数,调用`RegisterDeviceNotification`注册设备通知。2.在消息映射中添加`ON_MESSAGE(WM_DEVICECHANGE,OnDeviceChange)`来处理设备变化消息。3.在`OnDeviceChange`函数中,根据消息类型(如DBT_DEVICEARRIVAL)判断是否有设备插入。4.当有设备插入时,我们可以通过设备信息集(使用SetupDiGetClassDevs获取)来枚举设备,并获取设备的端口号。注意:获取USB设备的端口号需要遍历设备树,找到对应的物理设备对象(PDO),并获取其位置信息(LocationInformation)。在Windows中,USB设备的端口号通常包含在设备实例ID中,例如“Port_#0001.Hub_#0001”中的0001就是端口号。下面是一个简单的代码框架:###步骤1:在对话框类中添加消息处理函数声明在对话框头文件中(例如,MyDialog.h)添加:```cppafx_msgLRESULTOnDeviceChange(WPARAMwParam,LPARAMlParam);```###步骤2:在对话框的源文件(.cpp)中添加消息映射```cppBEGIN_MESSAGE_MAP(CMyDialog,CDialogEx)ON_MESSAGE(WM_DEVICECHANGE,&CMyDialog::OnDeviceChange)END_MESSAGE_MAP()```###步骤3:在OnInitDialog中注册设备通知```cppBOOLCMyDialog::OnInitDialog(){CDialogEx::OnInitDialog();//注册设备通知DEV_BROADCAST_DEVICEINTERFACENotificationFilter;ZeroMemory(&NotificationFilter,sizeof(NotificationFilter));NotificationFilter.dbcc_size=sizeof(DEV_BROADCAST_DEVICEINTERFACE);NotificationFilter.dbcc_devicetype=DBT_DEVTYP_DEVICEINTERFACE;//指定要接收的接口类GUID,这里使用USB设备的GUID//注意:我们可以使用GUID_DEVINTERFACE_USB_DEVICE,但需要包含usbiodef.h或定义它//如果不想包含,可以自己定义:DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE,0xA5DCBF10,0x6530,0x11D2,0x90,0x1F,0x00,0xC0,0x4F,0xB9,0x51,0xED);NotificationFilter.dbcc_classguid=GUID_DEVINTERFACE_USB_DEVICE;m_hDevNotify=RegisterDeviceNotification(GetSafeHwnd(),&NotificationFilter,DEVICE_NOTIFY_WINDOW_HANDLE);if(!m_hDevNotify){AfxMessageBox(_T("注册设备通知失败!"));}returnTRUE;}```注意:需要定义`m_hDevNotify`为成员变量(HDEVNOTIFY类型),并在析构函数中取消注册。###步骤4:实现OnDeviceChange函数```cppLRESULTCMyDialog::OnDeviceChange(WPARAMwParam,LPARAMlParam){PDEV_BROADCAST_HDRpHdr=(PDEV_BROADCAST_HDR)lParam;if(wParam==DBT_DEVICEARRIVAL)//设备插入{if(pHdr->dbch_devicetype==DBT_DEVTYP_DEVICEINTERFACE){PDEV_BROADCAST_DEVICEINTERFACEpDevInf=(PDEV_BROADCAST_DEVICEINTERFACE)pHdr;CStringstrDevicePath=pDevInf->dbcc_name;//设备路径//这里可以根据设备路径获取设备信息,包括端口号CStringstrPort=GetUsbPortFromDevicePath(strDevicePath);//显示端口号或做其他处理AfxMessageBox(_T("USB设备插入,端口号:")+strPort);}}elseif(wParam==DBT_DEVICEREMOVECOMPLETE)//设备拔出{//类似处理}return0;}```###步骤5:实现GetUsbPortFromDevicePath函数这个函数的作用是从设备路径中提取端口号。在Windows中,USB设备的设备路径通常包含端口号信息。例如,设备路径可能包含类似“Port_#0001.Hub_#0001”的字串,其中0001就是端口号。```cppCStringCMyDialog::GetUsbPortFromDevicePath(constCString&strDevicePath){CStringstrPort=_T("");//在设备路径中查找"Port_#"intnStart=strDevicePath.Find(_T("Port_#"));if(nStart!=-1){nStart+=6;//跳过"Port_#"intnEnd=strDevicePath.Find(_T("."),nStart);if(nEnd!=-1){strPort=strDevicePath.Mid(nStart,nEnd-nStart);}}returnstrPort;}```注意:这个方法依赖于设备路径字串的格式,不同系统版本或设备类型可能略有不同。更可靠的方法是使用SetupDi系列函数来获取设备的实例ID,然后解析实例ID。###使用SetupDi函数获取端口号(更可靠的方法)我们可以使用设备路径打开设备,然后获取设备信息(如设备实例ID),然后解析实例ID来获取端口号。但是,由于在OnDeviceChange中直接使用SetupDi可能会比较慢,我们可以考虑在后台线程中处理。这里提供另一种思路:在检测到设备插入后,我们可以枚举所有USB设备,并比较设备路径,然后获取设备实例ID,从中解析端口号。由于代码较长,这里只给出基本步骤:1.使用`SetupDiGetClassDevs`获取设备信息集。2.遍历设备信息集,使用`SetupDiEnumDeviceInfo`枚举设备。3.使用`SetupDiGetDeviceInstanceId`获取设备实例ID。4.在实例ID中查找端口号(通常包含在“Port_”后面)。示例代码片段:```cppCStringGetDevicePortByInstanceId(constCString&strDeviceInstanceId){CStringstrPort=_T("");intnStart=strDeviceInstanceId.Find(_T("Port_#"));if(nStart!=-1){nStart+=6;//跳过"Port_#"intnEnd=strDeviceInstanceId.Find(_T("&"),nStart);if(nEnd==-1)nEnd=strDeviceInstanceId.Find(_T("#"),nStart);if(nEnd==-1)nEnd=strDeviceInstanceId.Find(_T("."),nStart);if(nEnd!=-1){strPort=strDeviceInstanceId.Mid(nStart,nEnd-nStart);}}returnstrPort;}```然后在OnDeviceChange中,我们可以通过设备路径获取设备实例ID,再调用上述函数:```cpp//注意:这里需要包含setupapi.h和initguid.h,并链接Setupapi.lib#include<setupapi.h>#include<initguid.h>#pragmacomment(lib,"Setupapi.lib")CStringGetDeviceInstanceIdFromDevicePath(constCString&strDevicePath){CStringstrInstanceId=_T("");HDEVINFOhDevInfo=SetupDiGetClassDevs(NULL,NULL,NULL,DIGCF_ALLCLASSES|DIGCF_PROFILE);if(hDevInfo==INVALID_HANDLE_VALUE)returnstrInstanceId;SP_DEVINFO_DATAdevInfoData;devInfoData.cbSize=sizeof(SP_DEVINFO_DATA);for(DWORDi=0;SetupDiEnumDeviceInfo(hDevInfo,i,&devInfoData);i++){TCHARbuf[MAX_PATH]={0};DWORDnSize=0;//获取设备实例IDif(SetupDiGetDeviceInstanceId(hDevInfo,&devInfoData,buf,MAX_PATH,&nSize)){//我们需要将设备路径与设备实例ID关联?实际上,设备路径通常与设备接口相关,而设备实例ID是设备实例的唯一标识。//另一种方法是获取设备接口的详细信息,然后匹配设备路径。}}SetupDiDestroyDeviceInfoList(hDevInfo);returnstrInstanceId;}```但是,更直接的方法是通过设备接口获取设备信息。由于在OnDeviceChange中我们得到的是设备接口的路径,我们可以通过`SetupDiGetClassDevs`指定设备接口路径来获取设备信息。由于这个操作相对复杂,而且可能影响消息处理的性能,我们可以考虑在后台线程中处理设备插入事件。###注意事项1.由于获取设备信息可能需要一些时间,如果在OnDeviceChange中直接进行复杂的设备枚举操作,可能会导致界面卡顿。建议将获取设备信息的操作放在工作线程中。2.设备拔出时,设备路径可能已经无效,所以获取设备信息应该在设备插入时尽快完成。###总结以上代码提供了一个基本的框架,用于在MFC中检测USB设备插入并获取端口号。由于Windows设备管理的复杂性,实际应用中可能需要根据具体需求调整。特别是解析端口号的方法,可能需要根据实际设备实例ID的格式进行调整。为了确保代码的健壮性,建议使用SetupDi系列函数来获取设备信息,并解析设备实例ID中的端口号。###相关问题1.如何获取USB设备的其他信息,如设备描述、厂商ID等?2.如何处理多个USB设备同时插入的情况?3.在Windows中,如何区分不同的USB设备类型(如鼠标、键盘、存储设备)?</think>### 在VS2017中使用MFC检测USB设备插入并获取端口号 在MFC中实现USB设备插入检测和端口号获取,主要通过`WM_DEVICECHANGE`消息处理和Windows设备管理API实现。以下是实现步骤和示例代码: #### 核心步骤 1. **注册设备通知**:在窗口初始化时注册设备变化通知 2. **处理`WM_DEVICECHANGE`消息**:检测设备插入/移除事件 3. **获取设备信息**:使用SetupAPI获取USB设备端口号 4. **解析设备路径**:从设备实例ID中提取端口号 #### 示例代码 ##### 头文件声明 (`YourDialog.h`) ```cpp #include <Dbt.h> #include <SetupAPI.h> #include <cfgmgr32.h> #pragma comment(lib, "Setupapi.lib") class CYourDialog : public CDialogEx { // ... protected: HDEVNOTIFY m_hDevNotify; // 设备通知句柄 afx_msg LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam); CString GetUsbPortFromDevicePath(LPCTSTR pDevicePath); DECLARE_MESSAGE_MAP() }; ``` ##### 源文件实现 (`YourDialog.cpp`) ```cpp BEGIN_MESSAGE_MAP(CYourDialog, CDialogEx) ON_MESSAGE(WM_DEVICECHANGE, &CYourDialog::OnDeviceChange) END_MESSAGE_MAP() // 初始化时注册设备通知 BOOL CYourDialog::OnInitDialog() { CDialogEx::OnInitDialog(); DEV_BROADCAST_DEVICEINTERFACE NotificationFilter; ZeroMemory(&NotificationFilter, sizeof(NotificationFilter)); NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; // 注册USB设备通知 m_hDevNotify = RegisterDeviceNotification( GetSafeHwnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE ); if (!m_hDevNotify) { AfxMessageBox(_T("设备通知注册失败!")); } return TRUE; } // 设备变化消息处理 LRESULT CYourDialog::OnDeviceChange(WPARAM wParam, LPARAM lParam) { PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam; if (wParam == DBT_DEVICEARRIVAL) // 设备插入 { if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr; // 获取并显示端口号 CString strPort = GetUsbPortFromDevicePath(pDevInf->dbcc_name); CString strMsg; strMsg.Format(_T("USB设备插入! 端口号: %s"), strPort); AfxMessageBox(strMsg); } } else if (wParam == DBT_DEVICEREMOVECOMPLETE) // 设备移除 { AfxMessageBox(_T("USB设备已移除")); } return 0; } // 从设备路径获取端口号 CString CYourDialog::GetUsbPortFromDevicePath(LPCTSTR pDevicePath) { CString strDevicePath(pDevicePath); int nStart = strDevicePath.Find(_T("Port_#")); if (nStart != -1) { nStart += 6; // 跳过"Port_#"前缀 int nEnd = strDevicePath.Find(_T('.'), nStart); if (nEnd != -1) { return strDevicePath.Mid(nStart, nEnd - nStart); } } return _T("未知端口"); } // 关闭时取消注册 void CYourDialog::OnDestroy() { if (m_hDevNotify) { UnregisterDeviceNotification(m_hDevNotify); } CDialogEx::OnDestroy(); } ``` #### 关键说明 1. **设备路径格式**:USB设备路径通常包含`Port_#XXXX.Hub_#YYYY`格式,其中`XXXX`即为端口号 2. **SetupAPI使用**:需要包含`Setupapi.h`并链接`Setupapi.lib` 3. **权限要求**:应用程序需要管理员权限才能访问设备信息 4. **GUID定制**:如需监听特定设备类型,可在`NotificationFilter.dbcc_classguid`中指定设备GUID #### 测试注意事项 1. 在调试模式下运行程序(可能需要管理员权限) 2. 插入USB设备时观察消息框显示的端口号 3. 端口号格式通常为4位十六进制数(如`0001`, `0002`) ### 相关问题 1. **如何区分不同类型的USB设备(如鼠标、键盘、存储设备)?** [可通过设备接口GUID或解析设备描述实现,需在`RegisterDeviceNotification`时指定特定设备类GUID^1] 2. **当多个USB设备同时插入时如何正确处理?** [每个插入事件都会触发独立的`WM_DEVICECHANGE`消息,可通过设备路径区分不同设备^1] 3. **如何获取USB设备的其他详细信息(如厂商ID、产品ID)?** [需使用`SetupDiGetDeviceRegistryProperty`API从设备属性中提取,需解析设备实例ID中的VID/PID^1] 4. **在控制台应用程序中如何实现类似功能?** [需改用`DEVICE_NOTIFY_SERVICE_HANDLE`注册方式,配合Windows服务架构实现^1] > [^1]: 引用自USB设备检测的系统处理流程和Windows设备管理API文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wangxiaobei2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值