古臭いなぁとどうしても感じてしまうのがWin32APIのボタン表示。(その他にもあるけど)
これをWin32APIだけで解決できるように、ボタンウィンドウ(とそのツールチップ)の動作再現コードを
いろいろかき集めて構成してみました。
とりあえずシェード表示だけできればいいだろ、ってことで、ボタンウィンドウはuxthemeのBufferdPaintの利用でウィンドウクラス登録、ツールチップはCreateWindowしてAnimateWindowでShow/Hide、としています。GDI仕様。。。
追記:) ツールチップにシャドウがつくようにCS_DROPSHADOWを追加しました。
static const char button_wc[] = "MyBUTTON";
static const wchar_t button_tc[] = L"BUTTON";
static int extra_offset;
static WNDPROC orig_button_proc=NULL;
using button_t=
struct{
HTHEME theme;
DWORD style : 28;
DWORD hide_accel : 1;
DWORD hide_focus : 1;
DWORD no_redraw : 1;
DWORD laststate : 8;
HWND hTP;
DWORD bTrack : 1;
DWORD bHoverDisp : 1;
};
int button_init_module(void)
{
WNDCLASS wc;
GetClassInfo(NULL, "BUTTON", &wc);
orig_button_proc=wc.lpfnWndProc;
extra_offset=wc.cbWndExtra;
wc.lpfnWndProc=button_proc;
wc.cbWndExtra+=sizeof(button_t*);
wc.style |= CS_GLOBALCLASS;
wc.hInstance = NULL;
wc.lpszClassName = button_wc;
if(!RegisterClass(&wc)) return -1;
BufferedPaintInit();
return 0;
}
void button_fini_module(void)
{
BufferedPaintUnInit();
UnregisterClass(button_wc, NULL);
}
static void button_paint(HWND hbutton, button_t* button, HDC hdc, int state)
{
if(button->no_redraw) return;
RECT rect;
GetClientRect(hbutton, &rect);
HFONT old_hfont;
int old_bk_mode;
COLORREF old_text_color;
HRGN old_rgn;
HFONT hfont;
hfont=(HFONT)SendMessage(hbutton, WM_GETFONT, 0, 0); if(hfont==NULL) hfont=(HFONT)GetStockObject(SYSTEM_FONT);
old_hfont=(HFONT)SelectObject(hdc, hfont);
old_bk_mode=GetBkMode(hdc);
old_text_color=GetTextColor(hdc);
old_rgn=CreateRectRgn(0, 0, 0, 0); if(GetClipRgn(hdc, old_rgn)!=1){ DeleteObject(old_rgn); old_rgn=NULL; }
if(!button->theme && (button->style & BS_DEFPUSHBUTTON)){
SelectObject(hdc, GetSysColorBrush(COLOR_WINDOWFRAME));
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
rect.left+=1; rect.top+=1; rect.right-=1; rect.bottom-=1;
}
if(button->theme){
if(IsThemeBackgroundPartiallyTransparent(button->theme, BP_PUSHBUTTON, state)) DrawThemeParentBackground(hbutton, hdc, &rect);
RECT content;
GetThemeBackgroundContentRect(button->theme, hdc, BP_PUSHBUTTON, state, &rect, &content);
// IntersectClipRect(hdc, content.left, content.top, content.right, content.bottom);
DrawThemeBackground(button->theme, hdc, BP_PUSHBUTTON, state, &rect, &rect);
IntersectClipRect(hdc, content.left, content.top, content.right, content.bottom);
}else{
HWND parent=GetAncestor(hbutton, GA_PARENT);
if(parent==NULL) parent=hbutton;
HBRUSH brush=(HBRUSH)SendMessage(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hbutton);
if(brush==NULL) brush=(HBRUSH)DefWindowProc(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hbutton);
IntersectClipRect(hdc, rect.left, rect.top, rect.right, rect.bottom);
DrawFrameControl(hdc, &rect, DFC_BUTTON, DFCS_BUTTONPUSH | state);
if(state==DFCS_PUSHED) rect.left+=1; rect.top+=1; rect.right+=1; rect.bottom+=1;
rect.bottom-=2;
}
if((SendMessage(hbutton, BM_GETSTATE, 0, 0) & BST_FOCUS) && !button->hide_focus){
SelectClipRgn(hdc, NULL);
if(button->theme){
DrawFocusRect(hdc, &rect);
}else{
rect.left+=1; rect.top+=2; rect.right-=1; rect.bottom-=2;
DrawFocusRect(hdc, &rect);
rect.left+=1; rect.top+=1; rect.right-=1; rect.bottom-=1;
}
}
int flags=0;
if(button->style & BS_ICON){
HICON hicon=(HICON)SendMessage(hbutton, BM_GETIMAGE, IMAGE_ICON, 0);
if(hicon!=NULL){
flags=DST_ICON;
ICONINFO ii;
GetIconInfo(hicon, &ii);
BITMAP bmp;
SIZE size;
GetObject(ii.hbmMask, sizeof(BITMAP), &bmp);
size.cx=bmp.bmWidth; size.cy=bmp.bmHeight;
if(ii.hbmColor==NULL){ size.cy/=2; }else{ DeleteObject(ii.hbmColor); }
DeleteObject(ii.hbmMask);
if(button->style & WS_DISABLED) flags|=DSS_DISABLED;
DrawState(hdc, NULL, NULL, (LPARAM)hicon, 0, (rect.right+rect.left-size.cx)/2, (rect.bottom+rect.top-size.cy)/2,
size.cx, size.cy, flags);
}
}else{
flags=DT_VCENTER;
switch(button->style & (BS_TOP|BS_BOTTOM)){
case BS_TOP:
flags|=DT_TOP;
break;
case BS_BOTTOM:
flags|=DT_BOTTOM;
break;
}
switch(button->style & (BS_LEFT|BS_CENTER|BS_RIGHT)){
case BS_LEFT:
flags|=DT_LEFT;
break;
case BS_RIGHT:
flags|=DT_RIGHT;
break;
default:
if(GetWindowLong(hbutton, GWL_EXSTYLE) & WS_EX_RIGHT)
flags|=DT_RIGHT;
else
flags|=DT_CENTER;
break;
}
flags|=((button->style & BS_MULTILINE) ? DT_WORDBREAK : DT_SINGLELINE);
if(button->hide_accel) flags|=DT_HIDEPREFIX;
char buffer[256];
int n=SendMessage(hbutton, WM_GETTEXT, 255, (LPARAM)buffer);
if(button->theme){
wchar_t wbuffer[256]; MultiByteToWideChar(CP_ACP, 0, buffer, -1, wbuffer, 256);
DrawThemeText(button->theme, hdc, BP_PUSHBUTTON, state, wbuffer, n, flags, 0, &rect);
}else{
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
DrawText(hdc, buffer, n, &rect, flags);
}
}
SelectObject(hdc, old_hfont);
SetBkMode(hdc, old_bk_mode);
SetTextColor(hdc, old_text_color);
SelectClipRgn(hdc, old_rgn); if(old_rgn!=NULL) DeleteObject(old_rgn);
}
LRESULT CALLBACK button_proc(HWND hbutton, UINT msg, WPARAM wparam, LPARAM lparam)
{
button_t *button=(button_t*) GetWindowLongPtr(hbutton, extra_offset);
switch(msg){
case WM_MOUSEMOVE:
{
if((button->bTrack==false)&&(button->bHoverDisp==false)){
TRACKMOUSEEVENT tme={sizeof(TRACKMOUSEEVENT), TME_HOVER|TME_LEAVE, hbutton, 500 };
TrackMouseEvent(&tme);
button->bTrack=true;
}
button->bHoverDisp=false;
}
break;
case WM_MOUSEHOVER:
{
button->bTrack=true;
if(button->bHoverDisp==false){
POINT pt={LOWORD(lparam), HIWORD(lparam)};
RECT rc; GetWindowRect(hbutton, &rc);
MapWindowPoints(hbutton, NULL, &pt, 1); // num of points=1
if(PtInRect(&rc, pt)){
button->bHoverDisp=1;
button->hTP=CreateWindow("STATIC", "tooltip",
WS_POPUP|WS_BORDER, pt.x+10, pt.y+10, 100, 30,
hbutton, NULL, hInst, NULL);
DWORD style=GetClassLongPtr(button->hTP, GCL_STYLE);
SetClassLongPtr(button->hTP, GCL_STYLE, style|CS_DROPSHADOW);
HFONT hfont=(HFONT)SendMessage(hbutton, WM_GETFONT, 0, 0);
SendMessage(button->hTP, WM_SETFONT, (WPARAM)hfont, 0);
AnimateWindow(button->hTP, 250, AW_BLEND);
TRACKMOUSEEVENT tme={sizeof(TRACKMOUSEEVENT), TME_LEAVE, hbutton, 100};
button->bTrack=TrackMouseEvent(&tme);
}
}
}
break;
case WM_MOUSELEAVE:
{
button->bTrack=button->bHoverDisp=false;
if(button->hTP){ AnimateWindow(button->hTP, 250, AW_BLEND|AW_HIDE); DestroyWindow(button->hTP); button->hTP=NULL; }
}
break;
case WM_PAINT:
case WM_PRINTCLIENT:
{
if(!button->theme) break;
HDC hdc;
PAINTSTRUCT ps;
if(msg==WM_PAINT) hdc=BeginPaint(hbutton, &ps); else hdc=(HDC)wparam;
if(BufferedPaintRenderAnimation(hbutton, hdc)==TRUE){
// If this function returns TRUE, the application should do no further painting.
// If this function returns FALSE, the application should paint normally.
if(msg==WM_PAINT) EndPaint(hbutton, &ps);
break;
}else{
int state=PBS_NORMAL;
if(button->style & WS_DISABLED){
state=PBS_DISABLED;
}else{
LRESULT s=SendMessage(hbutton, BM_GETSTATE, 0, 0);
if(s & BST_PUSHED) state=PBS_PRESSED;
else if(s & BST_HOT) state=PBS_HOT;
else if(button->style & BS_DEFPUSHBUTTON) state=PBS_DEFAULTED;
}
if(button->laststate!=state){
BP_ANIMATIONPARAMS animParams={};
animParams.cbSize=sizeof(BP_ANIMATIONPARAMS);
animParams.style=BPAS_LINEAR;
animParams.dwDuration=250;
RECT rc;
GetClientRect(hbutton, &rc);
HDC hdcFrom, hdcTo;
HANIMATIONBUFFER hbpAnim=BeginBufferedAnimation(hbutton, hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &animParams, &hdcFrom, &hdcTo);
if(hbpAnim){
if(hdcFrom!=NULL) button_paint(hbutton, button, hdcFrom, button->laststate); // render hdcFrom
if(hdcTo!=NULL) button_paint(hbutton, button, hdcTo, state); // render hdcTo
EndBufferedAnimation(hbpAnim, TRUE); // start auto-animation
// If TRUE, updates the target DC with the animation.
// If FALSE, the animation is not started, the target DC is not updated, and the hbpAnimation parameter is freed.
}
button->laststate=state;
}else{
button_paint(hbutton, button, hdc, state); // 追記
}
}
if(msg==WM_PAINT) EndPaint(hbutton, &ps);
return 0;
}
break;
case WM_UPDATEUISTATE:
{
LRESULT ret=CallWindowProc(orig_button_proc, hbutton, WM_UPDATEUISTATE, wparam, lparam);
DWORD flags=SendMessage(hbutton, WM_QUERYUISTATE, 0, 0);
button->hide_focus=(flags&UISF_HIDEFOCUS) ? 1 : 0;
button->hide_accel=(flags&UISF_HIDEACCEL) ? 1 : 0;
if(!button->no_redraw) InvalidateRect(hbutton, NULL, FALSE);
return ret;
}
break;
case WM_LBUTTONDOWN:
{
}
break;
case WM_LBUTTONDBLCLK:
{
}
break;
case WM_SETREDRAW:
{
button->no_redraw=(!wparam);
}
break;
case WM_STYLECHANGED:
{
if(wparam==GWL_STYLE){
STYLESTRUCT* ss=(STYLESTRUCT*)lparam;
button->style=ss->styleNew;
}
}
break;
case WM_THEMECHANGED:
{
if(button->theme) CloseThemeData(button->theme);
button->theme=OpenThemeData(hbutton, button_tc);
InvalidateRect(hbutton, NULL, FALSE);
}
break;
case WM_SYSCOLORCHANGE:
{
InvalidateRect(hbutton, NULL, FALSE);
}
break;
case WM_NCCREATE:
{
if(!CallWindowProc(orig_button_proc, hbutton, WM_NCCREATE, wparam, lparam)) return FALSE;
button=(button_t *)malloc(sizeof(button_t));
memset(button, 0, sizeof(button_t));
button->style=((CREATESTRUCT*)lparam)->style;
SetWindowLongPtr(hbutton, extra_offset, (LONG_PTR)button);
return TRUE;
}
break;
case WM_CREATE:
{
if(CallWindowProc(orig_button_proc, hbutton, WM_CREATE, wparam, lparam)!=0) return -1;
button->theme=OpenThemeData(hbutton, button_tc);
DWORD ui_state=SendMessage(hbutton, WM_QUERYUISTATE, 0, 0);
button->hide_focus=(ui_state & UISF_HIDEFOCUS) ? 1 : 0;
button->hide_accel=(ui_state & UISF_HIDEACCEL) ? 1 : 0;
return 0;
}
break;
case WM_DESTROY:
{
if(button->theme){
CloseThemeData(button->theme);
button->theme=NULL;
}
}
break;
case WM_NCDESTROY:
{
if(button) free(button);
}
break;
}
return CallWindowProc(orig_button_proc, hbutton, msg, wparam, lparam);
}