一个剪贴板增强工具

内容 介绍 为什么我们需要一个剪贴板增强实用程序? 它是如何帮助 它是如何工作的 Windows剪贴板 将数据放入剪贴板 从剪贴板获取数据 剪贴板格式 高级剪贴板功能-延迟呈现 剪贴板查看器和剪贴板查看器链 SmartClip架构 要存多少钱? 类图 全球热键 全局热键-注意事项 SmartClip -“预览面板” 设置 提示和技巧 未来的增强 结论 修订历史 介绍 本文介绍了一个名为“SmartClip”的基于系统托盘的剪贴板实用程序,它可以通过添加存储多个剪贴板条目的功能来扩展Windows剪贴板的功能。这在功能上类似于“Office剪贴板”。我们首先研究为什么SmartClip是一个更适合程序员配置文件的剪贴板工具。然后我们继续了解可以用来创建这样一个实用程序的Windows剪贴板api。接下来讨论该实用程序的体系结构。 为什么我们需要一个剪贴板增强实用程序? Windows的原生剪贴板功能可以很好地传输少量数据,但如果您想操作大量数据,就会变得非常麻烦。每次剪切或复制时,新数据都会覆盖前一个剪贴板的内容。SmartClip通过捕获复制到剪贴板的每一段数据,并将其存储在内部,增强了Windows中常规剪贴板的功能。一旦数据在SmartClip中,你可以使用热键从SmartClip中选择一个项目——这个项目会自动被放到剪贴板上,准备粘贴到任何应用程序中。您可以在“预览面板”(一个热键激活的剪贴板查看器)上预览当前剪贴板内容。 它是如何帮助 与Office剪贴板相比,SmartClip的主要优势如下:与其他工具不同,SmartClip被设计为尽可能不干扰用户当前的活动。它提供了全局热键,消除了任何鼠标操作来激活它最常用的功能。即使在剪贴板预览面板(稍后描述)被激活时,用户也不会失去键盘焦点。此外,SmartClip在内存使用上非常保守,不会占用任何系统资源。它不需要任何复杂的安装。只需将它复制到一个目录并启动它。 它是如何工作的 为了解释这个实用程序,我将解释Windows剪贴板的基础知识。如果您熟悉剪贴板如何工作的概念,您可以跳过这一节,直接进入SmartClip架构。 Windows剪贴板 剪贴板是一组由Windows公开的函数,用于让应用程序在应用程序之间或内部传输数据。本质上,它是由Windows管理的一块内存。 将数据放入剪贴板 如果一个应用程序想要把一些数据放入剪贴板,它涉及以下步骤: 应用程序必须使用OpenClipboard函数“打开”剪贴板。此函数还可以防止其他应用程序修改剪贴板。 第二步是通过调用EmptyClipboard函数来清空当前剪贴板内容。这也使得当前应用程序成为剪贴板的所有者。 下一步是调用SetClipboardData函数将数据设置到剪贴板中。当我们将一些数据放入剪贴板时,我们还必须指定其格式。应用程序还可以将相同的数据以多种格式放入剪贴板中。为此,应用程序必须为它提供的每种格式调用SetClipboardData一次。我们将在下一节详细介绍剪贴板的格式。 最后一步是通过调用CloseClipboard函数关闭剪贴板。 下面的示例代码解释了如何将文本数据放入剪贴板。隐藏,收缩,复制Code

// An example that shows how to put 'Text' data into clipboard
// pTxtData -> memory pointer that contains the text to be copied
BOOL PutTextToClipboard(LPCTSTR pTxtData)
{
    // Open the clipboard - We pass in the Main window handle
    if (!OpenClipboard( AfxGetMainWnd() )) 
        return FALSE; 

    // Now that we have opened the clipboard, we empty its contenst.
    // Calling this function also makes the current application the
    // owner of the clipboard
    EmptyClipboard(); 

    // Allocate a global memory object for the text to be copied. 
    HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, lstrlen(pTxtData)); 

    // Lock the handle and copy the text to the buffer. 
    LPTSTR lptstrCopy = GlobalLock(hglbCopy); 
    memcpy(lptstrCopy, pTxtData, lstrlen(pTxtData)); 
    GlobalUnlock(hglbCopy); 

    // Place the handle on the clipboard. 
    // CF_TEXT is a standard clipboard format defined by Windows
    SetClipboardData(CF_TEXT, hglbCopy); 

    // Close the clipboard. 
    CloseClipboard(); 
    return TRUE; 
}

