Clipboard: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: Clipboard
| Old | New | Line | |
|---|---|---|---|
| 1 | + | \ | |
| 2 | + | \ | |
| 3 | + | ||
| 4 | + | ## Predefined types | |
| 5 | + | ||
| 6 | + | | TPredefinedClipboardFormat | MIME type | | |
| 7 | + | |----------------------------|----------------------------| | |
| 8 | + | | pcfText | text/plain | | |
| 9 | + | | pcfBitmap | image/bmp | | |
| 10 | + | | pcfPixmap | image/xpm | | |
| 11 | + | | pcfIcon | image/lcl.icon | | |
| 12 | + | | pcfPicture | image/lcl.picture | | |
| 13 | + | | pcfMetaFilePict | image/lcl.metafilepict | | |
| 14 | + | | pcfObject | application/lcl.object | | |
| 15 | + | | pcfComponent | application/lcl.component | | |
| 16 | + | | pcfCustomData | application/lcl.customdata | | |
| 17 | + | | | | | |
| 18 | + | ||
| 19 | + | ## Text | |
| 20 | + | ||
| 21 | + | For use of simple text Clipboard offer property AsText which can be used for reading an writing plain text. | |
| 22 | + | ||
| 23 | + | Writing text: | |
| 24 | + | ||
| 25 | + | ``` pascal | |
| 26 | + | Clipboard.AsText := 'Hello clipboard!'; | |
| 27 | + | ``` | |
| 28 | + | ||
| 29 | + | Reading text: | |
| 30 | + | ||
| 31 | + | ``` pascal | |
| 32 | + | ShowMessage('Clipboard content: ' + Clipboard.AsText); | |
| 33 | + | ``` | |
| 34 | + | ||
| 35 | + | Clipboard is a TClipboard variable and the Clipbrd unit should be added in the uses clause in order to use the variable: | |
| 36 | + | ||
| 37 | + | ``` pascal | |
| 38 | + | uses | |
| 39 | + | ..., Clipbrd; | |
| 40 | + | ``` | |
| 41 | + | ||
| 42 | + | ## Text oriented components | |
| 43 | + | ||
| 44 | + | Some visual components like [[TEdit]], [[TMemo]], [[TStringGrid]], [[TLabeledEdit]], [[TMaskEdit]], [[TSpinEdit]] and [[TFloatSpinEdit]] have ability to select a part of contained text and offer additional functionality for handling clipboard operations on selected text. | |
| 45 | + | ||
| 46 | + | ``` pascal | |
| 47 | + | ||
| 48 | + | procedure CopyToClipboard; | |
| 49 | + | procedure CutToClipboard; | |
| 50 | + | procedure PasteFromClipboard; | |
| 51 | + | ``` | |
| 52 | + | ||
| 53 | + | ## HTML source | |
| 54 | + | ||
| 55 | + | The ClipBoard supports reading and writing of [[HTML]]. | |
| 56 | + | ||
| 57 | + | Example of reading from and writing HTML source to clipboard: | |
| 58 | + | ||
| 59 | + | ``` pascal | |
| 60 | + | uses | |
| 61 | + | Clipbrd, ...; | |
| 62 | + | var | |
| 63 | + | Html, PlainText: String; | |
| 64 | + | ... | |
| 65 | + | begin | |
| 66 | + | Html := ClipBoard.GetAsHtml; | |
| 67 | + | ... | |
| 68 | + | Html := '<b>Formatted</b> text'; | |
| 69 | + | PlainText := 'Simple Text'; | |
| 70 | + | ClipBoard.SetAsHtml(Html, PlainText); | |
| 71 | + | end. | |
| 72 | + | ``` | |
| 73 | + | ||
| 74 | + | ### Windows | |
| 75 | + | ||
| 76 | + | Handling Html content on the clipboard in Windows requires fidling with headers on Windows.\ | |
| 77 | + | While in the past users had to do this manually, this now is handled transparently by the ClipBrd unit.\ | |
| 78 | + | ||
| 79 | + | ## Image | |
| 80 | + | ||
| 81 | + | ### Load from clipboard | |
| 82 | + | ||
| 83 | + | ``` pascal | |
| 84 | + | uses | |
| 85 | + | Clipbrd, LCLIntf, LCLType, ...; | |
| 86 | + | ||
| 87 | + | procedure LoadBitmapFromClipboard(Bitmap: TBitmap); | |
| 88 | + | begin | |
| 89 | + | if Clipboard.HasFormat(PredefinedClipboardFormat(pcfDelphiBitmap)) then | |
| 90 | + | Bitmap.LoadFromClipboardFormat(PredefinedClipboardFormat(pcfDelphiBitmap)); | |
| 91 | + | if Clipboard.HasFormat(PredefinedClipboardFormat(pcfBitmap)) then | |
| 92 | + | Bitmap.LoadFromClipboardFormat(PredefinedClipboardFormat(pcfBitmap)); | |
| 93 | + | end; | |
| 94 | + | ``` | |
| 95 | + | ||
| 96 | + | ### Save to clipboard | |
| 97 | + | ||
| 98 | + | ``` pascal | |
| 99 | + | uses | |
| 100 | + | Clipbrd, ...; | |
| 101 | + | ||
| 102 | + | procedure SaveBitmapToClipboard(Bitmap: TBitmap); | |
| 103 | + | begin | |
| 104 | + | Clipboard.Assign(Bitmap); | |
| 105 | + | end; | |
| 106 | + | ``` | |
| 107 | + | ||
| 108 | + | ## Custom format | |
| 109 | + | ||
| 110 | + | ## Multiple objects | |
| 111 | + | ||
| 112 | + | ## Getting notified of changes | |
| 113 | + | ||
| 114 | + | The LCL does not pass on Windows messages. It only passes on messages \> WM_USER. This means you have to write your own message handler. [Processing non - user messages in your window](http://wiki.lazarus.freepascal.org/Win32/64_Interface#Processing_non-user_messages_in_your_window) | |
| 115 | + | ||
| 116 | + | Sample code to implement message handler: | |
| 117 | + | ||
| 118 | + | ``` pascal | |
| 119 | + | unit Unit1; | |
| 120 | + | ||
| 121 | + | {$mode delphi}{$H+} | |
| 122 | + | ||
| 123 | + | interface | |
| 124 | + | ||
| 125 | + | uses | |
| 126 | + | Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, | |
| 127 | + | Clipbrd, StdCtrls, Windows, Messages; | |
| 128 | + | ||
| 129 | + | type | |
| 130 | + | TForm1 = class(TForm) | |
| 131 | + | Button1: TButton; | |
| 132 | + | procedure FormCreate(Sender: TObject); | |
| 133 | + | procedure FormDestroy(Sender: TObject); | |
| 134 | + | private | |
| 135 | + | FNextClipboardOwner: HWnd; // handle to the next viewer | |
| 136 | + | // Here are the clipboard event handlers | |
| 137 | + | function WMChangeCBChain(AwParam: WParam; AlParam: LParam):LRESULT; | |
| 138 | + | function WMDrawClipboard(AwParam: WParam; AlParam: LParam):LRESULT; | |
| 139 | + | public | |
| 140 | + | end; | |
| 141 | + | ||
| 142 | + | var | |
| 143 | + | Form1: TForm1; | |
| 144 | + | ||
| 145 | + | implementation | |
| 146 | + | ||
| 147 | + | {$R *.lfm} | |
| 148 | + | var | |
| 149 | + | PrevWndProc:windows.WNDPROC; | |
| 150 | + | ||
| 151 | + | function WndCallback(Ahwnd: HWND; uMsg: UINT; wParam: WParam; | |
| 152 | + | lParam: LParam): LRESULT; stdcall; | |
| 153 | + | begin | |
| 154 | + | if uMsg = WM_CHANGECBCHAIN then begin | |
| 155 | + | Result := Form1.WMChangeCBChain(wParam, lParam); | |
| 156 | + | Exit; | |
| 157 | + | end | |
| 158 | + | else if uMsg=WM_DRAWCLIPBOARD then begin | |
| 159 | + | Result := Form1.WMDrawClipboard(wParam, lParam); | |
| 160 | + | Exit; | |
| 161 | + | end; | |
| 162 | + | Result := CallWindowProc(PrevWndProc, Ahwnd, uMsg, WParam, LParam); | |
| 163 | + | end; | |
| 164 | + | ||
| 165 | + | { TForm1 } | |
| 166 | + | ||
| 167 | + | {Don't forget to assign to the OnCreate event.} | |
| 168 | + | procedure TForm1.FormCreate(Sender: TObject); | |
| 169 | + | begin | |
| 170 | + | PrevWndProc := Windows.WNDPROC(SetWindowLongPtr(Self.Handle, GWL_WNDPROC, PtrUInt(@WndCallback))); | |
| 171 | + | FNextClipboardOwner := SetClipboardViewer(Self.Handle); | |
| 172 | + | end; | |
| 173 | + | ||
| 174 | + | {Don't forget to assign to the OnDestroy event.} | |
| 175 | + | procedure TForm1.FormDestroy(Sender: TObject); | |
| 176 | + | begin | |
| 177 | + | ChangeClipboardChain(Handle, FNextClipboardOwner); | |
| 178 | + | end; | |
| 179 | + | ||
| 180 | + | function TForm1.WMChangeCBChain(AwParam: WParam; AlParam: LParam): LRESULT; | |
| 181 | + | var | |
| 182 | + | Remove, Next: THandle; | |
| 183 | + | begin | |
| 184 | + | Remove := AwParam; | |
| 185 | + | Next := AlParam; | |
| 186 | + | if FNextClipboardOwner = Remove then FNextClipboardOwner := Next | |
| 187 | + | else if FNextClipboardOwner <> 0 then | |
| 188 | + | SendMessage(FNextClipboardOwner, WM_ChangeCBChain, Remove, Next) | |
| 189 | + | Result := 0; | |
| 190 | + | end; | |
| 191 | + | ||
| 192 | + | function TForm1.WMDrawClipboard(AwParam: WParam; AlParam: LParam): LRESULT; | |
| 193 | + | begin | |
| 194 | + | if Clipboard.HasFormat(CF_TEXT) Then Begin | |
| 195 | + | ShowMessage(Clipboard.AsText); | |
| 196 | + | end; | |
| 197 | + | SendMessage(FNextClipboardOwner, WM_DRAWCLIPBOARD, 0, 0); // VERY IMPORTANT | |
| 198 | + | Result := 0; | |
| 199 | + | end; | |
| 200 | + | ||
| 201 | + | end. | |
| 202 | + | ``` | |
| 203 | + | ||
| 204 | + | ## View the clipboard contents | |
| 205 | + | ||
| 206 | + | Sometimes its useful to see whats actually in the clipboard at any one time. Here are a couple of methods I use, on a form with a TMemo and a one second timer to do just that - | |
| 207 | + | ||
| 208 | + | ``` pascal | |
| 209 | + | procedure TForm1.CheckClipboard(); | |
| 210 | + | var | |
| 211 | + | I : integer; | |
| 212 | + | List : TStringList; | |
| 213 | + | begin | |
| 214 | + | memo1.clear; | |
| 215 | + | Memo1.Append('[' + Clipboard.AsText + ']'); | |
| 216 | + | List := TStringList.Create; | |
| 217 | + | try | |
| 218 | + | ClipBoard.SupportedFormats(List); | |
| 219 | + | for i := 0 to List.Count-1 do begin | |
| 220 | + | //Memo1.Append(List.Strings[i]); // uncomment to see all available formats | |
| 221 | + | case List.Strings[i] of // show these specific ones | |
| 222 | + | 'Rich Text Format', 'text/plain', 'UTF8_STRING' : | |
| 223 | + | ReadClip(List.Strings[i]); | |
| 224 | + | end; | |
| 225 | + | end; | |
| 226 | + | finally | |
| 227 | + | List.Free; | |
| 228 | + | end; | |
| 229 | + | end; | |
| 230 | + | ||
| 231 | + | function TForm1.ReadClip(TheFormat : ANSIString) : ANSIString; | |
| 232 | + | var | |
| 233 | + | Stream: TMemoryStream; | |
| 234 | + | Fmt : TClipboardFormat; | |
| 235 | + | List : TStringList; | |
| 236 | + | begin | |
| 237 | + | if TheFormat = '' then exit; | |
| 238 | + | Stream := TMemoryStream.Create; | |
| 239 | + | List := TStringList.Create; | |
| 240 | + | try | |
| 241 | + | if Clipboard.HasFormatName(TheFormat) then begin | |
| 242 | + | Memo1.Append(#10+TheFormat); | |
| 243 | + | Fmt := ClipBoard.FindFormatID(TheFormat); | |
| 244 | + | ClipBoard.GetFormat(Fmt, Stream); | |
| 245 | + | if Stream.Size > 0 then begin | |
| 246 | + | Stream.Seek(0, soFromBeginning); | |
| 247 | + | List.LoadFromStream(Stream); | |
| 248 | + | Memo1.Lines.AddStrings(List, False); | |
| 249 | + | end; | |
| 250 | + | end; | |
| 251 | + | finally | |
| 252 | + | List.Free; | |
| 253 | + | Stream.Free; | |
| 254 | + | end; | |
| 255 | + | end; | |
| 256 | + | ``` | |
| 257 | + | ||
| 258 | + | ## How to fix empty GTK2 clipboard on exit | |
| 259 | + | ||
| 260 | + | Usually when your GTK2 app exits, it's clipboard becomes empty. Bad for usual user. This unit is a dirty fix, add it to "uses" somewhere. | |
| 261 | + | ||
| 262 | + | ``` pascal | |
| 263 | + | unit fix_gtk_clipboard; | |
| 264 | + | ||
| 265 | + | {$mode objfpc}{$H+} | |
| 266 | + | ||
| 267 | + | interface | |
| 268 | + | ||
| 269 | + | uses | |
| 270 | + | gtk2, gdk2, Clipbrd; | |
| 271 | + | ||
| 272 | + | implementation | |
| 273 | + | ||
| 274 | + | var | |
| 275 | + | c: PGtkClipboard; | |
| 276 | + | t: string; | |
| 277 | + | ||
| 278 | + | finalization | |
| 279 | + | c := gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); | |
| 280 | + | t := Clipboard.AsText; | |
| 281 | + | gtk_clipboard_set_text(c, PChar(t), Length(t)); | |
| 282 | + | gtk_clipboard_store(c); | |
| 283 | + | end. | |
| 284 | + | ``` | |
| 285 | + | ||
| 286 | + | In May 2018 I (dbannon) found that putting this bit of code in a finalization section did fix the problem when the clipboard contents had come from the application itself but if it was there before the application started, that is, the application did not write to the clipboard, it introduces another, similar problem. Clipboard contents are, again, cleared in that case. And appears to happen because by time the finalization clause is executed, the clipboard has already been cleared. | |
| 287 | + | ||
| 288 | + | An easy solution is to put that same code into the main form's onClose event. Its early enough that the contents, from either source are still there and late enough not to be subsequently cleared. | |
| 289 | + | ||
| 290 | + | ``` pascal | |
| 291 | + | uses .... {$ifdef LINUX}gtk2, gdk2, Clipbrd{$endif}; | |
| 292 | + | ..... | |
| 293 | + | procedure TMainForm.FormClose(Sender: TObject; var CloseAction: TCloseAction); | |
| 294 | + | var | |
| 295 | + | c: PGtkClipboard; | |
| 296 | + | t: string; | |
| 297 | + | begin | |
| 298 | + | {$ifdef LINUX} | |
| 299 | + | c := gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); | |
| 300 | + | t := Clipboard.AsText; | |
| 301 | + | gtk_clipboard_set_text(c, PChar(t), Length(t)); | |
| 302 | + | gtk_clipboard_store(c); | |
| 303 | + | {$endif} | |
| 304 | + | end; | |
| 305 | + | ``` | |
| 306 | + | ||
| 307 | + | ## External links | |
| 308 | + | ||
| 309 | + | - [Basic Clipboard Operations (Cut/Copy/Paste) using the TClipboard object](http://delphi.about.com/od/vclusing/a/tclipboard.htm) | |
| 310 | + | ||
| 311 | + | --- | |
| 312 | + | *From the Free Pascal wiki, page "Clipboard", as it stood on 2020-05-10 after a change by Kegge13, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
