Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

SetClipboardData and GetClipboardData functions


  • Please log in to reply
32 replies to this topic
PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
I gave a function to put strings in clipboard with non-standard (not CF_TEXT) format.

I improved it a bit to handle binary data as well:
SetClipboardData(_format, ByRef @data, _dataSize=0, _bEmptyClipboard=true)
{
	local res, mem, str, cfFormat, errorCode

	errorCode = 0

	If (_dataSize = 0)
	{
		; Assume it is a simple string; otherwise, should provide real length...
		_dataSize := StrLen(@data)
		If (_dataSize = 0)
		{
			errorCode = SIZE
			Goto SCD_End
		}
	}

	; Open the clipboard, and empty it.
	res := DllCall("OpenClipboard", "UInt", 0)
	If (res = 0)
	{
		errorCode = OC
		Goto SCD_End
	}
	If (_bEmptyClipboard)
	{
		DllCall("EmptyClipboard")
	}

	; Allocate a global memory object for the text
	mem := DllCall("GlobalAlloc"
			, "UInt", 2 ; GMEM_MOVEABLE
			, "UInt", _dataSize + 1) ; +1 in case it is a zero-terminated string
	If (mem = 0)
	{
		errorCode = GA
		Goto SCD_End
	}

	; Lock the handle and copy the text to the buffer
	str := DllCall("GlobalLock", "UInt", mem)
	; str is a pointer to a memory area, so is treated as UInt
	DllCall("RtlMoveMemory"
			, "UInt", str
			, "Str", @data
			, "UInt",_dataSize)
	; In case it is a zero-terminated string, we put the final zero
	DllCall("RtlZeroMemory", "UInt", str + _dataSize, "UInt", 1)
	DllCall("GlobalUnlock", "UInt", mem)

	; Handle format
	If _format is integer
	{
		cfFormat := _format
	}
	Else
	{
		cfFormat := DllCall("RegisterClipboardFormat", "Str", _format)
		If (cfFormat = 0)
		{
			errorCode = RCF
			Goto SCD_End
		}
	}

	; Place the handle on the clipboard
	res := DllCall("SetClipboardData"
			, "UInt", cfFormat
			, "UInt", mem)
	If (res = 0)
	{
		errorCode = SCD
		Goto SCD_End
	}

SCD_End:
	If errorCode != 0
	{
		errorCode = %errorCode%  (%A_LastError%)
	}
	; Close the clipboard
	DllCall("CloseClipboard")

	ErrorLevel := errorCode
}
You may want to set _bEmptyClipboard to false, for example putting CF_TEXT while emptying the clipboard, then adding CF_RTF to give choice for the target software.

The example uses Hex2Bin from my BinaryEncodingDecoding.ahk file.
#Include BinaryEncodingDecoding.ahk

CF_TEXT = 1
CF_UNICODETEXT = 13
CF_RTF = Rich Text Format
CF_RTFNOOBJS = Rich Text Format Without Objects
CF_DIB = 8
CF_DIBV5 = 17

