yam 蕃薯藤
天空部落
  • 網誌
  • 相簿
  • 影音
  • 夯集
  • PK吧!
  • 揪便宜
  • i鬥圖
  • la zorza
  • 買房子
新聞
  • 即時新聞
  • 影音新聞
  • 新聞專輯
  • 政治新聞
  • 財經新聞
  • 娛樂新聞
  • 運動新聞
  • 兩岸新聞
  • 科技新聞
註冊 登入
夯集
隨便逛
  • 正妹
  • 熱門
  • 網誌
  • 旅遊/攝影
  • 愛情/交友
  • 親子/教育
  • 美食/休閒
  • 設計/創作
  • 家族/同好
  • 影視/音樂
  • 社會/人文
  • 時尚/美容
  • 寵物/生活
  • 工作/職場
  • 科技/金融
  • 運動/健康
  • 交通/運輸
  • 相簿
  • COSPLAY
  • 布袋戲迷
  • 電玩漫畫
  • 女生照片
  • 藝術寫真
  • 攝影作品
  • 男生照片
  • 影視娛樂
  • 大眼小布
  • 情侶拍拍
  • 旅遊紀錄
  • 朋友團體
  • 人文藝術
  • more...
推薦這個部落格: 387

A HA! 心情雜物間

專供個人碎碎念的心得小屋,或是好物分享~~ ^_^

近日發現有色情業者留言洗版的狀況發生,一經發現,留言立刪。

日記 |網誌 |相簿 |好友 |留言板
[Extjs] Cross-site request | 主頁 | 在 Vista 64-bit 下使用 64-bit IE 無法安裝 ActiveX control
May 20, 2009
[VC] 不規則視窗或按鈕以文找文
csylvia 在天空部落發表於15:01:25 | C/C++
 

以前有寫過不規則視窗的程式,
使用 SetWindowRgn() 的方式來完成,
推薦這篇文章教學。


但這篇教學範例是用幾何圖形拼湊而成,
也就是矩形、圓形、橢圓形、多邊形等等,
如果是要做很不規則的形狀呢?

那就要自己去分析背景圖並產生 HRGN 物件了。

當時寫的不規則視窗程式,程式碼是從這個網站(已經找不到網頁)得到的,
但現在該網站已關,
所以我直接把程式碼分享出來。

