MFC Image Button with transparency - c++

I'm updating an MFC dialog with a number of buttons on it.
At present, the dialog has a Picture control covering the whole dialog providing a patterned background. On top of that, each button is a CBitmapButton using (opaque) images carefully generated to match the area of background they cover.
It would obviously be much easier if the images could be created as mostly transparent, so the background shows through automatically. However, I can't work out how to get MFC to render transparent images correctly in this case.
I understand that I might want a different class to CBitmapButton, or need to write a custom subclass; that's fine, but I don't know where to start. It would be nice to support 32-bit BMP or PNG with alpha channel, but I'd settle for the "specified colour should be transparent" type.

It may not be the best way to do it, but what I'd do is create a custom CButton derived class (assuming that you're actually using the rest of the CButton functionality), then override the DrawItem function to put your custom draw code in.
For the image itself I'd use a Bitmap GDI+ object (which will allow you to load either BMPs or PNGs with alpha channels) then use the regular DrawImage function to draw the bitmap.
If you're going to put PNGs into your resource file then you need to put them in as a "PNG" type. Make sure when you look in the resource file code that the entry looks like
IDB_PNG1 PNG "C:\temp\test.png"
and doesn't try to treat it as a BITMAP resource otherwise you'll have problems loading them.
Edit
Putting my response here so I can post code. Yes, I meant to derive a custom class from CButton, then add a Gdiplus::Bitmap member variable. Here is roughly what you'll need to do to get it to work, though I haven't checked that the code actually compiles and works, but hopefully you'll get the idea. It's not the most efficient way to do it, but if you've not done much custom drawing before then it does have the advantage of being simple!
void CMyButton::LoadImage(const int resourceID)
{
m_pBitmap = Gdiplus::Bitmap::FromResource(NULL, MAKEINTRESOURCE(resourceID));
ASSERT(m_pBitmap);
}
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);
CRect rcClient;
GetClientRect(&rcClient);
if (lpDrawItemStruct->itemState & ODS_SELECTED)
{
// If you want to do anything special when the button is pressed, do it here
// Maybe offset the rect to give the impression of the button being pressed?
rcClient.OffsetRect(1,1);
}
Graphics gr(lpDrawItemStruct->hDC);
gr.DrawImage(m_pBitmap, rcClient.left, rcClient.top);
}

Related

How to save image which is specific area that I clicked of picture Control?

Hello I'm studying MFC and I wanna know how to save a specific part of image which is in Picture Control when I click.
Here is the sequence what I want to do
I already make a function that when I click grab button, it shows the image in picture control.
After that, I want to save image when I click specific area.
I attach the image to help understand.
"I want to know how to save the surrounding area as an image when I click a specific part of the picture control as shown above."
It would be easy problem, but I'd appreciate it if you understand because it's my first time doing this.
I tryed to use ScreenToClient function, but I failed.
There are quite a few ways to do this, but the general idea is usually pretty much the same:
Get a DC to the screen or the window's client area (aka DC1).
Create a DC compatible with DC1 (aka DC2).
create a CBitmap compatible with DC1, of the size you want to copy/save.
select the bitmap into DC2.
bitblt the area you care about from DC1 to DC2.
Create a CImage object.
Detach the bitmap from the CBitmap, and attach it to the CImage.
Use CImage::Save to save the bitmap to a file.
There are variations (e.g., writing the data to a file without using a CImage), but they tend to be tedious, and unless you're willing to put quite a bit of effort into things, fairly limited as well. e.g., writing a .BMP file on your own isn't terribly difficult, but is a bit tedious.If you want to support writing JPEG, TIFF, PNG, etc., you either need to integrate a number of libraries, or else write quite a bit of code for the file formats. CImage is somewhat limited as well, but at least supports a half dozen or so of the most common formats (and most people find just JPEG, PNG and possibly GIF sufficient).
Here's a bit of tested sample code for saving a 200x200 chunk of whatever's displayed in the current view window into a file named capture.jpg:
void CPicCapView::OnLButtonDown(UINT nFlags, CPoint point) {
// width and height of area to capture
static const uint32_t width = 200;
static const uint32_t height = 200;
CClientDC dc(this);
CDC temp;
temp.CreateCompatibleDC(&dc);
CBitmap bmp;
bmp.CreateCompatibleBitmap(&dc, width, height);
temp.SelectObject(&bmp);
// x-width/2, y-width/2 to get area centered at click point
temp.BitBlt(0, 0, width, height, &dc, point.x- width/2, point.y-height/2, SRCCOPY);
CImage dest;
dest.Attach((HBITMAP)bmp.Detach());
// for the moment, just saving to a fixed file name
dest.Save(L".\\capture.jpg");
CView::OnLButtonDown(nFlags, point);
}
It might initially seem like you should be able to create the CImage object, use its Create to create a bitmap, and then blit directly from the screen bitmap to the CImage bitmap. Unfortunately, CImage's BitBlt uses the CImage as a source, not a destination, so although there may be some way to get this to work, the obvious method doesn't.