; Plain text
string1 = The quick brown Fox jumps over the lazy Dog!
; string3 trimed down (just some words underlined/italics/bold)
string2 = {\rtf1 The \ul quick\ulnone  brown \i Fox\i0  jumps over the lazy \b Dog\b0 !}
; Raw as copied from WordPad (idem with font changed)
string3 = {\rtf1\ansi\ansicpg1252\deff0\deflang1036{\fonttbl{\f0\fswiss\fcharset0 Arial`;}}`r`n\uc1\pard\f0\fs20 The \ul quick\ulnone  brown \i Fox\i0  jumps over the lazy \b Dog\b0 !}`r`n
; Copied from IrfanView, got, as the other data, with ClipSpy
imageHex =
( Join
280000001000000010000000010018000000000000030000130B0000130B0000
00000000000000006EAF6569AA6061A2575FA0545A9B4E5596494D8E404B8C3E
4687384182323C7D2C397A292F711F2D6F1D26681626681674B46A6EAF6569AA
6061A2575FA0545A9B4E5596495091434B8C3E438435418232397A2937782732
74222D6F1D26681676B66C71B1676EAF6569AA6091B3895091445A9B4E559649
5091434D8E406A9C5F6395583576253778273274222D6F1D7EBE7579B96F74B4
6AB7D8B2FCFEFC91B38961A2575A9B4E559649529346FCFEFCFCFEFC35762639
7A2937782734762480C07880C07879B96FB9DAB4FCFEFC91B38961A2575FA054
5C9D51559649FCFEFCFCFEFC3576264384353C7D2C37782789C98380C0787EBE
75BCDDB7FCFEFC91B3896BAC6264A55A5FA0545C9D51FCFEFCFCFEFC3A7B2B46
87384384353C7D2C8CCC8689C98383C37CBEDFBAFCFEFCC7D9C391B3896B9C60
47883A428334FCFEFCFCFEFC3C7E2E4B8C3E46873843843593D38E8CCC8686C6
7FC1E2BEFCFEFCFCFEFCFCFEFCFCFEFCFCFEFCFCFEFCFCFEFCFCFEFC3F803152
93464D8E4048893B9ADA9697D7928CCC86C4E5C1FCFEFCC7D9C3BCDDB7DBECD8
FCFEFCFCFEFCFCFEFCFCFEFC4183345596495596494B8C3E9ADA9697D79293D3
8EC8E9C5FCFEFC91B38980C0787BBB7276B66C71B167FCFEFCFCFEFC4485375C
9D5157984B529346A2E29F9DDD999ADA96C8E9C5FCFEFC91B38986C67F80C078
7BBB7274B46AFCFEFCFCFEFC46883A61A2575FA05457984BAAE9A7A2E29F9DDD
99CBECC9FCFEFC91B38989C98386C67F80C0787BBB72FCFEFCFCFEFC498A3C66
A75D61A2575FA054ADECAAAAE9A7A4E4A1CEEFCCFCFEFCAED0A990D08A89C983
86C67F80C078FCFEFCFCFEFC62A35769AA6069AA6061A257B3F1B1ADECAAA7E7
A4A4E4A1CEEFCC9ADA9693D38E90D08A8CCC8686C67F9FD0999DCD9676B66C71
B1676CAC6269AA60BAF7B7B6F4B4AFEEADAAE9A7A4E4A19FDF9C9ADA9697D792
90D08A89C98386C67F80C0787EBE7579B96F71B1676EAF65BDFABABAF7B7B3F1
B1AFEEADAAE9A7A4E4A19DDD999ADA9697D79290D08A8CCC8686C67F80C0787B
BB7279B96F74B46A
)

SetClipboardData(CF_TEXT, string1)
MsgBox %ErrorLevel%
SetClipboardData(CF_RTF, string2, 0, false)
MsgBox %ErrorLevel%
SetClipboardData(CF_RTF, string3)
MsgBox %ErrorLevel%
imageLen := Hex2Bin(image, imageHex)
SetClipboardData(CF_DIB, image, imageLen)
MsgBox %ErrorLevel%

[EDIT] In problem with MSword formatted clipboard result topic, I made a function to get formatted text too.
I just repaste it here for easy retrieval.
GetClipboardData(_format, ByRef @data)
{
	local cfFormat, hData, pData, dataLen

	If _format is integer
	{
		cfFormat := _format
	}
	Else
	{
		cfFormat := DllCall("RegisterClipboardFormat", "Str", _format, "UInt")
		If (cfFormat = 0)
		{
			Return "BAD_FORMAT"
		}
	}
	If (DllCall("IsClipboardFormatAvailable", "UInt", cfFormat) = 0)
	{
		return "NO_DATA"
	}
	If (DllCall("OpenClipboard", "UInt", 0) != 0)
	{
		hData := DllCall("GetClipboardData", "UInt", cfFormat, "UInt")
		If (hData != 0)
		{
			dataLen := DllCall("GlobalSize", "UInt", hData)
			pData := DllCall("GlobalLock", "UInt", hData, "UInt")
			VarSetCapacity(@data, dataLen, 0)
			; Might do a lstrcpyW (and lstrlenW) for Unicode format...
;~ 			r := DllCall("lstrcpy", "Str", @data, "UInt", pData, "UInt")
			DllCall("RtlMoveMemory"
					, "UInt", [email protected]	; Destination
					, "UInt", pData	; Source
					, "UInt", dataLen)	; Length
			DllCall("GlobalUnlock", "UInt", hData)
		}
		DllCall("CloseClipboard")
	}
	Return dataLen
}

GetClipboardData(CF_TEXT, str)
MsgBox %str%
GetClipboardData(CF_RTF, str)
MsgBox %str%

Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Very nice! Flexible clipboard functions like this would probably be a good thing to distribute with the program via the forthcoming standard library.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
I could only paste the Rich Text clipboard into Word and WordPad. When I attempt pasting it into Notepad or any other application, nothing shows up. They probably need multiple clipboard formats. I wonder, is HTML any better? Could you add support to HTML clipboards?

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005
This is a useful function.

You may want to explain the clipboard's behavior for handling data of different formats, and its ability to automatically synthesize data of certain formats.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
See the (latest) test code:
SetClipboardData(CF_TEXT, string1)
SetClipboardData(CF_RTF, string2, 0, false)

The false parameter says not to erase the clipboard before putting a format, allowing multiple formats.

The HTML format seems to be "HTML Format" (Firefox put also a Unicode "text/html" format) which seems to have a quite precise format:
Version:0.9
StartHTML:00000152
EndHTML:00000313
StartFragment:00000186
EndFragment:00000277
SourceURL:http://www.autohotkey.com/forum/posting.php
<html><body>
<!--StartFragment--><span class="postbody">This is <span style="font-weight: bold;">bold</span>, indeed.</span><!--EndFragment-->
</body>
</html>
Or, copied with IE:
Version:1.0
StartHTML:000000201
EndHTML:000000782
StartFragment:000000704
EndFragment:000000740
StartSelection:000000704
EndSelection:000000740
SourceURL:http://www.autohotkey.com/docs/FAQ.htm
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">

<HTML><HEAD><TITLE>AutoHotkey FAQ</TITLE><!-- DW6 --><LINK 
href="css/default.css" type=text/css rel=stylesheet>
<STYLE type=text/css>
<!--
ul {margin-top:0.7em; margin-bottom:0.7em;}
h2 {margin-top: 1.5em; margin-bottom: 0.5em; color:#009933; text-decoration:underline}
h3 {margin-top: 1.5em; margin-bottom: 0.5em}
-->
</STYLE>
</HEAD>

<BODY>

<P><!--StartFragment-->If <STRONG>Var1</STRONG> < %Var2%<!--EndFragment--></P>
</BODY>
</HTML>

Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

bumface
  • Members
  • 10 posts
  • Last active: Jun 09 2008 12:25 PM
  • Joined: 26 Apr 2008
Hello,

I'm really interested in the ability to paste generated html code. I want to be able to paste an tag into an outlook email. My hope is that this will allow me to send screenshots in a compressed format (e.g. PNG) instead of the default bitmap format.

I've tried to follow the example above, but can only get it to paste the code in plain text format...

Can anyone help?

thanks,

Mike

sd
  • Guests
  • Last active:
  • Joined: --
:D Welcome!

bumface
  • Members
  • 10 posts
  • Last active: Jun 09 2008 12:25 PM
  • Joined: 26 Apr 2008
Hi,

Sorry for my stupid question - I was a bit baffled with all the library calls and "windowsy" code. I've made some progress now, so I thought I'd share a little function I've hacked together which seems to give the right header for an HTML clip - you'll be able to see that I'm a novice with AHK :roll:

Let me know if you spot any bugs:

pushHTML(sHtmlFragment)
{
m_sDescription = 
(
Version:1.0
StartHTML:aaaaaaaaaa 
EndHTML:bbbbbbbbbb
StartFragment:cccccccccc 
EndFragment:dddddddddd 
)
sContextStart = <HTML><BODY>
sContextEnd = </BODY></HTML>

   sData = %m_sDescription% %sContextStart% %sHtmlFragment% %sContextEnd%
   mylen := StrLen(m_sDescription) +4
   thelen := SubStr("0000000000" mylen, -9)
   StringReplace sData, sData, aaaaaaaaaa, %thelen%
   mylen := StrLen(sData) +6 ; was 4
   thelen := SubStr("0000000000" mylen, -9)
   StringReplace sData, sData, bbbbbbbbbb, %thelen%
   mylen :=  StrLen(m_sDescription . sContextStart) +2 
   thelen := SubStr("0000000000" mylen, -9)
   StringReplace sData, sData, cccccccccc, %thelen%
   mylen :=  StrLen(m_sDescription . sContextStart . sHtmlFragment) +6
   thelen := SubStr("0000000000" mylen, -9)
   StringReplace sData, sData, dddddddddd, %thelen%  

   Return, sData
}   


bumface
  • Members
  • 10 posts
  • Last active: Jun 09 2008 12:25 PM
  • Joined: 26 Apr 2008
At work we frequently send screenshots to multiple recipients. As screens have got bigger, the uncompressed BMPs have reached 5 or 6 MB each which is grinding our exchange server to a halt. I wrote the script to try to transparently override the normal PrintScreen button's functionality. It saves a PNG file in a subdirectory, and creates a fragment of HTML which can be pasted in to an HTML email body. It's just cobbled together from the work of people (holomind and philho) who understand things better than me - http://www.autohotke...topic12012.html.

Here's the first working version (note the required libraries which can be found in this forum):

#Include GDIPlusHelper.ahk
#Include setClipBoardData.ahk
; see: http://www.autohotkey.com/forum/viewtopic.php?t=11860 for orignal

theImagePath = empty

OnExit, handle_exit

pushHTML(sHtmlFragment)
{
m_sDescription = 
(
Version:1.0
StartHTML:aaaaaaaaaa 
EndHTML:bbbbbbbbbb
StartFragment:cccccccccc 
EndFragment:dddddddddd 
)
sContextStart = <HTML><BODY>
sContextEnd = </BODY></HTML>

   sData = %m_sDescription% %sContextStart% %sHtmlFragment% %sContextEnd%
   mylen := StrLen(m_sDescription) +4
   thelen := SubStr("0000000000", mylen, -9)
   StringReplace sData, sData, aaaaaaaaaa, %thelen%
   mylen := StrLen(sData) +6 ; was 4
   thelen := SubStr("0000000000", mylen, -9)
   StringReplace sData, sData, bbbbbbbbbb, %thelen%
   mylen :=  StrLen(m_sDescription . sContextStart) +2 
   thelen := SubStr("0000000000", mylen, -9)
   StringReplace sData, sData, cccccccccc, %thelen%
   mylen :=  StrLen(m_sDescription . sContextStart . sHtmlFragment) +6
   thelen := SubStr("0000000000", mylen, -9)
   StringReplace sData, sData, dddddddddd, %thelen%  

   Return, sData
}   

main: 
  FileCreateDir, screens
  FileCreateDir, thumbs

  WinGet, hw_frame, id, "Program Manager"   ; Desktop ?
  hdc_frame := DllCall( "GetDC", "uint",  hw_frame )
  hdc_frame_full := DllCall( "GetDC", "uint",  hw_frame )
  counter:=0	; thumbnails
  counter_f:=0	; fullscreens
  thumb_w:= 200 
  thumb_h:= ceil( thumb_w * A_ScreenHeight / A_ScreenWidth ) ; keep screenratio
  use_antialize := 1
  

  ; buffer
  hdc_buffer := DllCall( "gdi32.dll\CreateCompatibleDC"     , "uint", hdc_frame )
  hbm_buffer := DllCall( "gdi32.dll\CreateCompatibleBitmap" , "uint", hdc_frame, "int", thumb_w, "int", thumb_h )
  r          := DllCall( "gdi32.dll\SelectObject"           , "uint", hdc_buffer, "uint", hbm_buffer )

  hdc_buffer_full := DllCall( "gdi32.dll\CreateCompatibleDC"     , "uint", hdc_frame_full )
  hbm_buffer_full := DllCall( "gdi32.dll\CreateCompatibleBitmap" , "uint", hdc_frame_full, "int", A_ScreenWidth, "int", A_ScreenHeight )
  r_full          := DllCall( "gdi32.dll\SelectObject"           , "uint", hdc_buffer_full, "uint", hbm_buffer_full )

  ; comment this line for speed but less quality
  if use_antialize = 1
    DllCall( "gdi32.dll\SetStretchBltMode", "uint", hdc_buffer, "int", 4 )  ; Halftone better quality with stretch

return


$PRINTSCREEN::
SendInput, {Printscreen}
SaveImage_Full:
  counter_f := counter_f +1
  FormatTime, myTime, , yyyyMMdd_hhmm
  ;fileNameDestP = screens\S_%myTime%_%counter_f%_%A_ScreenWidth%x%A_ScreenHeight%.png
  fileNameDestP = screens\S_%myTime%_%counter_f%.png

  If (GDIplus_Start() != 0)
     Goto GDIplusError

  ; Copy BMP from DC
  DllCall( "gdi32.dll\BitBlt" 
          , "uint", hdc_buffer_full, "int", 0, "int", 0, "int", A_ScreenWidth, "int", A_ScreenHeight
          , "uint", hdc_frame_full,  "int", 0, "int", 0, "uint", 0x00CC0020 )

  DllCall( "GDIplus\GdipCreateBitmapFromHBITMAP", uint, hbm_buffer_full, uint, 0, uintp, bitmap )

  ; Save to PNG

  If (GDIplus_GetEncoderCLSID(pngEncoder, #GDIplus_mimeType_png) != 0)
     Goto GDIplusError

  noParams = NONE
  If (GDIplus_SaveImage(bitmap, fileNameDestP, pngEncoder, noParams) != 0)
     Goto GDIplusError

  fileloc = %A_ScriptDir%\%fileNameDestP%

  theHTML = <img src="%fileloc%">

  theHTML := pushHTML(theHTML)
  msgbox %theImagePath%
  
   CF_HTML = HTML Format
   SetClipBoardData(CF_HTML, theHTML, 0, false)

Return 

GDIplusError:
   If (#GDIplus_lastError != "")
      MsgBox 16, GDIplus Test, Error in %#GDIplus_lastError%
   GDIplus_Stop()
Return

#x::
handle_exit:
   DllCall( "gdi32.dll\DeleteObject", "uint", hbm_buffer )
   DllCall( "gdi32.dll\DeleteDC"    , "uint", hdc_frame )
   DllCall( "gdi32.dll\DeleteDC"    , "uint", hdc_buffer )
ExitApp


BSB
  • Members
  • 7 posts
  • Last active: Aug 26 2008 02:55 PM
  • Joined: 26 Aug 2008
So I copy some text from Word (2002) to the clipboard (CTRL-C) & I can't seem to fetch it as simple text using GetClipboardData(1, txt). The result length appears OK, but the txt variable is empty. If I write ClipboardAll to a file, it appears as HTML text. Also, if I do:

CF_TEXT = 1
len = GetClipboardData(CF_TEXT, txt)

len returns BAD_FORMAT.

tank
  • Administrators
  • 4345 posts
  • AutoHotkey Foundation
  • Last active: May 02 2019 09:16 PM
  • Joined: 21 Dec 2007
Ok so being a complete ninja of stupidity
Im trying desparately to figure out how to
if i have some text on a web page selected
copy said text with the underlying html and then paste said html fragment to say notepad or any other imple text editor

And the genious heretofore eludes me
any example would be grand
while i know how to use COM in IE to create a text range and manipulate that im really trying to broaden from IE
Never lose.
WIN or LEARN.

automaticman
  • Members
  • 658 posts
  • Last active: Nov 20 2012 06:10 PM
  • Joined: 27 Oct 2006
Too bad, both functions reasons to exist are not explained in a way that a beginner like me can understand it. For example I do not know

1. What the function SetClipboardData does, explained in simple language? Just looking at its name it "sets" OK, "clipboard data" OK, but setting where? Why do we have to set it anywhere? :)
2. How to make use of SetClipboardData function in a practical situation giving an example a beginner can understand?

I suppose it is too difficult to explain complex topics in a simple way? I also suppose any topic related to management of Clipboard data needs some further information how the Clipboard in Windows works in detail.

What I know from the AutoHotkey help is, it is limited to unformatted plain text when used as %Clipboard%. There is a second option of using instead %ClipboardAll%, but then you have to write it out e.g. to some .clip file...

I write here because I am trying to solve this problem.

tester8900
  • Members
  • 18 posts
  • Last active: Dec 07 2009 05:24 PM
  • Joined: 23 Jan 2006
Bumface, did you ever get your code to work? I'm interested in that as a "paste into gmail" feature.

bumface
  • Members
  • 10 posts
  • Last active: Jun 09 2008 12:25 PM
  • Joined: 26 Apr 2008
Doesn't my post from May 04 2008 work? If not, I'll dig out the version I've been using ever since. It works with Outlook, and doesn't break when I paste into different apps, so I'm happy.

tester8900
  • Members
  • 18 posts
  • Last active: Dec 07 2009 05:24 PM
  • Joined: 23 Jan 2006
Oops, I think I misread something which made me think there were still known issues with it. Sorry about that.