HRGN cAppskin::GetBitmapRegion(HBITMAP hBMP, COLORREF cTransparentColor , COLORREF cTolerance , LPRECT lpDestRect)
{
        HRGN hRgn = NULL;
        RECT rcDest;

        // Get bitmap size
        BITMAP bm;
        GetObject(hBMP, sizeof(bm), &bm);

        if( lpDestRect != NULL )
        {
                  CopyRect( &rcDest, lpDestRect );
        }
        else
        {
                  rcDest.left = rcDest.top = 0;
                  rcDest.right = bm.bmWidth;
                  rcDest.bottom = bm.bmHeight;
        }

        if( !hBMP )
                  return FALSE;

        // Create a memory DC inside which we will scan the bitmap content
        HDC hMemDC = CreateCompatibleDC(NULL);
        if (hMemDC)
        {
                  // Create a 32 bits depth bitmap and select it into the memory DC 
                  BITMAPINFOHEADER RGB32BITSBITMAPINFO = { 
                              sizeof(BITMAPINFOHEADER), // biSize 
                              RECT_WIDTH(rcDest), // biWidth; 
                              RECT_HEIGHT(rcDest), // biHeight; 
                              1,       // biPlanes; 
                              32,       // biBitCount 
                              BI_RGB,      // biCompression; 
                              0,       // biSizeImage; 
                              0,       // biXPelsPerMeter; 
                              0,       // biYPelsPerMeter; 
                              0,       // biClrUsed; 
                              0       // biClrImportant; 
                  };
                  VOID * pbits32; 
                  HBITMAP hbm32 = CreateDIBSection(hMemDC, (BITMAPINFO *)&RGB32BITSBITMAPINFO, DIB_RGB_COLORS, &pbits32, NULL, 0);
                  if (hbm32)
                  {
                              HBITMAP holdBmp = (HBITMAP)SelectObject(hMemDC, hbm32);

                              // Create a DC just to copy the bitmap into the memory DC
                              HDC hDC = CreateCompatibleDC(hMemDC);
                              if (hDC)
                              {
                                           // Get how many bytes per row we have for the bitmap bits (rounded up to 32 bits)
                                           BITMAP bm32;
                                           GetObject(hbm32, sizeof(bm32), &bm32);
                                           while (bm32.bmWidthBytes % 4)
                                                          bm32.bmWidthBytes++;

                                           // Copy the bitmap into the memory DC
                                           HBITMAP holdBmp = (HBITMAP)SelectObject(hDC, hBMP);
                                           StretchBlt(hMemDC, 0, 0, RECT_WIDTH(rcDest), RECT_HEIGHT(rcDest), hDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);

                                           DWORD maxRects = ALLOC_UNIT;
                                           HANDLE hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects));
                                           RGNDATA *pData = (RGNDATA *)GlobalLock(hData);
                                           pData->rdh.dwSize = sizeof(RGNDATAHEADER);
                                           pData->rdh.iType = RDH_RECTANGLES;
                                           pData->rdh.nCount = pData->rdh.nRgnSize = 0;
                                           SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);

                                           // Keep on hand highest and lowest values for the "transparent" pixels
                                           BYTE lr = GetRValue(cTransparentColor);
                                           BYTE lg = GetGValue(cTransparentColor);
                                           BYTE lb = GetBValue(cTransparentColor);
                                           BYTE hr = min(0xff, lr + GetRValue(cTolerance));
                                           BYTE hg = min(0xff, lg + GetGValue(cTolerance));
                                           BYTE hb = min(0xff, lb + GetBValue(cTolerance));

                                           // Scan each bitmap row from bottom to top (the bitmap is inverted vertically)
                                           BYTE *p32 = (BYTE *)bm32.bmBits + (bm32.bmHeight - 1) * bm32.bmWidthBytes;
                                           for (int y = 0; y < bm32.bmHeight; y++)
                                           {
                                                          // Scan each bitmap pixel from left to right
                                                          for (int x = 0; x < bm32.bmWidth; x++)
                                                          {
                                                                        // Search for a continuous range of "non transparent pixels"
                                                                        int x0 = x;
                                                                        LONG *p = (LONG *)p32 + x;
                                                                        while (x < bm32.bmWidth)
                                                                        {
                                                                                     BYTE b = GetRValue(*p);
                                                                                     if (b >= lr && b <= hr)
                                                                                     {
                                                                                                  b = GetGValue(*p);
                                                                                                  if (b >= lg && b <= hg)
                                                                                                  {
                                                                                                             b = GetBValue(*p);
                                                                                                             if (b >= lb && b <= hb)
                                                                                                                          // This pixel is "transparent"
                                                                                                                          break;
                                                                                                  }
                                                                                     }
                                                                                     p++;
                                                                                     x++;
                                                                        }

                                                                        if (x > x0)
                                                                        {
                                                                                     // Add the pixels (x0, y) to (x, y+1) as a new rectangle in the region
                                                                                     if (pData->rdh.nCount >= maxRects)
                                                                                     {
                                                                                                  GlobalUnlock(hData);
                                                                                                  maxRects += ALLOC_UNIT;
                                                                                                  hData = GlobalReAlloc(hData, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), GMEM_MOVEABLE);
                                                                                                  pData = (RGNDATA *)GlobalLock(hData);
                                                                                     }
                                                                                     RECT *pr = (RECT *)&pData->Buffer;
                                                                                     SetRect(&pr[pData->rdh.nCount], x0, y, x, y+1);
                                                                                     if (x0 < pData->rdh.rcBound.left)
                                                                                                  pData->rdh.rcBound.left = x0;
                                                                                     if (y < pData->rdh.rcBound.top)
                                                                                                  pData->rdh.rcBound.top = y;
                                                                                     if (x > pData->rdh.rcBound.right)
                                                                                                  pData->rdh.rcBound.right = x;
                                                                                     if (y+1 > pData->rdh.rcBound.bottom)
                                                                                                  pData->rdh.rcBound.bottom = y+1;
                                                                                     pData->rdh.nCount++;

                                                                                     // On Windows98, ExtCreateRegion() may fail if the number of rectangles is too
                                                                                     // large (ie: > 4000). Therefore, we have to create the region by multiple steps.
                                                                                     if (pData->rdh.nCount == 2000)
                                                                                     {
                                                                                                  HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData);
                                                                                                  if (hRgn)
                                                                                                  {
                                                                                                                CombineRgn(hRgn, hRgn, h, RGN_OR);
                                                                                                                DeleteObject(h);
                                                                                                  }
                                                                                                  else
                                                                                                                 hRgn = h;
                                                                                                  pData->rdh.nCount = 0;
                                                                                                  SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);
                                                                                     }
                                                                        }
                                                         }

                                                         // Go to next row (remember, the bitmap is inverted vertically)
                                                         p32 -= bm32.bmWidthBytes;
                                          }

                                          // Create or extend the region with the remaining rectangles
                                          HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData);
                                          if (hRgn)
                                          {
                                                         CombineRgn(hRgn, hRgn, h, RGN_OR);
                                                         DeleteObject(h);
                                          }
                                          else
                                                         hRgn = h;

                                          // Clean up
                                          GlobalFree(hData);
                                          SelectObject(hDC, holdBmp);
                                          DeleteDC(hDC);
                              }

                              DeleteObject(SelectObject(hMemDC, holdBmp));
                 }

                 DeleteDC(hMemDC);
        } 

        return hRgn;
}
 

 