How to change looks of a disabled button and edit control?

Enabled
Disabled
When I disable a button ( Created with BS_BITMAP style flag ) it changes its look (Please see the above images), same thing happens with edit controls.
How do I make the controls not change when disabled ?
I can do that by subclassing the control, but is there an easier way ?
I don't want to subclass the controls just for that, if possible.
You do not need to subclass the control in order to do this, although I'd say it would be much cleaner. The alternative to set the BS_OWNERDRAW style and handle the WM_DRAWITEM message. That means you're taking over all drawing, but that's okay since you don't want it to look like a normal button anyway.
I could not agree more with Jonathan Potter's observation that it is extremely bad UI design to fail to indicate to the user which buttons are enabled and which ones are not. There are multiple ways to do this, but not doing it is not a viable option. Fortunately, it is easy to do with WM_DRAWITEM, since it tells you the button's current state.
So make the WM_DRAWITEM message handler look like this (in the parent window's window procedure):
case WM_DRAWITEM:
{
const DRAWITEMSTRUCT* pDIS = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
// See if this is the button we want to paint.
// You can either check the control ID, like I've done here,
// or check against the window handle (pDIS->hwndItem).
if (pDIS->CtlID == 1)
{
// Load the bitmap.
const HBITMAP hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
// Draw the bitmap to the button.
bool isEnabled = (pDIS->itemState & ODS_DISABLED) == 0;
DrawState(pDIS->hDC,
nullptr,
nullptr,
reinterpret_cast<LPARAM>(hBmp),
0, 0, 0, 0, 0,
DST_BITMAP | (isEnabled ? DSS_NORMAL : DSS_DISABLED));
// Delete the bitmap.
DeleteObject(hBmp);
// Draw the focus rectangle, if applicable.
if ((pDIS->itemState & ODS_FOCUS) && ((pDIS->itemState & ODS_NOFOCUSRECT) == 0))
{
DrawFocusRect(pDIS->hDC, &pDIS->rcItem);
}
// Indicate that we handled this message.
return TRUE;
}
break;
}
Naturally, you could optimize this code further by loading the bitmap a single time and caching it in a global object, rather than loading and destroying it each time the button needs painting.
Note that I've used the DrawState function, which can draw bitmaps either in a "normal" (DSS_NORMAL) or "disabled" (DSS_DISABLED) state. That simplifies the code considerably, and allows us to easily handle the disabled state, but unfortunately the result looks a little bit ugly. That's because the DrawState function converts the bitmap to monochrome before applying any effects other than normal.
You probably don't like that effect, so you'll need to do something else. For example, use two separate images, one for the enabled state and the other for the disabled state, and draw the appropriate one. Or convert your normal color image into grayscale, then draw that for the disabled state.
And if the custom-drawing code runs too slowly, you can optimize it even further by checking the value of pDIS->itemAction and only re-drawing the necessary portions.
Then, once you think you've got everything all polished and efficient, the inevitable bug reports will start to roll in. For example, keyboard accelerators are not supported. Then, once you add support for these, you'll need to indicate that in the UI. That will be difficult with a bitmap that already contains the text; the only way to draw a letter underlined is to draw the text yourself. This all proves that owner-draw is way too much work. Just let Windows draw the controls the normal way, don't break everything for your users just because some designer thinks it "looks cool".

How to save to a bitmap in MFC C++ application?

I am just starting with MFC so please be tolerant ;).
I have wrote (it was mostly generated to be honest) a simple application which should do the Paint chores: drawing lines, rectangulars, ellipses, changing a color of object to be drawn etc.
I need to save what has been drawn on the screen into a bmp file. Any ideas how can I achieve this ?
I do not know if that's relevant but I am drawing objects on the screen without the use of any CBitmaps or things like that. Here is a part of code responsible for drawing :
CPaintDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
Anchor.x=point.x;
Anchor.y=point.y;
OldPoint.x=Anchor.x;
OldPoint.y=Anchor.y;
if(pDoc->shapeCount>=MAX_SHAPES) return;
pDoc->shapeCount++;
if(bFreehand)
{
pDoc->m_shape[pDoc->shapeCount-1] = new Shape;
pDoc->m_shape[pDoc->shapeCount-1]->shape = ePoint;
}
if(bLine)
{
pDoc->m_shape[pDoc->shapeCount-1] = new CLine;
pDoc->m_shape[pDoc->shapeCount-1]->shape = eLine;
}
if(bRectangle)
{
pDoc->m_shape[pDoc->shapeCount-1] = new CRectangle;
pDoc->m_shape[pDoc->shapeCount-1]->shape = eRectangle;
}
if(bEllipse)
{
pDoc->m_shape[pDoc->shapeCount-1] = new CEllipse;
pDoc->m_shape[pDoc->shapeCount-1]->shape=eEllipse;
}
pDoc->m_shape[pDoc->shapeCount-1]->x=point.x;
pDoc->m_shape[pDoc->shapeCount-1]->y=point.y;
pDoc->m_shape[pDoc->shapeCount-1]->x2=point.x;
pDoc->m_shape[pDoc->shapeCount-1]->y2=point.y;
pDoc->m_shape[pDoc->shapeCount-1]->Pen=CurrentPen;
pDoc->m_shape[pDoc->shapeCount-1]->Brush=CurrentBrush;
bButtonDown=true;
SetCapture();
I have found this way to do it but I don't know how to obtain screen width and height to fill it in the CreateBitmap parameter's list
CBitmap *bitmap;
bitmap.CreateBitmap(desktopW, desktopH, 1, 32, rgbData);
CImage image;
image.Attach(bitmap);
image.Save(_T("C:\\test.bmp"), Gdiplus::ImageFormatBMP);
The CreateBitmap call only requires the desktop width and height if the image you wish to save is actually the entire size of the screen. If that's indeed your intent, you can use CWnd::GetDesktopWindow() to get a CWnd object that you can query for its width and height:
http://msdn.microsoft.com/en-us/library/bkxb36k8(v=VS.80).aspx
That gets dodgy in general...if for no other reason than multi-monitor scenarios...so I'd recommend against it unless you really feel like writing a screen capture app.
What you probably want to do isn't to take a full screen shot, but just save the contents of your program's window. Typically you'd do this by breaking out the drawing logic of your program so that in the paint method you call a helper function that is written to take a CDC device context. Then you can either call that function on the window-based DC you get in the paint call or on a DC you create from the bitmap to do your save. Note that you can use a CBitmap in CDC::SelectObject:
http://msdn.microsoft.com/en-us/library/432f18e2(v=VS.71).aspx
(Though let me pitch you on not using MFC. Try Qt instead. Way better.)

