剪贴板(ClipBoard)是内存中的一块区域,是Windows内置的一个非常有用的工具,通过剪贴板,架起了一座彩桥,使得在各种应用程序之间,传递和共享信息成为可能。监控剪贴板是比较常见的一种剪贴板应用方式,下面我们通过一个简单实列完成相关知识的学习。
剪贴板监控功能控制
在SystemPanel的菜单资源编辑器中添加新的菜单项
修改菜单变量名,添加菜单事件处理函数
在剪贴板监控控制菜单处理函数中添加如下代码:
void SystemPanel::Onm_ClipBoardSelected(wxCommandEvent& event)
{
if(event.IsChecked())
{
AddClipboardFormatListener(wxTheApp->GetTopWindow()->GetHandle());
}
else
{
RemoveClipboardFormatListener(wxTheApp->GetTopWindow()->GetHandle());
}
}
监控剪贴板消息
由于剪贴板变化消息WM_CLIPBOARDUPDATE属于系统级消息,目前只能在主窗口中获取,所以我们只能通过重载主窗口系统消息处理函数然后转发给SystemPanel中的处理函数来实现。
首先,我们在主窗口MSWWindowProc添加WM_CLIPBOARDUPDATE处理,代码如下:
//处理系统消息和自定义消息
WXLRESULT SDKLearnFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
{
switch (message)
{
case WM_CLIPBOARDUPDATE:
{
m_SystemPanel->DoClipboardUpdate(message,wParam,lParam);
}
break;
default:
break;
}
return wxFrame::MSWWindowProc(message, wParam, lParam);
}
剪贴板变化具体的处理过程将会放到SystemPanel的DoClipboardUpdate函数中进行。
处理剪贴板变化
在SystemPanel的DoClipboardUpdate函数中添加如下代码:
#include <filesystem>
using namespace filesystem;
//处理剪贴板改变消息
void SystemPanel::DoClipboardUpdate(WXUINT message,WXWPARAM wParam, WXLPARAM lParam)
{
cout<<_("message: 剪贴板改变了!")<<message<<endl;
if (!IsClipboardFormatAvailable(CF_HDROP))
return;
if (!OpenClipboard(NULL))
return;
HDROP hDrop = (HDROP)::GetClipboardData(CF_HDROP);
GlobalLock(hDrop);
if (hDrop != NULL)
{
TCHAR filename[MAX_PATH+1];
int fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
long indexItem;
for (int i = 0; i < fileCount; ++i)
{
DragQueryFile(hDrop, i, filename, sizeof(filename));
path file_path(filename);
UINT sizefile;
//判断file_path是否为文件夹
if(is_directory(file_path))
{
recursive_directory_iterator beg_iter(file_path);
recursive_directory_iterator end_iter;
for (; beg_iter != end_iter; ++beg_iter)
{
if (is_directory(*beg_iter))
{
continue;
}
else
{
if(m_FileList->FindItem(-1,beg_iter->path().wstring())!=wxNOT_FOUND)
continue;
indexItem = m_FileList->InsertItem(m_FileList->GetItemCount(),_(""));
if(is_regular_file(beg_iter->path())) //推断是否为普通文件
{
sizefile = file_size(beg_iter->path()); //文件大小(字节)
m_FileList->SetItem(indexItem, 0, beg_iter->path().wstring());
m_FileList->SetItem(indexItem, 1,to_string(file_size(beg_iter->path())));
}
}
}
}
//是文件
else
{
if(exists(file_path)) //推断文件存在性
{
if(m_FileList->FindItem(-1,file_path.wstring())!=wxNOT_FOUND)
continue;
indexItem = m_FileList->InsertItem(m_FileList->GetItemCount(),_(""));
sizefile = file_size(file_path); //文件大小(字节)
m_FileList->SetItem(indexItem, 0, file_path.wstring());
m_FileList->SetItem(indexItem, 1,to_string(sizefile));
// cout<<"File:"<<file_path.string()<<" SIZE:"<<ss.str()<<endl;
}
}
}
}
// }
GlobalUnlock(hDrop);
// DragFinish(hDrop);//释放文件名缓冲区
CloseClipboard();
}
在剪贴板变化消息处理过程中,我们希望把文件拷贝消息中的所有文件、目录、目录下的所有文件都显示在SystemPanel的ListCrtl控件中。
首先,需要在资源编辑窗口中重命名ListCtrl变量为m_FileList。
ListCtrl配置及初始化过程如下:
在资源属性编辑窗口中设置ListCtrl为Report模式。
在SystemPanel初始化函数中添加如下代码设置ListCtrl的表头
//设置列表表头
// 添加第一列的表头
wxListItem col0;
col0.SetId(0); // id必须设置,代表第0列
col0.SetText("文件名");
col0.SetWidth(350); // 设置列宽
col0.SetAlign(wxLIST_FORMAT_CENTER); // 此列的文本居中显示
m_FileList->InsertColumn(0, col0); // 插入列
// 添加第二列的表头
wxListItem col1;
col1.SetId(1);
col1.SetText("文件大小");
m_FileList->InsertColumn(1, col1);
// 添加第三列的表头
wxListItem col2;
col2.SetId(2);
col2.SetText("状态");
m_FileList->InsertColumn(2, col2);
剪贴板消息中如果包含文件或文件夹的信息,我们可以通过导入<filesystem>头文件来实现文件及文件夹相关操作。比如:
遍历目录获取目录中所有文件名
在菜单编辑窗口中,添加遍历目录菜单
消息响应函数代码如下:
//获取目录中所有文件
#include <filesystem>
using namespace std::filesystem;
#include <wx/dirdlg.h>
//提升用户权限,解决特殊目录监控软件崩溃的问题
#define LAA(se) {{se},SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT}
#define BEGIN_PRIVILEGES(tp, n) static const struct {ULONG PrivilegeCount;LUID_AND_ATTRIBUTES Privileges[n];} tp = {n,{
#define END_PRIVILEGES }};
// in case you not include wdm.h, where this defined
#define SE_BACKUP_PRIVILEGE (17L)
ULONG AdjustPrivileges()
{
if (ImpersonateSelf(SecurityImpersonation))
{
HANDLE hToken;
if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &hToken))
{
BEGIN_PRIVILEGES(tp, 1)
LAA(SE_BACKUP_PRIVILEGE),
END_PRIVILEGES
AdjustTokenPrivileges(hToken, FALSE, (PTOKEN_PRIVILEGES)&tp, 0, 0, 0);
CloseHandle(hToken);
}
}
return GetLastError();
}
//参考https://stackoverflow.com/questions/51799541/c-filesystem-iterator-invalid-argument
void SystemPanel::Onm_GetAllFilesSelected(wxCommandEvent& event)
{
wxString pathname=wxDirSelector(_("请选择要遍历的目录!"), "",
wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if ( pathname.empty() )
return;
AdjustPrivileges();//提升权限,解决特殊文件访问导致的软件崩溃
for(auto &p:std::filesystem::recursive_directory_iterator(pathname.ToStdString()))
{
std::cout << p.path() << endl;
if(m_FileList->FindItem(-1,p.path().wstring(),false)!=wxNOT_FOUND)
continue;
auto indexItem = m_FileList->InsertItem(m_FileList->GetItemCount(),_(""));
m_FileList->SetItem(indexItem, 0, (p.path().wstring()));
if(!std::filesystem::is_directory(p))
m_FileList->SetItem(indexItem, 1,to_string(std::filesystem::file_size(p)));
else
m_FileList->SetItem(indexItem, 1,_("Dir"));
m_FileList->SetItem(indexItem, 2,p.path().extension().wstring() );
}
cout<< "文件数量: "<<m_FileList->GetItemCount()<<endl;
}
在遍历目录的代码中增加了提升用户权限的代码,主要是针对部分系统目录或特殊软件目录,使用程序运行用户权限操作访问会导致错误提示,而我们没有提供错误处理机制,所以导致软件崩溃退出。
获取文件真实类型
在菜单编辑窗口中添加新菜单
在资源属性窗口中修改菜单变量并添加菜单响应函数
此时我们会用到一个特别的库libmagic,这是一个用来根据文件头识别文件类型的开发库。该库的主页地址如下:
libmagic download | SourceForge.net
在Msys2命令行窗口中运行如下命令,安装该库
pacman -S mingw-w64-x86_64-file
在Msys2命令行窗口中运行如下命令,检查该库是否工作正常
file -i --mime 文件全路径
在Msys2命令行窗口中运行如下命令,找到文件头信息配置文件magic.mgc拷贝到程序代码根目录
pacman -Ql mingw-w64-x86_64-file
在项目资源管理窗口中添加需要加载的库文件libmagic.dll.a
在文件类型检测菜单响应函数中添加如下代码:
//获取文件真实类型
#include <wx/filedlg.h>
#include <magic.h>
magic_t handle_;
void SystemPanel::Onm_GetFileTypeSelected(wxCommandEvent& event)
{
wxString filename=wxFileSelector(_("请选择文件!"));
if ( filename.empty() )
return;
std::string magic_path = "magic.mgc";//数据库文件路径
if (handle_ == NULL)
{
handle_ = magic_open(MAGIC_NONE | MAGIC_COMPRESS);
}
if (handle_ == NULL)
{
printf("magic_open handle failed!");
return ;
}
if (magic_load(handle_, magic_path.c_str()) != 0)
{
magic_close(handle_);
printf("magic_load handle failed!");
return ;
}
auto type = magic_file(handle_, filename.c_str());
if (type == NULL)
{
cout<< "The File Type unknown"<<endl;
return ;
}
std::string file_type = type;
cout<< "文件: "<<filename<<" 的类型是:"<<file_type<<endl;
}
编译并运行程序,此时监控拷贝目录消息、列出文件中所有文件及文件夹、文件类型检测功能已经实现。
至此,剪贴板监控功能已经完整实现。