用 SetWindowRgn() 方式處理很簡單,
以最簡單的幾何圖形為例,
新建一個視窗:

 

 

在建立視窗時設定 SetWindowRgn():

 

// get rect of window
RECT rc;
GetClientRect( hWnd, &rc );

// create an elliptical region
HRGN hRegion = CreateEllipticRgn( 0, 0, rc.right-rc.left, rc.bottom-rc.top);

// attach the region to the window
SetWindowRgn( hWnd, hRegion, true );

// delete the region object
DeleteObject( hRegion );

 

就會變成不規則形狀的視窗啦!

 

 

非常簡單!

除了單一幾何圖形外,
還可以使用 CombineRgn() 做幾何圖形的融合或分割,
可以參考上面那個網頁。

如果要移除視窗形狀限制,
執行

 

 SetWindowRgn( hWnd, NULL, true );

 

即可。

 

 

本來想,按鈕也是一種視窗,
應該用同樣方法就可以產生不規則按鈕了吧?

其實不然。

使用以下程式碼:

 

// create an elliptical region
HRGN hRegion = CreateEllipticRgn( 0, 0, 100, 50 );

 // create button
m_hWndBTN = CreateWindow( _T("BUTTON"), _T("Click me"), 
                                                          WS_VISIBLE | WS_CHILD| BS_BS_PUSHBUTTON,
                                                          50, 30, 100, 50,
                                                          hWnd, (HMENU)ID_BUTTON,
                                                          (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), NULL);

 // attach the region to the window
SetWindowRgn( m_hWndBTN, hRegion, true );

 // delete the region objects
DeleteObject( hRegion );

 

出來的還是灰色、方形的按鈕,
不同的地方在於,
只有設定 region 的範圍內可以執行「press」的動作。

設定 HRGN 還是有意義的,
因為這動作設定了按鈕有效區域範圍,
所以需要修改的部分只是將按鈕的 style 改成 BS_OWNERDRAW,
然後在 WM_DRAWITEM 事件處理中繪製按鈕圖片即可:

 

// create an elliptical region
HRGN hRegion = CreateEllipticRgn( 0, 0, 100, 50 );

