If I use CreateFont() to create a font (as non-static variable) and use SelectObject to use itin a function, then before exit that function, I have to select the previous object and use DeleteObject to delete the font.
But if I declare a global variable HFONT gFont = CreateFont(...), then in the WM_DESTROY message, should I call DeleteObject to delete the font?
Also in this case, if I use hOldFont = SelectObject(memDC, gFont); in a function, should I call SelectObject(memDC, hOldFont);before exit that function? It seems to me that, for Bitmap, we should do this clean up, but I don't know if this is true for other GDI objects.
Think of each DeviceContext (DC) as a canvas. Each of these canvases can only have one active GDI object for each type at a time. So, you can have one Brush, Pen, Font, etc selected for that canvas as the active one.
When you call SelectObject() you are setting the active object of that type. Think of it as "picking up the red pen to draw, then picking up the blue pen to draw." If a GDI function takes a Pen (such as Rectangle), it will use the last selected pen via SelectObject. This is why SelectObject returns the previous value so you can store it for restoring state when you are done.
You should always use DestroyObject on any created GDI object that you no longer need. In addition, you should always use DeleteDC for every CreateDC call and ReleaseDC for every GetDC call (when you no longer need the DC).
It is best practice to restore the DeviceContext (DC) to the way you found it. So if you set the Font or Brush, you should restore it to the original value before returning. The only time where restoring is not important is when you are about to dispose of the DC anyways, perhaps in the case of a temporary Bitmap.
If you you use GetStockObject, you do not need to call DeleteObject.
A Delphi (Pascal) example:
Procedure AngleTextOut(hDC: THandle; const sTxt: string; iX, iY, iH, iAngle: integer);
var
aryC: array[0..255] of Char;
hFont, hFontOld: THandle;
begin
StrPCopy(aryC, sTxt);
hFont:= CreateFont(-iH, 0, iAngle *10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'arial');
hFontOld:= SelectObject(hDC, hFont);
TextOut(hDC, iX, iY, aryC, StrLen(aryC));
SelectObject(hDC, hFontOld);
DeleteObject(hFont);
end;// AngleTextOut
Related
I created a bitmap using CreateDIBSection and specified .biWidth = 100 ; .biHeight = 100 like this pseudo-code :
pBitmapInfo->bmiHeader.biWidth = 100;
pBitmapInfo->bmiHeader.biHeight = 100;
....
CreateDIBSection(DibDC, pBitmapInfo, DIB_RGB_COLORS, 0, 0, 0);
Later, i want to reuse this bitmap, just change the bitmap size to 300x100 (and may clear the old image because i don't need it anymore). Many one say I need to create a new bitmap with new size and delete the old bitmap. But I expected in someway that we can re-use the old bitmap. I don't want to re-create a new bitmap because it cause slow performance while i need to do it repeatly many times. So is there any way to change the bitmap size without re-create a new bitmap?
If you are worried about performance it is indeed not a good idea to keep destroying and creating bitmaps.
There is however an easier solution. Simply create a pool of bitmaps in predefined sizes and use bitmaps from the pool as needed.
If you have a long lived DC, you can use:
hBitmap100x100 = CreateCompatibleBitmap(MyDC, 100,100);
hBitmap300x300 = CreateCompatibleBitmap(MyDC, 300,300);
If you keep changing DC's then use a DIB section
hBitmap100x100 = CreateDIBSection(DibDC, pBitmapInfo100x100, DIB_RGB_COLORS, null, 0, 0);
hBitmap300x300 = CreateDIBSection(DibDC, pBitmapInfo100x100, DIB_RGB_COLORS, null, 0, 0);
Just keep reusing these over and over.
You can even have a dozen of them in an array if you like.
You create them at program startup and dispose of them when done.
I'm working on an app that positions windows on the screen in a grid style. When Running this on Windows 10, there is a huge gap between the windows. Further investigation shows that GetWindowRect is returning unexpected values, including an invisible border, but I can't get it to return the real values with the visible border.
1) This thread suggests this is by design and you can "fix" it by linking with winver=6. My environment does not allow this but I've tried changing the PE MajorOperatingSystemVersion and MajorSubsystemVersion to 6 with no affect
2) That same thread also suggests using DwmGetWindowAttribute with DWMWA_EXTENDED_FRAME_BOUNDS to get the real coordinates from DWM, which works, but means changing everywhere that gets the window coordinates. It also doesn't allow the value to be set, leaving us to reverse the process to be able to set the window size.
3) This question suggests it's lack of the DPI awareness in the process. Neither setting the DPI awareness flag in the manifest, or calling SetProcessDpiAwareness had any result.
4) On a whim, I've also tried adding the Windows Vista, 7, 8, 8.1 and 10 compatibility flags, and the Windows themes manifest with no change.
This window is moved to 0x0, 1280x1024, supposedly to fill the entire screen, and when querying the coordinates back, we get the same values.
The window however is actually 14 pixels narrower, to take into account the border on older versions of Windows.
How can I convince Windows to let me work with the real window coordinates?
Windows 10 has thin invisible borders on left, right, and bottom, it is used to grip the mouse for resizing. The borders might look like this: 7,0,7,7 (left, top, right, bottom)
When you call SetWindowPos to put the window at this coordinates:
0, 0, 1280, 1024
The window will pick those exact coordinates, and GetWindowRect will return the same coordinates. But visually, the window appears to be here:
7, 0, 1273, 1017
You can fool the window and tell it to go here instead:
-7, 0, 1287, 1031
To do that, we get Windows 10 border thickness:
RECT rect, frame;
GetWindowRect(hwnd, &rect);
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));
//rect should be `0, 0, 1280, 1024`
//frame should be `7, 0, 1273, 1017`
RECT border;
border.left = frame.left - rect.left;
border.top = frame.top - rect.top;
border.right = rect.right - frame.right;
border.bottom = rect.bottom - frame.bottom;
//border should be `7, 0, 7, 7`
Then offset the rectangle like so:
rect.left -= border.left;
rect.top -= border.top;
rect.right += border.left + border.right;
rect.bottom += border.top + border.bottom;
//new rect should be `-7, 0, 1287, 1031`
Unless there is a simpler solution!
How can I convince Windows to let me work with the real window coordinates?
You are already working with the real coordinates. Windows10 has simply chosen to hide the borders from your eyes. But nonetheless they are still there. Mousing past the edges of the window, your cursor will change to the resizing cursor, meaning that its still actually over the window.
If you want your eyes to match what Windows is telling you, you could try exposing those borders so that they are visible again, using the Aero Lite theme:
http://winaero.com/blog/enable-the-hidden-aero-lite-theme-in-windows-10/
AdjustWindowRectEx (or on Windows 10 and later AdjustWindowRectExForDpi) might be of use. These functions will convert a client rectangle into a window size.
I'm guessing you don't want to overlap the borders though, so this probably isn't a full solution--but it may be part of the solution and may be useful to other people coming across this question.
Here's a quick snippet from my codebase where I've successfully used these to set the window size to get a desired client size, pardon the error handling macros:
DWORD window_style = (DWORD)GetWindowLong(global_context->window, GWL_STYLE);
CHECK_CODE(window_style);
CHECK(window_style != WS_OVERLAPPED); // Required by AdjustWindowRectEx
DWORD window_style_ex = (DWORD)GetWindowLong(global_context->window, GWL_EXSTYLE);
CHECK_CODE(window_style_ex);
// XXX: Use DPI aware version?
RECT requested_size = {};
requested_size.right = width;
requested_size.bottom = height;
AdjustWindowRectEx(
&requested_size,
window_style,
false, // XXX: Why always false here?
window_style_ex
);
UINT set_window_pos_flags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;
CHECK_CODE(SetWindowPos(
global_context->window,
nullptr,
0,
0,
requested_size.right - requested_size.left,
requested_size.bottom - requested_size.top,
set_window_pos_flags
));
There are still two ambiguities in the above use case:
My window does have a menu, but I have to pass in false for the menu param or I get the wrong size out. I'll update this answer with an explanation if I figure out why this is!
I haven't yet read about how Windows handles DPI awareness so I'm not sure when you want to use that function vs the non DPI aware one
You can respond to the WM_NCCALCSIZE message, modify WndProc's default behaviour to remove the invisible border.
As this document and this document explain, when wParam > 0, On request wParam.Rgrc[0] contains the new coordinates of the window and when the procedure returns, Response wParam.Rgrc[0] contains the coordinates of the new client rectangle.
The golang code sample:
case win.WM_NCCALCSIZE:
log.Println("----------------- WM_NCCALCSIZE:", wParam, lParam)
if wParam > 0 {
params := (*win.NCCALCSIZE_PARAMS)(unsafe.Pointer(lParam))
params.Rgrc[0].Top = params.Rgrc[2].Top
params.Rgrc[0].Left = params.Rgrc[0].Left + 1
params.Rgrc[0].Bottom = params.Rgrc[0].Bottom - 1
params.Rgrc[0].Right = params.Rgrc[0].Right - 1
return 0x0300
}
Given the following code snippet:
procedure TPicture.PaintLine(_Canvas: TCanvas; _Left, _Top, _Right, _Bottom: Integer);
begin
IntersectClipRect(_Canvas.Handle, _Left, _Top, _Right, _Bottom);
try
_Canvas.MoveTo(_Left - 10, _Top - 10);
_Canvas.LineTo(_Right + 10, _Bottom + 10);
// (This is an example only, the actual drawing is much more complex.)
finally
SelectClipRgn(_Canvas.Handle, 0); // This does too much
end;
end;
I want to undo the clipping effected by the call to IntersectClipRect so the previously active clipping becomes active again. In the above code, this is done by SelectClipRgn(...,0) which turns off clipping altogether. This works, kind of, but afterwards there is no clipping active so any drawing that is executed after the above will paint to areas that should not be painted to.
So, what is the correct way to undo only the effect of IntersectClipRect?
EDIT: Removed the unnecessary CreateRectRgn and DeleteObject code after I understood the comment from Sertac, to make the question more readable for others that might stumble upon it later.
You can save and restore the state of the DC:
var
// RGN: HRGN;
SavedDC: Integer;
begin
// RGN := CreateRectRgn(_Left, _Top, _Right, _Bottom);
SavedDC := SaveDC(_Canvas.Handle);
try
IntersectClipRect(_Canvas.Handle, _Left, _Top, _Right, _Bottom);
_Canvas.MoveTo(_Left - 10, _Top - 10);
_Canvas.LineTo(_Right + 10, _Bottom + 10);
// (This is an example only, the actual drawing is much more complex.)
finally
RestoreDC(_Canvas.Handle, SavedDC);
end;
...
IIRC, first store the current clip region using GetClipRgn, and after you're done, SelectClipRgn the stored region again.
Looking at your code, it should be enough to SelectClipRgnyour RGN again, because:
The IntersectClipRect function creates a new clipping region from the intersection of the current clipping region and the specified rectangle.
I am doing the screen shots of IE using PrintWindow. The problem is that some times I get images with black areas. It may be a whole html content what is black, some times only certain areas are black.
The content of the IE is NOT changed between taking shots.
What is strange is that on some computers I get black images very oftern, on some I never get them.
I tested with Fx, and had same black images.
HBITMAP ShootWindow(HWND hWnd)
{
RECT rect = {0};
GetWindowRect(hWnd, & rect);
HDC hDC = GetDC(hWnd);
if(hDC == NULL)
throw "GetDC failed.";
HDC hTargetDC = CreateCompatibleDC(hDC);
if(hTargetDC == NULL)
throw "CreateCompatibleDC failed.";
HBITMAP hBitmap = CreateCompatibleBitmap(hDC, rect.right - rect.left, rect.bottom - rect.top);
if(hBitmap == NULL)
throw "CreateCompatibleBitmap failed.";
if(!SelectObject(hTargetDC, hBitmap))
throw "SelectObject failed.";
if(!PrintWindow(hWnd, hTargetDC, 0))
throw "PrintWindow failed.";
ReleaseDC(hWnd, hDC);
ReleaseDC(hWnd, hTargetDC);
return hBitmap;
}
I have found some links, but they give no answer:
http://www.vbforums.com/showthread.php?t=555250
http://www.codeguru.com/forum/archive/index.php/t-357211.html
http://social.msdn.microsoft.com/forums/en-US/winforms/thread/3e3decd8-ced1-4f17-a745-466e5aa91391/
This seems to be common when taking screenshots of applications that are using the GPU. BitBlt can successfully copy pixels where PrintWindow fails.
WINDOWINFO wi;
GetWindowInfo(hWnd, &wi);
BitBlt(hdc, 0, 0, rect.right - rect.left, rect.bottom - rect.top, hDC, wi.rcClient.left, wi.rcClient.top, SRCCOPY);
The issue is that not all programs provide the needed functions to redraw the window when given the PrintWindow function or the WM_PRINT message.
use SetWindowLong to set WS_EX_COMPOSITED do the PrintWindow() set it back to what was before (or leave it with COMPOSITED to speed up... but that will affect the visibility of the real window unless hw acc is disabled) maybe trying to see if WS_EX_LAYERED and setting opacity to 254 would work better
(forgot to say... that this works, but only for the top level window, trying to PrintWindow some child wont work, even if you set the composited on the top level window)
You might take a look at Windows.Graphics.Capture. This a fairly new API that requires Windows 10 version 1803 or better. There is a some code example here.
It should work with applications that use GPU acceleration, such as Chrome.
This is what OBS use being the scenes when you chose "Windows 10" capture method.
EDIT: I've offered a bounty, since I doubt I'll be getting any answers otherwise.
Lately I've been working with listviews and I've decided to add an icon for each item indicating whether it's input or output. The icons add fine, but they're not transparent:
As can be seen, the icons are clearly not transparent. I'm currently doing something like this load the icons:
hImageList = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 2, 2);
if (hImageList != NULL)
{
iIN = ImageList_AddIcon(hImageList, LoadIcon(hInstance, MAKEINTRESOURCE(101)));
iOUT = ImageList_AddIcon(hImageList, LoadIcon(hInstance, MAKEINTRESOURCE(102)));
}
I've tried messing with the flags for ImageList_Create & LoadIcon/LoadImage but have had no luck and to be honest I've run out of ideas.
Any help would be very appreciated.
First up, ImageList_ReplaceIcon copies the icon data when adding it to an image list. So the HICON needs to be released afterwards.
Next, imagelists are natively bitmaps, not icons. And the way you are creating your imagelist makes the conversion of icon to bitmap very ambiguous. ILC_COLOR32 implies the imagelist should be created as a 32bit dib section, which typically contain transparency information via an embedded alpha channel. ILC_MASK instead implies that the internal bitmaps are DDB bitmaps, with the transparency information stored as a 1bpp mask bitmap.
The quickest solution to your problem - take your two icons:
Merge them into a single bitmap resource thats 32 pels wide by 16 high. Fill the background with a mask color :- purple or something.
Create the bitmap using ILC_COLOR|ILC_MASK
Load the bitmap being sure NOT to use LR_TRANSPARENT.
Add the bitmap using ImageList_AddMasked passing in a COLORREF that represents the mask color.
OR, for a better visual effect...
export your PNG data as a 32x16 32bpp bitmap file containing pre-multiplied alpha channel data.
Create the imagelist using the ILC_COLOR32 value.
LoadImage() with LR_CREATEDIBSECTION to load the bitmap as a 32bpp dib section.
Add the image using ImageList_Add()
(the last option is kind of tricky as the number of tools that support writing out 32bit bmp files with properly pre multiplied alpha channels is rather low).
Edited to add the following code sample. Using a 4bpp bitmap created in the dev environment this works just great :-
HWND hwndCtl = CreateWindowEx(0,WC_LISTVIEW,TEXT("ListView1"),WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL,0,0,cx,cy,hWnd,(HMENU)101,hModule,NULL);
HBITMAP hbm = (HBITMAP)LoadImage(hModule,MAKEINTRESOURCE(IDB_BITMAP1),IMAGE_BITMAP,0,0,0);
COLORREF crMask=RGB(255,0,255);
HIMAGELIST himl = ImageList_Create(16,16,ILC_COLOR|ILC_MASK,2,0);
ImageList_AddMasked(himl,hbm,crMask);
ListView_SetImageList(hwndCtl,himl,LVSIL_NORMAL);
You want to make your icons have a background color that isn't used anywhere else in the icon, like a really ugly purple, and then use LoadImage(..., LR_LOADTRANSPARENT); The flag says look at the first pixel at 0,0 and make everything that color transparent.
Your code looks fine to me, I always use LoadImage instead of LoadIcon but I suspect that doesn't matter. Have you checked that the icons do indeed have transparent areas and don't themselves have a solid background?
My LoadImage calls look like:
HICON hIcon = (HICON)LoadImage(hinstResources,MAKEINTRESOURCE(IDI_ICON),IMAGE_ICON,16,16,LR_DEFAULTCOLOR);
Here... Create an ImageList, as suggested, make your icons into a Bitmap, 16 pixels high, by 16*n long, where n= the number of icons...
Set the background color to 255, 0, 255, like you have done.
Then, load it, and add it to the image list as I did here:
m_ImageList.Create(16, 16, ILC_COLOR16 | ILC_MASK, 7, 1);
CBitmap bm;
bm.LoadBitmap(IDB_SUPERTREEICONS);
m_ImageList.Add(&bm, RGB(255, 0, 255));
GetTreeCtrl().SetImageList(&m_ImageList, TVSIL_NORMAL);
Of course, this was written in MFC, but as you know, it's just a wrapper to Win32...
Outside of this, you are going to have to go to a custom draw control, in which you draw the icon over whatever background the icon happens to be sitting on. There isn't really any magic "transparent" color, that I know of, in any of these controls.
In the case of a custom draw, you need to use code like the following:
#define TRANSPARENT_COLOR (255,0,255)
UINT iBitmap = IDB_ICON_UP
CDC *dc = GetDC();
int x = 0, y = 0;
CDC *pDisplayMemDC = new CDC;
CDC *pMaskDC = new CDC;
CDC *pMemDC = new CDC;
CBitmap *pBitmap = new CBitmap;
CBitmap *pMaskBitmap = new CBitmap;
CBitmap *pMemBitmap = new CBitmap;
int cxLogo, cyLogo;
BITMAP bm;
pBitmap->LoadBitmap(iBitmap);
pDisplayMemDC->CreateCompatibleDC(dc);
CBitmap *pOldBitmap = (CBitmap *)pDisplayMemDC->SelectObject(pBitmap);
pBitmap->GetObject(sizeof(bm), &bm);
cxLogo = bm.bmWidth;
cyLogo = bm.bmHeight;
pMaskBitmap->CreateBitmap(cxLogo, cyLogo, 1, 1, NULL);
pMaskDC->CreateCompatibleDC(dc);
CBitmap *pOldMask = (CBitmap *)pMaskDC->SelectObject(pMaskBitmap);
COLORREF oldBkColor = pDisplayMemDC->SetBkColor(TRANSPARENT_COLOR);
pMaskDC->BitBlt(0, 0, cxLogo, cyLogo, pDisplayMemDC, 0, 0, SRCCOPY);
pMemBitmap->CreateCompatibleBitmap(dc, cxLogo, cyLogo);
pMemDC->CreateCompatibleDC(dc);
CBitmap *pOldMem = (CBitmap *)pMemDC->SelectObject(pMemBitmap);
pMemDC->BitBlt(0, 0, cxLogo, cyLogo, dc, x, y, SRCCOPY);
pMemDC->BitBlt(0, 0, cxLogo, cyLogo, pDisplayMemDC, 0, 0, SRCINVERT);
pMemDC->BitBlt(0, 0, cxLogo, cyLogo, pMaskDC, 0, 0, SRCAND);
pMemDC->BitBlt(0, 0, cxLogo, cyLogo, pDisplayMemDC, 0, 0, SRCINVERT);
dc->BitBlt(x, y, cxLogo, cyLogo, pMemDC, 0, 0, SRCCOPY);
delete pMemDC->SelectObject(pOldMem);
delete pMemDC;
delete pMaskDC->SelectObject(pOldMask);
delete pMaskDC;
delete pDisplayMemDC->SelectObject(pOldBitmap);
delete pDisplayMemDC;
This code decides where to draw the icon, and takes a snapshot of the background, creates a mask for the icon, and then draws it over the background, giving it a fully transparent background...
Hope that helps somewhat. If not, please explain in more detail what you are trying to make happen, and what you are seeing, or what you are NOT seeing...
I struggled with the same issue using an ImageList in a Tree View. I eventually got Chris Becke's second solution to work, creating an ImageList using the ILC_COLOR32 flag and using LoadImage() with the LR_CREATEDIBSECTION flag. This solution, and probably also the first solution, requires what is described below.
Transparency (and themes) are only supported with comctl32.dll version 6+, to use the correct version, the pre-processor directive on this page worked for me:
https://learn.microsoft.com/en-us/windows/win32/controls/cookbook-overview