Using CreatePopupMenu() to create HMENU, adding text items to it via AppendMenu(), then loading an icon, using GetIconInfo() to get the bitmap, assigning that bitmap to the HMENU via SetMenuItemInfo(). Now I presume I need to keep that HBITMAP from GetIconInfo() valid so I can't delete it at this time.
The question is does DestroyMenu() delete the HBITMAP assigned to it, or do I need to loop through the menu and DeleteObject() for each item that has an assigned HBITMAP? If I do have to do that, do I have to remove it via setting HBITMAP in the HMENU to NULL prior to DeleteObject() or the fact that after the loop I'll be calling DestroyMenu() enough?
TIA!!
Related
Win32:
With a Tree Control created and the style changed to TVS_CHECKBOXES and then the ImageList for the TVSIL_STATE changed to a custom ImageList, do you need to delete the returned prior ImageList or is it a shared resource and should not be.
MFC:
Since there is an object hierarchy, in this case you don't know if the CImageList is replacing a one provided by the system or from one of the parent classes. In that case what is the proper handling? For ImageLists, can you CImageList::Attach(), CTreeCtrl::SetImageList(), CImageList::Detach() then CTreeCtrl::OnDestroy() go ahead and CImageList *pil=CTreeCtrl::SetImageList(NULL, TVSIL_STATE) and then pil->DeleteImageList(), but then what about the object, are we supposed to delete pil instead? Or are we always required to setup a member variable that is the image list and just CTreeCtrl::SetImageList() to change it then OnDestroy() put back the old one or just set it to NULL?
Can changing ImageList used by TVS_CHECKBOXES cause resource leak?
Yes. From Tree-View Control Window Styles:
Destroying the tree-view control does not destroy the check box state
image list. You must destroy it explicitly. Get the handle to the
state image list by sending the tree-view control a TVM_GETIMAGELIST
message. Then destroy the image list with ImageList_Destroy.
Bonus "Old New Thing" link: Beware of the leaked image list when using the TVS_CHECKBOXES style
Win32 Solution
We can take advantage of the fact that TVM_SETIMAGELIST (wrapped by TreeView_SetImageList()) returns the handle to the previous image list.
HIMAGELIST hNewImageList = ImageList_Create(/* insert arguments */);
HIMAGELIST hOldImageList = TreeView_SetImageList( hwndTreeView, hNewImageList, TVSIL_STATE );
if( hOldImageList ) // a good habit, to check if handle is not NULL
{
ImageList_Destroy( hOldImageList );
hOldImageList = NULL;
}
After the tree control has been destroyed, you also have to ImageList_Destroy the new image list.
MFC Solution
MFC is not that much different, i. e. it doesn't automatically clean up the image list created by the TVS_CHECKBOXES style.
Create a member variable CImageList m_newImageList; in the declaration of the class that hosts the tree control (e. g. a CDialog derived class). This makes sure that the new image lists lifetime does not end before that of the tree control window and automatically destroys the image list through its destructor.
m_newImageList.Create(/* insert arguments */);
CImageList* pOldImageList = m_treeCtrl.SetImageList( &m_newImageList, TVSIL_STATE );
if( pOldImageList )
{
pOldImageList->DeleteImageList();
pOldImageList = nullptr;
}
CTreeCtrl::SetImageList() returns a pointer to a temporary object (via CImageList::FromHandle()) that does not own the handle it wraps. You have to DeleteImageList() to avoid the resource leak, but never delete on the pointer returned by SetImageList. MFC automatically cleans up temporary objects during idle processing (in CWinApp::OnIdle()).
Further reading:
TN003: Mapping of Windows Handles to Objects.
What is the lifetime of a CWnd obtained from CWnd::FromHandle?
(replace CWnd by CImageList, the FromHandle() methods of MFC classes all work in the same way).
I have a shared icon HICON from the following snippet.
::SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 500, &icon)
Then I retrieved its ICONINFO and copied out the hbmColor and hbmMask. As I notice in other snippets, people then call ::DeleteObject() to destroy hbmColor and hbmMask.
Is that also valid for a shared icon?
As mentioned in MSDN, we should not call DestroyIcon on a shared icon, which sounds like we should not call DestroyObject on a shared icon's hbmColor & hbmMask either.
It is only necessary to call DestroyIcon for icons and cursors created
with the following functions: CreateIconFromResourceEx (if called
without the LR_SHARED flag), CreateIconIndirect, and CopyIcon.
The documentation for GetIconInfo says:
GetIconInfo creates bitmaps for the hbmMask and hbmColor members of ICONINFO. The calling application must manage these bitmaps and delete them when they are no longer necessary.
This is true equally for shared icons. You will have to delete the bitmaps when you are finished with them.
I am incredibly confused by the whole "window" verses "listbox" thing in the WIN32 API. I am simply trying to create a window as a "listbox" and add elements to it. My end goal will be a listbox similar to the on here: http://msdn.microsoft.com/en-us/library/windows/desktop/hh298365%28v=vs.85%29.aspx
I start by create a window with a listbox like so:
hDlg = CreateWindowExA(
WS_EX_CLIENTEDGE,
"ListBox",
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
NULL, NULL, hInstance, NULL);
But from what I understand, hDlg now points to the entire window and not the listbox.
Ideally I want to be able to add items to the listbox similar to this:
int pos = (int)SendMessage(hwndList, LB_ADDSTRING, 0,
(LPARAM) "Test Item1");
However I cannot get the handle of the list in the same way as the tutorial because they use this line:
HWND hwndList = GetDlgItem(hDlg, IDC_LISTBOX_EXAMPLE);
but IDC_LISTBOX_EXAMPLE throws a compilation error because it isn't included anywhere. And for the life of me, I cannot google a correct result for the second parameter int nIDDlgItem.
Can someone please explain to me how I can find the value GetDlgItem() or otherwise find a handle to my listbox from CreateWindowExA()?
The problem is that you're using the predefined ListBox window class to create a top-level window.
hDlg = CreateWindowExA(WS_EX_CLIENTEDGE, "ListBox", ...
This is creating a listbox control. The second parameter to CreateWindowEx specifies the window class - this tells the system what type of window you want to create. Since you're passing "ListBox" for that value, it will be creating an instance of the ListBox class.
The trouble is that you're creating that window to be a top-level window. WS_OVERLAPPEDWINDOW is a window style used for top-level windows (i.e. the one titled "List Box Example" in the above screenshot). Child windows, such as the listbox control, need to have the WS_CHILD style set.
What you really need to do is use RegisterClass to register your own window class for the top-level window. You would use this class name when calling CreateWindowEx to create the main window, and then create the various controls as children of that window.
I am creating an application that can get text from the selected rectangle on any window. So I am doing this by hooking the ExtTextOut(A/W), TextOut(A/W), DrawText(A/W) apis.
I am able to retrieve the text from windows but in some special cases I am not able to get the text properly.
When I tried to debug it I saw that the HDC parameter of ExtTextOutW can not been used to get the HWND, because when I try to call WindowFromDC(hdc), it returns NULL HWND.
I tried to find out the reason then I found that this HDC is memoryDC so is there any way to get the HWND from that memory DC.
So Please somebody help me to get the actual HWND from the HDC.
I think you will have to follow that HDC as it is likely that it will be BitBlt(), or similar, to a HDC that is associated with an actual HWND:
cache the text written to memory HDCs
hook BitBlt(), and others, and check if the the source HDC of the BitBlt() is one of the memory HDCs that has had text written to it. If it is, check if the destination HDC has a HWND and if not then it is a memory HDC which must again be followed
Can anyone point me at an WINAPI example of embedding a property sheet in a dialog box using WINAPI (not MFC)?
For the record, here are some of the things I learned while
investigating this question. I obtained most/all of this from
asking questions here or by searching the web. Thanks to all for
your help!
I have written a class to encapsulate everything I've discovered;
if interested in getting a copy send me an email mdorl#wisc.edu.
Pass the HWND of the parent window for your property sheet in
.hwndParent of the PROPSHEETHEADER when creating the property sheet
with the PropertySheet page function.
I used a picture control on the dialog as the parent of the property
sheet so that's the HWND I used as the hwndParent.
Change the style of the property sheet using the call back
pfnCallback field in the header, don't forget to include
PSH_USECALLBACK in dwFlags. See PropSheetProc Function in
the MSDN. In the PSCB_PRECREATE handler of the callback,
adjust the window style of the property sheet by removing
WS_POPUP and adding WS_CHILD. I did not want a border or title
bar on the property sheet so I also removed WS_CAPTION, WS_SYSEMNU,
& DS_MODALFRAME
LONG L = ((LPDLGTEMPLATE)lParam)->style;
L &= ~WS_POPUP;
L &= ~WS_CAPTION; // gets rid of title bar
L &= ~WS_SYSMENU;
L &= ~DS_MODALFRAME; // gets rid of border
L |= WS_CHILD; // 40000000
((LPDLGTEMPLATE)lParam)->style = L;
Using a child window instead of a popup window neatly solves
some problems such as moving the property sheet when the parent
moves, keeping the blue/gray state of the parent window properly
colored, and clipping issues. I.e. you don't have to worry about these things.
If you stopped here, you'd face another problem. The dialog support
code in WIN32 would get in a cpu loop trying to find the property sheet and
controls in its Windows. See
What is WS_EX_CONTROLPARENT and DS_CONTROL For?
for a pretty good explanation of
this problem. The solution is to add the WS_EX_CONTROLPARENT extended
style to the property sheet. You can find information on this
on the web by searching for WS_EX_CONTROLPARENT. What you won't find
caused me much grief. If you use a control on the dialog as the
parent, you must also set its WS_EX_CONTROLPARENT.
I do not know what the difference between WS_EX_CONTROLPARENT and DS_CONTROL is nor do I know if you could substitute DS_CONTROL for WS_EX_CONTROLPARENT. From the web I gather that DS_CONTROL has something to do with tabs. My test app works the same with respect to tabs with or without DS_CONTROL.
I took care of the WS_EX_CONTROLPARENT business after calling the
PropertySheet function to create the property sheet. I do not know
if you could do this in the call back routine or not. I suppose
you could use GetParent in the call back to get the HWND of the
control.
LONG S;
S = GetWindowLong (hwndPS, GWL_EXSTYLE) | WS_EX_CONTROLPARENT;
SetWindowLong (hwndPS, GWL_EXSTYLE, S);
S = GetWindowLong (hwndPS_Area, GWL_EXSTYLE) | WS_EX_CONTROLPARENT;
SetWindowLong (hwndPS_Area, GWL_EXSTYLE, S);
After creating the property sheet with the PropertySheet function,
you have to position it properly:
SetWindowPos(hwndPS, HWND_TOP, 2, 2, -1, -1,
SWP_NOSIZE | SWP_NOACTIVATE);
I allowed a couple of pixels for the border.
If you want to get rid of all the property sheet buttons and the space they take:
RECT rectWnd;
RECT rectButton;
GetWindowRect(hwndPS, &rectWnd);
HWND hWnd = ::GetDlgItem(hwndPS, IDOK);
if(!hWnd)
{
DebugBreak();
}
GetWindowRect(hWnd, &rectButton);
SetWindowPos (hwndPS, NULL, 0, 0,
rectWnd.right - rectWnd.left, rectButton.top - rectWnd.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
Individual buttons can be eliminated:
hwndOk = GetDlgItem (hwndPS,IDOK); // Hide the OK button
ShowWindow (hwndOk, SW_HIDE);
EnableWindow (hwndOk, FALSE);
You can get rid of the APPLY button using the PSH_NOAPPLYNOW
dwFlags in the PROPERTYSHEETHEADER when you create the property sheet.
The property sheet pages will NOT be created until the user first
activates them. I wanted them all to be created when the property
sheet was created.
unsigned int iP;
for (iP=0; iP<Header.nPages; iP++)
{
if (iP != Header.nStartPage)
SendMessage (hwndPS, PSM_SETCURSEL,iP,NULL);
}
SendMessage (hwndPS, PSM_SETCURSEL,Header.nStartPage,NULL);
There's a PROPSHEETPAGE flag that does the same thing (from the MSDN)
PSP_PREMATURE Version 4.71. Causes the page to be created when the
property sheet is created. If this flag is not specified, the page
will not be created until it is selected the first time.
But I was worried about the Version 4.71 business so I did it myself.
You can change the text on any of the buttons:
SetDlgItemText (hwndPS,ButtonID,Text);
where ButtonID is one of IDOK IDCANCEL IDHELP IDAPPLYNOW
IDAPPLYNOW is undefined on my system so
#define IDAPPLYNOW 0x3021
Here's how I intend to use my property sheet. I'm going to put all the initialization and
termination code in the dialog containing the property sheet and eliminate all the property sheet buttons. I can set all the initial values there and retrieve all the final values there when the user pushes some DO IT button. I notice that the act of setting, for example, a text box causes the containing page dialog to get a WM_COMMAND/EN_CHANGE. If you use this message to enable the APPLY button, you may need to disable the change flag for the page after setting any list boxes. I solved this problem by clearing all the changed flags after setting the initial value. (I suspect the property sheet clears the changed flag after all the INITDIALOG messages have been sent. In any case APPLY is not enabled if the text boxes are set in the page INITDIALOG handlers.) If I find an error I will select that property sheet page and put some red text in the parent dialog describing the error. The only thing the property sheet dialog procedures need to do is manipulate their controls.
One of the things that puzzles me is how to determine if all pages have returned PSNRET_NOERROR from their WS_NOTIFY/PSN_APPLY messages. I've had some success counting the
number of successive PSNRET_NOERROR replies setting the count to zero when a PSNRET_INVALID is reported or a PSN_KILLACTIVE is received. If the count reaches the number of pages in the property sheet header, I assume all pages have returned PSNRET_NOERROR. But I worry about things like disabled and invisible pages.