现在我们已经学习了如何将内容放到剪贴板中,我们将继续下一节,解释为什么我们需要不同的剪贴板格式。 从剪贴板获取数据 如果一个应用程序想要从剪贴板获得一些数据,它涉及以下步骤: 再一次,它应该通过调用OpenClipboard函数来“打开”剪贴板。 下一步是确定要检索哪一种可用的剪贴板格式。 一旦我们知道要获取哪种格式,我们就可以通过调用GetClipboardData函数来检索所选格式的数据句柄。 GetClipboardData返回的句柄仍然属于剪贴板,因此应用程序不能释放它或将其锁定。因此,它应该从句柄获取一份数据副本。 与前面一样,最后一步是通过调用CloseClipboard函数关闭剪贴板。 下面的示例代码解释了如何从剪贴板获取文本数据:收缩,复制Code

// An example that shows how to get 'Text' data into clipboard
// Returns the data got from the Clipboard, if not NULL
LPCTSTR GetTextFromClipboard()
{
    LPCTSTR   = NULL;

    // Check if the clipboard contains textual data
    // CF_TEXT is a standard clipboard format for TEXT 
    // defined in Winuser.h
    if (!IsClipboardFormatAvailable(CF_TEXT)) 
        return strTxtData;

    // Open the clipboard
    if (!OpenClipboard(hwndMain)) 
        return strTxtData; 

    // Get the handle of the Global memory that contains the text
    HGLOBAL hglb = GetClipboardData(CF_TEXT); 
    if (hglb != NULL) 
    { 
        // Get the size of the data
        SIZE_T DataSize = GlobalSize(hData);
        LPTSTR lptstr = GlobalLock(hglb); 
        if (lptstr != NULL) 
        { 
            // Allocate data and copy the data
            strTxtData = new BYTE[DataSize];
            memcpy(strTxtData, lptstr, DataSize);
            GlobalUnlock(hglb); 
        } 
    } 

    // before returning close the clipboard
    CloseClipboard(); 

    return strTxtData; 
}

剪贴板格式 任何其他应用程序要使用您放入剪贴板的数据,它需要理解t保存数据的“格式”。为了方便实现这一点,在调用SetClipboardData函数时,还必须指定数据的格式。Windows已经预定义了一些剪贴板格式,它们被称为“标准剪贴板格式”。这些预定义格式的例子有: CF_TEXT -文本格式。每行以回车/换行(CR-LF)组合结束。空字符表示数据的结束。ANSI文本使用此格式。 CF_BITMAP—位图(HBITMAP)的句柄。等。 当应用程序将一些数据放入剪贴板时,建议使用尽可能多的格式。例如,当有人在MS Excel中复制一个单元格时,它会将相同的数据以超过20种不同的剪贴板格式(内部多次调用SetClipboardData)包括CF_BITMAP。这解释了为什么我们能够“粘贴”内容复制从Excel甚至到MSPaint。人们可能会怀疑,在内存中保存相同数据(但格式不同)的这么多副本是否真的有效。“延迟渲染”是用来解决这个问题。 高级剪贴板功能-延迟呈现 当在剪贴板上放置剪贴板格式时,应用程序可以延迟以该格式呈现数据,直到需要数据时才显示该数据。为此,应用程序可以为SetClipboardData函数的hData参数指定NULL。如果应用程序支持几种剪贴板格式,其中一些或所有格式的渲染都很耗时,那么这是很有用的。通过传递一个空句柄,窗口只在需要时才呈现复杂的剪贴板格式。 如果一个窗口延迟了剪贴板格式的呈现,那么只要它是剪贴板的所有者,它就必须准备好根据请求呈现格式。系统发送给剪贴板所有者一个WM_RENDERFORMAT消息,当一个请求被接收到一个特定的格式,没有被呈现。在接收到此消息后,窗口应该调用SetClipboardData函数,以请求的格式在剪贴板上放置全局内存句柄。如果剪贴板所有者被销毁,并已延迟呈现一些或所有剪贴板格式,它收到WM_RENDERALLFORMATS消息。在接收到此消息后,窗口应该为它提供的所有剪贴板格式在剪贴板上放置有效的内存句柄。这确保了这些格式在剪贴板所有者被销毁后仍然可用。Microsoft Office应用程序使用“延迟渲染”来避免在将这么多格式放入剪贴板时内存浪费。如果在关闭MSWord之前尝试从它中复制大量内容,您将收到以下消息。 这是MSWord内部使用“延迟渲染”的一个可视化指示。 剪贴板查看器和剪贴板查看器链 剪贴板查看器是显示剪贴板当前内容的窗口。在%windir%\system32\ clipdb .exe中找到的剪贴簿查看器是剪贴板查看器的一个示例。当剪贴板的内容改变时,系统发送一个WM_DRAWCLIPBOARD到注册的剪贴板查看器。 剪贴板查看器链是将两个或多个剪贴板查看器链接在一起,以便它们在操作时相互依赖。这个相互依赖(链)允许所有运行的剪贴板查看器应用程序接收发送到当前剪贴板的消息。应用程序窗口可以通过调用SetClipboardViewer函数将自己添加到剪贴板查看器链中。返回值是链中下一个剪贴板查看器窗口的句柄。每个剪贴板查看器窗口必须跟踪剪贴板查看器链中的下一个窗口。在关闭之前,剪贴板查看器窗口必须通过调用ChangeClipboardChain函数从剪贴板查看器链中删除自己。然后系统发送一个WM_CHANGECBCHAIN消息到链中的第一个窗口。现在我们已经了解了Windows剪贴板的基础知识,接下来让我们看看SmartClip的架构。 SmartClip架构 如前所述,SmartClip是一个基于系统托盘的应用程序。由于我们的应用程序的主要目标是捕获和存储剪贴板内容,因此每当剪贴板内容发生变化时,我们都需要得到通知。为此,我们将自己注册为剪贴板查看器。隐藏,复制Code