CStatic Custom Control

I am trying to create a custom CStatic control in vc++ and have a few problems.
I originally was just using a CStatic control with the SS_BLACKRECT style. This was good for the situation until I needed to display an image over the control on demand.
I figured out all the logistics behind actually drawing the image onto the control but I cant seem to figure out how to do so without interfering with other things.
Basically I want the control to function as a normal CStatic with the SS_BLACKRECT style most of the time.
Then I need to be able to call a method that will cause it to draw an image over the control instead. I am doing the drawing using GDI and have tried it both in the OnPaint() method and the DrawItem() method without success. I can get it to draw in the OnPaint() but when I call the base CStatic::OnPaint() it draws over my image.
I need to be able to allow it to draw like normal but then just throw an image in on top. When I tried to do it in the DrawItem() method I had a problem because obviously it was not drawing using the SS_BLACKRECT style but waiting for me to draw the control like its supposed to.
I guess what I think I'm looking for is one of three things. A way to draw using GDI after the base OnPaint() method finishes. A way to have the control draw the default SS_BLACKRECT style and then OWNERDRAW the image afterwards. Or the code to mimic the drawing of SS_BLACKRECT.
The last one might be the easiest but I just don't know all the things I need to set up to draw a CStatic control like the default DrawItem.
Try calling Default() in your OnPaint() handler.
Then, depending on whether you're drawing your image, you can then draw over the top of the standard CStatic control.
Here's a couple ideas:
If CStatic::OnPaint() draws over your image, then try calling it first and drawing your image afterwards.
Otherwise, from what little I've seen of SS_BLACKRECT, you should be able to replicate it's drawing simply be calling CDC::FillSolidRect() passing the rectangle of your control obtained through GetClientRect() and using the color returned by GetSysColor(COLOR_WINDOWFRAME)