// create button
m_hWndBTN = CreateWindow( _T("BUTTON"), _T("Click me"), 
                                                          WS_VISIBLE | WS_CHILD| BS_OWNERDRAW,
                                                          50, 30, 100, 50,
                                                          hWnd, (HMENU)ID_BUTTON,
                                                          (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), NULL);

// attach the region to the window
SetWindowRgn( m_hWndBTN, hRegion, true );

// delete the region objects
DeleteObject( hRegion );

......

case WM_DRAWITEM:
{
             DRAWITEMSTRUCT *lpdraw = (DRAWITEMSTRUCT *)lParam;
             switch( lpdraw->CtlID )
             {
             case ID_BUTTON:
                        // load bitmap
                        DWORD idResource = IDB_BUTTON;
                        if( lpdraw->itemState & ODS_DISABLED ){
                                         idResource = IDB_BUTTON_HOVER;
                        }
                        HBITMAP hBmp = LoadBitmap( (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), MAKEINTRESOURCE(idResource) );

                        // get bitmap's info
                        BITMAP bitmap_info;
                        GetObject(hBmp, sizeof(BITMAP), &bitmap_info);
                        int wx = bitmap_info.bmWidth;
                        int wy = bitmap_info.bmHeight;

                        // draw the button
                        HDC hdc = lpdraw->hDC;
                         HDC hdc_mem = CreateCompatibleDC(hdc);
                         SelectObject(hdc_mem, hBmp);
                         BitBlt(hdc, 0, 0, wx, wy, hdc_mem, 0, 0, SRCCOPY);

                        // release
                        DeleteDC(hdc_mem);
                        DeleteObject(hBmp);
                        break;
             }
}
break;

 

呈現的按鈕(藍色橢圓形)如下:

 

 

還可以根據狀態變換圖形,
這是按鈕被 disable 後變成綠色按鈕的範例:

 

 

網路上針對不規則按鈕還有其他作法,
像是 SetMask 等,
可以參考看看~~ ^^

 

 

 

【不規則 or 透明視窗】

  • flipcode - Win32 Window Skinning
  • windows sdk编程系列文章 ---- 利用镂空制作特效界面_一切从C开始
  • CodeProject: Skins in a Dialog Based Application.

 

【不規則 or 透明按鈕】

  • CodeProject: Transparent Button with Region and Anti-Aliased Edges.
  • CodeProject: An easy way to create transparent button.
  • CodeProject: Irregular shaped buttons – owner drawn buttons made easy.
  • How to make button's image transparent (in MFC)? : C++, MFC
  • 如何在CBitmapButton類別的按鈕貼上透明圖?
  • 用sdk创建不规则窗口遇到的问题。 VC/MFC / 界面 - CSDN社区
  • 如果制作圆形按钮? VC/MFC / 界面 - CSDN社区
  • VC知识库文章 - 自绘按钮补遗

 


誰推薦這篇文章:
留言 (6) | 引用 (0) | 人氣 () | 轉寄 | 檢舉
此分類上一篇:[VC] About Tooltips | 主頁 | 此分類下一篇:[VC] Change mouse cursor
引用 (你可以針對此文寫一篇屬於自己的blog/想法,並給作者一個通告)
引用
留言 (6筆)
1.
若是要按照圖片而產生不規則的對話框,我的用法是使用GDI+ 貼 PNG 圖檔(背景為透明),
貼上 Dialog ,再用筆刷(CBrush) 刷(Dialog)底色,

接著使用SetWindowLong(...LWA_COLORKEY)把底色濾掉(遮蓋),這樣就可以得到不規則圖形樣式的Dialog~

GDI+的變化多,可支援圖檔也多,鋸齒邊緣也可調整,繪圖速度快,蠻好用的!
我比較喜歡用png,色彩較漂亮,檔案也較小...

