winapi - WM_DRAWITEM - comparing HMENU with HWND - winapi

I have implemented a custom drawn context menu (MFT_OWNERDRAW). When I create a context menu, I get an HMENU handle when calling CreatePopupMenu(). When I handle the WM_DRAWITEM message, I get an LPDRAWITEMSTRUCT:
LPDRAWITEMSTRUCT drawItem = (LPDRAWITEMSTRUCT)lParam;
The DRAWITEMSTRUCT structure documentation describes the hwndItem field this way:
A handle to the control for combo boxes, list boxes, buttons, and static controls. For menus, this member is a handle to the menu that contains the item.
I need to check if the WM_DRAWITEM message belongs to my custom context menu. Does it mean that I can compare my context menu handle (HMENU) with the hwndItem (HWND) in this way?
//getHighlightMenuId returns HMENU returned by CreatePopupMenu
if((int)highlightMenu->getHighlightMenuId() == (int)drawItem->hwndItem))
{
}
Is it correct?

Yes, you would compare the DRAWITEMSTRUCT::hwndItem to your HMENU, eg:
LPDRAWITEMSTRUCT drawItem = (LPDRAWITEMSTRUCT)lParam;
if (drawItem->CtlType == ODT_MENU)
{
HMENU hMenu = (HMENU)(drawItem->hwndItem);
if (highlightMenu->getHighlightMenuId() == hMenu)
{
// draw the menu item for drawItem->itemID within hMenu as needed ...
}
}

Related

How do I make a combobox show a tab control like VS' when setting a color?

