Win32:Does ImageList_ReplaceIcon failed with unproper ImageList_Create - c++

As the title says, I'm trying to write a simple window program, but when I try to change the icon of my TreeView, it goes wrong. I'm pretty sure my icon was loaded because I did this:
HICON hIcon;
//hinst is my global variable
hIcon = LoadIcon(hinst,(char*)IDI_ICON_MAIN);
if (hIcon == NULL)
{
MessageBox(NULL, "LoadIcon failed", "error", MB_OK);
}
It works fine then I use ImageList_ReplaceIcon():
if (ImageList_ReplaceIcon(iml, 3, hIcon) == -1)
{
MessageBox(NULL, "replace icon failed", "error", MB_OK);
}
TreeView_SetImageList(hwndTV, iml, TVSIL_STATE);
First, I thought, maybe it's because I gave the wrong ILC_COLOR in ImageList_Create(), then I rechecked the bit of my icon then reset the parameter, but it's still not working.
Can anyone give me some clue of what is wrong? I already checked with Google and read the docs mutiple times, perhaps I missed something?
UPDATE [2022/05/31]
Here is my TreeView:
I'm tring to change my icon to the red circle.

I can see my icon now, thanks. I appreciate those who gave me advice.
ReplaceIcon() can only be used when I have already added an icon into it. If there's no icon in it then the only condition I can use is to set the index to -1, so that the ReplaceIcon() can add the icon for me.

Related

LoadImageW returns null c++ win32

I started making a simple c++ Win32 program. I was simply trying to load an Image into a window, but it didn't really work. I was debugging for some time, and I know that the problem is that my LoadImageW() function returns null. Code:
void loadImages() {
hPic1 = (HBITMAP)LoadImageW(NULL, L"pic.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (hPic1== NULL) {
MessageBox(NULL, L"Error", L"LoadImage error", MB_OK);
}
}
The .bmp file is in the same directory with the .cpp file, and I even tried with the whole path but it didn't work. I get no errors and the main window loads correctly, but the image doesn't display and the message box appears. The loadImages() function gets called at WM_CREATE of the window. The SendMessageW() function looks like this:
SendMessageW(hImageWindow, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hPic1);
Any help is appreciated, and have a nice day!
After calling LoadImageW or any win api function that leads to unexpected result! check documentation in MADN especially "Remarks" section!
Based on documentation may be it is good to call GetLastError()! By checking error code you find the problem, such as "file not found", access privilege error and etc.
You can find description of each error code returned by GetLastError() in page https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes or check by visual studio IDE in Tools-> Error lookup menu!

Why is my window losing its HTMEME when I call SetWindowLongPtr(GWL_STYLE) on it?

I'm coding a custom Win32 UI control that I want to incorporate visual themes in. I load themes in its WM_NCCREATE as such:
case WM_NCCREATE:
{
HTHEME hTheme = ::OpenThemeData(hWnd, L"EDIT");
assert(hTheme);
assert(::GetWindowTheme(hWnd) != 0);
}
return 1;
and then release them when control is destroyed:
case WM_DESTROY:
{
HTHEME hTheme = ::GetWindowTheme(hWnd);
assert(hTheme);
if(::CloseThemeData(hTheme) != S_OK)
{
assert(NULL);
}
}
break;
This works well, until someone tries to change that control's styles. The following call (just by itself without even changing any styles):
::SetWindowLongPtr(hChildWnd, GWL_STYLE, dwStyle);
will make GetWindowTheme on hChildWnd return NULL.
So, is it a bug or a feature?
PS. To make a reproducible Win32 example I had to adjust the stock Win32 solution from the VS 2017. (Here is its full source code.) The way it works is this: in it I create a small child control (shown in gray below) that has theme in question:
Then when you click on the white area of the main window, I try to change its styles and its theme disappears:
To see the full Win32 code for that project, I also posted it on PasteBin.
According to Window Styles document:
"After the window has been created, these styles cannot be modified,
except as noted."
Because this is not permitted, the theme engine does not always check for changed styles and in some circumstances will draw the caption based on old data. And the only guaranteed and supportable solution is for the application to destroy the window and recreate it with the new styles rather than trying to change them on the fly.
A similar discussion can be found:
http://social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/7b5ef777-ff0d-4f15-afed-5588f93f0e23

MFC's dialog-based app title bar highlighting visual artifacts on Windows 10 (i.e. bugs in CDialogEx)

