UWSCでクリップボードを管理する
UWSCでクリップボードを管理するモジュールを書いてみました。
C++でもVBでも同じですね。
気になった文言
クリップボードのデータを、内容を問わずに一時待避させるような方法を見付けることができませんでした。
課題としては面白い内容なのですが実力不足で残念です。ここは他の方の応援に期待したいです・・・
他の方、、、他の方、、、他の方、、、私か?
そうだ私に違いない!(自意識過剰!!!)
ということで。
スクリプト
Clipboard.uws
OPTION EXPLICIT IFB GET_UWSC_NAME = "Clipboard.uws" THEN DIM loop = TRUE, i, num, p, j, fmt WHILE loop WHILE G_MOUSE_X SLEEP(0.1) WEND GETKEYSTATE(VK_CTRL) IF GETKEYSTATE(VK_CTRL) THEN Clipboard.Get() num = Clipboard.Count() p = SAFEARRAY(0, num * 4) FOR i = 1 TO num fmt = Clipboard.GetTypicalFormat(i) SELECT fmt CASE Clipboard.F_UNICODETEXT p[i*4-4] = Clipboard.Peek(fmt, i) j = POS("<#CR>", p[i*4-4]) WHILE j = 1 p[i*4-4] = COPY(p[i*4-4], 3) j = POS("<#CR>", p[i*4-4]) WEND IF j THEN p[i*4-4] = COPY(p[i*4-4], 1, j - 1) DEFAULT p[i*4-4] = Clipboard.GetFormatName(fmt) IF LENGTH(p[i*4-4]) = 0 THEN p[i*4-4] = "fmt:" + fmt SELEND IF LENGTH(p[i*4-4]) = 0 THEN p[i*4-4] = "-" p[i*4-3] = "{&Set" p[i*4-2] = "&View" p[i*4-1] = "&Del}" NEXT p[num * 4] = "&End" j = POPUPMENU(p) i = INT(j / 4) + 1 IFB j = -1 THEN // nop ELSEIF j = num * 4 THEN loop = FALSE ELSEIF j MOD 4 = 3 THEN Clipboard.Del(i) ELSEIF j MOD 4 = 2 THEN fmt = Clipboard.GetTypicalFormat(i) p = Clipboard.Peek(fmt, i, TRUE) SELECT fmt CASE Clipboard.F_UNICODETEXT PRINT p CASE Clipboard.F_HDROP FOR i = 0 TO LENGTH(p) - 1 PRINT p[i] NEXT SELEND PRINT "" ELSE Clipboard.Set(i, FALSE) ENDIF WEND Clipboard.Dispose() ENDIF MODULE Clipboard DEF_DLL OpenClipboard(HWND): BOOL: user32 DEF_DLL CloseClipboard(): BOOL: user32 DEF_DLL EmptyClipboard(): BOOL: user32 DEF_DLL CountClipboardFormats(): int: user32 DEF_DLL EnumClipboardFormats(DWORD): DWORD: user32 DEF_DLL GetClipboardFormatNameW(DWORD,var wstring,int): int: user32 DEF_DLL GetClipboardData(DWORD): DWORD: user32 DEF_DLL SetClipboardData(DWORD, DWORD): DWORD: user32 DEF_DLL GlobalAlloc(DWORD, DWORD): DWORD: kernel32 DEF_DLL GlobalLock(DWORD): DWORD: kernel32 DEF_DLL GlobalUnlock(DWORD): BOOL: kernel32 DEF_DLL GlobalSize(DWORD): DWORD: kernel32 DEF_DLL GlobalFree(DWORD): DWORD: kernel32 DEF_DLL GetProcessHeap(): DWORD: kernel32 DEF_DLL HeapAlloc(DWORD, DWORD, DWORD): DWORD: kernel32 DEF_DLL HeapFree(DWORD, DWORD, DWORD): BOOL: kernel32 DEF_DLL HeapSize(DWORD, DWORD, DWORD): LONG: kernel32 DEF_DLL RtlMoveMemory(DWORD, DWORD, DWORD): kernel32 DEF_DLL DragQueryFileW(DWORD,DWORD,var wstring,DWORD): DWORD: shell32 HASHTBL _datas DIM _hwnd, _heap CONST F_TEXT = 1 CONST F_BITMAP = 2 CONST F_METAFILEPICT = 3 CONST F_SYLK = 4 CONST F_DIF = 5 CONST F_TIFF = 6 CONST F_OEMTEXT = 7 CONST F_DIB = 8 CONST F_PALETTE = 9 CONST F_PENDATA = 10 CONST F_RIFF = 11 CONST F_WAVE = 12 CONST F_UNICODETEXT = 13 CONST F_ENHMETAFILE = 14 CONST F_HDROP = 15 CONST F_LOCALE = 16 CONST F_DIBV5 = 17 PROCEDURE Clipboard _hwnd = IDTOHND(GETID(GET_THISUWSC_WIN)) _heap = GetProcessHeap() FEND PROCEDURE Dispose() WHILE Del(); WEND FEND FUNCTION GetTypicalFormat(n=-1) RESULT = 0 IF n = -1 THEN n = LENGTH(_datas) IF (n <= 0) OR (n > LENGTH(_datas)) THEN EXIT n = _datas[n - 1, HASH_KEY] DIM data = _datas[n], i, hMem, fmtName, size IF LENGTH(GetFormatName(data[0])) THEN RESULT = data[0] FOR i = 0 TO LENGTH(data) - 1 STEP 2 SELECT data[i] CASE F_UNICODETEXT, F_DIBV5, F_HDROP RESULT = data[i] BREAK DEFAULT IFB RESULT = 0 THEN IF LENGTH(GetFormatName(data[i])) THEN RESULT = data[i] ENDIF SELEND NEXT IF RESULT = 0 THEN RESULT = data[0] FEND FUNCTION Trans(fmt) IF fmt = F_BITMAP OR fmt = F_DIB THEN fmt = F_DIBV5 // F_BITMAPではないけど、がんばれ! IF fmt = F_ENHMETAFILE THEN fmt = F_METAFILEPICT IF fmt = F_TEXT OR fmt = F_OEMTEXT THEN fmt = F_UNICODETEXT RESULT = fmt FEND FUNCTION Peek(fmt=F_HDROP, n=-1, p=FALSE) // 相互変換の読み替え fmt = Trans(fmt) IFB fmt = F_HDROP THEN RESULT = SPLIT(EMPTY) ELSEIF fmt = F_UNICODETEXT THEN RESULT = EMPTY ELSE RESULT = 0 ENDIF IF n = -1 THEN n = LENGTH(_datas) IF (n <= 0) OR (n > LENGTH(_datas)) THEN EXIT n = _datas[n - 1, HASH_KEY] DIM data = _datas[n], i, hMem, size FOR i = 0 TO LENGTH(data) - 1 STEP 2 IF p THEN PRINT "Peek " + data[i] + ":" + GetFormatName(data[i]) IFB data[i] = fmt THEN hMem = data[i+1] IFB fmt = F_HDROP THEN DIM fileNum = 0, fileName, j IF hMem THEN fileNum = DragQueryFileW(hMem, -1, NULL, 0) IF fileNum THEN RESULT = SAFEARRAY(0, fileNum - 1) FOR j = 0 TO fileNum - 1 size = DragQueryFileW(hMem, j, NULL, 0) IFB size THEN size = size + 1 fileName = FORMAT(CHR(0), size) IF DragQueryFileW(hMem, j, fileName, size) THEN RESULT[j] = fileName ENDIF NEXT ELSEIF fmt = F_UNICODETEXT THEN size = HeapSize(_heap, 0, hMem) RESULT = FORMAT(CHR(0), size / 2) DEF_DLL RtlMoveMemory(var wstring, DWORD, DWORD): kernel32 RtlMoveMemory(RESULT, hMem, size) DEF_DLL RtlMoveMemory(DWORD, DWORD, DWORD): kernel32 ELSE RESULT = hMem ENDIF ENDIF NEXT FEND FUNCTION GetFormatName(fmt) DIM size = 64, fmtName = FORMAT(CHR(0), size) SELECT fmt CASE F_TEXT fmtName = "Text" CASE F_BITMAP fmtName = "Bitmap" CASE F_METAFILEPICT fmtName = "Meta File Pict" CASE F_SYLK fmtName = "SYLK" CASE F_DIF fmtName = "DIF" CASE F_TIFF fmtName = "Tiff" CASE F_OEMTEXT fmtName = "OEM Text" CASE F_DIB fmtName = "DIB" CASE F_PALETTE fmtName = "Palette" CASE F_PENDATA fmtName = "Pen Data" CASE F_RIFF fmtName = "Riff" CASE F_WAVE fmtName = "Wave" CASE F_UNICODETEXT fmtName = "Unicode Text" CASE F_ENHMETAFILE fmtName = "Enh-Meta File" CASE F_HDROP fmtName = "Files" CASE F_LOCALE fmtName = "Locale" CASE F_DIBV5 fmtName = "Image" DEFAULT WHILE GetClipboardFormatNameW(fmt, fmtName, size - 1) >= size - 2 size = size * 2 fmtName = FORMAT(CHR(0), size) WEND SELEND RESULT = fmtName FEND FUNCTION Count() RESULT = LENGTH(_datas) FEND FUNCTION Check(fmt, hMem, size, n=-1) RESULT = FALSE EXIT // 内容チェックを高速に行う方法を思いついたら真面目に実装する IF n = -1 THEN n = LENGTH(_datas) IF (n <= 0) OR (n > LENGTH(_datas)) THEN EXIT n = _datas[n - 1, HASH_KEY] DIM data = _datas[n], i, hMemTar, sizeTar FOR i = 0 TO LENGTH(data) - 1 STEP 2 IFB fmt = data[i] THEN // サイズをチェックする // 同じなら内容をチェックする IFB size = HeapSize(_heap, 0, data[i+1]) THEN ENDIF ENDIF NEXT FEND FUNCTION Get() RESULT = OpenClipboard(_hwnd) IFB RESULT THEN DIM hClip, size, data = SAFEARRAY(0, 0), index = 0, bPrev = TRUE DIM hMem, fmt = EnumClipboardFormats(0) WHILE fmt <> 0 hClip = GetClipBoardData(fmt) size = GlobalSize(hClip) hMem = GlobalLock(hClip) bPrev = bPrev AND Check(fmt, hMem, size) IFB fmt = Trans(fmt) THEN // CF_BITMAPはCF_DIBV5に期待して捨てる。CF_ENHMETAFILEは嫌な予感がするので捨てる // CF_DIB / CF_TEXT / CF_OEMTEXTも相互変換があるので捨てる(CF_DIBV5 と CF_UNICODETEXT) RESIZE(data, LENGTH(data) + 1) data[index] = fmt data[index+1] = HeapAlloc(_heap, 0, size) RtlMoveMemory(data[index+1], hMem, size) index = index + 2 ENDIF GlobalUnlock(hClip) fmt = EnumClipboardFormats(fmt) WEND IFB LENGTH(data) > 1 AND !bPrev THEN // 一つ余分なので削る RESIZE(data, LENGTH(data) - 2) index = 1 IF LENGTH(_datas) THEN index = VAL(_datas[LENGTH(_datas) - 1, HASH_KEY]) + 1 _datas[index] = data RESULT = LENGTH(_datas) ELSEIF bPrev THEN RESULT = LENGTH(_datas) ELSE RESULT = 0 ENDIF CloseClipboard() ELSE MSGBOX("OpenClipboard fail.") ENDIF FEND FUNCTION Set(n=-1, del=TRUE) IF n = -1 THEN n = LENGTH(_datas) RESULT = (n > 0) AND (n <= LENGTH(_datas)) IFB RESULT THEN RESULT = OpenClipboard(_hwnd) ELSE MSGBOX("No Clipboard stack. " + n) EXIT ENDIF IFB RESULT THEN RESULT = EmptyClipboard() ELSE MSGBOX("OpenClipboard fail.") EXIT ENDIF IFB RESULT THEN DIM data = _datas[n - 1, HASH_VAL], size = LENGTH(data), index = 0, i = 0, j, k, m IF del THEN j = _datas[n, HASH_REMOVE] WHILE (index < size) AND (RESULT) k = HeapSize(_heap, 0, data[index+1]) m = GlobalAlloc(2, k) j = GlobalLock(m) IFB j THEN RtlMoveMemory(j, data[index+1], k) GlobalUnlock(m) RESULT = (SetClipboardData(data[index], m) > 0) ELSE MSGBOX("GlobalLock fail.") RESULT = FALSE ENDIF IF del THEN HeapFree(_heap, 0, data[index+1]) i = i + 1 index = index + 2 WEND CloseClipboard() ELSE CloseClipboard() MSGBOX("EmptyClipboard fail.") ENDIF FEND FUNCTION Del(n=-1) RESULT = FALSE IF n = -1 THEN n = LENGTH(_datas) IF (n <= 0) OR (n > LENGTH(_datas)) THEN EXIT DIM key = _datas[n - 1, HASH_KEY], data = _datas[key], i RESULT = _datas[key, HASH_REMOVE] FOR i = 1 TO LENGTH(data) - 1 STEP 2 HeapFree(_heap, 0, data[i]) NEXT FEND ENDMODULE
クリップボードには歴史があるため、いろいろ古いAPIをルールに従って使う必要があるのです。
基本的な使い方は
CALL Clipboard // して // クリップボード操作前に待避 Clipboard.Get() // クリップボードをいじり、終わったら // クリップボードを戻す Clipboard.Set()
という想定。
MODULE内部のHASHTBLにGet時の内容を積むので、Ctrl+CしてGetして、Ctrl+CしてGetして、最初のをSetして、、、なんてことも可能なはず。
Getのリターンは、0:失敗か、現在までの積んだ数。
オフィス付属のクリップボード管理と同じことができるのです!
なお、考えるのが面倒だったので、CF_BITMAPとCF_ENHMETAFILEは捨ててしまっています。
が、CF_BITMAPはCF_DIBまたはCF_DIBV5に変換されるため、絵も保持されます。
これらの自動変換を考えると、メモリーに無駄に待避してますが、、、ま、いいでしょ。
Peek関数は、クリップボードの中身を見るために作成しようとしました。
が、テキストの取得は標準関数でもできるので、ためしにファイルコピーを実装しました。
エクスプローラーでファイルを選択し、Ctrl+Cしてからこのスクリプトを実行すると、選択したファイルのフルパスがログ出力されると思います。
追記 2013/02/18
少し使い勝手を向上。
ちょっとしたバグの修正。
試験コードを、単体で利用できそうなものに変更。
POPUPMENUから、クリップボードの差し替えや内容のログ出力・破棄が可能。
POPUPMENUの「End」を選択すると終了する。
DIBV5がクリップボードにある場合、POPUPMENUに表示しようかとも思ったけど、面倒になったのでやめた。
Win32APIを呼びまくれば可能。(ASMモジュールは不要)
追記 2013/02/21
POPUPMENUに画像表示する版を作成。
UWSCのPOPUPMENUにBitmapを表示する - じゅんじゅんのきまぐれ