I mean this control:
When you click on this, instead of regular options, a tab control with the colors is displayed. How can I do this? is this a owner-draw combobox or something else? I'm aware on how draw text, rectangles, images, etc with a owner-draw combobox but I don't know how add controls over there. I have no code to show yet because I have no idea how do that. I've tried something like call CreateWindow() in WM_DRAWITEM using the values from DRAWITEMSTRUCT.rcItem but I can't make a control inside the groupbox's client area, the button gets behind the control.
Looks like you are looking for CBN_DROPDOWN.
Sent when the list box of a combo box is about to be made visible. The
parent window of the combo box receives this notification code through
the WM_COMMAND message.
Some code:
HWND hWndComboBox = CreateWindow(
WC_COMBOBOX,
TEXT(""),
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST,
10, 20, 70, 17,
hWnd, (HMENU)IDB_COMBOX, hInstance, NULL);
...
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDB_COMBOX:
{
switch (HIWORD(wParam))
{
case CBN_DROPDOWN:
{
CHOOSECOLOR cc; // common dialog box structure
static COLORREF acrCustClr[16]; // array of custom colors
static DWORD rgbCurrent; // initial color selection
// Initialize CHOOSECOLOR
ZeroMemory(&cc, sizeof(cc));
cc.lStructSize = sizeof(cc);
cc.hwndOwner = hWnd;
cc.lpCustColors = (LPDWORD)acrCustClr;
cc.rgbResult = rgbCurrent;
cc.Flags = CC_FULLOPEN | CC_RGBINIT;
ChooseColor(&cc);
}
break;
...
Debug:

Forcing a combobox to "dropdown" above instead of below

When you click on the "dropdown" button of a combobox, the dropped down listbox appears below the combobox, unless there is not enough space below, in which case the listbox appears above.
Now I wonder if there is a possibility to force the lisbox to appear above the combobox, even if there is enough space below.
Illustration
When I click on the combo box, I'd like the "drop down" list box appear always above as on the left screen copy.
Everything is possible, and you don't need to implement the control "from scratch".
First, you can subclass the ListBox part of your ComboBox to get complete control over it, as explained in MSDN. You can create a class, derived from CListBox, using the Class Wizard. You only need to implement WM_WINPOSITIONCHANGING handler in it:
void CTopListBox::OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
CListBox::OnWindowPosChanging(lpwndpos);
if ((lpwndpos->flags & SWP_NOMOVE) == 0)
{
lpwndpos->y -= lpwndpos->cy + 30;
}
}
Here, for simplicity, I am moving the box up by the (heights+30). You can get the height of your ComboBox instead of my 30.
Then you declare a member variable in your dialog class:
CTopListBox m_listbox;
and subclass it like that:
HBRUSH CMFCDlgDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
if (nCtlColor == CTLCOLOR_LISTBOX)
{
if (m_listbox.GetSafeHwnd() == NULL)
{
m_listbox.SubclassWindow(pWnd->GetSafeHwnd());
CRect r;
m_listbox.GetWindowRect(r);
m_listbox.MoveWindow(r);
}
}
HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
return hbr;
}
Note that I am calling m_listbox.MoveWindow(r) there; it is needed because first WM_CONTROLCOLOR message for that list box comes after it is positioned, so the very first time it would drop down instead of up.
Disclaimer: this is not a very clean solution, as, if you have windows animation enabled, you'd see that the list unrolls from top to bottom.
Alternatively, you should be able to "fool" the combobox that it is too close to the bottom of the screen; then it will drop up by itself. I leave it as an exercise for the readers :)
This would be relatively easy except when combo box has "slide open" effect. If you move the dropdown listbox to the top, and the combo slides open from top-to-bottom, it would look odd. So you have to disable the animation or reverse it.
In this function I call AnimateWindow in OnWindowPosChanging, it doesn't seem to cause any problems but I am not a 100% sure about it!
class CComboBox_ListBox : public CListBox
{
public:
CWnd *comboBox;
void OnWindowPosChanging(WINDOWPOS *wndpos)
{
CListBox::OnWindowPosChanging(wndpos);
if (comboBox && wndpos->cx && wndpos->cy && !(wndpos->flags & SWP_NOMOVE))
{
CRect rc;
comboBox->GetWindowRect(&rc);
//if listbox is at the bottom...
if (wndpos->y > rc.top) {
//if there is enough room for listbox to go on top...
if (rc.top > wndpos->cy) {
wndpos->y = rc.top - wndpos->cy;
BOOL animation;
SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &animation, 0);
//if combobox slides open...
if (animation) {
//we have to set the x coordinate otherwise listbox
//is in the wrong place when parent window moves
SetWindowPos(0, wndpos->x, wndpos->y, 0, 0,
SWP_NOSENDCHANGING | SWP_HIDEWINDOW | SWP_NOSIZE);
AnimateWindow(100, AW_VER_NEGATIVE);
}
}
}
}
}
DECLARE_MESSAGE_MAP()
};
Usage:
COMBOBOXINFO ci = { sizeof(COMBOBOXINFO) };
comboBox.GetComboBoxInfo(&ci);
CComboBox_ListBox *listBox = new CComboBox_ListBox;
listBox->comboBox = &comboBox;
listBox->SubclassWindow(ci.hwndList);
Also you can use SetMinVisibleItems to reduce the listbox height and make sure the dropdown list fits on top.

Button Control does not get/forward mouseDown/mouseUp/MouseMove messages

I have title-less MFC Dialog.
So it does not move if you drag it and you need to take care movements yourself.
I do this like this:
bool mbMousedown;
void MyDialog::OnLButtonDown(UINT nFlags, CPoint point)
{
mbMousedown = true;
CDialogEx::OnLButtonDown(nFlags, point);
}
void MyDialog::OnLButtonUp(UINT nFlags, CPoint point)
{
mbMousedown = false;
CDialogEx::OnLButtonUp(nFlags, point);
}
afx_msg LRESULT MyDialog::OnNcHitTest(CPoint point)
{
CRect r;
GetClientRect(&r);
ClientToScreen(&r);
if (r.PtInRect(point))
{
if (mbMousedown)
{
return HTCAPTION;
}
}
return CDialogEx::OnNcHitTest(point);
}
It works - when i drag the dialog it moves.
But when i place button control on the dialog (yes i set notify to true) then the button control forward only click messages to the dialog but not mousedown/mouseup/mousemove.
It just does not have these handlers in class wizard.
So if user press the image inside the button control and drag the dialog like this then nothing happens since dialog did not get any messages from the button control.
I know i can solve this using activeX - i do not want to add activeX.
I know i can derive from button box and do that - but really there is no easiest solution than that to get button to pass the notification or to make my dialog to move when user drag it with the picture control ? I have many button controls on the dialog ...

