UWSCのPOPUPMENUにBitmapを表示する
UWSCのPOPUPMENUでBitmapやアイコンを表示できるようにしました!
これに伴い、先日のクリップボード管理を更新しました、、、が、
単体では動かなくなる(このPopupMenuExモジュールに依存する)ので、こちらに書いてます。
拡張PopupMenu
使い方は、、、
- POPUPMENUにBitmap表示用配列を追加
- PopupMenuEx.Show(pp, ppex[, n, x, y])といった感じ。
- ついでに、消すまでの秒数を指定可能にしてみた。(省略または0は、消さない)
- が、XPでは少々上手くいっておりません。
- ppexの指定は、ppと同じように「{」「}」で階層化できますが、、、「{-」「-}」には対応できてません。
- 原因不明。何やらメニューのPositionが不可解な感じ、、、。
- XPだと幅の調整が上手くいかなかったり、選択するとイメージが反転表示されたりします。(Vista以降は大丈夫なはず)
ppexの各項目は
- .exeを指定するとそのアイコンを取得して表示(オプションは、「/1」を付与で大きいアイコン)
- .icoを指定するとそのアイコンを表示(オプションは、「/w/h」で幅と高さを指定)
- .bmpを指定すると画像を表示(オプションは、「/w/h」で幅と高さを指定)
あと一つあるけど、それはHBITMAPを渡す必要があるので、ここでは割愛。
例では、面倒なので同じものを指定していますが、その必要はもちろんありません。
例を実行してみる場合は、適当なビットマップファイルを「print.bmp」、適当なアイコンファイルを「warn.ico」として同じフォルダーに置いとくと効果がわかります。
PopupMenuEx.uws
OPTION EXPLICIT IFB GET_UWSC_NAME = "PopupMenuEx.uws" THEN DIM pp = SPLIT("exe {notepad.exe/1 uwsc.exe calc.exe} print.bmp/16/16 aaa {warn.ico {warn.ico/64/64} print.bmp} warn.ico/16/16") DIM res = PopupMenuEx.Show(pp, pp, 3) IF res <> -1 THEN MSGBOX(pp[res]) ENDIF MODULE PopupMenuEx HASHTBL _hBitmaps DEF_DLL SendMessageW(HWND, DWORD, DWORD, DWORD): DWORD: user32 DEF_DLL SetMenuItemInfoW(DWORD, DWORD, BOOL, var DWORD[]): BOOL: user32 DEF_DLL GetSubMenu(DWORD, int): DWORD: user32 DEF_DLL DeleteObject(DWORD): BOOL: gdi32 DEF_DLL LoadImageW(DWORD, wstring, DWORD, int, int, DWORD): DWORD: user32 DEF_DLL RtlMoveMemory(DWORD[], DWORD, DWORD): kernel32 DEF_DLL ExtractIconExW(wstring, int, var DWORD, var DWORD, DWORD): DWORD: shell32 DEF_DLL DestroyIcon(DWORD): BOOL: user32 DEF_DLL GetIconInfo(DWORD, var DWORD[]): BOOL: user32 DEF_DLL GetSysColor(int): DWORD: user32 DEF_DLL DrawIconEx(DWORD, int, int, DWORD, int, int, DWORD, DWORD, DWORD): BOOL: user32 DEF_DLL CreateSolidBrush(DWORD): DWORD: gdi32 DEF_DLL GetDC(HWND): DWORD: user32 DEF_DLL ReleaseDC(HWND, DWORD): int: user32 DEF_DLL CreateCompatibleDC(DWORD): DWORD: gdi32 DEF_DLL DeleteDC(DWORD): BOOL: gdi32 DEF_DLL SelectObject(DWORD, DWORD): DWORD: gdi32 DEF_DLL GetDIBits(DWORD, DWORD, DWORD, DWORD, var DWORD[], var DWORD[], DWORD): int: gdi32 DEF_DLL StretchBlt(DWORD, int, int, int, int, DWORD, int, int, int, int, DWORD): BOOL: gdi32 DEF_DLL CreateCompatibleBitmap(DWORD, int, int): DWORD: gdi32 FUNCTION Show(pp, pe, n=0, x=ERR_VALUE, y=ERR_VALUE) RESULT = -1 THREAD EditPopup(pe, n) IF x = ERR_VALUE THEN x = G_MOUSE_X IF y = ERR_VALUE THEN y = G_MOUSE_Y RESULT = POPUPMENU(pp, x, y) DIM key WHILE LENGTH(_hBitmaps) key = _hBitmaps[0, HASH_KEY] IF _hBitmaps[key] THEN DeleteObject(_hBitmaps[key]) key = _hBitmaps[key, HASH_REMOVE] WEND FEND PROCEDURE EditPopup(pp, n) DIM start = GETTIME() + G_TIME_ZZ / 1000 DIM wid = GETID("", "#32768", -1) DIM hnd = IDTOHND(wid) DIM hmenu = SendMessageW(hnd, $1E1, 0, 0) HASHTBL hms, mis IF VARTYPE(pp) < VAR_ARRAY THEN pp = SPLIT(pp, "<#CR>") DIM mi[11], i, j = 0, k mi[0] = 48 mi[1] = $80 FOR i = 0 TO LENGTH(pp) - 1 k = FALSE IFB COPY(pp[i], 1, 1) = "{" THEN hms[LENGTH(hms)] = hmenu mis[LENGTH(mis)] = j hmenu = GetSubMenu(hmenu, j - 1) j = 0 IFB pp[i] = "{-" THEN j = j + 1 // TODO: positionが増えている?うまくいかない CONTINUE ENDIF pp[i] = COPY(pp[i], 2) ENDIF IFB COPY(pp[i], LENGTH(pp[i])) = "}" THEN IFB pp[i] = "-}" THEN hmenu = hms[LENGTH(hms) - 1] j = mis[LENGTH(mis) - 1] k = hms[LENGTH(hms) - 1, HASH_REMOVE] k = mis[LENGTH(mis) - 1, HASH_REMOVE] CONTINUE ENDIF pp[i] = COPY(pp[i], 1, LENGTH(pp[i]) - 1) k = TRUE ENDIF _hBitmaps[i] = GetBitmap(hnd, pp[i]) mi[11] = _hBitmaps[i] IF mi[11] THEN SetMenuItemInfoW(hmenu, j, TRUE, mi) j = j + 1 IFB k THEN hmenu = hms[LENGTH(hms) - 1] j = mis[LENGTH(mis) - 1] k = hms[LENGTH(hms) - 1, HASH_REMOVE] k = mis[LENGTH(mis) - 1, HASH_REMOVE] ENDIF NEXT IFB n > 0 THEN WHILE (GETTIME() + G_TIME_ZZ / 1000 < start + n) SLEEP(start + n - GETTIME() - G_TIME_ZZ / 1000 - 0.01) WEND IF IDTOHND(wid) = hnd THEN CTRLWIN(wid, HIDE) ENDIF FEND FUNCTION GetBitmap(hnd, data) RESULT = NULL DIM datas = SPLIT(data, "/"), l, op[2], i IF LENGTH(datas) = 0 THEN EXIT l = LENGTH(datas[0]) FOR i = 0 TO LENGTH(op) - 1 op[i] = 0 IF LENGTH(datas) > i+1 THEN op[i] = VAL(datas[i+1]) NEXT SELECT COPY(datas[0], l - 3) CASE ".exe" RESULT = GetBitmapExeIcon(hnd, datas[0], op[0]) CASE ".bmp" RESULT = GetBitmapFile(hnd, datas[0], 0, op[0], op[1]) CASE ".ico" RESULT = GetBitmapIcon(hnd, GetBitmapFile(hnd, datas[0], 1, op[0], op[1])) CASE "|hbm" RESULT = GetBitmapCopy(hnd, op[0], op[1], op[2]) SELEND FEND FUNCTION GetBitmapFile(hnd, path, type=0, cx=0, cy=0) RESULT = LoadImageW(0, path, type, cx, cy, 16) FEND FUNCTION GetBitmapExeIcon(hnd, path, l=FALSE) RESULT = NULL DIM hIconLarge, hIconSmall IFB ExtractIconExW(path, 0, hIconLarge, hIconSmall, 1) THEN IF l THEN RESULT = GetBitmapIcon(hnd, hIconLarge) ELSE RESULT = GetBitmapIcon(hnd, hIconSmall) ENDIF ENDIF DestroyIcon(hIconLarge) DestroyIcon(hIconSmall) FEND FUNCTION GetBitmapIcon(hnd, hIcon) RESULT = NULL DIM ii[4], hdcOrg = GetDC(hnd) IFB GetIconInfo(hIcon, ii) THEN DIM hdc = CreateCompatibleDC(hdcOrg), hbmp = SelectObject(hdc, ii[4]) RESULT = ii[4] DIM br = CreateSolidBrush(GetSysColor(4)) DrawIconEx(hdc, 0, 0, hIcon, 0, 0, 0, br, 3) DeleteObject(br) SelectObject(hdc, hbmp) IF ii[3] > 0 THEN DeleteObject(ii[3]) DeleteDC(hdc) ENDIF ReleaseDC(hnd, hdcOrg) FEND FUNCTION GetBitmapCopy(hnd, hbmp, w=0, h=0) RESULT = NULL DIM hdcOrg = GetDC(hnd), hdcs = CreateCompatibleDC(hdcOrg) DIM hbmps = SelectObject(hdcs, hbmp), bi[9] bi[0] = 40 IFB GetDIBits(hdcs, hbmp, 0, 0, NULL, bi, 0) <> 0 THEN IFB w = 0 THEN w = bi[1] IFB h = 0 THEN h = bi[2] ELSE w = bi[1] * h / bi[2] ENDIF ELSEIF h = 0 THEN h = w * bi[2] / bi[1] ENDIF DIM hdcd = CreateCompatibleDC(hdcOrg), hbmpd = CreateCompatibleBitmap(hdcs, w, h) DIM hbmpdb = SelectObject(hdcd, hbmpd) IFB StretchBlt(hdcd, 0, 0, w, h, hdcs, 0, 0, bi[1], bi[2], $CC0020) THEN RESULT = hbmpd ELSE DeleteObject(hbmpd) ENDIF SelectObject(hdcd, hbmpdb) DeleteDC(hdcd) ENDIF SelectObject(hdcs, hbmps) DeleteDC(hdcs) ReleaseDC(hnd, hdcOrg) FEND ENDMODULE
DrawIconExなんてAPI、知らなかった、、、。
GetBitmapCopy関数、CreateCompatibleBitmapの第一引数はhdcs(hdcdではない)
PopupMenuEx依存クリップボード管理
左端(マウスポインターのX座標0)にすると、今度はイメージを表示します。
また、文字列を保持している場合は、それをそのままppexに渡すため、exeをフルパスでコピーしても面白いかもしれません。
パスの通っているものなら、たとえば「notepad.exe」をコピーするだけでもいけます。
グラフィック表示があると、やっぱり嬉しいですね!
Clipboard2.uws
OPTION EXPLICIT CALL PopupMenuEx IFB GET_UWSC_NAME = "Clipboard2.uws" THEN DIM loop = TRUE, i, num, p, j, fmt, pex 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) pex = 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) //pex[i*4-4] = "notepad.exe/1" CASE Clipboard.F_BITMAP p[i*4-4] = Clipboard.GetFormatName(fmt) pex[i*4-4] = "|hbm/" + Clipboard.Peek(fmt, i) + "/64" 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" FOR i = 0 TO LENGTH(pex) - 1 IF LENGTH(pex[i]) = 0 THEN pex[i] = p[i] NEXT j = PopupMenuEx.Show(p, pex) 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 CASE Clipboard.F_BITMAP j = SAFEARRAY(0, 1) pex = SAFEARRAY(0, 9) j[0] = PopupMenuEx.GetDC(Clipboard._hwnd) j[1] = PopupMenuEx.SelectObject(j[0], p) pex[0] = 40 IFB PopupMenuEx.GetDIBits(j[0], p, 0, 0, NULL, pex, 0) <> 0 THEN PRINT "(" + pex[1] + ", " + pex[2] + ") " + INT(pex[3] / $10000) + "bpp" ENDIF PopupMenuEx.SelectObject(j[0], j[1]) PopupMenuEx.ReleaseDC(Clipboard._hwnd, j[0]) 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 DragQueryFileW(DWORD,DWORD,var wstring,DWORD): DWORD: shell32 HASHTBL _datas PUBLIC _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_BITMAP, 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_DIBV5 OR fmt = F_DIB THEN fmt = 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 = "DIBv5" 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 Get() RESULT = OpenClipboard(_hwnd) IFB RESULT THEN DIM hClip, size, data = SAFEARRAY(0, 0), index = 0 DIM fmt = EnumClipboardFormats(0) DEF_DLL RtlMoveMemory(DWORD, DWORD, DWORD): kernel32 WHILE fmt <> 0 hClip = GetClipBoardData(fmt) IFB fmt = Trans(fmt) THEN // 相互変換があるのは捨てる RESIZE(data, LENGTH(data) + 1) data[index] = fmt IFB fmt = F_BITMAP THEN data[index+1] = PopupMenuEx.GetBitmapCopy(_hwnd, hClip) ELSE size = GlobalSize(hClip) data[index+1] = HeapAlloc(_heap, 0, size) RtlMoveMemory(data[index+1], GlobalLock(hClip), size) GlobalUnlock(hClip) ENDIF index = index + 2 ENDIF fmt = EnumClipboardFormats(fmt) WEND DEF_DLL RtlMoveMemory(DWORD[], DWORD, DWORD): kernel32 IFB LENGTH(data) > 1 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) 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, j, k, m IF del THEN j = _datas[n, HASH_REMOVE] DEF_DLL RtlMoveMemory(DWORD, DWORD, DWORD): kernel32 WHILE (index < size) AND (RESULT) IFB data[index] = F_BITMAP THEN RESULT = (SetClipboardData(data[index], PopupMenuEx.GetBitmapCopy(_hwnd, data[index+1])) > 0) IF del THEN PopupMenuEx.DeleteObject(data[index+1]) ELSE 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]) ENDIF index = index + 2 WEND DEF_DLL RtlMoveMemory(DWORD[], DWORD, DWORD): kernel32 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 = 0 TO LENGTH(data) - 1 STEP 2 IFB data[i] = F_BITMAP THEN PopupMenuEx.DeleteObject(data[i+1]) ELSE HeapFree(_heap, 0, data[i+1]) ENDIF NEXT FEND ENDMODULE
なお、イメージの場合、幅64ピクセルに縮小(場合によっては拡大)します。
サイズを変えたい場合は、"/64"で探すとすぐ見つかると思います。
前バージョンは、処理を共通化するため、CF_BITMAPを捨ててましたが、今度はDIB系を捨ててCF_BITMAPを使ってます。
(PopupMenuExで使いまわす利便性の都合上)