I'm not sure why am I getting this visual artifact?
Here's how to repro:
I'm using Visual Studio 2017 Community. Create a new C++ -> MFC project:
Then specify "dialog based":
Then build as "Debug" x86 app and run it.
So I'm running it on Windows 10.
When this dialog-based process has focus, it looks as I would expect it:
but if I switch keyboard focus to some other app (by clicking on it), this dialog-based process still retains its title bar color:
I'm not sure if it's just a matter of a visual glitch or if there's a deeper mess-up with the window message handling. How do I correct it? (This wasn't an issue with older MFC projects.)
I managed to replicate your problem and found a quick fix for it.
You need to add the WM_ACTIVATE message handler to your main dialog, comment out the base class OnActivate and modify it like this:
void CMFCApplication1Dlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
//CDialogEx::OnActivate(nState, pWndOther, bMinimized);
// TODO: Add your message handler code here
this->Default();
}
CWnd::Default call is needed to keep the active/inactive visualization of the default button.
OK, as much as I appreciate #VuVirt's solution, it doesn't completely remove all the bugs that are shipped in the default Dialog-based solution in VS2017. It solves the title bar focus issue, but while continuing to develop my project I encountered another bug. So I'm copy-and-pasting it from my comment to his answer:
There's still some kinda screw-up there. I'm not sure if it's related to this fix or not. Ex: If you create a button and then in its handler try to do: CFileDialog d(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_EXPLORER, NULL, this); d.DoModal(); to open a file picker dialog. When file picker opens up, close it and see if the title bar of the parent MFC dialog window goes back to being active. In my case it remains inactive until I click onto the Windows taskbar and then back onto that MFC app.
After banging my head against the wall trying to see what is going on there, I decided to try an earlier solution proposed by #zett42 in the comments to my original question (i.e. to replace CDialogEx with CDialog) and it worked! All the bugs are gone!
So here's my verdict: CDialogEx is buggy af.
The resolution is quite simple: When you create a new dialog-based project use project-wide find-and-replace (in the Edit menu) and replace all occurrences of CDialogEx with CDialog. And that is it. (I tried to use VS2017's refactoring tool for that but it messed it up and didn't replace it all. So simple search-and-replace does the job.)
And if you think that you'll be missing some functionality without CDialogEx, then you won't. All it does (besides introducing bugs) is that it adds background images and colors to the dialog.
So until MS fixes those glaring bugs in their templates I'm sticking with this approach.
This seems to be a bug in CDialogImpl::OnActivate and CDialogImpl::OnNcActivate:
void CDialogImpl::OnNcActivate(BOOL& bActive)
{
if (m_Dlg.m_nFlags & WF_STAYACTIVE)
bActive = TRUE;
if (!m_Dlg.IsWindowEnabled())
bActive = FALSE;
}
void CDialogImpl::OnActivate(UINT nState, CWnd* pWndOther)
{
m_Dlg.m_nFlags &= ~WF_STAYACTIVE;
CWnd* pWndActive = (nState == WA_INACTIVE) ? pWndOther : &m_Dlg;
if (pWndActive != NULL)
{
BOOL bStayActive = (pWndActive->GetSafeHwnd() == m_Dlg.GetSafeHwnd()
|| pWndActive->SendMessage(WM_FLOATSTATUS, FS_SYNCACTIVE));
if (bStayActive)
m_Dlg.m_nFlags |= WF_STAYACTIVE;
}
else
{
m_Dlg.SendMessage(WM_NCPAINT, 1);
}
}
This is meant to give CDialogEx the ability to stay active, for example, when CMFCPopupMenu is shown.
But m_Dlg.SendMessage(WM_NCPAINT, 1) is a suspicious call. The usage doesn't match the documentation for WM_NCPAINT:
Parameters
wParam
A handle to the update region of the window. The update region is clipped to the window frame.
lParam
This parameter is not used.
Additionally, OnNcActivate has an override based on IsWindowEnabled(). This seems to be a patch to fix the earlier problem in OnActivate. But it causes problems elsewhere, for example when using CFileDialog in CDialogEx
Suggested solution:
Modify CDialogEx::OnActivate so that it runs the default procedure. Or, change it such that it will force repaint.
BOOL CDialogEx::OnNcActivate(BOOL active)
{
if(m_nFlags & WF_STAYACTIVE)
active = TRUE;
return(BOOL)DefWindowProc(WM_NCACTIVATE, active, 0L);
}
void CDialogEx::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
Default();
}
or
void CDialogEx::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
Default();
//save the previous flag
UINT previous_flag = m_nFlags;
m_nFlags &= ~WF_STAYACTIVE;
// Determine if this window should be active or not:
CWnd* pWndActive = (nState == WA_INACTIVE) ? pWndOther : this;
if(pWndActive != NULL)
{
BOOL bStayActive = pWndActive->GetSafeHwnd() == GetSafeHwnd() ||
pWndActive->SendMessage(WM_FLOATSTATUS, FS_SYNCACTIVE);
if(bStayActive)
m_nFlags |= WF_STAYACTIVE;
}
if(previous_flag != m_nFlags && previous_flag & WF_STAYACTIVE)
{
//if the flag is changed,
//and if WF_STAYACTIVE was previously set,
//then OnNcActivate had handled it wrongly, do it again
SendMessage(WM_NCACTIVATE, FALSE); //<- less wrong!
}
}
This should work with CMFCPopupMenu for example. The MFC menu will open without deactivating the dialog.
I am not sure what SendMessage(WM_FLOATSTATUS, FS_SYNCACTIVE) is for, I haven't been able to test it... If it's necessary, it seems the code could be added on OnNcActivate, and then OnActivate is left alone.