How do I get the screen or window coordinates of CTreeCtrl node?

I want to generate a context menu but I need to know where to put it, so I need the coordinates of the node currently selected.
Use CTreeCtrl::GetItemRect(). This will state the rectangle of the tree node.
You can use 'GetCursorPos' and'HitTest method in treeclick event for displaying context menu as below.
//here i am asuming that you want to display menu on mouse rightclick
void MyDialog::OnRclickTree(NMHDR* pNMHDR, LRESULT* pResult)
{
CPoint CurPos;
GetCursorPos(&CurPos);
CPoint CurP=CurPos;
m_pwTree.ScreenToClient(&CurPos);// m_pwTree is object of CTreeCtrl class
UINT nFlags;
HTREEITEM htItem = m_pwTree.HitTest(CurPos, &nFlags);
if (htItem != NULL ) {
CMenu menu;
CMenu* pContextMenu;
menu.LoadMenu(IDR_MyMenu)//load appropriate menu
pContextMenu=menu.GetSubMenu(0); pContextMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,CurP.x,CurP.y,this,0);
}
}

WM_NOTIFY isn't posted to winProc function (parrent of child is correct, WM_COMMAND works excelent)

I am writing some library with using WIN API. I have a problem with receiving WM_NOTIFY message from WC_TABCONTROL class window in parent window WinProc function.
I check by "debug prints", that parent of child was set correctly. I receives WM_COMMAND messages and correctly in some function.
I have not idea what can be a reason for this. Tab control in window looks good and responds for mouse clicks with do visual tab item select change.
for example, when I click to unselected tab, I receive following messages http://pastie.org/6571509
You can check my WIN Proc function here http://goo.gl/knJ4Z , line 346.
Create tab control:
ps_ext->d_handle = CreateWindowExW(0, // no extended styles
WC_TABCONTROL, // class name
L"", // default text
WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, // overlapped window
CW_USEDEFAULT, // default horizontal position
CW_USEDEFAULT, // default vertical position
CW_USEDEFAULT, // default width
CW_USEDEFAULT, // default height
HWND_MESSAGE, // no parent or owner window
(HMENU)WINSEM_Window_NextComponentID(), // class menu used
WINSEM_Window_hInstance,// instance handle
(LPVOID)&ps_ext->s_window); // no window creation data
After it, set correct parent by SetParent function call.
Tab is resized by something like:
uFlags = SWP_NOOWNERZORDER | SWP_NOZORDER;
if (SetWindowPos(ps_window->d_handle, NULL, s0_x, s0_y, s0_w, s0_h, uFlags)==0)
{
DWORD dErr;
dErr = GetLastError();
HaveWinLastError_Error(ps_pack, WINSEM_WINDOW_fromerror_windowPos+0, dErr, dErr);
break;
}
And showing window and clicking on tab control don't generate WM_NOTIFY message received by parent window winProc function.
This is my message receive code:
bRet = PeekMessage( &msg, NULL, 0, 0, PM_REMOVE);
if (bRet==FALSE)
{
// no messages received
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Have anybody any idea about this problem? Thanks for you ideas and time.
I suspect that the tab control caches its parent window when it is created and never updates it. If you re-parent it, the messages will still go to the original parent - which is an invalid window in this case.
Why are you creating it with HWND_MESSAGE as a parent anyway? Why not just create it with its proper parent to begin with?

Resources