按鈕的樣式,我依然習慣使用貼圖,然後把不需要的地方遮蓋掉(OnEraseBkgnd(CDC *pDC)),
這樣就可以隨心所欲創造圖形化按鈕了^^
板主回覆:
是可以用透明色的背景方式作不規則圖形,
但重點是「作用區域」,
如果只貼圖不用 SetWindowRgn 的方式處理作用區域的話,
當滑鼠移到按鈕的圖上任何一點(即使該點是透明底)也是會 trigger 該按鈕的行為。
.
↑ 這樣講好像很複雜喔? "^^a
Ethan 於 May 26, 2009 留言 |

2.
您好,看了您的文章,獲益良多,但我這裡在製作上遇到個問題,
想請教您:
1.我有使用SetWindowRGN製作一個橢圓區域按鈕,如您範例程式
2.顯示仍為預設方形按鈕,但使用SPY觀看,的確只有橢圓區域才有點下去的作用
3.在WM_DRAWITEM的地方,使用BitBlt貼上要的圖片

但是這樣做後,仍是一整張圖片貼在整個方型預設按鈕區域上,不是所創建的橢圓區域,使用SPY觀看,仍是只有橢圓區域有作用,就是外觀呈現上,不知道有沒有辦法如您範例圖上顯示的只有橢圓的樣子呢?
在此先謝謝您的指教
板主回覆:
喔對,文章少寫了一點,就是「去背/去色」圖的問題。

範例圖是因為按鈕圖非按鈕的部分就是白色,
剛好融於背景色為白色的視窗上,
所以感覺像是透明圖的樣子,
真要實作透明圖要用


TransparentBlt( hdc, 0, 0, wx, wy, hdc_mem, 0, 0, wx, wy, m_cTransparentColor );


而不是使用「BitBlt」,
其中「m_cTransparentColor」就是要變成透明的色系。

希望有解決你的問題~~ ^^
Anne 於 Jul 28, 2009 留言 |

3.
嗯嗯,今天晚餐前利用TransparentBlt做出我想要的,剛剛想說來回應一下,沒想到您已經先解說了,但還是很謝謝您喔。
板主回覆:
有解決就好,不客氣~~(笑)
Anne 於 Jul 30, 2009 留言 |

4.
板大好~
最近也在研究不規則框....
我依你的步驟做 但button仍出不來耶~
可否開放你的code讓我學習研究
或mail 給我 (eststock@kimo.com)
感激不盡~

板主回覆:
Hi,

我的程式碼全都貼在網誌中,沒有什麼額外的 code 了耶,
除了上面的留言2有做一些補充,
你要不要說看看是什麼狀況,是出現錯誤訊息?按鈕沒有以長方型呈現?還是如何?

還是你是使用 MFC 實作?
這裡採用的是 SDK 寫法,不過應該不會差很多才是......
Frank 於 Sep 22, 2009 留言 |

5.
感謝你的回覆....我確實是用MFC ~我想在dialog 上放個自定形狀的button....
http://www.codeguru.com/Cpp/controls/buttonctrl/non-rectangularbuttons/article.php/c2085
這個網址有範例,......
但我想說板大這種(CreateWindow)方式應該也能應用在MFC才對,但一直試不出來!!!!

// create button
m_hWndBTN = CreateWindow() <===建不出來( m_hWndBTN = 0x000000)
不知是要include什麼還是說有其他問題???
板主回覆:
m_hWndBTN = CreateWindow(...);

無法建立出來?
可以把你建立的程式碼貼給我看看嗎?

如果是寫在 MFC 中最好這樣寫:

m_hWndBTN = ::CreateWindow(...);

前面的兩個冒號是 scope operator 的意思,
不過我想你 compile 的時候應該會出現錯誤才是,
如果沒有 compile error 就應該不是這個問題。


PS. 想起我有在 MFC 中用相同的程式碼,是可以運作的。
Frank 於 Sep 23, 2009 留言 |

6.
今天放假還沒try!!
嗯~我先試試看,有什麼問題再跟板大討教~謝謝喔!!
板主回覆:
好的,不客氣~~ ^^
Frank 於 Sep 25, 2009 留言 |

