如何创建自己的非系统剪贴板?

3

有没有可能自己实现剪贴板?如果可以的话,该如何实现呢?

我的意思是实现类似于Windows剪贴板的复制和粘贴功能,但不会干扰系统剪贴板。

为了更好地说明,这是我尝试过的方法:

uses
  ClipBrd;

...

procedure TMainForm.actCopyExecute(Sender: TObject);
var
  MyClipboard: TClipboard;
begin
  MyClipboard := TClipboard.Create;
  try
    MyClipboard.AsText := 'Copy this text';
  finally
    MyClipboard.Free;
  end;
end;

这样做可以将字符串"Copy this text"复制到剪贴板中,但它会覆盖Windows剪贴板上原有的内容。

以上代码只是创建了一个Windows剪贴板的实例,并没有创建你自己的剪贴板。

需要注意的是,自定义剪贴板可以保存任何类型的数据,而不仅限于纯文本。它应该与Windows剪贴板一样工作,但不会干扰它(不会删除其中的内容)。

如何实现呢?

谢谢。


1
只有Windows剪贴板。它没有“实例”。TClipboard仅是Windows剪贴板API的VCL包装器。根据我的第一条评论,它应该被用作单例类。 - Andreas Rejbrand
2
创建一个列表,当你复制时将物品放入其中,当你粘贴时再取出来。 - David Heffernan
1
如果您创建另一个Windows剪贴板,它会有什么用途呢?就像在Word中复制某些内容,但无法粘贴到Excel、记事本等中...这是为了什么目的呢?是为了应用程序独立于操作系统吗?操作系统旨在与其他应用程序交互,同时尽可能地给予它们更多的“自由”。 - RBA
2
对我来说,这个问题听起来像是:“我想要制作一个轮子,就像一个轮子,但它不应该是一个轮子。”如果它是圆的并且像轮子一样工作,那么它就是一个轮子。请解释为什么你认为需要自己的私人剪贴板,因为按照现在的情况,你只是在试图重新发明轮子。 - Cosmin Prund
2
如果您只想为自己的类型进行特殊处理,那么RegisterClipboardFormat是正确的方法,@Ken的答案也是正确的方法。这样,如果适用的话,您还可以在使用系统剪贴板时在自己应用程序的实例之间复制和粘贴。 - Marjan Venema
显示剩余8条评论
4个回答

5
你的问题有些混乱;你说你想在不影响系统剪贴板的情况下完成,但是(从你对自己问题的评论中可以看出)你似乎想实现类似于MS Office的“粘贴特殊”功能。
如果是第一种情况,正如其他人所说,你不能使用TClipboard包装器来做到这一点;你必须自己实现,并且在应用程序之间传递信息将非常困难。
如果是第二种情况,你可以使用Windows API RegisterClipboardFormat 来定义自己的格式。
type
  TForm1=class(TForm)
    YourCustomFormat: Word;
    procedure FormCreate(Sender: TObject);
  end;

implementation

constructor TForm1.FormCreate(Sender: TObject);
begin
  YourCustomFormat := RegisterClipboardFormat('Your Custom Format Name');
end;

为了将信息以自定义格式放入剪贴板中,您需要使用GlobalAllocGlobalLock来分配和锁定全局内存块,将数据复制到该块中,使用GlobalUnlock解锁该块,使用TClipboard.SetAsHandle将内存块传输到剪贴板。然后需要调用GlobalFree来释放内存块。
要检索自定义格式中的内容,您基本上需要执行相同的步骤,但需要反转一些步骤。您可以像之前一样使用GlobalAlloc/GlobalLock,使用TClipboard.GetAsHandle检索剪贴板的内容,将其复制到本地变量中,然后调用GlobalFree。

这是一个旧的例子,演示了如何将自定义格式(在本例中为RTF文本)放入剪贴板中 - 它来自Dr. Peter Below(TeamB成员)的新闻组帖子。 (代码和格式来自原始帖子,我没有测试过它甚至编译过它。)根据我上面的说明,将其取回的过程应该很清楚,我将让您自行解决。 :)

