の続編。
自分の作ったアプリで、年に1回あるかどうかの設定更新が必要なものがあります。
「まずこのボタンを押して、動作を確認したら次にこのボタンを押してー」ってのがあるんですが、その際にいちいちマニュアル見るのも面倒だし、マニュアル見ながらでも操作を間違う人はいます。というか自分ですw
なので画面上の誘導が欲しいのですが、レイヤードウィンドウで作ったオーバーレイを重ねただけじゃ透明度の設定で躓くし、WS_EX_NOREDIRECTIONBITMAP
でつくったウィンドウを重ねると下のボタンを押せないということがあって、お手軽に作るにはどうすっかなぁと思ってました。
自分自身をキャプチャして同サイズのオーバーレイ画面に描画して重ね合わせ、その上に矢印とか描画できるといいなとは思っていたのですが、案だけで終わってました。
今回、時間ができてキャプチャまで手を出したので、少しやってみることにしました。
前回のサンプルアプリに、追加していきます。
HWND hwndOverlay=NULL;
MyWin mywinO;
unsigned int overlay_image_width, overlay_image_height;
ID2D1Bitmap1 *Overlay_Image_Bitmap=NULL;
32bit-pngで作っておいた矢印のビットマップを読み込んでおきます。
sprintf(filename, "%s\\RedArrow.png", HomeFolderName);
if(PathFileExists(filename)==TRUE){
printf("load image %s\n", filename);
IWICBitmapSource *pWIC_BitmapSource=CreateWICBITMAPfromFile(filename);
if(pWIC_BitmapSource!=NULL){
pWIC_BitmapSource->GetSize(&overlay_image_width, &overlay_image_height);
printf("image_size=(%d,%d)\n", overlay_image_width, overlay_image_height);
ID2D1DeviceContext *pD2D_DCtemp;
pD2D_Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, reinterpret_cast<ID2D1DeviceContext **>(&pD2D_DCtemp));
pD2D_DCtemp->CreateBitmapFromWicBitmap(pWIC_BitmapSource, &Overlay_Image_Bitmap);
pD2D_DCtemp->Release();
pWIC_BitmapSource->Release();
}
}
WS_EX_LAYERED| WS_EX_TRANSPARENT| WS_EX_TOPMOST
の組み合わせでレイヤードウィンドウを作成します。
wc.lpfnWndProc =WndProcOverlay;
wc.hInstance =hInstance;
wc.lpszClassName ="MyTest_Class_Overlay";
wc.hbrBackground =(HBRUSH)GetStockObject(BLACK_BRUSH);
ATOM atomOverlay=RegisterClass(&wc);
hwndOverlay=CreateWindowEx(WS_EX_LAYERED| WS_EX_TRANSPARENT| WS_EX_TOPMOST,
MAKEINTATOM(atomOverlay), "", WS_POPUP, // not available WS_POPUP and WS_CHILD simultaneously
0, 0, 0, 0, NULL/*hwndMain*/, (HMENU)NULL, hInstance, NULL);
float transparency=1.f;
SetLayeredWindowAttributes(hwndOverlay, 0, (unsigned char)(transparency*(float)0xff), LWA_ALPHA);
RECT rc{};
GetWindowRect(hwndOverlay, &rc);
mywinO.CreateSwapChain(hwndOverlay, rc.right-rc.left, rc.bottom-rc.top);
ShowWindow(hwndOverlay, SW_HIDE);
WndProc()
はこんな感じで
LONG_PTR __stdcall WndProcOverlay(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg){
case WM_CREATE:
{
RECT rc; GetClientRect(hwndMain, &rc);
POINT p{}; MapWindowPoints(hwndMain, NULL, &p, 1); // num of points=1
SetWindowPos(hwnd, 0,
p.x+rc.left, p.y+rc.top, rc.right-rc.left, rc.bottom-rc.top,
SWP_NOZORDER|SWP_NOACTIVATE);
}
break;
case WM_MOUSEACTIVATE:
return MA_NOACTIVATE;
default:
return DefWindowProc(hwnd, msg, wparam, lparam);
}
return 0;
}
メインウィンドウと追従するように、メインウィンドウのWndProc()
に追加
case WM_MOVE:
{
if(hwndOverlay!=NULL){
RECT rc; GetClientRect(hwndMain, &rc);
POINT p{}; MapWindowPoints(hwndMain, NULL, &p, 1); // num of points=1
SetWindowPos(hwndOverlay, 0,
p.x+rc.left, p.y+rc.top, rc.right-rc.left, rc.bottom-rc.top,
SWP_NOZORDER|SWP_NOACTIVATE);
}
}
break;
キャプチャしたテクスチャを適当な位置合わせでコピー、サンプルなんでごめんなさい^^;
bool CaptWinRT_onFrameArrived(IDXGISwapChain1 *swapchain,
winrt::Direct3D11CaptureFramePool const& sender, winrt::IInspectable const& args =nullptr)
{
winrt::Direct3D11CaptureFrame capt_frame =sender.TryGetNextFrame();
if(capt_frame==nullptr) return false;
DXGI_SWAP_CHAIN_DESC1 sc_desc{};
swapchain->GetDesc1(&sc_desc);
int offset_x=1;
int offset_y=GetSystemMetrics(SM_CYCAPTION)+8;
D3D11_BOX src_region{};
src_region.left =offset_x;
src_region.right =offset_x+sc_desc.Width;
src_region.top =offset_y;
src_region.bottom =offset_y+sc_desc.Height;
src_region.front =0;
src_region.back =1;
winrt::SizeInt32 c_size =capt_frame.ContentSize();
auto access =capt_frame.Surface().as<IDirect3DDxgiInterfaceAccess>();
ID3D11Texture2D *texture_src;
access->GetInterface(winrt::guid_of<ID3D11Texture2D>(), reinterpret_cast<void **>(&texture_src));
ID3D11Texture2D *texture_dst;
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&texture_dst);
pD3D_DC->CopySubresourceRegion(texture_dst, 0, 0, 0, 0, texture_src, 0, &src_region);
texture_dst->Release();
texture_src->Release();
capt_frame.Close();
if(capt_automode==true) swapchain->Present(1, 0);
return true;
}
マニュアルモードでキャプチャを開始して、メッセージループ内でキャプチャ画像が上がってきたら矢印を描画
MSG msg;
for(;;){
if(PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)!=0){ // ==0: no message !=0: has message
if(msg.message==WM_QUIT) break;
if((msg.message==WM_KEYDOWN)&&(msg.wParam == VK_ESCAPE)){ DestroyWindow(hwndOverlay); PostQuitMessage(0); continue; }
DispatchMessage(&msg);
}
if(mywinO.pDXGI_DC!=NULL){
if((capt_session!=nullptr)&&(capt_framepool!=nullptr)){
if((capt_automode==false)&&(CaptWinRT_onFrameArrived(mywinO.pDXGI_SwapChain, capt_framepool)==true)){
mywinO.pDXGI_DC->BeginDraw();
mywinO.pDXGI_DC->SetTransform(D2D1::Matrix3x2F::Identity());
mywinO.pDXGI_DC->DrawBitmap(Overlay_Image_Bitmap,
D2D1::RectF(220.f, 0.f, 220.f+(float)overlay_image_width, 0.f+(float)overlay_image_height),
1.f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR);
mywinO.pDXGI_DC->EndDraw();
mywinO.pDXGI_SwapChain->Present(1, 0);
ShowWindow(hwndOverlay, SW_SHOWNA);
}
}
}
}
まぁこんなもんか。あとはアニメーションマネージャでも使って経過時間に従った表示位置の移動をすればいいか。
レイヤードウィンドウだとDirectXの恩恵は受けないんだっけ?
でやっぱり、「あなた、盗撮されてますわよ」の黄色い枠が邪魔ですなw