發表你的留言 (字數限制 最多 2000 個中文字)
私密留言: 是 否
Name:



是 否
內容:
檢視行動版網頁  |  檢視正常版網頁
系統公告
熱情贊助
系統工具

Sylvia 小檔案
個人圖檔
ID:csylvia
暱稱:Sylvia
地區:臺北市
  • 好友 |
    • 好友功能
    • 觀看好友列表
    • 觀看人緣列表
  • 人氣 |
  • 簡介 

文章分類
  • 語言筆記 (3)
    • 其他語言 (2)
    • 日文 (6)
    • 英文 (19)
  • 創作欣賞 (7)
    • 文章 (1)
    • 設計 (2)
    • 毛線球 (2)
    • CG (8)
    • 不織布 DIY (5)
    • 串珠飾品 (12)
    • 其他創作 (1)
  • 電視影集 (3)
    • 紀錄片/連續劇 (11)
    • 經典影集 (14)
    • 綜藝節目 (7)
  • 科技資訊 (20)
    • AJAX (42)
    • C/C++ (61)
    • WM5.0 (26)
    • Code::Blocks 相關 (29)
    • Symbian (26)
    • JAVA (8)
    • ActiveX (25)
    • Database (7)
    • ASP (9)
    • AIR (2)
    • JavaScript (36)
    • FLASH (15)
    • 網頁相關 (13)
    • PHP (5)
    • Batch File (9)
    • VLC (15)
    • Linux 相關 (3)
    • 其他 (38)
    • Install (2)
    • SOAP (1)
  • 動漫遊戲 (3)
    • 其他動漫 (1)
    • 日本動漫 (22)
    • 西洋動漫 (9)
  • 生活資訊 (6)
    • 飲食美味 (2)
    • 美容美髮 (6)
    • 體操按摩 (5)
    • 有用資訊 (11)
    • 螢養保健 (11)
    • 小辭典 (13)
  • 資源連結 (4)
    • 介紹&推薦 (45)
    • BLOG小玩意 (44)
    • 噗浪 (9)
    • 資源整理 (12)
  • 真人電影 (4)
    • 東洋電影 (2)
    • 國片電影 (5)
    • 電影介紹&推薦 (20)
    • 西洋電影 (35)
  • Amway (3)
    • 其他筆記 (4)
    • 教室課程 (4)
    • 紀錄 (3)
  • 個人記事 (6)
  • 書籍藝文 (39)
  • 話題探討 (30)
  • 烹飪學習簿 (0)
  • @ (0)
csylvia的最新的回應
  • 路人:
    你好:) ...
  • simth:
    綠林的電子書不錯,...
  • 匿名:
    您好,我們是愛閱二手書...
  • clive:
    謝謝您呀!最近也在尋找...
  • 匿名:
    您好,我們是愛閱二手書...
  • Joyce:
    私密留言
  • ronaldochow2011:
    喜迎回訪小弟的blog...
  • A-HA:
    哈,胡言亂語~不懂的,...
  • 【無米樂】導演徵求宣傳義勇軍:
    各位兄弟姐妹 大家好:...
  • 欣:
    不好意思版大再請教一些...
網站集結
aNobii 書櫃
天氣
珍惜台灣
台灣是我的國家
台灣貨‧暢其流
天下雜誌綠色行動4力串聯貼紙

該推就是要推
禁虐



搶救生命 棄兒不捨!
天下雜誌台灣不願面對的真相貼紙



Chine Free

力挺泰山串聯貼紙
RANKING
Google PageRank Checker



Free PageRank Meter for blog.webs-tv.net/csylvia
RSS 訂閱


人氣指數
當日人次:
累積人次:
網誌搜尋
搜尋:
參觀者
RSS 訂閱
RSS2
ATOM
其它資訊
本部落所刊登之內容,皆由作者個人所提供,不代表 yam 天空部落 本身立場。
POWERED BY
POWERED BY 天空部落
會員登入│免費註冊