procedure TForm1.BtnSetRTFClick(Sender: TObject);
Const
  testtext: PChar = '{\rtf1\ansi\pard\plain 12{\ul 44444}}';
  testtext2: PChar = '{\rtf1\ansi'+
  '\deff4\deflang1033{\fonttbl{\f4\froman\fcharset0\fprq2 Times New Roman;}}'
                     +'\pard\plain 12{\ul 44444}}';
  flap: Boolean = False;
Var
  MemHandle: THandle;
  rtfstring: PChar;
begin
    If flap Then
      rtfstring := testtext2
    Else
      rtfstring := testtext;
    flap := not flap;
    MemHandle := GlobalAlloc( GHND or GMEM_SHARE, StrLen(rtfstring)+1 );
    If MemHandle <> 0 Then Begin
      try
        StrCopy( GlobalLock( MemHandle ), rtfstring );
        GlobalUnlock( MemHandle );
        With Clipboard Do Begin
          Open;
          try
            AsText := '1244444';
            SetAsHandle( CF_RTF, MemHandle );
          finally
            Close;
          end;
        End;
      Finally
        GlobalFree( MemHandle );
      End;
    End
    Else
      MessageDlg('Global Alloc failed!',
                 mtError, [mbOK], 0 );
end;

3
+1 RegisterClipboardFormat 确实是 @Blobby 的不二选择。 - Marjan Venema
1
一个权威的医生(他真的是医生吗?)编写的代码,可以将RTF片段作为复制品(这并不是私有格式),不可能解决假设的OP在使用“特殊粘贴”时遇到的问题。 - OnTheFly
RTF不是预定义的剪贴板格式,而Text是(CF_OEMTEXT或CF_TEXT或CF_UNICODETEXT):如果使用WinAPI,则必须将其实现为自定义剪贴板格式。EasyGPS使用gpx自定义格式,实际上是XML(Text)。 - menjaraz
2
我在使用自定义格式时遇到了一个问题,即在粘贴端看不到它。这是因为我在复制中使用了GlobalFree(MemHandle);这一行代码,它在指向粘贴端的数据之前释放了我的句柄。当仅复制标准格式(如文本)时,可能不适用。这有点具体,但也许这些信息会对一些人有所帮助! - Chucky

2

您应该定义自己的自定义剪贴板。它可能看起来像这样:

type
  TMyCustomClipboard = class
  private
    FStream: TMemoryStream;
    function GetAsText: string;
    procedure SetAsText(const Value: string);
    ...
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    property AsText: string read GetAsText write SetAsText;
    procedure AsAnyThing: AnyType read GetAsAnyThing write AsAnyThing;
    ...
  end;

那么你可以使用FStream作为自定义剪贴板容器。在该流中存储(复制)任何数据,并在需要时使用(粘贴)它。您只需编写一些用于您的数据类型的Get/Set方法即可。


有趣的帖子,感谢提供如何实现的示例。 - user1175743
1
在理想的情况下,应该是这样的,抽象基类TClipboard,然后是WinAPI具体派生类和X/freedesktop具体派生类,以此类推... - OnTheFly

1

TClipboard是一个封装系统剪贴板的类,因此您不能使用它来实例化另一个剪贴板的副本。您应该实现自己的类,表示具有设置器和获取器的通用缓冲区。


这并不是完全琐碎的事情,虽然肯定可以做到。 - Andreas Rejbrand
这要看情况。如果你想“理解”您自定义剪贴板支持的每种数据类型,那么这并不是简单的。但是,只要剪贴板客户端知道设置和获取什么,您可能会承认将数据存储和恢复为“原样”(例如二进制流)。 - Stan
另一个 TClipboard 实例的行为与一个 Clipboard 函数返回的行为相同。因此,扩展 TClipboard 将需要对此函数进行一些钩子操作。 - OnTheFly

1

不行。你可以拥有一个内部的内存缓冲区,用它来移动数据,如果你想的话,你可以称之为 "复制" 和 "粘贴",但不要以这种方式放在用户界面上,否则你会把你的用户搞糊涂的。只有一个系统剪贴板,你不能在不影响其他程序的情况下将数据放入其中。如果你接下来想到的是保存剪贴板内容,用你的东西覆盖它,然后恢复原始内容,那就别费力了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接