// Enter the Clipboard Viewer Chain
m_hWndNextViewer = SetClipboardViewer();

这确保了无论何时剪贴板的内容发生变化,我们都会通过CWnd::OnDrawClipboard方法得到通知。在这个方法的实现中,我们要求CClipboardMgr的实例保存放入剪贴板中的内容的副本。然后,我们将消息传递给链中的下一个剪贴板查看器,如下所示:复制Code

void CMainFrame::OnDrawClipboard() 
{
    CFrameWnd::OnDrawClipboard();

    if(m_bEnabled)
    {
        HWND hOwner = ::GetClipboardOwner();
        if(hOwner != m_hWnd)
        {
            // Capture the data!!
            m_ClipboardMgr.GetClipboardEntries();
        }
        // Pass the message to the Next Viewer
        ::SendMessage(m_hWndNextViewer, WM_DRAWCLIPBOARD,
                                                NULL, NULL);
    }
    Invalidate();
}

要存多少钱? 我们应该注意到,每次有人想要将一些内容放入剪贴板时,他们都想要以尽可能多的格式进行操作。通常,这不会造成内存开销,因为剪贴板所有者不会呈现所有这些格式,除非有人特别要求一种格式。如果我们的政策是拯救a复制任何放入剪贴板的内容,这显然会导致大量内存浪费。让我们以Excel将一些内容复制到剪贴板作为示例场景。Excel将以超过20种不同的格式存储数据(Excel内部使用延迟呈现优化内存使用)。对于特定格式,数据的平均大小可以假设为200 Kb左右(如果选择很大,这是很有可能的)。如果SmartClip决定保留所有格式的副本,这将导致大约4mb的数据在内存中。由于SmartClip将多个剪贴板操作的副本保存在内存中,因此导致内存问题恶化。为了避免这个内存问题,SmartClip在存储格式上是有选择性的。在当前的实现中,我们只存储文本数据,尽管体系结构允许将其扩展到所需的任意格式。 类图 为了更好地理解SmartClip的架构,让我们看看它的类层次结构。 对于我们想要存储的每个剪贴板格式,我们创建一个派生自CClipboardEntry类的类的实例。CClipboardEntry是一个抽象基类,包含GetFromClipboard()和PutToClipboard()等方法。我们在基类中有这些方法,因为从剪贴板获取和放入数据的逻辑是通用的,并且独立于剪贴板格式,如下所示:收缩,Code

BOOL CClipboardEntry::GetFromClipboard()
{
    // Clipboard is already opened by CClipboardEntryArray 
    ASSERT(m_ClipboardType != CB_CF_EMPTY);
    ASSERT(m_pCBData == NULL);

    // Get the data
    HANDLE hData = GetClipboardData(m_ClipboardType);
    ASSERT(hData != NULL);

    if(hData)
    {
        m_dwCBDataSize = GlobalSize(hData);
        BYTE* pData = (BYTE*)::GlobalLock(hData);
        if(pData != NULL)
        {
            // Allocate data and copy the entry
            m_pCBData = new BYTE[m_dwCBDataSize];

            ASSERT(m_pCBData != NULL);

            if(m_pCBData)
                memcpy(m_pCBData, pData, m_dwCBDataSize);
        }

        GlobalUnlock(hData);
    }
    return TRUE;
}

Hide副本,复制Code

BOOL CClipboardEntry::PutToClipboard()
{
    // Clipboard is already opened by CClipboardEntryArray 
    ASSERT(m_ClipboardType != CB_CF_EMPTY);
    ASSERT(m_pCBData != NULL);

    if(m_pCBData)
    {
        // Allocate a global memory object for the Data
        HGLOBAL hgData = GlobalAlloc(GMEM_MOVEABLE,
                             m_dwCBDataSize); 
        if (hgData == NULL) 
        { 
            return FALSE; 
        } 

        // Lock the handle and copy the text to the buffer. 
        BYTE* pData = (BYTE*)GlobalLock(hgData); 
        memcpy(pData, m_pCBData, m_dwCBDataSize);
        GlobalUnlock(hgData); 

        // Place the handle on the clipboard. 
        SetClipboardData(m_ClipboardType, hgData); 
    }
    return TRUE;
}