Transparent window containing opaque text and buttons

I'm creating a non-intrusive popup window to notify the user when processing a time-consuming operation. At the moment I'm setting its transparency by calling SetLayeredWindowAttributes which gives me a reasonable result:
alt text http://img6.imageshack.us/img6/3144/transparentn.jpg
However I'd like the text and close button to appear opaque (it doesn't quite look right with white text) while keeping the background transparent - is there a way of doing this?
In order to do "proper" alpha in a layered window you need to supply the window manager with a PARGB bitmap by a call to UpdateLayeredWindow.
The cleanest way to achieve this that I know of is the following:
Create a GDI+ Bitmap object with the PixelFormat32bppPARGB pixel format.
Create a Graphics object to draw in this Bitmap object.
Do all your drawing into this object using GDI+.
Destroy the Graphics object created in step 2.
Call the GetHBITMAP method on the Bitmap object to get a Windows HBITMAP.
Destroy the Bitmap object.
Create a memory DC using CreateCompatibleDC and select the HBITMAP from step 5 into it.
Call UpdateLayeredWindow using the memory DC as a source.
Select previous bitmap and delete the memory DC.
Destroy the HBITMAP created in step 5.
This method should allow you to control the alpha channel of everything that is drawn: transparent for the background, opaque for the text and button.
Also, since you are going to be outputting text, I recommend that you call SystemParametersInfo to get the default antialiasing setting (SPI_GETFONTSMOOTHING), and then the SetTextRenderingHint on the Graphics object to set the antialiasing type to the same type that is configured by the user, for a nicer look.
I suspect you'll need two top level windows rather than one - one that has the alpha blend and a second that is display above the first with the opaque text and button but with a transparent background. To accomplish this with a single window you'll need to use the UpdateLayeredWindow API call, but using this will cause your buttons to not redraw when they are interacted with (hover highlights, focus etc.)
It is possible that if this application is for Vista only there is a new API call that you can use, but I do not believe it is available in XP or earlier.
I can't say for sure, you'll need to try it, but since everything is a window, you could try setting the layered attributes for your button to make it opaque.
As for the text, you may be able to put that in its own frame with a set background and foreground color, and modify its layered attributes to make the background color transparent...
But since these are child windows and not the top-level window, I really don't know that it'll work.