Jump to content
  • Advertisement
Sign in to follow this  
gretty

[Win32 C++] Change Control Windows Shape

This topic is 3171 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello, yes I am back again talking about making a Win32 Custom Control :P I am not going to let it beat me lol. Anyway, I have made success on creating my Custom Control/Window. But I want to make the control/window an elliptical shape, not rectangular. Can anyone suggest how I do this? I know how to make an Elliptical region & I know how to change the position of the window/control(SetWindowPos()) but is there a function or way to change the shape of my control to circular? Here is my attempt in the draw event, but it just makes a circular region INSIDE my rectangular control, I know this because even when I left click outside the circle the control still receives the WM_LBUTTONDOWN message Below is my Custom Controls' WndProc() ( not my Main WndProc() ):
LRESULT CALLBACK CustomControlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   // Post: Receives all CustomControl window messages
   
   CustomControl *cc = (CustomControl*)GetWindowLong(hwnd,0);
   
   switch (msg) 
   {
         case WM_NCCREATE:
         {
              cc = (CustomControl*)malloc(sizeof(CustomControl));
              
              if (cc==false) {
                  MessageBox(NULL,"Failed to create CustomControl window","Error",MB_OK|MB_ICONERROR);
                  return 0;
              }
              
              SetWindowLong(hwnd,0,(LONG)cc); // Attach custom class to this window.
         }
         break;
         case WM_PAINT:
         {
              HDC hdc;
              PAINTSTRUCT ps;
              hdc = BeginPaint(hwnd,&ps);
              
              // Is there a function that allows me to change the shape of my hwnd?
              
              // Create Circular window
              HRGN foo = CreateEllipticRgn(0,0,100,100);
              HBRUSH bgBrush  = CreateSolidBrush(RGB(255,0,255));
              FillRgn(hdc,foo,bgBrush);
               
              EndPaint(hwnd,&ps);
         }    
         break;
         case WM_LBUTTONDOWN:
         {
              MessageBox(hwnd,"You clicked me","Notify",MB_OK);
              InvalidateRect(hwnd,NULL,true);
              UpdateWindow(hwnd);
         }
         break;
         case WM_NCDESTROY:
              free(cc);
         break;
         default:
         break;
   }
   return DefWindowProc(hwnd, msg, wParam, lParam);
}

Share this post


Link to post
Share on other sites
Advertisement
SetWindowRgn will do that. That means after setting an ellipsic region the window will have round edges.

Also note you need to destroy the region you created with DeleteObject (unless you call SetWindowRgn).

Share this post


Link to post
Share on other sites
Thanks :)

I have made my control elliptical which is cool but I change the window shape to circular in the WM_CREATE event. Is this the best place to do this or should I do it in the WM_PAINT event?

Also I want to change the background colour of my control but there is no function GetClientRegion() function so I cant do this:


case WM_PAINT:
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd,&ps);

HRGN foo;
GetClientRgn(hwnd,&foo); // As far as I know this function doesn't exist
HBRUSH bgBrush = CreateSolidBrush(RGB(255,0,255));
FillRgn(hdc,&foo,bgBrush);

EndPaint(hwnd,&ps);
break;




Is the only way to change the background colour like this:

case WM_NCCREATE:
{
cc = (CustomControl*)malloc(sizeof(CustomControl));

if (cc==false) {
MessageBox(NULL,"Failed to create CustomControl window","Error",MB_OK|MB_ICONERROR);
return 0;
}

SetWindowLong(hwnd,0,(LONG)cc); // Attach custom class to this window.

// Create Circular window
HRGN foo = CreateEllipticRgn(0,0,100,100);
SetWindowRgn(hwnd,foo,true);
}
break;
case WM_PAINT:
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd,&ps);

RECT foo;
GetClientRect(hwnd,&foo);
HBRUSH bgBrush = CreateSolidBrush(RGB(255,0,255));
FillRect(hdc,&foo,bgBrush);

EndPaint(hwnd,&ps);
break;






From what I read of the MSDN SetWindowRgn() function:
"The window region determines the area within the window where the system permits drawing. The system does not display any portion of a window that lies outside of the window region."
So the window is still rectangular, but only the circular HRGN is visible, so does that mean the only way to fill the window with a colour is to use a RECT not a HRGN?

Share this post


Link to post
Share on other sites
You don't need a GetClientRegion() function.

The way it works is that for a single instance of a custom control that you want to be non-rectangular, you need to call SetWindowRgn() exactly once, so do it in WM_CREATE or just after you call CreateWindow but definitely don't do it in WM_PAINT. You pass SetWindowRgn() an HRGN that determines the shape of the window. That region should be relative to the upper left hand corner of the window rect i.e. point (0,0) of the region maps to point (r.left,r.top) if r was filled with GetWindowRect().

After you call SetWindowRgn I believe Win32 owns the region you gave it, so don't call DeleteObject on it, and after you call SetWindowRgn the window's clipping region is then after changed to the shape that you provided. So if you, say, paint the rect returned by GetClientRect() in WM_PAINT it will automatically clip to the region without you doing anything.

SetWindowRgn() is actually *very* easy to use.

[Edited by - jwezorek on April 15, 2010 4:31:32 AM]

Share this post


Link to post
Share on other sites
What jwezorek said, you do not need to manually clip drawing to the region at all once you called SetWindowRgn.

Here's a function that might come in handy, in converts a HBITMAP with a color key into a HRGN (useful for complex shapes):


HRGN CWinUtils::BitmapToRegion( HBITMAP hBmp, COLORREF cTransparentColor )
{

HRGN hRgn = NULL;

if ( hBmp == NULL )
{
return NULL;
}

// Create a memory DC inside which we will scan the bitmap content
HDC hMemDC = CreateCompatibleDC( NULL );

if ( hMemDC == NULL )
{
return NULL;
}

// Get bitmap size
BITMAP bm;

GetObject( hBmp, sizeof( bm ), &bm );

// Create a 32 bits depth bitmap and select it into the memory DC
BITMAPINFOHEADER RGB32BITSBITMAPINFO =
{
sizeof(BITMAPINFOHEADER), // biSize
bm.bmWidth, // biWidth;
bm.bmHeight, // 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 == NULL )
{
DeleteDC( hMemDC );
return NULL;
}

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 );

BitBlt( hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, hDC, 0, 0, SRCCOPY );

// For better performances, we will use the ExtCreateRegion() function to create the
// region. This function take a RGNDATA structure on entry. We will add rectangles by
// amount of ALLOC_UNIT number in this structure.

GR::u32 maxRects = 100;
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

// 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 < bm.bmHeight; y++)
{
// Scan each bitmap pixel from left to right
for (int x = 0; x < bm.bmWidth; x++)
{
// Search for a continuous range of "non transparent pixels"
int x0 = x;
LONG *p = (LONG *)p32 + x;
while (x < bm.bmWidth)
{
if ( *p == cTransparentColor )
{
// 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 += 100;
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
SelectObject(hDC, holdBmp);
DeleteDC(hDC);

GlobalUnlock( hData );
GlobalFree( hData );
}

DeleteObject(SelectObject(hMemDC, holdBmp));
DeleteDC( hMemDC );

return hRgn;

}

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!