在SmartClip中,CClipboardEntry有两个派生类——用于保存文本数据的CClipboardCFTextEntry和用于保存位图数据的CClipboardBitmapEntry。派生类中的重要方法是RenderData(),它具有将内容绘制到给定的设备上下文中的逻辑。因为一个复制到剪贴板的操作可以产生存储在剪贴板中的多种剪贴板格式,所以我们使用包含在CClipboardEntryArray类中的CClipboardEntry数组来捕获它。CClipboardEntryArray类的定义如下:复制Code

class CClipboardEntryArray : public CArray<CClipboardEntry
                                           CClipboardEntry*, CClipboardEntry*>

由于SmartClip能够存储可配置数量的剪贴板操作,它在CCLipboardList类中内部维护一个CCLipboardEntryArrays列表。我们还有一个管理器类——CClipboardMgr,它包含对CCLipboardList的引用。 全球热键 现在我们已经了解了SmartClip是如何存储剪贴板数据的,接下来我们来看看如何使用这个工具。SmartClip带有全局热键,可以帮助你导航和预览剪贴板的内容。在CClipboardList中向上导航的默认热键是'Ctrl+Alt+左箭头',向下导航是'Ctrl+Alt+右箭头'。导航操作会自动将新内容放入剪贴板,并打开剪贴板预览面板。 全局热键-注意事项 使用RegisterHotKey方法注册全局热键。当用户按热键时,Windows将向应用程序窗口发送WM_HOTKEY消息。我发现,如果应用程序没有任何可见的窗口,窗口将不会发送此通知。我用来解决这个问题的方法是让窗口一直可见。因此,当不需要窗口时,我没有隐藏它,而是将它移动到桌面可见区域之外。 SmartClip -“预览面板” SmartClip有一个能够呈现当前剪贴板内容的查看器或预览面板。还可以使用热键激活此查看器。默认热键是'Ctrl+Alt+C'。当激活时,这个查看器将显示为桌面右下角的一个小窗口,靠近系统托盘。查看器的设计使其不会干扰用户的当前活动。为了方便这一点,一旦观察者被激活,它会在短时间内关闭或隐藏自己。负责呈现查看器的代码是派生类中CClipboardEntry::RenderData()的实现。下面是预览面板的快照,显示CClipboardCFTextEntry所呈现的内容。 设置 下图是SmartClip的设置界面。 通过这个对话框,用户可以设置如下内容:要跟踪多少剪贴板条目(我建议使用20这样的值)、各种操作的默认热键等等。用户还可以暂时启用或禁用SmartClip。另一个有用的功能是让SmartClip在Windows启动时自动启动。为此,SmartClip在以下注册表键中注册自己- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run。 提示和技巧 当你从“丰富”源粘贴内容时,你会发现格式被保存在目的地。虽然这是非常有用的,但有时会被证明是非常恼人的。在OfficeXP中,有一个粘贴“智能标签”,如果需要,它可以帮助你删除格式。SmartClip帮助您非常容易地剥离格式化信息。这就是你如何做o: 复制内容-如果它是一个丰富的来源,数据将在剪贴板中以多种格式可用。但SmartClip只选择性地复制文本格式,不包含格式信息。 从当前剪贴板条目导航。默认的热键是“Ctrl+Alt+左箭头”。这将清除当前剪贴板并将其替换为新内容。 现在,回到原始内容。默认的热键是'Ctrl+Alt+右箭头'。这将把原始内容放回剪贴板。唯一的区别是它只能以CF_TEXT格式提供。 现在,粘贴的东西,将不会有任何格式信息! 未来的增强 我将要做的其中一项改进是添加更多剪贴板格式。另外,作为一个用户,如果你发现任何明显的限制,请发送给我的反馈@chiramattel.com。请务必提及SmartClip作为主题。另外,访问这个URL以获得任何未来的更新。 结论 作为一名程序员,我经常使用剪贴板,我发现缺乏对本机剪贴板的支持。因此我编写了这个工具。我已经使用这个工具相当一段时间了,现在发现它非常有用。我得说,一旦你开始使用SmartClip,它就会让你上瘾。 修订历史 25 - 4月- 2004 原文 本文转载于:http://www.diyabc.com/frontweb/news369.html

posted @ 2020-08-06 02:05  Dincat  阅读(290)  评论(0编辑  收藏  举报