Change cursor when hovering a child window

I have a window that asks for Login/Password with five child windows:
An editable one: Login
Another editable one: Password
An OK button
A "forgot your password" one
And a "Register" one.
Basically, when you click on either of the last two, you are sent to a website where you can perform the appropriate actions.
It's all fine and dandy, but I would love to know how it's possible to check (with messages I guess) if the mouse cursor is hovering over one of the two links, and if that's the case, to change it to a hand cursor.
I'd especially like to know how to detect it! I can figure out how to change the cursor afterwards with SetCursor and such!
EDIT: I actually found out that WM_SETCURSOR is a really easy message to handle. Basically, you check if the wParam is equal to the handle of the child window it's hovering over and voilĂ !
But I actually find the SetCursor to be a bigger issue.
Here's what I did:
The declaration of my cursors:
static HCURSOR hCursorHand;
static HCURSOR hCursorArrow;
The value is set here (in the handle for WM_CREATE):
hCursorHand = LoadCursor( NULL, IDC_HAND );
hCursorArrow = LoadCursor( NULL, IDC_ARROW );
And here's where I set it:
else if (msg == WM_SETCURSOR)
{
if ((HWND)wParam == hwndLinkFPasswd || (HWND)wParam == hwndLinkSignUp)
SetCursor(hCursorHand);
else
SetCursor(hCursorArrow);
}
I know the cursor is properly detected (thank you breakpoints), but it doesn't seem to do anything. The cursor stays an arrow...
So! As I said, I figured it out! (I just couldn't answer my question within the first 8 hours!)
Here's what I missed: (for anyone having the same problem)
else if (msg == WM_SETCURSOR)
{
if ((HWND)wParam == hwndLinkFPasswd || (HWND)wParam == hwndLinkSignUp)
{
SetCursor(hCursorHand);
return(TRUE);
}
}
I find the documentation on this API awful, so I hope my contribution will one day help someone in my situation! ;)

Retrieve Handle to Windows Explorer's Address bar Edit Control

I need to retrieve full path of most active Windows Explorer instance.
So I have got the handle to Explorer.exe by making a call to
HWND l_pExplorerhwnd = ::GetForegroundWindow();
Using this handle retrieved, I need to retrieve it's Address bar edit control.
I have used Spy++ and got the class name of edit control as ToolbarWindow32
Now, I have tried to find the window using FindWindowEx using the following code snippet. But I am unable to retrieve it. Please help
HWND l_pExplorerhwnd = ::GetForegroundWindow();
TCHAR l_szTempName[MAX_PATH];
if(l_pExplorerhwnd)
{
::GetWindowModuleFileName(l_pExplorerhwnd, l_szTempName, MAX_PATH);
MessageBox(0, l_szTempName, 0, 0);
if(::FindWindowEx(l_pExplorerhwnd, NULL, L"ToolbarWindow32", NULL))
{
::GetWindowText(::FindWindowEx(l_pExplorerhwnd, NULL, L"ToolbarWindow32", NULL), l_szTempName, MAX_PATH);
MessageBox(0, l_szTempName, 0, 0);
}
else
{
MessageBox(0, L"Error Error ", 0, 0);
}
}
else
{
MessageBox(0, L"Error Error Error", 0, 0);
}
To answer your question directly, FindWindowEx works on direct children of the parent window, not descendants. Hence, you would need to traverse down the children one by one:
CabinetWClass
WorkerW
ReBarWindow32
Address Band Root
msctls_progress32
Breadcrumb Parent
ToolbarWindow32
Note that this hierarchy is only what it is on my system right now. As mentioned by Tom Whittock, it would be very bad practice to use this. You have no idea whether the window hierarchy could change across updates, or even by design at runtime.
One more comment about your code. Since you are using TCHAR mappings (even though there is usually no reason to use it now unless you are intending to support Win98-), your string literals should be _T("") instead of L"".