0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

button表示の雛形(エフェクト表示、非矩形表示等の検討用)

Last updated at Posted at 2018-11-24

古臭いなぁとどうしても感じてしまうのが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);
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?